diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..f94ee26f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: true +contact_links: + - name: Non-API docs issues + url: https://github.com/Qiskit/documentation/issues/new/choose + about: Open an issue about documentation in the Start, Build, Transpile, Verify, Run, or Migration guides sections of docs.quantum.ibm.com (non-API documentation) \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 01a763c81..7601abde1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,8 +33,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install -c constraints.txt -r requirements-dev.txt + pip install -c constraints.txt -r requirements-dev.txt -e . - name: Run black run: make style - name: Run lint @@ -99,8 +98,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install -c constraints.txt -r requirements-dev.txt + pip install -c constraints.txt -r requirements-dev.txt -e . - name: Run unit tests run: make unit-test-coverage - name: Report coverage to coveralls.io @@ -142,8 +140,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install -c constraints.txt -r requirements-dev.txt + pip install -c constraints.txt -r requirements-dev.txt -e . - name: Run integration tests run: make integration-test tests-finished: diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 934e6e216..45971290d 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,5 +1,5 @@ --- -name: Docs Publish +name: Docs Upload on: push: tags: @@ -22,32 +22,10 @@ jobs: python -m pip install --upgrade pip pip install -U virtualenv setuptools wheel tox sudo apt-get install -y graphviz pandoc - - name: Build and publish - env: - encrypted_rclone_key: ${{ secrets.encrypted_rclone_key }} - encrypted_rclone_iv: ${{ secrets.encrypted_rclone_iv }} - run: | - tools/deploy_documentation.sh - deploy-translatable-strings: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - name: Set up Python - uses: actions/setup-python@v2 + - name: Build docs + run: tox -e docs + - name: Upload artifact + uses: actions/upload-artifact@v2 with: - python-version: '3.9.12' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install jupyter sphinx_rtd_theme qiskit-terra[visualization] 'torchvision<0.10.0' tox - sudo apt-get install -y graphviz pandoc - - name: Build and publish - env: - encrypted_deploy_po_branch_key: ${{ secrets.encrypted_deploy_po_branch_key }} - encrypted_deploy_po_branch_iv: ${{ secrets.encrypted_deploy_po_branch_iv }} - QISKIT_PARALLEL: False - QISKIT_DOCS_BUILD_TUTORIALS: 'always' - run: | - tools/deploy_translatable_strings.sh + name: html_docs + path: docs/_build/html diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 5773e3551..0738bccc7 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -47,7 +47,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install -c constraints.txt -r requirements-dev.txt + pip install -c constraints.txt -r requirements-dev.txt -e . - name: Run e2e tests - run: make e2e-test \ No newline at end of file + run: make e2e-test diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index a3c6c2cf4..f6b4b4c01 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -47,7 +47,6 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install -c constraints.txt -r requirements-dev.txt + pip install -c constraints.txt -r requirements-dev.txt -e . - name: Run integration tests run: make integration-test diff --git a/.github/workflows/q-ctrl-tests.yml b/.github/workflows/q-ctrl-tests.yml new file mode 100644 index 000000000..971195584 --- /dev/null +++ b/.github/workflows/q-ctrl-tests.yml @@ -0,0 +1,52 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +name: Q-CTRL Tests +on: + push: + tags: + - "*" + workflow_dispatch: +jobs: + integration-tests: + name: Run integration tests - ${{ matrix.environment }} + runs-on: ${{ matrix.os }} + strategy: + # avoid cancellation of in-progress jobs if any matrix job fails + fail-fast: false + matrix: + python-version: [ 3.9 ] + os: [ "ubuntu-latest" ] + environment: [ "ibm-cloud-staging" ] + environment: ${{ matrix.environment }} + env: + QISKIT_IBM_TOKEN: ${{ secrets.QISKIT_IBM_TOKEN_QCTRL }} + QISKIT_IBM_URL: ${{ secrets.QISKIT_IBM_URL }} + QISKIT_IBM_INSTANCE: ${{ secrets.QISKIT_IBM_INSTANCE_QCTRL }} + CHANNEL_STRATEGY: q-ctrl + LOG_LEVEL: DEBUG + STREAM_LOG: True + QISKIT_IN_PARALLEL: True + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -c constraints.txt -r requirements-dev.txt -e . + - name: Run q-ctrl tests + run: python -m unittest test/qctrl/test_qctrl.py diff --git a/.github/workflows/unit-tests-terra-main.yml b/.github/workflows/unit-tests-terra-main.yml index 20f99bcb1..70e0b0933 100644 --- a/.github/workflows/unit-tests-terra-main.yml +++ b/.github/workflows/unit-tests-terra-main.yml @@ -18,7 +18,7 @@ on: jobs: unit-tests-latest-qiskit-terra: if: github.repository_owner == 'Qiskit' - name: Run unit tests with latest code of qiskit-terra + name: Run unit tests with latest code of Qiskit runs-on: "ubuntu-latest" env: LOG_LEVEL: DEBUG @@ -35,10 +35,10 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -e . - pip install -c constraints.txt -r requirements-dev.txt - pip install -U git+https://github.com/Qiskit/qiskit-terra.git + # Installing the complete environment should happen in one `pip install` step, + # or pip will generally allow broken combinations of packages to be installed. + pip install -c constraints.txt -r requirements-dev.txt -e . git+https://github.com/Qiskit/qiskit.git - name: Run tests # running unit tests against latest (non-released) code of qiskit-terra gives a basic level # of confidence that the integration between qiskit-ibm-runtime and qiskit-terra works - run: make unit-test \ No newline at end of file + run: make unit-test diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 87e485a6b..b04de6418 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -4,7 +4,7 @@ Contributing First read the overall project contributing guidelines. These are all included in the qiskit documentation: -https://qiskit.org/documentation/contributing_to_qiskit.html +https://github.com/Qiskit/qiskit/blob/main/CONTRIBUTING.md ## Contributing to qiskit-ibm-runtime @@ -13,6 +13,10 @@ https://qiskit.org/documentation/contributing_to_qiskit.html In addition to the general guidelines there are specific details for contributing to qiskit-ibm-runtime, these are documented below. +### Open an issue + +* For documentation issues relating to pages in the Start, Build, Transpile, Verify, Run, and Migration guides sections of https://docs.quantum.ibm.com, please open an issue in the [Qiskit/documentation repo](https://github.com/Qiskit/documentation/issues/new/choose) rather than the Qiskit/qiskit-ibm-runtime repo. In other words, any page that DOES NOT have `/api/` in the url should be addressed in the Qiskit/documentation repo. (Exception: the Migration guide urls contain `/api/` but are managed in the Qiskit/documentation repo.) +* For issues relating to API reference pages (any page that contains /api/ in the url), please open an issue in the repo specific to that API reference. ### Pull request checklist diff --git a/MANIFEST.in b/MANIFEST.in index dbe3951ce..b092be471 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include LICENSE.txt README.md include qiskit_ibm_runtime/VERSION.txt +recursive-include qiskit_ibm_runtime/fake_provider/backends *.json recursive-include test *.py diff --git a/README.md b/README.md index 5bbb1e37d..8bfafc7fe 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ All quantum applications and algorithms level are fundamentally built using thre 2. Define the observable or the classical register to be measured. 4. Execute the quantum circuits by using a primitive (Estimator or Sampler). -**Primitives** are base-level functions that serve as building blocks for many quantum algorithms and applications. The [primitive interfaces](https://qiskit.org/documentation/apidoc/primitives.html) are defined in Qiskit. +**Primitives** are base-level functions that serve as building blocks for many quantum algorithms and applications. The [primitive interfaces](https://docs.quantum.ibm.com/api/qiskit/primitives) are defined in Qiskit. The IBM Runtime service offers these primitives with additional features, such as built-in error suppression and mitigation. @@ -319,7 +319,7 @@ By participating, you are expected to uphold to this code. We use [GitHub issues] for tracking requests and bugs. Please use our [slack] for discussion and simple questions. To join our Slack community use the -invite link at [Qiskit.org]. For questions that are more suited for a forum we +invite link at [ibm.com/quantum/qiskit]. For questions that are more suited for a forum we use the `Qiskit` tag in [Stack Exchange]. ## License @@ -334,7 +334,7 @@ use the `Qiskit` tag in [Stack Exchange]. [code of conduct]: https://github.com/Qiskit/qiskit-ibm-runtime/blob/main/CODE_OF_CONDUCT.md [GitHub issues]: https://github.com/Qiskit/qiskit-ibm-runtime/issues [slack]: https://qiskit.slack.com -[Qiskit.org]: https://qiskit.org +[ibm.com/quantum/qiskit]: https://www.ibm.com/quantum/qiskit [Stack Exchange]: https://quantumcomputing.stackexchange.com/questions/tagged/qiskit [many people]: https://github.com/Qiskit/qiskit-ibm-runtime/graphs/contributors [BibTeX file]: https://github.com/Qiskit/qiskit/blob/master/Qiskit.bib diff --git a/docs/_templates/theme_variables.jinja b/docs/_templates/theme_variables.jinja deleted file mode 100644 index 0c272af50..000000000 --- a/docs/_templates/theme_variables.jinja +++ /dev/null @@ -1,26 +0,0 @@ -{%- set external_urls = { - 'github': 'https://github.com/Qiskit/qiskit-ibm-runtime', - 'github_issues': 'https://github.com/Qiskit/qiskit-ibm-runtime/issues', - 'contributing': 'https://github.com/Qiskit/qiskit/blob/master/CONTRIBUTING.md', - 'docs': 'https://qiskit.org/documentation/', - 'api': 'https://runtime-us-east.quantum-computing.ibm.com/openapi/', - 'ml': 'https://qiskit.org/documentation/machine-learning/', - 'nature': 'https://qiskit.org/documentation/nature/', - 'finance': 'https://qiskit.org/documentation/finance/', - 'optim': 'https://qiskit.org/documentation/optimization/', - 'experiments': 'https://qiskit.org/documentation/experiments/', - 'partners': 'https://qiskit.org/documentation/partners/', - 'twitter': 'https://twitter.com/qiskit', - 'events': 'https://qiskit.org/events', - 'textbook': 'https://qiskit.org/textbook', - 'slack': 'https://qiskit.slack.com', - 'home': 'https://qiskit.org/', - 'blog': 'https://pytorch.org/blog/', - 'resources': 'https://qiskit.org/learn', - 'support': 'https://pytorch.org/support', - 'youtube': 'https://www.youtube.com/qiskit', - 'iqx': 'https://quantum-computing.ibm.com/', - 'iqx_systems': 'https://quantum-computing.ibm.com/docs/manage/backends/', - 'ibm': 'https://www.ibm.com/quantum-computing/', -} --%} diff --git a/docs/cloud/architecture-workload-isolation.rst b/docs/cloud/architecture-workload-isolation.rst deleted file mode 100644 index 2b64de908..000000000 --- a/docs/cloud/architecture-workload-isolation.rst +++ /dev/null @@ -1,15 +0,0 @@ -Learning about Qiskit Runtime architecture and workload isolation -================================================================= - - -Qiskit Runtime jobs run in individual containers in an internal Kubernetes cluster to isolate jobs from any other activities of other users. Jobs are not shared or visible between service instances. However, all users that can access a service instance can see that instance’s jobs, or submit jobs the account owner might be charged for. - - -Restricting access to service instances ---------------------------------------- - -With Qiskit Runtime, you can create service instances that are IAM-managed resources. Accordingly, IAM-based access control can be used for these service instances. -User access to Qiskit Runtime service instances can be configured through different mechanisms: -- Resource groups can be used to group service instances. This lets you manage access permissions based on resource group assignment. -- Access groups can be used to assign access to individual service instances. Service IDs (with their API keys) can be assigned to these access groups. -- IAM tags can be used to categorize service instances and use these tags through access groups. diff --git a/docs/cloud/at-events.rst b/docs/cloud/at-events.rst deleted file mode 100644 index b5839ed0a..000000000 --- a/docs/cloud/at-events.rst +++ /dev/null @@ -1,38 +0,0 @@ -Audit events for Qiskit Runtime -=============================== - -As a security officer, auditor, or manager, you can use the IBM Cloud® Activity Tracker service to track how users and applications interact with the Qiskit Runtime service in IBM Cloud. - -IBM Cloud Activity Tracker records user-initiated activities that change the state of a service in IBM Cloud. You can use this service to investigate abnormal activity and critical actions and to comply with regulatory audit requirements. In addition, you can be alerted about actions as they happen. The events that are collected comply with the Cloud Auditing Data Federation (CADF) standard. For more information, see the `getting started tutorial for IBM Cloud Activity Tracker `__. - -List of data events -------------------- - -The following table lists the Qiskit Runtime actions that generate an event: - -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| Action | Description | -+======================================+=================================================================================================================================================+ -| ``quantum-computing.program.create`` | An event is generated a program is uploaded. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| ``quantum-computing.program.read`` | An event is generated when program information is returned. For example, when you list programs or program details. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| ``quantum-computing.program.delete`` | An event is generated when a program is deleted. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| ``quantum-computing.program.update`` | An event is generated when a program metadata is updated. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| ``quantum-computing.job.create`` | An event is generated when a job is started. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| ``quantum-computing.job.read`` | An event is generated when job information is returned. This includes listing jobs, job details, job results, job interim results, or job logs. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| ``quantum-computing.job.delete`` | An event is generated when a job is deleted. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ -| ``quantum-computing.job.cancel`` | An event is generated when a job is cancelled. | -+--------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------+ - -Viewing events --------------- - -Events that are generated by an instance of the *YourServiceName* service are automatically forwarded to the IBM Cloud Activity Trackerservice instance that is available in the same location. - -IBM Cloud Activity Tracker can have only one instance per location. To view events, you must access the web UI of the IBM Cloud Activity Tracker service in the same location where your service instance is available. For more information, see `Launching the UI `__. \ No newline at end of file diff --git a/docs/cloud/cloud-provider-org.rst b/docs/cloud/cloud-provider-org.rst deleted file mode 100644 index 19668c356..000000000 --- a/docs/cloud/cloud-provider-org.rst +++ /dev/null @@ -1,370 +0,0 @@ -Manage users -====================== - -You can manage IBM Cloud users or ID provider (IDP) users. Follow the instructions in the relevant section, depending on your setup. - -* :ref:`cloud-users` -* :ref:`provider-cloud` -* :ref:`provider-appid` - -.. _cloud-users: - -Manage IBM Cloud users ----------------------- - -This tutorial shows how to use IBM Cloud to enable users who have IBM Cloud accounts and gives instructions for users to access the environment. - - -Invite users -~~~~~~~~~~~~~ - -1. Ensure that the users that you want to invite have IBM Cloud accounts. - -2. Go to Manage → Access (IAM) and click `Invite users `__. - -3. Enter the email addresses of users to be added. - -4. Select the access group or groups of the projects that the users will be part of. These assignments can be changed later. - -5. Click Add to confirm the access group selection. - -6. Click Invite to send the invitation to the users. - - .. note:: - Users cannot be managed until they accept the invitation and log in at least once. - -Optional: Modify users’ project assignments -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. Go to `Manage → Access (IAM) → Users `__ and click the user. - - |change1| - -2. Add access groups with **Assign group** or remove the user from an access group by clicking the three dot menu and choosing **Remove user**. - -User flow -~~~~~~~~~~~~~ - -1. After they accept an invitation, users can log in through the `IBM Cloud portal `__. -2. To work with Qiskit Runtime service instances, users must create an API key by going to `Manage → Access (IAM) → API keys `__. -3. For further information, users can review `Getting started, Step 2 `__. - -Example scenario -~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In our example, we want to create the following setup: - -- We have two projects, ``ml`` and ``finance``. - - - The ``ml`` project should have access to the service instances ``QR-ml`` and ``QR-common``. - - The ``finance`` project should have access to the service instances ``QR-finance`` and ``QR-common``. - -- We have three users: - - - Fatima should have access to the ``ml`` project. - - Ravi should have access to the ``finance`` project. - - Amyra should have access to both projects. - -- We will use access groups without resource groups. -- Users are defined in IBM Cloud and project assignments are done there as well. -- Users should be able to delete jobs. - -The steps to implement this setup are: - -2. The Cloud administrator creates three service instances: ``QR-ml``, ``QR finance``, and ``QR-common``. -3. The Cloud administrator creates a custom rule that includes the ``quantum-computing.job.delete`` action. -4. The Cloud administrator creates two access groups: - - - The ``ml`` access group can access ``QR-ml`` and ``QR-common``. - - The ``finance`` access group can access ``QR-finance`` and ``QR-common``. - -5. The Cloud administrator invites cloud users to the appropriate project. Specifically, they invite and assign users to an access group that includes the project. - - - Fatima is added to the “ml” access group. - - Ravi is added to the “finance” access group. - - Amyra is added to both the “ml” and “finance” access groups. - -6. Users can log in through the IBM Cloud portal, create API keys, and work with their projects’ service instances. - -.. _provider-cloud: - -Manage ID provider users with IBM Cloud -------------------------------------------- - -App ID creates an ID provider so you can add users directly in App ID or connect to other external ID providers. This tutorial describes how to set up your ID provider to work with IBM Cloud users, and gives instructions for users to access the environment. - - -Create an App ID instance -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. `Open App ID from the IBM Cloud catalog `__ and log in if necessary. Specify the following values: - - - For **Select a location**, it is recommended that you keep it in the same location as the Qiskit Runtime service, which is ``Washington DC (us-east)``. - - **Select a pricing plan**: - - - The **Lite** plan is free of charge and is enough to get started. If needed, you can seamlessly upgrade to the graduated tier later. - - The **Graduated tier** is paid per event and per user beyond the lite tier limits. This tier supports more features such as multi-factor authentication. The Cloud administrator as the owning account of the App ID instance is charged for any fees for the graduated tier instances. - - - Complete the values for **Service name** (the App ID instance name), **Resource group** (if one is being used), and any tags you want. - - |create1| - -2. Read and agree to the terms and click **Create**. - -Configure the ID provider -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We will use the **Cloud Directory** capability to add users to the ID provider. Refer to the `App ID documentation `__ for instructions how to integrate other ID providers into App ID. - -1. Open the `IBM Cloud resource list `__, expand the **Services and software** section, find your App ID instance and click its name to view its details. -2. Click **Manage Authentication** and deselect any login options that you don’t need, such as Facebook and Google. -3. Go to **Manage Authentication** → **Cloud Directory** → **Settings** and choose whether user logins should use email or usernames. -4. Optionally go to **Manage Authentication** → **Cloud Directory** → **Password Policies** to define the password strength. -5. Optionally open **Login Customization** and customize the appearance of the login page. - -Integrate the App ID instance as the ID provider for the administrator’s account -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. Go to `Manage → Access (IAM) → Identity Providers `__. For **Type**, choose **IBM Cloud App ID**, then click **Create**. - -2. Specify a name and select the App ID instance from the drop-down list. - -3. Select the checkbox to enable the ID provider. - - |identity1| - -4. The default IdP URL is shown. Share this URL with users when they need to log in. - -Add users -~~~~~~~~~~ - -When you use App ID as ID provider with the Cloud directory, you can create users in the IBM Cloud user interface. - -1. Open the App ID instance page from the `resource list `__ Services and software section. -2. Go to **Manage Authentication** → **Cloud Directory** → **Users**, and click **Create User**. Enter the user details. - -Create or modify users’ project assignments -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. Go to `Manage → Access (IAM) → Users `__ and click the user. - - |access1| - - .. note:: - If you don’t see the user that you want to manage, verify that they logged in to IBM Cloud at least once. See step 1 in the :ref:`user-org`. - -2. Add access groups with **Assign group** or remove the user from an access group by clicking the three dot menu and choosing **Remove user**. - -.. _user-org: - -User flow -~~~~~~~~~~~~ - -1. A user is sent the ID provider URL for the IBM Cloud account. They use this URL and the login information to access the system. The user should change their password after they log on. - - .. note:: - The administrator can always go to `Manage → Access (IAM) → Identity providers `__ to look up the ID provider URL. - -2. To work with Qiskit Runtime and access service instances, users need to create an API key from `Manage → Access (IAM) → API keys `__. - -3. For further information, users can review `Getting started, Step 2 `__. - -Example scenario -~~~~~~~~~~~~~~~~ - -In our example, we want to create the following setup: - -- We have two projects, ``ml`` and ``finance``. - - - The ``ml`` project needs access to the service instances ``QR-ml`` and ``QR-common``. - - The ``finance`` project should have access to the service instances ``QR-finance`` and ``QR-common``. - -- We have three users: - - - Fatima needs access to the ``ml`` project. - - Ravi needs access to the ``finance`` project. - - Amyra needs access to both projects. - -- We will use access groups without resource groups. -- Users are defined in IBM Cloud but project assignments are done in an App ID instance. -- Users should be able to delete jobs. - -The steps to implement this setup are: - -1. The Cloud administrator creates an App ID instance and ensures that it is linked in the Cloud administrator’s account. The administrator notes the ID provider URL to share it with users. -2. The Cloud administrator creates three service instances: ``QR-ml``, ``QR finance``, and ``QR-common``. -3. The Cloud administrator creates a custom rule that includes the ``quantum-computing.job.delete`` action. -4. The Cloud administrator creates two access groups: - - - The ``ml`` access group can access ``QR-ml`` and ``QR-common``. This access group needs a dynamic rule for the App ID IDP that accepts users whose ``project`` attribute contains ``ml``. - - The ``finance`` access group can access ``QR-finance`` and ``QR-common``. This access group nees a dynamic rule for the App ID IDP that accepts users whose ``project`` attribute contains ``finance``. - -5. The ID provider administrator defines the three users in the IBM Cloud user interface. -6. Users log in at least once. -7. The cloud administrator assigns access by adding users to the access groups that give them access to the projects: - - - Fatima is given access to the ``ml`` project. - - Ravi is given access to the ``finance`` project. - - Amyra is given access to the ``ml`` and ``finanace`` projects. - -8. Users can log in through the ID provider URL, create API keys, and work with their projects’ service instances. - -.. _provider-appid: - -Manage ID provider users with the ID provider ------------------------------------------------ - -App ID creates an ID provider so you can add users directly in App ID or connect to other external ID providers. This tutorial describes how to set up your ID provider to work with users that do not have IBM Cloud accounts. - - -Create an App ID instance -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. `Open App ID from the IBM Cloud catalog `__ and log in. Specify the following values: - - - For **Select a location**, it is recommended to keep it in the same location as the Qiskit Runtime service, which is ``Washington DC (us-east)``. - - **Select a pricing plan**: - - - The **Lite** plan is free of charge and is enough to get started. If needed, you can seamlessly upgrade to the graduated tier later. - - The **Graduated tier** is paid per event and per user beyond the Lite tier limits. This tier supports more features such as multi-factor authentication. The Cloud administrator as the owning account of the App ID instance is charged for any costs for the graduated tier instances. - - - Complete the values for **Service name** (the App ID instance name), **Resource group** (if one is being used), and any tags you want. - - |create| - -2. Read and agree to the terms and click **Create**. - -Configure the ID provider -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We will use the **Cloud Directory** capability to add users to the ID provider. Refer to the `App ID documentation `__ for instructions how to integrate other ID providers into App ID. - -1. Open the `IBM Cloud resource list `__, expand the **Services and software** section, find your App ID instance and click its name to view its details. -2. Click **Manage Authentication** and deselect any login options that you don’t need, such as Facebook and Google. -3. Navigate to **Manage Authentication** → **Cloud Directory** → **Settings** and choose whether user logins should use email or usernames. -4. Optional: Open **Manage Authentication** → **Cloud Directory** → **Password Policies** to define the password strength. -5. Optionally open **Login Customization** and customize the appearance of the login page. - -Integrate the App ID instance as the ID provider for the administrator’s account -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -1. Go to `Manage → Access (IAM) → Identity Providers `__. For **Type**, choose **IBM Cloud App ID**, then click **Create**. - -2. Specify a name and select the App ID instance from the drop-down list. - -3. Select the checkbox to enable the ID provider. - - |identity| - -4. The default IdP URL is shown. Share this URL with users when they need to log in. - -Add a dynamic rule to the access group -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The access group needs a dynamic rule to test whether it should be applied to an IDP user when they log in. - -Because the dynamic rules are evaluated during login, any changes are picked up the next time the user logs in. {: note} - -1. Navigate to `Manage → IAM → Access groups `__ and click your access group to open its details page. -2. Click the **Dynamic rules** tab, then click **Add**. - - - Provide a name. - - For the Authentication method, choose **Users federated by IBM Cloud AppID**, then select the IDP from the Identity provider drop-down list. - - |Dynamic| -3. Click **Add a condition**, complete the following values, then click **Add**. - - - In the **Allow users when** field, enter the attribute key that is used by the IDP administrator in the ID provider user attributes, such as ``project`` (this string is a convention that is defined during planning). - - Select **Contains** as the **Qualifier**. - - In **Values**, enter the value, such as ``ml``. This is the same value that the IDP administrator uses in the IDP user profile definition. It is typically the project name. - - You might want to increase the **Session duration** to increase the period before users must log back in. Logged-in users keep their access group membership for that period, and reevaluation takes place on the next login. - - |Condition| - -Add users -~~~~~~~~~~~~~~~ - -When you use App ID as ID provider with the Cloud directory, you can create users in the Cloud user interface. - -1. Open the App ID instance page from the `resource list `__ Services and software section. -2. Go to **Manage Authentication** → **Cloud Directory** → **Users**, and click **Create User**. Enter the user details. - -Create or modify users’ project assignments -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -If the IDP administrator will assign users to projects, you can define project values in the user’s attributes. - -1. Open the App ID instance page from the `resource list `__ Services and software section. - -2. Go to **Manage Authentication** → **Cloud Directory** → **Users**, and click a user to open it. - -3. Scroll down to **Custom Attributes**, and click **Edit**. - -4. Enter a key value pair that can will checked by the dynamic rules of the access groups, then click **Save**. You can add several values in the same string (for example, ``{"project":"ml finance"}``); the **contains** qualifier of the dynamic rule detects a match of a substring. For our example, add ``{"project":"ml"}``. - - The value ``project`` corresponds to the convention defined in the planning section. ``ml`` is the project that the user belongs to. - - This check is done on every login, so changes in the ID provider user attributes will be effective when a user next logs in. - -User flow -~~~~~~~~~~~ - -1. A user is sent the ID provider URL for the IBM Cloud account. - - .. note:: - The administrator can always go to `Manage → Access (IAM) → Identity providers `__ to look up the ID provider URL. - -2. To work with Qiskit Runtime service instances, users must create an API key by going to `Manage → Access (IAM) → API keys `__. - -3. For further information, users can review `Getting started, Step 2 `__. - -Example scenario -~~~~~~~~~~~~~~~~ - -In our example, we want to create the following setup: - -- We have two projects, ``ml`` and ``finance``. - - - The ``ml`` project needs access to the service instances ``QR-ml`` and ``QR-common``. - - The ``finance`` project needs access to the service instances ``QR-finance`` and ``QR-common``. - -- We have three users: - - - Fatima needs access to the ``ml`` project. - - Ravi needs access to the ``finance`` project. - - Amyra needs access to both projects. - -- We will use access groups without resource groups. -- Users are defined in an App ID instance and project assignments are also done there. -- Users should be able to delete jobs. - -The steps to implement this setup are: - -1. The Cloud administrator creates an App ID instance and ensures that it is linked in the Cloud administrator’s account. The administrator notes the ID provider URL to share it with users. -2. The Cloud administrator creates three service instances: ``QR-ml``, ``QR finance``, and ``QR-common``. -3. The Cloud administrator creates a custom rule that includes the ``quantum-computing.job.delete`` action. -4. The Cloud administrator creates two access groups: - - - The ``ml`` access group can access ``QR-ml`` and ``QR-common``. This access group needs a dynamic rule for the App ID IDP that accepts users whose ``project`` attribute contains ``ml``. - - The ``finance`` access group can access ``QR-finance`` and ``QR-common``. This access group needs a dynamic rule for the App ID IDP that accepts users whose ``project`` attribute contains ``finance``. - -5. The IDP administrator uses the App ID instance that the Cloud administrator created and defines the three users: - - - For Fatima, the custom attributes contain ``{"project":"ml"}``. - - For Ravi, the custom attributes contain ``{"project":"finance"}``. - - For Amyra, the custom attributes contain ``{"project":"ml finance"}``. - -6. Users can log in through the ID provider URL, create API keys, and work with their projects’ service instances. - - -Next steps ----------- - -.. |create1| image:: ../images/org-guide-create-appid.png -.. |identity1| image:: ../images/org-guide-idp-reference.png -.. |access1| image:: ../images/org-guide-manage-user.png -.. |change1| image:: ../images/org-guide-manage-user.png -.. |create| image:: ../images/org-guide-create-appid.png -.. |identity| image:: ../images/org-guide-idp-reference.png -.. |Dynamic| image:: ../images/org-guide-create-dynamic-rule1.png -.. |Condition| image:: ../images/org-guide-create-dynamic-rule2.png diff --git a/docs/cloud/cost.rst b/docs/cloud/cost.rst deleted file mode 100644 index b313daa53..000000000 --- a/docs/cloud/cost.rst +++ /dev/null @@ -1,80 +0,0 @@ -Manage costs -############ - -The Standard plan is not free, except when running jobs on simulators. Use the information in this topic to help you understand how much you’re paying and how to limit your costs. - -Time limits on programs -*********************** - -The maximum execution time for the Sampler primitive is 10000 seconds (2.78 hours). The maximum execution time for the Estimator primitive is 18000 seconds (5 hours). - -Additionally, the system limit on the system execution time is 3 hours for a job that is running on a simulator and 8 hours for a job running on a physical system. - -How to limit your cost -*********************** - -The time your job takes (and therefore, its cost) depends on how many iterations you make in a session and how many shots are run in each iteration. Therefore, you can manage your cost by running only as many iterations and shots as you need. - -Additionally, an instance administrator can limit how much is spent. To set cost limits, navigate to the `IBM Cloud Instances page `__, then click the instance and set the **Cost limit**. The cost limit refers to the total cost of all jobs run with this instance since it was created, and it will always be greater than or equal to the Total cost. After the instance reaches the specified number of total seconds, no further jobs can be run and no more cost is incurred. - -.. note:: - The cost limit is always specified in US dollars (USD), then converted to runtime seconds. However, for monthly billing purposes, you are charged in your local currency, specified on your IBM Cloud account. Because currency exchange rates can fluctuate, the cost for `X` runtime seconds might be different when initially calculated in USD than when you're actually charged in your local currency. As a result, if your local currency is not USD, the total amount charged for the number of seconds specified in this field could vary from the dollar amount you specify. - -How to remove a cost limit -**************************** - -An instance administrator can remove the cost limit. To do so, navigate to the `IBM Cloud Instances page `__, then open the instance and click the edit button by the **Cost limit**. Delete the value and click **Save**. - -What happens when the cost limit is reached -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When the instance's cost limit is reached, the currently running job is stopped. Its status is set to `Cancelled` with a reason of `Ran too long`. Any available partial results are kept. - -No further jobs can be submitted by using this instance until the cost limit is increased. - - -How to see what you’re being charged -************************************* - -You are sent a monthly invoice that provides details about your resource charges. You can check how much has been spent at any time on the `IBM Cloud Billing and usage page `__. - -Additionally, you can determine cost per instance or per job at any time. - -View instance cost -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To determine how much has been billed to an instance during the current billing cycle, from the `Instances page `__, click the instance to open its details page. - -These are the fields relevant to cost: - -- **Billing cycle usage**: Qiskit Runtime usage by this instance during the current billing cycle. This usage is the time counted by Qiskit Runtime to process a job, and is determined by the use of internal resources. -- **Billing cycle cost**: The total cost of running jobs during the current billing cycle. -- **Total usage**: Qiskit Runtime usage by this instance since it was created. -- **Total cost**: The total cost of running jobs on this instance since it was created (only administrators can set this value). - -You can view your billing cycle on the `Billing and usage page `__. - -View job cost -~~~~~~~~~~~~~~~~ - -To determine how much has been billed to each job associated with an instance, from the `Instances page `__, click the instance to open its details page. Next, on the left side, click Jobs. - -These are the columns relevant to cost: - -- **Usage**: Qiskit Runtime used by this job. This usage is the time counted by Qiskit Runtime to process a job, and is determined by the use of internal resources. -- **Cost**: The total cost of running this job - - -Set up spending notifications -******************************* - -You can set up spending notifications to get notified when your account or a particular service reaches a specific spending threshold that you set. For information, see the `IBM Cloud account Type description `__. IBM Cloud spending notifications must be used with other methods of cost management for several reasons: - -- The notifications trigger only *after* cost surpasses the specified limit. -- Cost is submitted to the billing system hourly. Therefore, a long delay might occur between the job submission and the spending notification being sent. -- The billing system can take multiple days to get information to the invoicing system, which might cause further delay in notifications. For more information about how the IBM Cloud billing system works, see `Setting spending notifications `__. - -Next steps -****************** - -See `Qiskit Runtime plans `__ to learn about the plans. diff --git a/docs/cloud/data-security.rst b/docs/cloud/data-security.rst deleted file mode 100644 index 2fa4d5fbe..000000000 --- a/docs/cloud/data-security.rst +++ /dev/null @@ -1,21 +0,0 @@ -Securing your data in Qiskit Runtime -==================================== - -To ensure that you can securely manage your data when you use Qiskit Runtime, it is important to know exactly what data is stored and encrypted and how you can delete any stored data. - - -Protecting your sensitive data in Qiskit Runtime ------------------------------------------------- - -The data that you store in IBM Cloud is encrypted at rest by using a randomly generated key. - - -Deleting your data in Qiskit Runtime ------------------------------------- - -Deleting a service instance removes all of the content associated with that instance, such as your jobs, results, parameters, and programs. To delete an instance, from the `Instances page `__, find the instance you want to remove, click its overflow menu, then click **Delete**. You will be asked to confirm the deletion. - -Deleting Qiskit Runtime instances -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -The Qiskit Runtime data retention policy describes how long your data is stored after you delete the service. The data retention policy is included in the Qiskit Runtime service description, which you can find in the `IBM Cloud Terms `__. diff --git a/docs/cloud/plans.rst b/docs/cloud/plans.rst deleted file mode 100644 index bb071dd3c..000000000 --- a/docs/cloud/plans.rst +++ /dev/null @@ -1,59 +0,0 @@ -Qiskit Runtime plans -==================== - -The Qiskit Runtime service offers these plans for running quantum programs: - -- Lite Plan: Simulator access plan (free) -- Standard Plan: Quantum hardware and simulator access plan - -Lite plan ---------- - -A free plan that gives you access to quantum simulators to help you get started with Qiskit Runtime. It does not include access to IBM Quantum systems. The following simulators are included in this plan: - -- ``ibmq_qasm_simulator``: A general-purpose simulator for simulating quantum circuits both ideally and subject to noise modeling. The simulation method is automatically selected based on the input circuits and parameters. - - - **Type**: General, context-aware - - **Simulated Qubits**: 32 - -- ``simulator_statevector``: Simulates a quantum circuit by computing the wave function of the qubit’s state vector as gates and instructions are applied. Supports general noise modeling. - - - **Type**: Schrödinger wave function - - **Simulated Qubits**: 32 - -- ``simulator_mps``: A tensor-network simulator that uses a Matrix Product State (MPS) representation for the state that is often more efficient for states with weak entanglement. - - - **Type**: Matrix Product State - - **Simulated Qubits**: 100 - -- ``simulator_stabilizer``: An efficient simulator of Clifford circuits. Can simulate noisy evolution if the noise operators are also Clifford gates. - - - **Type**: Clifford - - **Simulated Qubits**: 5000 - -- ``simulator_extended_stabilizer``: Approximates the action of a quantum circuit by using a ranked-stabilizer decomposition. The number of non-Clifford gates determines the number of stabilizer terms. - - - **Type**: Extended Clifford (for example, Clifford+T) - - **Simulated Qubits**: 63 - -Standard plan -------------- - -A pay-as-you-go plan for accessing IBM Quantum systems. Build your own programs and access all the benefits of Qiskit Runtime by running on real quantum hardware, while maintaining access to all of the simulators available in the Lite plan. - -Pricing overview ----------------- - -The Lite plan is free. The Standard plan charges you per *QR second* when running on physical systems. The following diagram illustrates what is included in a QR second. Any time spent waiting for results or in the queue for the quantum computer are excluded. - -.. figure:: ../images/Runtime_Accounting_Diagram.png - :alt: This diagram shows that everything before the program starts (such as queuing) is free. After the job starts, it costs $1.60 per second. - - -Qiskit Runtime usage is the time counted by Qiskit Runtime to process a job, and is determined by the use of internal resources. - -Next steps ----------- - -See `Manage costs `__ to learn how to determine and minimize your costs. - diff --git a/docs/cloud/quickstart-org.rst b/docs/cloud/quickstart-org.rst deleted file mode 100644 index 9d51362b8..000000000 --- a/docs/cloud/quickstart-org.rst +++ /dev/null @@ -1,117 +0,0 @@ -Plan Qiskit Runtime for an organization -======================================= - -In an organization where individuals might work on several projects, Qiskit Runtime governance can seem complex. However, access management can be used to easily enable user collaboration and to restrict visibility of users and projects when necessary. Managing access becomes more relevant with Qiskit Runtime resources that are not free: that is, Qiskit Runtime service instances that use the Standard plan (which organizations are charged for). - -Overview --------- - -.. note:: - - IBM Cloud provides various ways to implement these mechanisms described in this tutorial. There are several ways to achieve these objectives. Additionally, most of the steps in this tutorial are generic to IBM Cloud and not specific to Qiskit Runtime, except the custom role details. - -Involved personas -~~~~~~~~~~~~~~~~~ - -The are several main personas that are mentioned in this tutorial: - -- **User**: Someone who gets access to Qiskit Runtime resources (*service instances*) and can potentially collaborate with other users on these resources. Users’ access is controlled by an administrator and they cannot create or delete service instances. -- **Cloud administrator**: An IBM Cloud account owner who owns Qiskit Runtime resources and manages which users can access these resources. As the resource owner, the administrator is charged for any paid resource use. -- **IDP administrator**: An administrator who defines identities and their attributes in an identity provider (IDP). - -Terminology -~~~~~~~~~~~ - -This tutorial uses the following terms: - -- *Resource*: A generic IBM Cloud term that refers to an object that can be managed through the Cloud user interface, CLI, or API. For this tutorial, a *resource* is a Qiskit Runtime service instance. - -- *Service instance*: A service instance is used to access Cloud functions. Specifically, quantum computing on real devices or simulators. It is defined through the catalog. You can define several service instances based on the same or different plans, which offer access to different quantum computing backends. See `Qiskit Runtime plans `__ for more details. - -- *Project*: A grouping unit that enables users to work on the same resources. This tutorial uses two projects; ``ml`` and ``finance``. See `Hierarchical project structures `__ for more information. - - .. note:: - - This project is not related to the “project” concept in IBM Quantum Platform. - -Decisions ---------------- - -Before you set up Qiskit Runtime for your organization, you need to make these decisions: - -- How are user identities defined? You can set up IBM Cloud users, users from another IDP, or both. - - - If you are using a different IDP, does the Cloud administrator or the IDP administrator assign users to project resources? - - If the IDP administrator assigns users to projects, you need a string to be used as a key, such as ``project`` (which this tutorial uses) for project comparisons. - -- What are the projects and which service instances will belong to each? You must plan your project names carefully. - - - Do not make project names substrings of another. For example, if you use ``ml`` and ``chemlab`` for project names, then later you set up a project match for ``ml``, it triggers on both values, accidentally granting more access than expected. Instead, use unique names such as ``ml`` and ``chem-lab``. Alternatively, use prefix or suffix values to avoid such unintended substring matches. - - Using naming conventions, along with prefix or suffix values can help you easily allow access to several projects. - - Quantum experiments (jobs) belong to service instances, and users that have access to an instance can see its jobs. - - Service instances can be based on different plans, allowing access to different backends like real devices or simulators. See `Choose a system or simulator <../how_to/choose-system.html>`__ for details. - -- Which users need to access which projects? -- Should users be able to delete jobs? Keeping jobs in service instances gives more traceability for billing costs. This information combines well with the audit trail of `Activity Tracker `__, which tracks which user submitted the job. -- Will you use access groups that directly reference Qiskit Runtime service instances or organize services into resource groups? - - - **Access groups** are a convenient and common way of controlling user access for IBM Cloud resources. They are a simple but powerful means to consistently assign user access. We create an access group for each project and map users to access groups. Each access group uses a custom role that allows users to access specific Qiskit Runtime service instances or resource groups. - - **Resource groups** are used only when you need to maintain a clear separation of service instances. If more service instances are created in a resource group, all users that have access to the resource group see them automatically without updating access groups. If you choose to use resource groups, you will create access groups and then assign them to resource groups. - - .. note:: - - A service instance can belong to only one resource group, and after instances are assigned into resource groups, they cannot be changed. This also means that the resource group assignment can happen only at service instance creation. Therefore, resource groups might not provide enough flexibility if assignments of service instances to resource groups might need to change. - -Considerations ----------------- - -You should understand the following considerations when setting up your environment. - -Auditability -~~~~~~~~~~~~~ - -Activity tracker logs significant actions performed on Qiskit Runtime service instances. Create an instance of Activity Tracker in the region of your Qiskit Runtime instances to get an audit trail of events. Refer to the Qiskit Runtime `Activity Tracker page `__ for details about the events logged. - -This audit log contains the fields ``initiator_authnName`` and ``initiator_authnId``, which match the name shown in `Manage → Access (IAM) → Users `__. To view this field, click on the user name, then **Details** in the **IAM ID** field. - -|event| - -To capture App ID events, open your App ID instance, then navigate to **Manage Authentication -> Authentication settings** and enable **Runtime Activity**. - -Define more fine grained roles -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The actions in the custom roles can be used for more fine grained access control. For example, some users might need full access to work on service instances, while others might only need Read access to service instances, programs, and jobs. - -To achieve that, define two different custom roles such as ``MLreader`` and ``MLwriter``. Remove all cancel, delete, and update roles from the ``MLreader`` custom role, and include all actions in the ``MLwriter`` custom role. Next, add the roles to two different access groups accordingly. - -When using dynamic rules, that is, when the IDP administrator manages access through custom IDP user attributes, do not use IDP custom user attributes that are substrings of each other. For instance, don't use ``ml`` and ``mlReader``, as the string comparison of ``ml`` would also accept ``mlReader``. You could use ``MLreader`` and ``MLwriter`` to avoid this conflict. - -For an example of setting up custom roles, see `Create access groups for projects `__. - -Other Cloud resources -~~~~~~~~~~~~~~~~~~~~~~~ - -The steps used in this tutorial can be used to manage access to other Cloud resources as well. Include the appropriate permissions to the access groups of the relevant projects. - -Hierarchical project structures -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -In this tutorial, the mapping of users to projects and service instances was kept simple. However, by associating several users with access groups and referencing service instances from several access groups, more complex mappings can be implemented. - -This method can accommodate a hierarchical structure. That is, it can align to how users might be assigned to the Hub/Group/Project access structure in the IBM Quantum Platform. For example, a *group* could be an access group that is assigned to all service instances of the group’s projects. Users who should get access to all of the group’s projects would then only have to be added to the group’s access group. - -Consistent and repeatable deployment of the configuration -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The steps of this tutorial can be automated for consistent and repeatable management of users, projects, and the mapping between those. Refer to the `Terraform IBM Cloud Provider documentation `__ for templates. - - - - -Next steps ----------- - -See `Configure Qiskit Runtime for an organization `__ for the steps to set up Qiskit Runtime. - -.. |event| image:: ../images/org-guide-audit-example.png diff --git a/docs/cloud/quickstart-steps-org.rst b/docs/cloud/quickstart-steps-org.rst deleted file mode 100644 index e4aca0031..000000000 --- a/docs/cloud/quickstart-steps-org.rst +++ /dev/null @@ -1,108 +0,0 @@ -Configure Qiskit Runtime for an organization -============================================ - -Follow these steps to start setting up Qiskit runtime. - -Configure IAM settings ----------------------- - -First, configure some settings in the administrator’s Identity and Access Management (IAM) account. To review and configure these settings, go to `Manage → IAM → Settings `__. - -- **User list visibility** determines whether users can see each other, regardless of project assignment. The ``enabled`` setting restricts user visibility. That is, users in your account cannot see each other, even if they can access the same resources. Choose the appropriate value for your environment. See `Controlling user visibility `__ for more information. -- **API key creation** controls whether users can create API keys. In Qiskit Runtime, it is common to use API keys. If API keys are being used, choose ``disabled``. Alternatively, you can give specific permissions to each user. - -|IAM settings| - -.. _create-group-org: - -(Optional) Create resource groups ---------------------------------- - -Skip this step if you are using access groups that directly reference Qiskit Runtime service instances. - -If you chose to use resource groups, go to `Manage → Account → Resource groups (in Account resources) `__ and click **Create**. - -Create Qiskit Runtime service instances ---------------------------------------- - -If you already created Qiskit Runtime service instances, skip this step. - -- If you are using resource groups, make sure to create the service instances on the appropriate resource group. -- The service instance name, such as ``QR-ml``, is needed for access group references. - -1. From the `Qiskit Runtime Provisioning page `__, select the Create tab, then choose the appropriate service plan, depending on what you need access to: - - - **Lite**: Free simulators-only plan to help you get started with Qiskit Runtime. Learn to use Qiskit Runtime by following our examples and tutorials for one of the pre-built programs available for running circuits efficiently. - - **Standard**: A pay-as-you-go model for accessing IBM Quantum systems and simulators. Build your own programs and use all the benefits of Qiskit Runtime by running on real quantum hardware. - -2. Complete the required information, then click **Create**. - -Create access groups for projects ---------------------------------- - -First, we create a custom role that allows users to perform actions to work with Qiskit Runtime service instances for each access group. Next, we create an access group for each project and give that group the minimum set of permissions that are required to work with the project resources. In a later step, we map users to access groups. - -Follow these steps to set up an access group: - -1. Create a custom role. - - 1. From `Manage → IAM → Roles `__, click ``Create``. - - 2. Enter a name, ID, description, and select ``Qiskit Runtime`` from the service, as shown in the image: - - |custom| - - 3. Select the following roles, then click **Create**. - - - quantum-computing.device.read - - quantum-computing.job.cancel - - quantum-computing.job.create - - quantum-computing.job.read - - quantum-computing.program.create - - quantum-computing.program.delete - - quantum-computing.program.read - - quantum-computing.program.update - - quantum-computing.user.logout - - Select quantum-computing.job.delete if you want to allow users to delete jobs. - - |actions| - - .. note:: - You can optionally define more fine grained roles by following `these instructions `__. - -2. Create an access group. - - 1. Go to `Manage → IAM → Access groups `__ and click **Create**. - 2. Enter a name, like ``project-ml``, and a description. - -3. Assign access to the group. - - 1. Select the Access tab and click **Assign access**. - - 2. In the Service list, search for **Qiskit Runtime** and select it, then click **Next**. - - |service| - - 3. In Resources, select **Specific resources**. For Attribute type, choose **Service Instance**. - - 4. From the drop-down list, select the service instance that you want to add to the access group, for example, ``QR-ml``. If you are using resource groups, select the resource group instead of selecting individual service instances. Click **Next**. - - |resources| - - 5. For Roles and actions, select **Viewer** and the custom role that was created previously. Click **Add**, then **Assign**. - - |roles| - - 6. Repeat this step if you want to give an access group permission to several service instances. - -Set up your ID provider and assign users ----------------------------------------- - -Follow the steps in the `manage users topic `__. - -.. |IAM settings| image:: ../images/org-guide-iam-settings.png -.. |custom| image:: ../images/org-guide-create-custom-role.png -.. |actions| image:: ../images/org-guide-custom-role-actions.png -.. |service| image:: ../images/org-guide-create-access-group-1.png -.. |resources| image:: ../images/org-guide-create-access-group-2.png -.. |roles| image:: ../images/org-guide-create-access-group-3.png diff --git a/docs/cloud/quickstart.rst b/docs/cloud/quickstart.rst deleted file mode 100644 index 29bfc77ea..000000000 --- a/docs/cloud/quickstart.rst +++ /dev/null @@ -1,104 +0,0 @@ -Getting started -================ - -This tutorial walks you through the steps to set up a Qiskit Runtime service instance, log in to your service instance, and run your first job on a quantum computer. - -If you are an administrator who needs to set up Qiskit Runtime for your organization, refer to `Plan Qiskit Runtime for an organization `__ for instructions to set up a service instance and work with users. - - -Create a service instance ---------------------------------- - - -If you already created a Qiskit Runtime service instance or were invited to one by an administrator, skip to the next step. To determine whether you already have access to an instance, check your `IBM Cloud Instances page `__. If you have one or more instances shown, you can skip ahead to :ref:`install-packages`. - -.. figure:: ../images/instances.png - :alt: This image shows an Instances page with two instances. - - Instances page showing multiple instances. - -1. From the `Qiskit Runtime Provisioning page `__, choose the appropriate service plan, depending on what you need access to. For more information about these plans, see the `Qiskit Runtime plans `__ topic. - - - **Lite**: Free simulators-only plan to help you get started with Qiskit Runtime. Learn to use Qiskit Runtime by following our examples and tutorials for one of the pre-built programs available for running circuits efficiently. - - **Standard**: A pay-as-you-go model for accessing IBM Quantum systems and simulators. Build your own programs and use all the benefits of Qiskit Runtime by running on real quantum hardware. - - Because this is not a free plan, it is important to understand how to best manage your costs. See `Manage the cost `__ for tips to limit your cost, how to set up spending notifications, and more. - - -2. Complete the required information, then click **Create**. - -.. _install-packages: - -Install or update Qiskit packages ------------------------------------ - -Install or update the following packages in your development environment. They let you create circuits and work with primitives with Qiskit Runtime. For detailed instructions, refer to the `Qiskit textbook `__. Periodically check the `Qiskit release notes `__ (or rerun these commands) so that you always have the latest version. - - .. note:: - - Be sure to run these commands even if you already installed the packages, to ensure that you have the latest versions. - - -.. code-block:: python - - # Installs the latest version of the Qiskit meta-package for circuit creation. - pip install qiskit -U - - -.. code-block:: python - - # Installs the latest version of the Qiskit Runtime package, which is needed to interact with the Qiskit Runtime primitives on IBM Cloud. - pip install qiskit-ibm-runtime -U - - -Authenticate to the service ------------------------------------ - - -To authenticate to the service, call ``QiskitRuntimeService`` with your IBM Cloud API key and the CRN: - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - service = QiskitRuntimeService(channel="ibm_cloud", token="", instance="") - -.. _credentials: - -Find your access credentials -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -1. Find your API key. From the `API keys page `__, view or create your API key, then copy it to a secure location so you can use it for authentication. -2. Find your Cloud Resource Name (CRN). Open the `Instances page `__ and click your instance. In the page that opens, click the icon to copy your CRN. Save it in a secure location so you can use it for authentication. - - -Optionally save your credentials to disk -------------------------------------------- - - -Optionally save your credentials to disk (in the ``$HOME/.qiskit/qiskit-ibm.json`` file). If you don't save your credentials to disk, you must specify your credentials every time you start a new session. - -If you save your credentials to disk, you can use ``QiskitRuntimeService()`` in the future to initialize your account. - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - # Save account to disk. - QiskitRuntimeService.save_account(channel="ibm_cloud", token="", instance="") - - service = QiskitRuntimeService() - - -If you need to update your saved credentials, run ``save_account`` again, passing in ``overwrite=True`` and the updated credentials. For more information about managing your account, see the `account management topic <../how_to/account-management.html>`__. - - -Choose a primitive to run -------------------------- - - -Qiskit Runtime uses primitives to interface with quantum computers and they are publicly available. Choose the appropriate link to continue learning how to run a primitive. - -`Getting started with Sampler `__ - -`Getting started with Estimator `__ diff --git a/docs/cloud/setup-terraform.rst b/docs/cloud/setup-terraform.rst deleted file mode 100644 index 00222209e..000000000 --- a/docs/cloud/setup-terraform.rst +++ /dev/null @@ -1,55 +0,0 @@ -Set up Terraform for Qiskit Runtime -=================================== - -If you use Terraform to manage your infrastructure, the `IBM Cloud provider for Terraform `__ supports provisioning Qiskit Runtime service instances. The generic ``ibm_resource_instance`` resource is used for that. The following parameters have to be specified: - -Provisioning with Terraform ---------------------------- - -If you use Terraform to manage your infrastructure, the `IBM Cloud provider for Terraform `__ supports provisioning Qiskit Runtime service instances. The generic ``ibm_resource_instance`` resource is used for that. The following parameters have to be specified: - -- ``name`` – The name of your service instance. -- ``service`` – Specify ``quantum-computing`` to provision Qiskit Runtime instances. -- ``plan`` – Can be ``lite`` or ``paygo-standard``. -- ``location`` – Currently, this must be ``us-east``. - -Optional parameters include: - -- ``resource_group_id`` – Creates the service instance in the specified resource group. -- ``tags`` – Add tags to the resource. - -Example: Creating a service instance of Qiskit Runtime ------------------------------------------------------- - -After the job completes, you can view the results. - -1. In your Terraform configuration file, add the following code. Change the name of the service instance (parameter “name”) and the plan (parameter “plan”) according to your requirements: - - .. code:: - - resource "ibm_resource_instance" "my-instance" { - name = "my-instance-name" - service = "quantum-computing" - plan = "lite" - location = "us-east" - } - - - -2. Create a Terraform execution plan. - - .. code:: - - terraform plan - - -3. Remove the namespace and re-create it with a new name. Note that this process might take a few minutes to complete. - - .. code:: - - terraform apply - - -4. Verify on the `Instances page `__ that your service instance has been created. - -Qiskit Runtime service instances are IAM managed resources. Access can be shaped through terraform using IAM user policies. See `IBM IAM user policy `__ for more details and examples. diff --git a/docs/compare.rst b/docs/compare.rst deleted file mode 100644 index 066468922..000000000 --- a/docs/compare.rst +++ /dev/null @@ -1,39 +0,0 @@ -How do Qiskit Runtime primitives differ from backend.run? -========================================================= - -There are two methods for accessing IBM Quantum systems. First, the -`qiskit-ibm-provider` package provides the ``backend.run()`` interface, -allowing direct access to IBM Quantum systems with no pre- or post-processing -involved. This level of access is suitable for those users who want precise -control over circuit execution and result processing. This level of access -is needed for those looking to work at the level Kernel developer developing, -for example, circuit optimization routines, error mitigation techniques, or -characterizing quantum systems. - -In contrast, Qiskit Runtime is designed to streamline algorithm and application construction -by removing the need for users to understand -technical hardware and low-level software details. Advanced processing techniques -for error suppression and mitigation are automatically applied, giving users -high-fidelity results without the burden of having to code these routines -themselves. The inclusion of sessions within Qiskit Runtime allows users -to run iterative algorithm circuits back to back, or batch collections of circuits -without having to re-queue each job. This results in more efficient quantum processor utilization -and reduces the total amount of time users spend running -complex computations. - - -+---------------------------------------------------------------------------------+-----------------------+---------------------------+ -| Function | backend.run | Qiskit Runtime Primitives | -+=================================================================================+=======================+===========================+ -| Abstracted interface for circuits and variational workloads | No | Yes | -+---------------------------------------------------------------------------------+-----------------------+---------------------------+ -| Sessions to improve performance for a sequence of jobs | No | Yes | -+---------------------------------------------------------------------------------+-----------------------+---------------------------+ -| Automated application of error suppression and mitigation techniques | No | Yes | -+---------------------------------------------------------------------------------+-----------------------+---------------------------+ -| Increased performance for variational algorithms | No | Yes | -+---------------------------------------------------------------------------------+-----------------------+---------------------------+ -| Pulse Gates | Yes | Yes | -+---------------------------------------------------------------------------------+-----------------------+---------------------------+ -| Dynamic circuits | Yes | No | -+---------------------------------------------------------------------------------+-----------------------+---------------------------+ diff --git a/docs/conf.py b/docs/conf.py index 6ba75df0b..e8f3e7eb0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- - -# This code is part of Qiskit. +# This code is a Qiskit project. # # (C) Copyright IBM 2022. # @@ -12,39 +10,23 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -# pylint: disable=invalid-name -# Configuration file for the Sphinx documentation builder. -# -# This file does only contain a selection of the most common options. For a -# full list see the documentation: -# http://www.sphinx-doc.org/en/master/config - # -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# import os import sys sys.path.insert(0, os.path.abspath('.')) -# Set env flag so that we can doc functions that may otherwise not be loaded -# see for example interactive visualizations in qiskit.visualization. -os.environ['QISKIT_DOCS'] = 'TRUE' - # -- Project information ----------------------------------------------------- project = 'Qiskit Runtime IBM Client' -copyright = '2022, Qiskit Development Team' # pylint: disable=redefined-builtin +project_copyright = '2022, Qiskit Development Team' author = 'Qiskit Development Team' +language = 'en' # The short X.Y version version = '' # The full version, including alpha/beta/rc tags -release = '0.17.0' - docs_url_prefix = "ecosystem/ibm-runtime" +release = '0.18.1' # -- General configuration --------------------------------------------------- @@ -52,16 +34,13 @@ 'sphinx.ext.napoleon', 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.extlinks', - 'sphinx_tabs.tabs', + # This is used by qiskit/documentation to generate links to github.com. + "sphinx.ext.viewcode", 'jupyter_sphinx', 'sphinx_autodoc_typehints', 'reno.sphinxext', 'nbsphinx', - 'sphinx_design', - "qiskit_sphinx_theme", + 'sphinxcontrib.katex', ] templates_path = ['_templates'] @@ -91,10 +70,6 @@ autosummary_generate = True -# ----------------------------------------------------------------------------- -# Autodoc -# ----------------------------------------------------------------------------- - autodoc_default_options = { 'inherited-members': None, } @@ -111,16 +86,6 @@ 'table': 'Table %s' } - -translations_list = [ - ('en', 'English'), - ('ja_JP', 'Japanese'), - ('es_UN', 'Spanish'), -] -language = 'en' -locale_dirs = ['locale/'] -gettext_compact = False # optional. - # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. @@ -140,26 +105,15 @@ # package. Works only for the HTML builder currently. modindex_common_prefix = ['qiskit.'] -# -- Configuration for extlinks extension ------------------------------------ -# Refer to https://www.sphinx-doc.org/en/master/usage/extensions/extlinks.html - - # -- Options for HTML output ------------------------------------------------- -html_theme = "qiskit-ecosystem" +# Even though alabaster isn't very pretty, we use it +# over the normal qiskit-ecosystem theme because it's +# faster to build and these docs are only necessary +# so the API docs can be integrated into docs.quantum.ibm.com. +html_theme = "alabaster" html_title = f"{project} {release}" -html_logo = "images/ibm-quantum-logo.png" - -html_theme_options = { - # Because this is an IBM-focused project, we use a blue color scheme. - "light_css_variables": { - "color-brand-primary": "var(--qiskit-color-blue)", - }, -} - html_last_updated_fmt = '%Y/%m/%d' - html_sourcelink_suffix = '' - autoclass_content = 'both' diff --git a/docs/errors.rst b/docs/errors.rst deleted file mode 100644 index 26b8603f3..000000000 --- a/docs/errors.rst +++ /dev/null @@ -1,1446 +0,0 @@ -.. _errors: - -############### -API error codes -############### - -1XXX -==== -.. _error1xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error1000: - - **1000** - - API Internal error. - - Try the action again. If it happens again, contact IBM Quantum through `Slack `_ for help. - - * - .. _error1001: - - **1001** - - ``qObject`` is larger than the maximum size. - - Run a small Job. Split the circuits in smaller jobs. - - * - .. _error1002: - - **1002** - - Error in the validation process of the job. - - Check the Job, it is not valid to run on this backend. - - * - .. _error1003: - - **1003** - - Error in transpilation process. - - Check the Job, it is not valid to run on this backend. - - * - .. _error1004: - - **1004** - - The backend is not available. - - Use another backend to run the job. - - * - .. _error1005: - - **1005** - - Basis gates not available. - - Use another backend with basis gates. - - - * - .. _error1006: - - **1006** - - Error during call to converter microservice. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error1007: - - **1007** - - Backend not found. - - Check the backend name, maybe it is wrong. - - * - .. _error1008: - - **1008** - - Error during the validation process of a job. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error1009: - - **1009** - - Required backend information not found. - - Use another backend to run the job. - - * - .. _error1010: - - **1010** - - Error returned at backend level. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error1011: - - **1011** - - Error publishing job at the backend queue. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error1012: - - **1012** - - The user reached the maximum number of jobs running concurrently. - - Wait until some previous jobs were finished. You can cancel pending jobs to run new jobs. - - * - .. _error1101: - - **1101** - - ``Qobj`` does not conform to the schema. - - Verify the ``Qobj`` for your job conforms to the ``Qobj`` schema. - - * - .. _error1102: - - **1102** - - The number of experiments in the ``Qobj`` is higher than the number of experiments supported by the backend. - - Split the experiments into multiple jobs. The maximum number of experiments the backend supports can be found in its configuration data. - - * - .. _error1103: - - **1103** - - The number of shots in the ``Qobj`` is higher than the number of shots supported by the backend. - - Use fewer shots. The maximum number of shots the backend supports can be found in its configuration data. - - * - .. _error1104: - - **1104** - - The ``Qobj`` requests memory measurement, but the backend does not support memory. - - Run the job on a backend that supports memory or don't request memory measurement. Whether a backend supports memory measurement can be found in its configuration data. - - * - .. _error1105: - - **1105** - - The number of qubits used in the ``Qobj`` is higher than the number of quantum registers defined in the ``Qobj``. - - Correct your program and try again. - - - * - .. _error1106: - - **1106** - - The ``Qobj`` uses gates that are not among the backend's basis gates. - - Correct your program and try again. Transpiling your program will convert high level gates to basis gates. The basis gates for a backend can be found in its configuration data. - - * - .. _error1107: - - **1107** - - The ``Qobj`` includes an instruction that assumes a coupling map that is different from the backend's coupling map. - - Correct your program and try again. Transpiling your program will map instructions to the correct qubits based on the backend's coupling map. The coupling map for a backend can be found in its configuration data. - - * - .. _error1108: - - **1108** - - The backend does not support open pulse. - - Run the job on a backend that supports open pulse. Whether a backend supports open pulse can be found in its configuration data. - - * - .. _error1109: - - **1109** - - The number of qubits used in the ``Qobj`` is more than the number of qubits supported by the backend. - - Run the job on a backend that supports sufficient number of qubits for the job. The number of qubits a backend supports can be found in its configuration data. - - * - .. _error1999: - - **1999** - - Planned outage. The service is undergoing maintenance. - - Please wait. The service will be back up soon. The website portal will have more information about what the expected time window for the maintenance work is. - - -2XXX -==== -.. _error2xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error2000: - - **2000** - - Backend not found. - - Check the backend name, maybe it is wrong. - - * - .. _error2001: - - **2001** - - Backend not available for booking. - - Use another backend to book a time slot. - - * - .. _error2002: - - **2002** - - Backend not available for this action. - - Use another backend. - - * - .. _error2100: - - **2100** - - Invalid URL to Upload to Bluemix. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2200: - - **2200** - - A booking already exists. - - Select another date to book. - - * - .. _error2201: - - **2201** - - Booking data is not valid. - - Check the booking data, maybe it is wrong. - - - * - .. _error2202: - - **2202** - - Cannot cancel booking. - - Check the booking to cancel. - - * - .. _error2203: - - **2203** - - Provider does not have enough remaining time to book. - - Use another provider to book or contact your Group Administrator. - - * - .. _error2204: - - **2204** - - User already has a booking on that date. - - Select another date to book. - - * - .. _error2205: - - **2205** - - Booking not found. - - Check the booking data, maybe it is wrong. - - * - .. _error2206: - - **2206** - - Booking on calibration time. - - Select another date to book. - - * - .. _error2300: - - **2300** - - Code ID not found. - - Check the code data, maybe it is wrong. - - * - .. _error2301: - - **2301** - - Code not updated. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2302: - - **2302** - - Code wrong. - - Check the code data, maybe it is wrong. - - * - .. _error2304: - - **2304** - - Error parsing QASM. - - Check the code data, maybe it is wrong. - - - * - .. _error2305: - - **2305** - - Invalid Code. - - Check the code data, maybe it is wrong. - - * - .. _error2306: - - **2306** - - Invalid result. - - Check the code data, maybe it is wrong. - - * - .. _error2307: - - **2307** - - The ``Qobj`` requests memory measurement, but the backend does not support memory. - - Check the code data, maybe it is wrong. - - * - .. _error2308: - - **2308** - - User role not found. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - - * - .. _error2309: - - **2309** - - Code not found. - - Check the code data, maybe it is wrong. - - - * - .. _error2310: - - **2310** - - Failed to export. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2311: - - **2311** - - Image wrong. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2313: - - **2313** - - QASM not found. - - Check the code data, maybe it is wrong. - - * - .. _error2400: - - **2400** - - Error wrong data received. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2402: - - **2402** - - Maximum attempts reached. - - Reduce the number of concurrent requests. - - * - .. _error2403: - - **2403** - - Missing data in HTTP request. - - Check your request to the endpoint. - - - * - .. _error2404: - - **2404** - - Model not found in database. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2405: - - **2405** - - Error saving new data. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2407: - - **2407** - - Authentication required. - - Try to log in again. - - * - .. _error2408: - - **2408** - - Invalid Access Token. - - Try to log in again. - - * - .. _error2409: - - **2409** - - Forbidden. - - You don't have authority to perform the action. - - * - .. _error2410: - - **2410** - - Service not accessible. - - You don't have authority to perform the action. - - * - .. _error2411: - - **2411** - - Operation not available. - - You don't have authority to perform the action. - - * - .. _error2412: - - **2412** - - Error retrieving data from database. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2600: - - **2600** - - Configuration not available for this system. - - Try to use another backend. - - * - .. _error2602: - - **2602** - - System not allowed. - - Try to use another backend. - - * - .. _error2603: - - **2603** - - Error getting topology attributes. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - - * - .. _error2604: - - **2604** - - Error getting topology queues. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2609: - - **2609** - - Properties are empty. - - Try to use another backend. - - * - .. _error2614: - - **2614** - - Topology without kind established. - - Try to use another backend. Contact an IBM Quantum administrator. - - * - .. _error2615: - - **2615** - - The system is not available. - - Try to use another backend. - - * - .. _error2616: - - **2616** - - This system can only be used for running jobs. - - Try the Jobs API. Try to use another backend. - - * - .. _error2618: - - **2618** - - Basis gates not available. - - Try to use another backend. - - * - .. _error2620: - - **2620** - - System not found. - - Try to use another backend. - - * - .. _error2622: - - **2622** - - Properties not found. - - Try to use another backend. - - * - .. _error2900: - - **2900** - - An error occur getting the hub. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2901: - - **2901** - - Error checking hub or group administrators. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2902: - - **2902** - - Error checking systems in the Hub. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2903: - - **2903** - - Hub info not found. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - - * - .. _error2904: - - **2904** - - Invalid backend to configure for booking. - - Use another backend. - - * - .. _error2905: - - **2905** - - Invalid parameters to configure for booking. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2906: - - **2906** - - Invalid priority value. - - Change the priority Value. - - * - .. _error2907: - - **2907** - - System not available for Hub. - - Use another backend. - - * - .. _error2908: - - **2908** - - Error checking user in the Hub. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error2909: - - **2909** - - Group not found. - - Use another Group. - - * - .. _error2910: - - **2910** - - Hub not found. - - Use another Hub. - - * - .. _error2911: - - **2911** - - Invalid Hub/Group/Project. - - Use another provider. - - * - .. _error2912: - - **2912** - - Invalid mode to configure for booking. - - Use another mode to book a backend. - - * - .. _error2913: - - **2913** - - Project not found. - - Use another project. - - * - .. _error2914: - - **2914** - - This hub is not allowed to view analytics. - - Use another hub. - -3XXX -==== -.. _error3xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error3200: - - **3200** - - Backend not valid. - - Use another backend. - - * - .. _error3202: - - **3202** - - Cannot get presigned download URL. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3203: - - **3203** - - Cannot get presigned upload URL. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3204: - - **3204** - - Error during call to converter microservice. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - - * - .. _error3207: - - **3207** - - Job access not allowed. - - Access another job. - - * - .. _error3208: - - **3208** - - Job not cancelled. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3209: - - **3209** - - Job not running. - - Check if the action makes sense. - - * - .. _error3210: - - **3210** - - Job not saved. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3211: - - **3211** - - Job not valid. - - Check the Job sent, maybe it is wrong. - - * - .. _error3212: - - **3212** - - Job not validated. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3213: - - **3213** - - Job status not valid. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3214: - - **3214** - - Job transition not valid. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3215: - - **3215** - - Job without code identifier. - - Check the Job sent, maybe it is wrong. - - - * - .. _error3216: - - **3216** - - Limit not valid. - - Change the limit sent into the request. - - * - .. _error3218: - - **3218** - - Number of Shots not allowed. - - Change the number of shots. - - * - .. _error3220: - - **3220** - - Payload not valid. - - Change the body sent into the request. Maybe its format is wrong. - - * - .. _error3224: - - **3224** - - Q-Object memory not allowed. - - Disable the memory parameter in the Job. - - - * - .. _error3226: - - **3226** - - Q-Object not valid. - - Check the format of the Job. Maybe it is wrong. - - - * - .. _error3228: - - **3228** - - Q-Object-External-Storage property not allowed in this backend. - - Send the content of the Job inside of the body. - - * - .. _error3229: - - **3229** - - QASM no longer accepted. - - Use Q-Object format. - - * - .. _error3230: - - **3230** - - Seed not allowed. - - Don't send seed parameter. - - * - .. _error3233: - - **3233** - - The job can't be created. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3234: - - **3234** - - The job can't be validated. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3235: - - **3235** - - Job cost cannot be calculated. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - - * - .. _error3236: - - **3236** - - The job is empty. - - Check the job sent. Maybe it is empty. - - * - .. _error3237: - - **3237** - - The job is invalid. - - Check the job sent. Maybe it is wrong. - - * - .. _error3239: - - **3239** - - Number of registers exceed the number of qubits. - - Define the same ``creg`` as ``qreg``. - - * - .. _error3242: - - **3242** - - Circuit count exceeded. - - Send smaller number of circuits in the Job. - - * - .. _error3243: - - **3243** - - Circuit is too big. - - Reduce the content of the circuit. - - * - .. _error3245: - - **3245** - - The queue is disabled. - - Use another backend. - - * - .. _error3246: - - **3246** - - The queue is unavailable. - - Use another backend. - - * - .. _error3248: - - **3248** - - Your job is too long. - - Reduce the content of the job. - - * - .. _error3249: - - **3249** - - Job fields are empty. - - Check the Job content. Maybe it is empty. - - * - .. _error3250: - - **3250** - - Job not found. - - Check the job ID to query. It is wrong. - - * - .. _error3251: - - **3251** - - Job not uploaded to object storage. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - - * - .. _error3252: - - **3252** - - Object storage not allowed. - - Send the job into the body of the request. - - * - .. _error3253: - - **3253** - - Timeout getting the result. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3254: - - **3254** - - The job is not in queue. - - Check the status of the job. - - * - .. _error3255: - - **3255** - - Invalid share level. - - Update the share level. - - * - .. _error3259: - - **3259** - - This system can only be used for running jobs. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3265: - - **3265** - - Input type not allowed by backend. - - Use another backend. - - * - .. _error3300: - - **3300** - - Cannot download job data. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3301: - - **3301** - - Cannot upload job data. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3302: - - **3302** - - Job not found. - - Check the job information. Maybe it is wrong. - - * - .. _error3400: - - **3400** - - License not found. - - Accept the license. - - * - .. _error3402: - - **3402** - - API key not found. - - Regenerate the API Token. - - * - .. _error3405: - - **3405** - - Codes not deleted. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - - * - .. _error3407: - - **3407** - - User API token not valid. - - Check the API Token. - - * - .. _error3409: - - **3409** - - Error deleting entities from user. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3410: - - **3410** - - Error deleting user relations. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3418: - - **3418** - - Failed to create the token for the user. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3422: - - **3422** - - Old password is incorrect. - - Check your old password. It is wrong. - - * - .. _error3423: - - **3423** - - Passwords do not match. - - Check the password. It is wrong. - - * - .. _error3424: - - **3424** - - Retrieving last version licenses, including future ones. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3425: - - **3425** - - Retrieving last version licenses. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3440: - - **3440** - - Authentication is required to perform that action. - - Try to log in again. - - * - .. _error3443: - - **3443** - - Failed to check login. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3444: - - **3444** - - License required. You need to accept the License. - - Accept the license. - - * - .. _error3445: - - **3445** - - Login with IBM ID required. - - Login using IBM ID. - - * - .. _error3446: - - **3446** - - Login failed. - - Try to login again. - - - * - .. _error3452: - - **3452** - - The license is not accepted. - - Accept the License. - - * - .. _error3453: - - **3453** - - The license is required. - - Accept the License. - - * - .. _error3458: - - **3458** - - User reached the maximum limits of concurrent jobs. - - Wait until some previous jobs were finished. You can cancel pending jobs to run new jobs. - - * - .. _error3459: - - **3459** - - User is blocked by wrong password. - - Wait 5 minutes, then log in again. - - * - .. _error3460: - - **3460** - - User is blocked. - - Contact an IBM Quantum Administrator. - - * - .. _error3467: - - **3467** - - Failed to create or renew API token. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3468: - - **3468** - - Failed to get API token. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3500: - - **3500** - - Body is wrong. - - Check the body of the request. - - * - .. _error3704: - - **3704** - - Error getting status from the queue. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3811: - - **3811** - - Request not found. - - Check the request that you are trying to perform. - - * - .. _error3900: - - **3900** - - Empty response from the stats micro-service. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3901: - - **3901** - - Error parsing stats. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3902: - - **3902** - - Error retrieving stats. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3903: - - **3903** - - Invalid date. - - Update the dates. - - * - .. _error3904: - - **3904** - - Invalid end date. - - Update the end date. - - * - .. _error3905: - - **3905** - - Invalid input to the stats micro-service. - - Check the query. It is incorrect. - - * - .. _error3906: - - **3906** - - Invalid key. - - Check the query. It is incorrect. - - * - .. _error3907: - - **3907** - - Invalid start date. - - Update the start date. - - * - .. _error3908: - - **3908** - - Invalid stats type. - - Check the query. It is incorrect. - - * - .. _error3909: - - **3909** - - Missing mandatory user stats info. - - Check the query. It is incorrect. - - * - .. _error3910: - - **3910** - - Number of months too big. - - Reduce the number of months. - - * - .. _error3911: - - **3911** - - Stats micro-service is not available. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3912: - - **3912** - - Stats not found. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3913: - - **3913** - - Analytics stats not found. - - Retry the action. If it happens again, contact IBM Quantum through `Slack `__ for help. - - * - .. _error3914: - - **3914** - - Project level does not support aggregated analytics stats. - - Try to use another project. - - * - .. _error3915: - - **3915** - - Missing start/end dates; ``allTime`` not set to true for analytics stats. - - Set start and end date in the query. - -4XXX -==== -.. _error4xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error4001: - - **4001** - - Job is part of a session that's been closed - - Ensure session is not closed before all jobs in session have run - -5XXX -==== -.. _error5xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error5201: - - **5201** - - Job timed out after {} seconds. - - Reduce the complexity of the job, or number of shots. - - * - .. _error5202: - - **5202** - - Job was canceled. - - None. Job was canceled. - - * - .. _error5203: - - **5203** - - Failed to run job. - - Try to run the job again. - - * - .. _error5204: - - **5204** - - Error raised when execution on AER failed. - - Try to run the job again. - - -6XXX -==== -.. _error6xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error6000: - - **6000** - - Too many shots given ({} > {}). - - Reduce the requested number of shots. - - * - .. _error6001: - - **6001** - - Too few shots given ({} < {}). - - Increase the requested number of shots. - - * - .. _error6002: - - **6002** - - Too many experiments given ({} > {}). - - Reduce the number of experiments. - - * - .. _error6003: - - **6003** - - Too few experiments given ({} < {}). - - Increase the number of experiments. - - -7XXX -==== -.. _error7xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error7000: - - **7000** - - Instruction not in basis gates:
instruction: {}, qubits: {}, ``params``: {} - - Instruction not supported by backend. Remove the instruction shown in the error message. - - * - .. _error7001: - - **7001** - - Instruction {} is not supported. - - Remove unsupported instruction, or run on a simulator that supports it. - - * - .. _error7002: - - **7002** - - Memory output is disabled. - - Select a different backend or set ``memory=False`` in transpile / execute. - - * - .. _error7003: - - **7003** - - qubits: {} and classical bits: {} do not have equal lengths. - - Length of memory slots must be same as number of qubits used. - - * - .. _error7004: - - **7004** - - Qubit measured multiple times in circuit. - - Remove multiple measurements on qubits. - - * - .. _error7005: - - **7005** - - Error in supplied instruction. - - Refer to the `Operations glossary <../operations_glossary>`__ and verify that the instructions are correct. - - * - .. _error7006: - - **7006** - - Qubit measurement is followed by instructions. - - Cannot perform any instruction on a measured qubit. Remove all instructions following a measurement. - -8XXX -==== -.. _error8xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error8000: - - **8000** - - Channel {}{} lo setting: {} is not within acceptable range of {}. - - Set channel LO within specified range. - - * - .. _error8001: - - **8001** - - Qubits {} in measurement are not mapped. - - Assign qubits to a classical memory slot. - - * - .. _error8002: - - **8002** - - Total samples exceeds the maximum number of samples for channel {}. ({} > {}). - - Reduce number of samples below specified limit. - - * - .. _error8003: - - **8003** - - Total pulses exceeds the maximum number of pulses for channel: {}, ({} > {}). - - Reduce number of pulses below specified limit. - - * - .. _error8004: - - **8004** - - Channel {}{} is not available. - - Must use available drive channels. - - * - .. _error8006: - - **8006** - - Gate {} in line {}s not understood ({}). - - This instruction is not supported. Make sure that the gate name is correct and is found within the `Operations glossary `__. - - * - .. _error8007: - - **8007** - - QASM gate not understood: {}. - - This instruction is not understood. Make sure it is found within the `Operations glossary `__. - - * - .. _error8008: - - **8008** - - Unconnected Qubits. - - Check the topology diagram for this system (go to the `Compute resources page `__ and click the system) and make sure the qubits are connected. - - * - .. _error8009: - - **8009** - - Measurement level is not supported. - - The given measurement level is not supported on this backend. Change it to 0-2 except the measurement level specified. - - * - .. _error8011: - - **8011** - - Pulse experiments are not supported on this system. - - Pulse experiment is not supported on this backend. Use a backend that supports pulse to run this experiment. - - * - .. _error8013: - - **8013** - - This backend does not support conditional pulses. - - Conditionals are not supported on this backend. Remove the conditional instruction in your program. - - * - .. _error8014: - - **8014** - - Reset instructions are not supported. - - Reset instructions are not supported at this time for this backend. Remove the reset instruction. - - * - .. _error8016: - - **8016** - - Pulse {} has too few samples ({} > {}). - - Add more samples. - - * - .. _error8017: - - **8017** - - Pulse not a multiple of {} samples. - - Due to hardware limitations, pulses must be a multiple of a given number of samples. - - * - .. _error8018: - - **8018** - - Waveform memory exceeds the maximum amount of memory currently available. - - Reduce the number of samples in the waveform. - - * - .. _error8019: - - **8019** - - For channel {}{}, Final channel time exceeds max time ({} > {}). - - Reduce the total length of pulse sequence on the specified channel. - - * - .. _error8020: - - **8020** - - Circuit runtime is greater than the device repetition rate. - - Circuit too long, reduce length of circuit. - - - * - .. _error8021: - - **8021** - - Acquires have durations of different length. - - Set acquire operations to have the same length. - - * - .. _error8022: - - **8022** - - Pulse {} has too many samples ({} > {}). - - Reduce the number of samples in the specified pulse. - - * - .. _error8023: - - **8023** - - {0} {1} is an invalid entry. {0} should be a positive integer. - - Make the entry a positive integer. - - * - .. _error8024: - - **8024** - - At most one acquire currently supported per acquisition channel. - - Use only one acquire command per channel. - - * - .. _error8026: - - **8026** - - Supplied qubits ({0}) in acquire are not valid. - - Fix the qubits specified in the acquire commands. - - * - .. _error8027: - - **8027** - - Channel specified: {} is not available. - - Channel does not exist on system. - - * - .. _error8029: - - **8029** - - Repetition time ({0}) is not supported. - - Repetition time must be changed to a supported value. - - * - .. _error8030: - - **8030** - - Repetition delay ({0}) is not supported. - - The delay is not supported. - - - * - .. _error8031: - - **8031** - - Submitted job is too long. - - Reduce the length of the job. - - * - .. _error8033: - - **8033** - - ``Qobj`` ``type`` not provided in ``config``. - - Add ``type`` to ``qobj['config']``. - - * - .. _error8035: - - **8035** - - Instruction {0} at timestep {1}dt overlaps with instruction {2} at timestep {3}dt on channel {4}. - - Two instructions cannot be played at the same time on a channel. - - * - .. _error8036: - - **8036** - - All measure(circuit) and acquire(pulse) instructions must align to a 16 sample boundary. Measurements may be impacted by delays which have non-multiple of 16 durations. - - Due to hardware limitations, measure and acquire instructions must occur at 16 sample multiples. - - * - .. _error8037: - - **8037** - - ESP readout not enabled on this device. - - Set ``use_measure_esp=False`` or remove from run options. - - * - .. _error8039: - - **8039** - - A combination of pulses on the logical channels is exceeding the hardware output due to internal usage of hardware output. This will typically be a result of drive and control channels being mapped to the same physical channel in the hardware and the summed total of the applied pulses (including additional internal pulses for system-specific hardware functionality) exceeding unit norm. - - Lower the amplitudes of the input pulses. - - * - .. _error8041: - - **8041** - - An amplitude was requested with a norm of greater than 1. - - Lower the amplitudes of the input pulses. - - * - .. _error8042: - - **8042** - - The input pulse had some parameters which were not validated. This can be because certain parameters are expected to be real, while others are complex. It may also be due to the amplitude or duration of the pulse exceeding a limit, or other invalid combinations of parameters (for example, a Gaussian square pulse with a flat-top width greater than the pulse's total duration). - - Verify the pulse input parameters. - - * - .. _error8044: - - **8044** - - Number of samples is less than the minimum pulse width. - - Verify that the duration of all pulses meets or exceeds the minimum pulse duration. If necessary and possible, you may consider zero-padding the start/end of very short pulses such that they meet or exceed the minimum duration. - -9XXX -==== -.. _error9xxx: - -.. list-table:: - :header-rows: 1 - - * - Error code - - Message - - Solution - - * - .. _error9999: - - **9999** - - Internal error. - - Contact IBM Quantum through `Slack `__ for help. diff --git a/docs/faqs.rst b/docs/faqs.rst deleted file mode 100644 index f30adcbe1..000000000 --- a/docs/faqs.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _faqs: - -######################################### -Frequently asked questions -######################################### - -.. toctree:: - faqs/open_source_vs_ibm_cloud_primitives - faqs/max_execution_time - FAQs for IBM Cloud Qiskit Runtime - - - -.. Hiding - Indices and tables - :ref:`genindex` - :ref:`modindex` - :ref:`search` diff --git a/docs/faqs/max_execution_time.rst b/docs/faqs/max_execution_time.rst deleted file mode 100644 index 32e763372..000000000 --- a/docs/faqs/max_execution_time.rst +++ /dev/null @@ -1,61 +0,0 @@ -.. _faqs/max_execution_time: - -======================================================================= -What is the maximum execution time for a Qiskit Runtime job or session? -======================================================================= - -Job maximum execution time -*************************** - -To ensure fairness, and as a way to help control cost, there is a -maximum execution time for each Qiskit Runtime job. If -a job exceeds this time limit, it is forcibly cancelled and a ``RuntimeJobMaxTimeoutError`` -exception is raised. - -.. note:: - As of August 7, 2023, the ``max_execution_time`` value is based on system execution time, which is the time that the QPU - complex (including control software, control electronics, QPU, and so on) is engaged in - processing the job, instead of wall clock time. - - Simulator jobs continue to use wall clock time. - -You can set the maximum execution time (in seconds) on the job options by using one of the following methods: - -.. code-block:: python - - # Initiate the Options class with parameters - options = Options(max_execution_time=360) - -.. code-block:: python - - # Create the options object with attributes and values - options = {"max_execution_time": 360} - -You can also find the system execution time for previously completed jobs by using: - -.. code-block:: python - - # Find the system execution time - print(f"Job {job.job_id()} system execution time was {job.metrics()['usage']['seconds']} seconds") - -In addition, the system calculates an appropriate job timeout value based on the -input circuits and options. This system-calculated timeout is currently capped -at 3 hours to ensure fair device usage. If a ``max_execution_time`` is -also specified for the job, the lesser of the two values is used. - -For example, if you specify ``max_execution_time=5000``, but the system determines -it should not take more than 5 minutes (300 seconds) to execute the job, then the job will be -cancelled after 5 minutes. - -Session maximum execution time -******************************* - -When a session is started, it is assigned a maximum session timeout value. After this timeout is reached, the session is terminated, any jobs that are already running continue running, and any queued jobs that remain in the session are put into a failed state. For instructions to set the session maximum time, see `Specify the session length <../how_to/run_session#session_length.html>`__. - - -Other limitations -*************************** - -- Programs cannot exceed 750KB in size. -- Inputs to jobs cannot exceed 64MB in size. -- Open plan users can use up to 10 minutes of system execution time per month (resets at 00:00 UTC on the first of each month). System execution time is the amount of time that the system is dedicated to processing your job. You can track your monthly usage on the `Platform dashboard, `__ `Jobs, `__ and `Account `__ page. \ No newline at end of file diff --git a/docs/faqs/open_source_vs_ibm_cloud_primitives.rst b/docs/faqs/open_source_vs_ibm_cloud_primitives.rst deleted file mode 100644 index 5dfa6678c..000000000 --- a/docs/faqs/open_source_vs_ibm_cloud_primitives.rst +++ /dev/null @@ -1,9 +0,0 @@ -.. _faqs/open_source_vs_ibm_cloud_primitives: - -========================================================================================================== -What is the difference between the open source primitives and primitives available through Qiskit Runtime? -========================================================================================================== - -The open source primitive contains the base classes (to define interfaces) and a reference implementation. -The Qiskit Runtime primitives provide more sophisticated implementation (such as with error -mitigation) as a cloud-based service. \ No newline at end of file diff --git a/docs/getting_started.rst b/docs/getting_started.rst deleted file mode 100644 index 026907ab3..000000000 --- a/docs/getting_started.rst +++ /dev/null @@ -1,84 +0,0 @@ -######################################### -Getting started -######################################### - -Install Qiskit packages -======================== - -Installing the following packages lets you create circuits and work with primitives -through Qiskit Runtime: - -.. code-block:: bash - - pip install qiskit - pip install qiskit-ibm-runtime - - -Find your access credentials -============================== - -You can access Qiskit Runtime from either IBM Quantum or IBM Cloud. - -**IBM Quantum** - -`Retrieve your IBM Quantum token `_, and optionally save it for easy access later. - -.. note:: - Account credentials are saved in plain text, so only do so if you are using a trusted device. - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - # Save an IBM Quantum account. - QiskitRuntimeService.save_account(channel="ibm_quantum", token="MY_IBM_QUANTUM_TOKEN") - - - -**IBM Cloud** - -Retrieve your IBM Cloud access credentials, and optionally save it for easy access later. - -* `Retrieve your IBM Cloud token `__ -* To retrieve your Cloud Resource Name (CRN), open the `Instances page `__ and click your instance. In the page that opens, click the icon to copy your CRN. - -.. note:: - Account credentials are saved in plain text, so only do so if you are using a trusted device. - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - # Save an IBM Cloud account. - QiskitRuntimeService.save_account(channel="ibm_cloud", token="MY_IBM_CLOUD_API_KEY", instance="MY_IBM_CLOUD_CRN") - - -Test your setup -============================== - -Run a simple circuit using `Sampler` to ensure that your environment is set up properly: - -.. code-block:: python - - from qiskit.test.reference_circuits import ReferenceCircuits - from qiskit_ibm_runtime import QiskitRuntimeService, Sampler - - # You'll need to specify the credentials when initializing QiskitRuntimeService, if they are not previously saved. - service = QiskitRuntimeService() - backend = service.backend("ibmq_qasm_simulator") - job = Sampler(backend).run(ReferenceCircuits.bell()) - print(f"job id: {job.job_id()}") - result = job.result() - print(result) - - -Getting started with primitives -================================= - -.. nbgallery:: - - tutorials/how-to-getting-started-with-sampler - tutorials/how-to-getting-started-with-estimator - - -`See more tutorials `_ diff --git a/docs/how_to/account-management.rst b/docs/how_to/account-management.rst deleted file mode 100644 index 9555dce1e..000000000 --- a/docs/how_to/account-management.rst +++ /dev/null @@ -1,62 +0,0 @@ -Manage your account -================================= - -Qiskit Runtime is available on both IBM Cloud and IBM Quantum Platform. The former requires an IBM Cloud account and the latter requires an IBM Quantum account. If you don't have the necessary account, refer to the appropriate link: - -* `Setting up your IBM Cloud account `__ -* `Access your IBM Quantum account `__ - -There are several methods for account management. Your account credentials can be saved to disk or used in a session and never saved. - -* `save_account()`: Save your account to disk for future use. -* `delete_account()`: Delete the saved account from disk. -* `active_account()`: List the account currently in the session. -* `saved_account()`: List the account stored on disk. - -Store credentials ------------------ - -The ``save_account()`` method can be used to store your account credentials on disk, in the ``$HOME/.qiskit/qiskit-ibm.json`` file. After the credentials are saved, you will only need to use ``QiskitRuntimeService()`` to initialize your account in the future. - -.. note:: - Account credentials are saved in plain text, so only do so if you are using a trusted device. - -Following are examples of saving an IBM Cloud and an IBM Quantum account. The ``channel`` parameter allows to distinguish between different account types. If you are saving multiple accounts per channel, consider using the ``name`` parameter to differentiate them. - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - # Save an IBM Cloud account on disk. - QiskitRuntimeService.save_account(channel="ibm_cloud", token=<*IBM Cloud API key*>, instance=<*IBM Cloud CRN*> or <*IBM Cloud service name*>) - - # Save an IBM Quantum account on disk. - QiskitRuntimeService.save_account(channel="ibm_quantum", token=<*IBM Quantum API token*>) - -Initialize your account -------------------------- - -You need to initialize your account in a Python session before you can start using Qiskit Runtime. If you have the credentials already saved, you can initialize an ``QiskitRuntimeService`` instance without additional parameters. - -.. code-block:: python - - # Read default credentials from disk. - service = QiskitRuntimeService() - -If you have both an IBM Cloud and an IBM Quantum accounts saved, ``QiskitRuntimeService()`` loads the IBM Cloud account by default. To load the IBM Quantum account instead, specify ``QiskitRuntimeService(channel="ibm_quantum")``. - -Alternatively, if you specified a name for your account when saving it, you can also specify the name of the account to load. - -.. code-block:: python - - # Save an IBM Cloud account on disk and give it a name. - QiskitRuntimeService.save_account(channel="ibm_cloud", token=<*IBM Cloud API key*>, instance=<*IBM Cloud CRN*>, name="prod") - - service = QiskitRuntimeService(name="prod") - -If you want to use your credentials for just the session rather than saving it, you can pass the credentials in when initializing the ``QiskitRuntimeService`` instance: - -.. code-block:: python - - # Initialize an IBM Cloud account without saving it. - service = QiskitRuntimeService(channel="ibm_cloud", token=<*IBM Cloud API key*>, instance=<*IBM Cloud CRN*>) diff --git a/docs/how_to/backends.rst b/docs/how_to/backends.rst deleted file mode 100644 index 3ec7d47b0..000000000 --- a/docs/how_to/backends.rst +++ /dev/null @@ -1,159 +0,0 @@ -Run on quantum backends -================================= - -A **backend** represents either a simulator or a real quantum computer and are responsible for running quantum circuits, running pulse schedules, and returning results. - -In qiskit-ibm-runtime, a backend is represented by an instance of the ``IBMBackend`` class. Attributes of this class provides information about this backend. For example: - -* ``name``: Name of the backend. -* ``instructions``: A list of instructions the backend supports. -* ``operation_names``: A list of instruction names the backend supported. -* ``num_qubits``: The number of qubits the backend has. -* ``coupling_map``: Coupling map of the backend. -* ``dt``: System time resolution of input signals. -* ``dtm``: System time resolution of output signals. - -Refer to the `API reference `__ for a complete list of attributes and methods. - -Initialize the service ------------------------- - -Before calling ``IBMBackend``, initialize the service: - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - # Initialize the account first. - service = QiskitRuntimeService() - -List backends -------------- - -Use the ``backends()`` method to list all backends you have access to. This method returns a list of ``IBMBackend`` instances: - -.. code-block:: python - - service.backends() - -.. code-block:: - - [, - , - , - , - ] - -The ``backend()`` (note that this is singular: *backend*) method takes the name of the backend as the input parameter and returns an ``IBMBackend`` instance representing that particular backend: - -.. code-block:: python - - service.backend("ibmq_qasm_simulator") - -.. code-block:: - - - - -Filter backends ----------------- - -You may also optionally filter the set backends, by passing arguments that query the backend's configuration, status, or properties. For more general filters, you can make advanced functions using a lambda function. Refer to the API documentation for more details. - -Let's try getting only backends that fit these criteria: - -* Are real quantum devices (``simulator=False``) -* Are currently operational (``operational=True``) -* Have at least 5 qubits (``min_num_qubits=5``) - -.. code-block:: python - - service.backends(simulator=False, operational=True, min_num_qubits=5) - -A similar method is ``least_busy()``, which takes the same filters as ``backends()`` but returns the backend that matches the filters and has the least number of jobs pending in the queue: - -.. code-block:: python - - service.least_busy(operational=True, min_num_qubits=5) - - -Determine backend attributes -------------------------------------- - -As mentioned previously, the ``IBMBackend`` class attributes provide information about the backend. For example: - -.. code-block:: python - - backend = service.backend("ibmq_qasm_simulator") - backend.name #returns the backend's name - backend.backend_version #returns the version number - backend.simulator #returns True or False, depending on whether it is a simulator - backend.num_qubits #returns the number of qubits the backend has - -See the |IBMBackend_documentation|_ for the full list of backend attributes. - -.. |IBMBackend_documentation| replace:: ``IBMBackend`` class documentation -.. _IBMBackend_documentation: https://qiskit.org/documentation/partners/qiskit_ibm_runtime/stubs/qiskit_ibm_runtime.IBMBackend.html#qiskit_ibm_runtime.IBMBackend - -Find backend information from other channels --------------------------------------------------- - -To find your available systems and simulators on **IBM Cloud**, view the `Compute resources page `__. You must be logged in to see your available compute resources. You are shown a snapshot of each backend. To see full details, click the backend name. You can also search for backends from this page. - -To find your available systems and simulators on **IBM Quantum Platform**, view the `Compute resources page `__. You are shown a snapshot of each backend. To see full details, click the backend name. You can also sort, filter, and search from this page. - -Specify a backend when running a job ---------------------------------------- - -If you are using a runtime session, add the ``backend`` option when starting your session. For details about working with sessions, see `Run a primitive in a session `__. - -.. code-block:: python - - from qiskit.circuit.random import random_circuit - from qiskit.quantum_info import SparsePauliOp - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options - - circuit = random_circuit(2, 2, seed=1).decompose(reps=1) - observable = SparsePauliOp("IY") - - options = Options() - options.optimization_level = 2 - options.resilience_level = 2 - - service = QiskitRuntimeService() - with Session(service=service, backend="ibmq_qasm_simulator") as session: - estimator = Estimator(session=session, options=options) - job = estimator.run(circuit, observable) - result = job.result() - - display(circuit.draw("mpl")) - print(f" > Observable: {observable.paulis}") - print(f" > Expectation value: {result.values[0]}") - print(f" > Metadata: {result.metadata[0]}") - - -If you are not using a runtime session, you can pass the backend when initializing the primitive class. - -.. code-block:: python - - from qiskit.circuit.random import random_circuit - from qiskit.quantum_info import SparsePauliOp - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options - - circuit = random_circuit(2, 2, seed=1).decompose(reps=1) - observable = SparsePauliOp("IY") - - options = Options() - options.optimization_level = 2 - options.resilience_level = 2 - - service = QiskitRuntimeService() - backend = service.backend("ibmq_qasm_simulator") - estimator = Estimator(backend, options=options) - job = estimator.run(circuit, observable) - result = job.result() - - display(circuit.draw("mpl")) - print(f" > Observable: {observable.paulis}") - print(f" > Expectation value: {result.values[0]}") - print(f" > Metadata: {result.metadata[0]}") diff --git a/docs/how_to/error-mitigation.rst b/docs/how_to/error-mitigation.rst deleted file mode 100644 index 166916fc2..000000000 --- a/docs/how_to/error-mitigation.rst +++ /dev/null @@ -1,228 +0,0 @@ -Configure error mitigation -============================= - -.. vale IBMQuantum.Definitions = NO - -Error mitigation techniques allow users to mitigate circuit errors by modeling the device noise at the time of execution. This typically results in quantum pre-processing overhead related to model training and classical post-processing overhead to mitigate errors in the raw results by using the generated model. - -The error mitigation techniques built in to primitives are advanced resilience options. To specify these options, use the ``resilience_level`` option when submitting your job. - -The resilience level specifies how much resilience to build against errors. Higher levels generate more accurate results, at the expense of longer processing times. Resilience levels can be used to configure the cost/accuracy trade-off when applying error mitigation to your primitive query. Error mitigation reduces errors (bias) in results by processing the outputs from a collection, or ensemble, of related circuits. The degree of error reduction depends on the method applied. The resilience level abstracts the detailed choice of error mitigation method to allow users to reason about the cost/accuracy trade that is appropriate to their application. - -Given this, each level corresponds to a method or methods with increasing level of quantum sampling overhead to enable you experiment with different time-accuracy tradeoffs. The following table shows you which levels and corresponding methods are available for each of the primitives. - -.. note:: - Error mitigation is task specific so the techniques you are able to apply vary based whether you are sampling a distribution or generating expectation values. - -+------------------+-------------------------------------------------------+-----------------------------------+---------+ -| Resilience Level | Definition | Estimator | Sampler | -+==================+=======================================================+===================================+=========+ -| 0 | No mitigation | None | None | -+------------------+-------------------------------------------------------+-----------------------------------+---------+ -| 1 [Default] | Minimal mitigation costs: Mitigate error associated | Twirled Readout Error eXtinction | M3 | -| | with readout errors | (:ref:`TREX `) | | -+------------------+-------------------------------------------------------+-----------------------------------+---------+ -| 2 | Medium mitigation costs. Typically reduces bias | Zero Noise Extrapolation | --- | -| | in estimators, but is not guaranteed to be zero bias. | (:ref:`ZNE `) | | -+------------------+-------------------------------------------------------+-----------------------------------+---------+ -| 3 | Heavy mitigation with layer sampling. Theoretically | Probabilistic Error Cancellation | --- | -| | expected to deliver zero bias estimators. | (:ref:`PEC `) | | -+------------------+-------------------------------------------------------+-----------------------------------+---------+ - -.. note:: - Resilience levels are currently in beta so sampling overhead and solution quality will vary from circuit to circuit. New features, advanced options and management tools will be released on a rolling basis. Specific error mitigation methods are not guaranteed to be applied at each resilience level. - -Configure the Estimator with resilience levels ------------------------------------------------ - -.. raw:: html - -
- Resilience Level 0 - -No error mitigation is applied to the user program. - -.. raw:: html - -
- -.. raw:: html - -
- Resilience Level 1 - -.. _TREX: - -Level 1 applies error mitigation methods that particularly address readout errors. In the Estimator, we apply a model-free technique known as Twirled Readout Error eXtinction (TREX). It reduces measurement error by diagonalizing the noise channel associated with measurement by randomly flipping qubits through X gates immediately before measurement, and flipping the corresponding measured bit if an X gate was applied. A rescaling term from the diagonal noise channel is learned by benchmarking random circuits initialized in the zero state. This allows the service to remove bias from expectation values that result from readout noise. This approach is described further in `Model-free readout-error mitigation for quantum expectation values `__. - -.. raw:: html - -
- -.. raw:: html - -
- Resilience Level 2 - -.. _ZNE: - -Level 2 uses the Zero Noise Extrapolation method (ZNE) which computes an expectation value of the observable for different noise factors (amplification stage) and then uses the measured expectation values to infer the ideal expectation value at the zero-noise limit (extrapolation stage). This approach tends to reduce errors in expectation values, but is not guaranteed to produce an unbiased result. - -.. figure:: ../images/resiliance-2.png - :alt: This image shows a graph that compares the noise amplification factor to expectation values. - - Illustration of the ZNE method - -The overhead of this method scales with the number of noise factors. The default settings sample the expectation value at three noise factors, leading to a roughly 3x overhead when employing this resilience level. - -.. raw:: html - -
- -.. raw:: html - -
- Resilience Level 3 - -.. _PEC: - -Level 3 enables the Probabilistic Error Cancellation (PEC) method. This approach mitigates error by learning and inverting a sparse noise model that is able to capture correlated noise. PEC returns an unbiased estimate of an expectation value so long as learned noise model faithfully represents the actual noise model at the time of mitigation. In practice, the experimental procedure for learning the noise model has ambiguities due to certain error terms that cannot be independently distinguished. These are resolved by a symmetry assumption, which depending on the true underlying noise may lead a biased estimate of the mitigated expectation values due to using an imperfect noise model. - -The Qiskit Runtime primitive implementation of PEC specifically addresses noise in self-inverse two-qubit gates, so it first *stratifies* each input circuit into an alternating sequence of simultaneous 1-qubit gates followed by a layer of simultaneous 2-qubit gates. Then it learns the noise model associated with each unique 2-qubit gate layer. - -.. figure:: ../images/stratified.png - :alt: This image shows a stratified circuit. - - This is an example of a `stratified` circuit, where the layers of two-qubit gates are labeled layer 1 through n. Note that each :math:`U_l` is composed of two-qubit gates on the native connectivity graph of the quantum processor. The open boxes represent arbitrary single-qubit gates. - -The overhead of this method scales with the number of noise factors. The default settings sample the expectation value at three noise factors, leading to a roughly 3x overhead when employing this resilience level. - -PEC uses a quasi-probability method to mimic the effect of inverting the learned noise. This requires sampling from a randomized circuit family associated with the user’s original circuit. Applying PEC will increase the variability of the returned expectation value estimates unless the number of samples per circuit is also increased for both input and characterization circuits. The amount of samples required to counter this variability scales exponentially with the noise strength of the mitigated circuit. - -How this works: - -When estimating an unmitigated Pauli observable :math:`\langle P\rangle` the standard error in the estimated expectation value is given by :math:`\frac{1}{\sqrt{N_{\mbox{shots}}}}\left(1- \langle P\rangle^2\right)` where :math:`N_{\mbox{shots}}` is the number of shots used to estimate :math:`\langle P\rangle`. When applying PEC mitigation, the standard error becomes :math:`\sqrt{\frac{S}{N_{\mbox{samples}}}}\left(1- \langle P\rangle^2\right)` where :math:`N_{\mbox{samples}}` is the number of PEC samples. - -The sampling overhead scales exponentially with a parameter that characterizes the collective noise of the input circuit. As the Qiskit Runtime primitive learns the noise of your circuit, it will return metadata about the sampling overhead associated with that particular layer. Let's label the overhead of layer :math:`l` as :math:`\gamma_l`. Then the total sampling overhead for mitigating your circuit is the product of all the layer overheads, that is: - -:math:`S = \prod_l \gamma_l` - -When the Estimator completes the model-learning phase of the primitive query, it will return metadata about the total sampling overhead for circuit. - -Depending on the precision required by your application, you will need to scale the number of samples accordingly. The following plot illustrates the relationship between estimator error and number of circuit samples for different total sampling overheads. - -.. figure:: ../images/sampling-overhead.png - :alt: This image shows that sampling overhead goes down as the number of samples increases. - -Note that the number of samples required to deliver a desired accuracy is not known before the primitive query because the mitigation scaling factor is discovered during the learning phase of PEC. - -We suggest starting with short depth circuits to get a feel for the scaling of the sampling overhead of PEC before attempting larger problems. - -.. raw:: html - -
- -Example -^^^^^^^ - -The Estimator interface lets users seamlessly work with the variety of error mitigation methods to reduce error in expectation values of observables. The following code uses Zero Noise Extrapolation by simply setting ``resilience_level 2``. - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options - - service = QiskitRuntimeService() - options = Options() - options.resilience_level = 2 - options.optimization_level = 3 - - with Session(service=service, backend="ibmq_qasm_simulator") as session: - estimator = Estimator(session=session, options=options) - job = estimator.run(circuits=[psi1], observables=[H1], parameter_values=[theta1]) - psi1_H1 = job.result() - -.. note:: - As you increase the resilience level, you will be able to use additional methods to improve the accuracy of your result. However, because the methods become more advanced with each level, they require additional sampling overhead (time) to generate more accurate expectation values. - Note that higher resilience levels do not guarantee better quality. Higher levels only mean greater overhead. Each method has its strengths and weaknesses. For example, TREX (Twirled Readout Error eXtinction) is good for shallow circuits because of its readout error mitigation whereas ZNE (Zero Noise Extrapolation) is good for deeper circuits. PEC can mitigate arbitrary errors but may not work in practice because of its large overhead. - -Configure Sampler with resilience levels ------------------------------------------ - - -The Sampler default resilience setting (level 1) enables readout error mitigation to allow users to generate mitigated quasi-probability distributions. - -.. raw:: html - -
- Resilience Level 1 - -Level 1 uses matrix-free measurement mitigation (M3) routine to mitigate readout error. M3 works in a reduced subspace defined by the noisy input bit strings that are to be corrected. Because the number of unique bit strings can be much smaller than the dimensionality of the full multi-qubit Hilbert space, the resulting linear system of equations is nominally much easier to solve. - -.. figure:: ../images/m3.png - :alt: This image illustrates the M3 routine. - - Illustration of the M3 method - -.. raw:: html - -
- -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler, Options - - service = QiskitRuntimeService() - options = Options() - options.resilience_level = 1 - options.optimization_level = 3 - - with Session(service=service, backend="ibmq_qasm_simulator") as session: - sampler = Sampler(session=session, options=options) - -Advanced resilience options ----------------------------- - -You can tune advanced options to configure your resilience strategy further. These methods can be used alongside resilience levels where you change the specific options of interest and let your previously set resilience level manage the rest. - -As a part of the beta release of the resilience options, users will be able configure ZNE by using the following advanced options. We will soon add options to tune other resilience levels that include PEC. - -+---------------------------------------------------------------+----------------------------------+--------------------------------------------------------+ -| Options | Inputs | Description | -+===============================================================+==================================+========================================================+ -| options.resilience.noise_amplifier(Optional[str]) | ``LocalFoldingAmplifier`` | Amplifies noise of all gates by performing local | -| (currently only one available option) | | gate folding. | -+---------------------------------------------------------------+----------------------------------+--------------------------------------------------------+ -| options.resilience.noise_factors((Optional[Sequence[float]]) | (1, 3, 5) [Default] | Noise amplification factors, where `1` represents the | -| | | baseline noise. They all need to be greater than or | -| | | equal to the baseline. | -+---------------------------------------------------------------+----------------------------------+--------------------------------------------------------+ -| options.resilience.extrapolator(Optional[str]) | ``LinearExtrapolator`` [Default] | Polynomial extrapolation of degree one. | -| +----------------------------------+--------------------------------------------------------+ -| | ``QuadraticExtrapolator`` | Polynomial extrapolation of degree two and lower. | -| +----------------------------------+--------------------------------------------------------+ -| | ``CubicExtrapolator`` | Polynomial extrapolation of degree three and lower. | -| +----------------------------------+--------------------------------------------------------+ -| | ``QuarticExtrapolator`` | Polynomial extrapolation of degree four and lower. | -+---------------------------------------------------------------+----------------------------------+--------------------------------------------------------+ - -Example of adding ``resilience_options`` into your estimator session -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options - - service = QiskitRuntimeService() - options = Options() - options.optimization_level = 3 - options.resilience_level = 2 - options.resilience.noise_factors = (1, 2, 3, 4) - options.resilience.noise_amplifier = 'LocalFoldingAmplifier' - options.resilience.extrapolator = 'QuadraticExtrapolator' - - - with Session(service=service, backend="ibmq_qasm_simulator") as session: - estimator = Estimator(session=session, options=options) - job = estimator.run(circuits=[psi1], observables=[H1], parameter_values=[theta1]) - psi1_H1 = job.result() - diff --git a/docs/how_to/error-suppression.rst b/docs/how_to/error-suppression.rst deleted file mode 100644 index 71cb45834..000000000 --- a/docs/how_to/error-suppression.rst +++ /dev/null @@ -1,103 +0,0 @@ -Configure error suppression -============================= - -Error suppression techniques optimize and transform your circuit at the point of compilation to minimize errors. This is the most basic error handling technique. - -Error suppression typically results in some classical pre-processing overhead to your overall runtime. Therefore, it is important to achieve a balance between perfecting your results and ensuring that your job completes in a reasonable amount of time. - -Primitives let you employ error suppression techniques by setting the optimization level (``optimization_level`` option) and by choosing advanced transpilation options. - -Setting the optimization level -------------------------------- - -The ``optimization_level`` setting specifies how much optimization to perform on the circuits. Higher levels generate more optimized circuits, at the expense of longer transpilation times. - -..note:: - When using primitives, optimization levels 2 and 3 behave like level 1. - -+--------------------+---------------------------------------------------------------------------------------------------+ -| Optimization Level | Estimator & Sampler | -+====================+===================================================================================================+ -| 0 | No optimization: typically used for hardware characterization | -| | | -| | - basic translation | -| | - layout (as specified) | -| | - routing (stochastic swaps) | -| | | -+--------------------+---------------------------------------------------------------------------------------------------+ -| 1, 2, 3 | Light optimization: | -| | | -| | - Layout (trivial → vf2 → SabreLayout if routing is required) | -| | - routing (SabreSWAPs if needed) | -| | - 1Q gate optimization | -| | - Error Suppression: Dynamical Decoupling | -| | | -+--------------------+---------------------------------------------------------------------------------------------------+ - -..note:: - If you want to use more advanced optimization, use the Qiskit transpiler locally and then pass the transpiled circuits to the primitives. For instructions see the `Submitting user-transpiled circuits using primitives `__ tutorial. - -Example: configure Estimator with optimization levels -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options - from qiskit.circuit.library import RealAmplitudes - from qiskit.quantum_info import SparsePauliOp - - service = QiskitRuntimeService() - options = Options(optimization_level=1) - - psi = RealAmplitudes(num_qubits=2, reps=2) - H = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)]) - theta = [0, 1, 1, 2, 3, 5] - - with Session(service=service, backend="ibmq_qasm_simulator") as session: - estimator = Estimator(session=session, options=options) - job = estimator.run(circuits=[psi], observables=[H], parameter_values=[theta]) - psi1_H1 = job.result() - -.. note:: - If optimization level is not specified, the service uses ``optimization_level = 1``. - -Example: configure Sampler with optimization levels -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler, Options - - service = QiskitRuntimeService() - options = Options(optimization_level=1) - - with Session(service=service, backend="ibmq_qasm_simulator") as session: - sampler = Sampler(session=session, options=options) - -Advanced transpilation options ------------------------------- - -You also have the ability to tune a variety of advanced options to configure your transpilation strategy further. These methods can be used alongside optimization levels. They allow you to change the options of interest and let your optimization level manage the rest. - -Most of the transpilation options are inherited from `qiskit.compiler.transpile `__. - -+---------------------------------------------------------------+-------------------------------------------------------------------------+ -| Options | Description | -+===============================================================+=========================================================================+ -| options.transpilation.initial_layout(Union[dict, List, None]) | Initial position of virtual qubits on physical qubits. | -+---------------------------------------------------------------+-------------------------------------------------------------------------+ -| options.transpilation.layout_method (Optional[str]) | Name of layout selection pass. One of ``trivial``, ``dense``, | -| | ``noise_adaptive``, ``sabre``. | -+---------------------------------------------------------------+-------------------------------------------------------------------------+ -| options.transpilation.routing_method (Optional[str]) | Name of routing pass: ``basic``, ``lookahead``, ``stochastic``, | -| | ``sabre``, ``none``. | -+---------------------------------------------------------------+-------------------------------------------------------------------------+ -| options.transpilation.skip_transpilation (bool) | This option is specific to Qiskit Runtime primitives. | -| | Allows for skipping transpilation entirely. If you use this method, | -| | make sure to verify that your circuit in written using the basis gates | -| | on the backend you are running on. | -+---------------------------------------------------------------+-------------------------------------------------------------------------+ -| options.transpilation.approximation_degree (Optional[float]) | heuristic dial used for circuit approximation | -| | (1.0=no approximation, 0.0=maximal approximation). | -| | Defaults to no approximation for all optimization levels | -+---------------------------------------------------------------+-------------------------------------------------------------------------+ \ No newline at end of file diff --git a/docs/how_to/noisy_simulators.rst b/docs/how_to/noisy_simulators.rst deleted file mode 100644 index 961309c0d..000000000 --- a/docs/how_to/noisy_simulators.rst +++ /dev/null @@ -1,449 +0,0 @@ -Noisy simulators in Qiskit Runtime -================================== - -This notebook shows how to set up ``ibmq_qasm_simulator`` and map a basic noise -model for an IBM Quantum hardware device in **Qiskit Runtime**, and use this -noise model to perform noisy simulations of ``QuantumCircuits`` using -``Sampler`` and ``Estimator`` to study the effects of errors which occur on -real devices. - -Set up your local development environment ------------------------------------------ - -This tutorial requires a Qiskit Runtime service instance to be setup. If -you haven’t done so already, follow `these -steps `__ -to set one up. - -.. code-block:: python - - # load necessary Runtime libraries - from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Estimator, Session, Options - - service = QiskitRuntimeService(channel="ibm_quantum") - -Preparing the environment -------------------------- - -To demonstrate the routine, we shall proceed with running an example -routine. One of the major benefits of using primitives is simplification -of binding multiple parameters in parameterized circuits. To check this, -here is an example circuit with a controlled -`P-gate `__ -as implemented in the following code. Here, we parametrise the ``P-gate`` with a -rotation parameter ``theta``. To learn how to create circuits and bind -parameters to them by using Qiskit, see the `Circuit -Basics `__ -and `Advanced -Circuits `__ -in Qiskit documentation. - -.. code-block:: python - - from qiskit.circuit import Parameter - from qiskit import QuantumCircuit - - theta = Parameter('theta') - - qc = QuantumCircuit(2,1) - qc.x(1) - qc.h(0) - qc.cp(theta,0,1) - qc.h(0) - qc.measure(0,0) - - qc.draw('mpl') - - - - -.. image:: ../images/noisy-sim-circuit.png - - - -The circuit shown by the previous cell is parameterized with the eigenvalue -being kicked back into qubit 0 to be measured. The amount of kickback will be -determined by the parameter theta. Now in the following cell, we shall define -our parameters for our circuit as a list. The parameters here will be from -:math:`0` to :math:`2\pi` divided over 50 evenly spaced points. - -.. code-block:: python - - import numpy as np - - phases = np.linspace(0, 2*np.pi, 50) - - # phases need to be expressed as a list of lists in order to work - individual_phases = [[phase] for phase in phases] - -Running on the ideal simulator ------------------------------- - -Set the backend and options to use -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -First we shall demonstrate a run using an ideal case without any -``noise_model``, ``optimization_level`` or ``resilience_level`` for both -Sampler and Estimator. We shall proceed to setup the options in the following -code: - -.. code-block:: python - - backend = "ibmq_qasm_simulator" # use the simulator - -.. code-block:: python - - options = Options() - options.simulator.seed_simulator = 42 - options.execution.shots = 1000 - options.optimization_level = 0 # no optimization - options.resilience_level = 0 # no error mitigation - -Run the circuits on Sampler -~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -We shall now sample the circuit to get the result probability -distribution using the `Sampler primitive -`__ -to do the same. To learn how to use the ``Sampler`` primitive and how to -get started using Qiskit Runtime Sessions, you can check this tutorial: -`Get started with the Sampler -primitive `__. - -.. code-block:: python - - with Session(service=service, backend=backend): - sampler = Sampler(options=options) - job = sampler.run( - circuits=[qc]*len(phases), - parameter_values=individual_phases - ) - result = job.result() - -.. code-block:: python - - import matplotlib.pyplot as plt - - # the probablity of being in the 1 state for each of these values - prob_values = [dist.get(1, 0) for dist in result.quasi_dists] - - plt.plot(phases, prob_values, 'o', label='Simulator') - plt.plot(phases, np.sin(phases/2,)**2, label='Theory') - plt.xlabel('Phase') - plt.ylabel('Probability') - plt.legend() - - - - -.. parsed-literal:: - - - - - - -.. image:: ../images/noisy-sim-sampler-ideal.png - - -Run the circuits on Estimator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To learn how to start a session for Estimator, you may check this -tutorial: `Get started with the Estimator -primitive `__. - -The Estimator will bind single-qubit rotations to get Hamiltonians -before it returns expectation values of quantum operators. Therefore, -the circuit doesn’t require any measurements. Currently the circuit -``qc`` has measurements so we will remove these with -``remove_final_measurements``. - -.. code-block:: python - - qc_no_meas = qc.remove_final_measurements(inplace=False) - qc_no_meas.draw('mpl') - - - - -.. image:: ../images/noisy-sim-estimator-circuit.png - - - -.. code-block:: python - - from qiskit.quantum_info import SparsePauliOp - - ZZ = SparsePauliOp.from_list([("ZZ", 1)]) - print(f" > Observable: {ZZ.paulis}") - - -.. parsed-literal:: - - > Observable: ['ZZ'] - - -With this observable, the expectation value is calculated by the -following equation. - -.. math:: - - \langle ZZ\rangle =\langle \psi | ZZ | \psi\rangle=\langle \psi|(|0\rangle\langle 0| -|1\rangle\langle 1|)\otimes(|0\rangle\langle 0| - |1\rangle\langle 1|) |\psi\rangle =|\langle 00|\psi\rangle|^2 - |\langle 01 | \psi\rangle|^2 - |\langle 10 | \psi\rangle|^2 + |\langle 11|\psi\rangle|^2 - -The next cell will implement this as shown. - -.. code-block:: python - - with Session(service=service, backend=backend): - estimator = Estimator(options=options) - job = estimator.run( - circuits=[qc_no_meas]*len(phases), - parameter_values=individual_phases, - observables=[ZZ]*len(phases) - ) - result = job.result() - -.. code-block:: python - - exp_values = result.values - - plt.plot(phases, exp_values, 'o', label='Simulator') - plt.plot(phases, 2*np.sin(phases/2)**2-1, label='Theory') - plt.xlabel('Phase') - plt.ylabel('Expectation') - plt.legend() - - - - -.. parsed-literal:: - - - - - - -.. image:: ../images/noisy-sim-estimator-ideal.png - - -Running a noisy simulation --------------------------- - -Now we’ll setup our simulator to run a noisy simulation rather than the -ideal one. We can pass a custom ``noise_model`` to the simulator on -Runtime by specifying it in the ``Options`` parameter. Here we will try -to mimic a real backend and map on the ``noise_model`` from a -``FakeBackend`` class. The noise model can be extracted from the -``FakeBackend`` and passed as a ``simulator`` parameter in options. If -you want to know more about ``fake_provider``, check `Fake -Provider `__ -in Qiskit documentation. - -Since we are trying to mimic a real backend, we can also pass in the -``coupling_map`` that the backend topology has and the ``basis_gates`` -that the backend supports to have a more realistic noisy simulation. - -.. code-block:: python - - from qiskit.providers.fake_provider import FakeManila - from qiskit_aer.noise import NoiseModel - - # Make a noise model - fake_backend = FakeManila() - noise_model = NoiseModel.from_backend(fake_backend) - - # Set options to include the noise model - options = Options() - options.simulator = { - "noise_model": noise_model, - "basis_gates": fake_backend.configuration().basis_gates, - "coupling_map": fake_backend.configuration().coupling_map, - "seed_simulator": 42 - } - - # Set number of shots, optimization_level and resilience_level - options.execution.shots = 1000 - options.optimization_level = 0 - options.resilience_level = 0 - -:meth:`~.options.SimulatorOptions.set_backend` is the syntactic sugar for setting options. -The following code is equivalent. - -.. code-block:: python - - from qiskit.providers.fake_provider import FakeManila - - # Make a noise model - fake_backend = FakeManila() - - # Set options to include the noise model - options = Options() - options.simulator.set_backend(fake_backend) - options.simulator.seed_simulator = 42 - - # Set number of shots, optimization_level and resilience_level - options.execution.shots = 1000 - options.optimization_level = 0 - options.resilience_level = 0 - -The ``ibmq_qasm_simulator`` allows for the activation of the -``resilience_levels`` offered by the Qiskit Runtime Service, and use of -these levels on simulators is best demonstrated using the noisy -simulation as we have described previously. - -To see the comparison, we shall define two set of ``Options``. The -``ibmq_qasm_simulator`` allows for the activation of the resilience levels -offered by Qiskit Runtime, and the use of these levels on simulators is best -demonstrated using the noisy simulation that we have built. Here, ``options`` -is set to\ ``resilience level`` = ``0`` to represent a normal run without error -mitigation, and ``options with em`` is set to ``resilience level`` = ``1`` to -represent a run with error mitigation enabled. - -.. code-block:: python - - # Set options to include the noise model with error mitigation - options_with_em = Options() - options_with_em.simulator = { - "noise_model": noise_model, - "basis_gates": fake_backend.configuration().basis_gates, - "coupling_map": fake_backend.configuration().coupling_map, - "seed_simulator": 42 - } - - # Set number of shots, optimization_level and resilience_level - options_with_em.execution.shots = 1000 - options_with_em.optimization_level = 0 # no optimization - options_with_em.resilience_level = 1 # M3 for Sampler and T-REx for Estimator - -When you set the ``resilience_level`` to 1, M3 is activated in Sampler. -All available resilience level configurations can be found -`here `__. - -.. code-block:: python - - with Session(service=service, backend=backend): - # include the noise model without M3 - sampler = Sampler(options=options) - job = sampler.run( - circuits=[qc]*len(phases), - parameter_values=individual_phases - ) - result = job.result() - prob_values = [1-dist[0] for dist in result.quasi_dists] - - # include the noise model with M3 - sampler = Sampler(options=options_with_em) - job = sampler.run( - circuits=[qc]*len(phases), - parameter_values=individual_phases - ) - result = job.result() - prob_values_with_em = [1-dist[0] for dist in result.quasi_dists] - -.. code-block:: python - - plt.plot(phases, prob_values, 'o', label='Noisy') - plt.plot(phases, prob_values_with_em, 'o', label='Mitigated') - plt.plot(phases, np.sin(phases/2,)**2, label='Theory') - plt.xlabel('Phase') - plt.ylabel('Probability') - plt.legend() - - - - -.. parsed-literal:: - - - - - - -.. image:: ../images/noisy-sim-sampler-noisy.png - - -``T-REx`` is triggered in Estimator when the resilience level is set to -1. - -.. code-block:: python - - with Session(service=service, backend=backend): - # include the noise model without T-REx - estimator = Estimator(options=options) - job = estimator.run( - circuits=[qc_no_meas]*len(phases), - parameter_values=individual_phases, - observables=[ZZ]*len(phases) - ) - result = job.result() - exp_values = result.values - - # include the noise model with T-REx - estimator = Estimator(options=options_with_em) - job = estimator.run( - circuits=[qc_no_meas]*len(phases), - parameter_values=individual_phases, - observables=[ZZ]*len(phases)) - result = job.result() - exp_values_with_em = result.values - -.. code-block:: python - - plt.plot(phases, exp_values, 'o', label='Noisy') - plt.plot(phases, exp_values_with_em, 'o', label='Mitigated') - plt.plot(phases, 2*np.sin(phases/2)**2-1, label='Theory') - plt.xlabel('Phase') - plt.ylabel('Expectation') - plt.legend() - - - - -.. parsed-literal:: - - - - - - -.. image:: ../images/noisy-sim-estimator-noisy.png - - -.. vale IBMQuantum.Definitions = NO - -Resilience levels are currently in beta so sampling overhead and -solution quality will vary from circuit to circuit. New features, -advanced options and management tools will be released on a rolling -basis. You can also play around with higher levels of resilience and -explore additional options offered by them. If you want to learn more -about activating features like *Digital-ZNE*, *PEC* in addition to *M3* -and *T-REx* as shown in the previous examples, check out this tutorial: -`Error suppression and error mitigation with Qiskit -Runtime `__. - -.. code-block:: python - - import qiskit_ibm_runtime - qiskit_ibm_runtime.version.get_version_info() - - - - -.. parsed-literal:: - - '0.8.0' - - - -.. code-block:: python - - from qiskit.tools.jupyter import * - %qiskit_version_table - - - -.. raw:: html - -

Version Information

Qiskit SoftwareVersion
qiskit-terra0.22.2
qiskit-aer0.11.1
qiskit-ibmq-provider0.19.2
qiskit0.39.2
qiskit-nature0.5.0
qiskit-finance0.3.4
qiskit-optimization0.4.0
qiskit-machine-learning0.5.0
System information
Python version3.8.13
Python compilerGCC 10.3.0
Python builddefault, Mar 25 2022 06:04:10
OSLinux
CPUs8
Memory (Gb)31.211326599121094
Wed Nov 30 02:43:41 2022 UTC
- diff --git a/docs/how_to/options.rst b/docs/how_to/options.rst deleted file mode 100644 index e2ab0bb56..000000000 --- a/docs/how_to/options.rst +++ /dev/null @@ -1,52 +0,0 @@ -Configure primitive options -=========================== - -When calling the primitives, you can pass in options, as shown in the line ``estimator = Estimator(options=options)`` in the following code example: - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options - - service = QiskitRuntimeService() - options = Options(optimization_level=1) - # Options class also supports auto-completion. - options.resilience_level = 1 - # And are grouped by the category for easy search. - option.execution.shots = 1000 - - with Session(service=service, backend="ibmq_qasm_simulator"): - estimator = Estimator(options=options) - -You can also overwrite specific options for a job when calling ``run()``. In the following code example, the job will run with optimization level 1 and 4000 shots (instead of 1000): - -.. code-block:: python - - from qiskit.test.reference_circuits import ReferenceCircuits - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler, Options - - service = QiskitRuntimeService() - backend = service.backend("ibmq_qasm_simulator") - options = Options() - options.optimization_level = 1 - option.execution.shots = 1000 - - sampler = Sampler(backend, options=options) - job = sampler.run(ReferenceCircuits.bell(), shots=4000) - - -The most commonly used options are for error suppression and mitigation, which are described in this topic. For a full list of available options, see the `Options API reference `__. - -Overview of error suppression and mitigation --------------------------------------------- - -No computing platform is perfect, and because quantum computers are such new and complex technology, we have to find new ways of dealing with these imperfections. There are several possible causes for errors: “noise” - disturbances in the physical environment, and “bit errors”, which cause the qubit's value or phase to change. IBM builds redundancy into the hardware to ensure that even if some qubits error out, an accurate result is still returned. However, we can further address errors by using error suppression and error mitigation techniques These strategies make use of pre- and post-processing to improve the quality of the results produced for the input circuit. - -* **Error suppression**: Techniques that optimize and transform your circuit at the point of compilation to minimize errors. This is the most basic error handling technique. Error suppression typically results in some classical pre-processing overhead to your overall runtime. - -Primitives let you employ error suppression techniques by setting the optimization level (``optimization_level`` option) and by choosing advanced transpilation options. See `Configure error suppression `__ for details. - -* **Error mitigation**: Techniques that allow users to mitigate circuit errors by modeling the device noise at the time of execution. This typically results in quantum pre-processing overhead related to model training, and classical post-processing overhead to mitigate errors in the raw results by using the generated model. - -The error mitigation techniques built in to primitives are advanced resilience options. To specify these options, use the ``resilience_level`` when submitting your job. See `Configure error mitigation `__ for details. - - diff --git a/docs/how_to/retrieve_results.rst b/docs/how_to/retrieve_results.rst deleted file mode 100644 index 85f72bded..000000000 --- a/docs/how_to/retrieve_results.rst +++ /dev/null @@ -1,43 +0,0 @@ -Retrieve job results -================================= - -After submitting your job, a `RuntimeJob `_ instance is returned. Use the job instance to check the job status or retrieve the results by calling the appropriate method: - -.. list-table:: Job methods - - * - job.result() - - Review job results immediately after the job completes. Job results are available after the job completes. Therefore, `job.result()` is a blocking call until the job completes. - * - job.job_id() - - Return the ID that uniquely identifies that job. Retrieving the job results at a later time requires the job ID. Therefore, it is recommended that you save the IDs of jobs you might want to retrieve later. - * - job.status() - - Check the job status. - * - job = service.job() - - Retrieve a job you previously submitted. This call requires the job ID. - -Jobs are also listed on the Jobs page for your quantum service channel: - -* For the IBM Cloud channel, from the IBM Cloud console quantum `Instances page `__, click the name of your instance, then click the Jobs tab. To see the status of your job, click the refresh arrow in the upper right corner. -* For the IBM Quantum channel, in IBM Quantum platform, open the `Jobs page `__. - - -Retrieve job results at a later time -************************************ - -Call `service.job()` to retrieve a job you previously submitted. If you don't have the job ID, or if you want to retrieve multiple jobs at once; including jobs from retired systems, call `service.jobs()` with optional filters instead. See `QiskitRuntimeService.jobs `__. - -.. note:: - `service.jobs()` returns only Qiskit Runtime jobs. To retrieve other jobs, use `qiskit-ibm-provider `__ instead. - -Example -------- - -This example returns the 10 most recent runtime jobs that were run on ``ibmq_qasm_simulator``: - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - # Initialize the account first. - service = QiskitRuntimeService() - - service.jobs(backend_name="ibmq_qasm_simulator") diff --git a/docs/how_to/run_session.rst b/docs/how_to/run_session.rst deleted file mode 100644 index 9511f74b0..000000000 --- a/docs/how_to/run_session.rst +++ /dev/null @@ -1,295 +0,0 @@ -Run jobs in a session -================================= - -There are several ways to set up and use sessions. The following information should not be considered mandatory steps to follow. Instead, choose the configuration that best suits your needs. To learn more about sessions, see `Introduction to sessions <../sessions.html>`__. This information assumes that you are using Qiskit Runtime `primitives <../primitives.html>`__. - -Prerequisites --------------- - -Before starting a session, you must `Set up Qiskit Runtime `__ and initialize it as a service: - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - service = QiskitRuntimeService() - -Open a session ------------------ - -You can open a runtime session by using the context manager `with Session(…)` or by initializing the `Session` class. When you start a session, you can specify options, such as the backend to run on. This topic describes the most commonly used options. For the full list, see the `Sessions API documentation `__. - -.. important:: - If the first session job is canceled, subsequent session jobs will all fail. - -**Session class** - -A session can be created by initializing the `Session` class, which can then be passed to the desired primitives. Example: - -.. code-block:: python - - session= Session(service=service, backend="ibmq_qasm_simulator") - estimator = Estimator(session=session) - sampler = Sampler(session=session) - -**Context manager** - -The context manager automatically opens and closes a session for you. A session is started when the first primitive job in this context manager starts (not when it is queued). Primitives created in the context automatically use that session. Example: - -.. code-block:: python - - with Session(service=service, backend="ibmq_qasm_simulator"): - estimator = Estimator() - sampler = Sampler() - - -Specify a backend ------------------ - -When you start a session, you can specify session options, such as the backend to run on. A backend is required if you are using the IBM Quantum channel, but optional if you are using the IBM Cloud channel. Once specified, you cannot change the backend used for a session and you cannot specify multiple backends within a session. To use a different backend, open a new session. - -There are two ways to specify a backend in a session: - -**Directly specify a string with the backend name.** - -Example: - -.. code-block:: python - - service = QiskitRuntimeService() - with Session(service=service, backend="ibmq_qasm_simulator"): - ... - -**Pass the backend object.** Example: - -.. code-block:: python - - backend = service.get_backend("ibmq_qasm_simulator") - with Session(backend=backend): - ... - -.. _session_length: - -Specify the session length --------------------------- - -When a session is started, it is assigned a maximum session timeout value. After the session has been open the specified amount of time, the session expires and is forcefully closed. You can no longer submit jobs to that session. See `What happens when a session ends <../sessions.html#ends>`__ for further details. - -You can configure the maximum session timeout value through the ``max_time`` parameter, which can be specified as seconds (int) or a string, like "2h 30m 40s". This value has to be greater than the ``max_execution_time`` of the job and less than the system’s ``max_time``. The default value is the system’s ``max_time``. See `Determine session details <#determine-session-details>`__ to determine the system limit. - -When setting the session length, consider how long each job within the session might take. For example, if you run five jobs within a session and each job is estimated to be five minutes long, the maximum time for the session should at least 25 min. - -.. code-block:: python - - with Session(service=service, backend=backend, max_time="25m"): - ... - -There is also an interactive timeout value (ITTL) that cannot be configured. If no session jobs are queued within that window, the session is temporarily deactivated. For more details about session length and timeout, see `How long a session stays active <../sessions.html#active>`__. To determine a session's ITTL, follow the instructions in `Determine session details <#determine-session-details>`__ and look for the ``interactive_timeout`` value. - - -.. _close_session: - -Close a session ---------------- - -With `qiskit-ibm-runtime` 0.13 or later releases, when the session context manager is exited, the session is put into `In progress, not accepting new jobs` status. This means that the session will finish processing all running or queued jobs until the maximum timeout value is reached. After all jobs are completed, the session is immediately closed. This allows the -scheduler to run the next job without waiting for the session interactive timeout, -therefore reducing the average job queueing time. You cannot submit jobs to a -closed session. - -This behavior exists in `qiskit-ibm-runtime` 0.13 or later releases only. Previously, `session.close()` **canceled** the session. - -.. code-block:: python - - with Session(service=service, backend=backend): - estimator = Estimator() - job = estimator.run(...) - - # The session is no longer accepting jobs but the submitted job will run to completion - result = job.result() - -.. _cancel_session: - -Cancel a session ----------------- - -If a session is canceled, the session is put into `Closed` status. Any jobs that are already running continue to run but queued jobs are put into a failed state and no further jobs can be submitted to the session. This is a convenient way to quickly fail all queued jobs within a session. - -### For Qiskit runtime releases 0.13 or later - -Use the `session.cancel()` method to cancel a session. - -.. code-block:: python - - with Session(service=service, backend=backend) as session: - estimator = Estimator() - job1 = estimator.run(...) - job2 = estimator.run(...) - # You can use session.cancel() to fail all pending jobs, for example, - # if you realize you made a mistake. - session.cancel() - -For Qiskit Runtime releases 0.13 or later -+++++++++++++++++++++++++++++++++++++++++ - -Use the `session.cancel()` method to cancel a session. - -.. code-block:: python - - with Session(service=service, backend=backend) as session: - estimator = Estimator() - job1 = estimator.run(...) - job2 = estimator.run(...) - # You can use session.cancel() to fail all pending jobs, for example, - # if you realize you made a mistake. - session.cancel() - -For Qiskit Runtime releases before 0.13 -+++++++++++++++++++++++++++++++++++++++++ - -Use the `session.close()` method to cancel a session. This allows the -scheduler to run the next job without waiting for the session timeout, -therefore making it easier for everyone. You cannot submit jobs to a -closed session. - -.. code-block:: python - - with Session(service=service, backend=backend) as session: - estimator = Estimator() - job = estimator.run(...) - # Do not close here, the job might not be completed! - result = job.result() - # Reaching this line means that the job is finished. - # This close() method would fail all pending jobs. - session.close() - -Invoke multiple primitives in a session ----------------------------------------- -You are not restricted to a single primitive function within a session. In this section we will show you an example of using multiple primitives. - -First we prepare a circuit for the Sampler primitive. - -.. code-block:: python - - from qiskit.circuit.random import random_circuit - - sampler_circuit = random_circuit(2, 2, seed=0).decompose(reps=1) - sampler_circuit.measure_all() - display(circuit.draw("mpl")) - -The following example shows how you can create both an instance of the `Sampler` class and one of the `Estimator` class and invoke their `run()` methods within a session. - -.. code-block:: python - - from qiskit_ibm_runtime import Session, Sampler, Estimator - - with Session(backend=backend): - sampler = Sampler() - estimator = Estimator() - - result = sampler.run(sampler_circuit).result() - print(f">>> Quasi-probability distribution from the sampler job: {result.quasi_dists[0]}") - - result = estimator.run(circuit, observable).result() - print(f">>> Expectation value from the estimator job: {result.values[0]}") - -The calls can also be synchronous. You don’t need to wait for the result of a previous job before submitting another one, as shown below: - -.. code-block:: python - - from qiskit_ibm_runtime import Session, Sampler, Estimator - - with Session(backend=backend): - sampler = Sampler() - estimator = Estimator() - - sampler_job = sampler.run(sampler_circuit) - estimator_job = estimator.run(circuit, observable) - - print( - f">>> Quasi-probability distribution from the sampler job: {sampler_job.result().quasi_dists[0]}" - ) - print(f">>> Expectation value from the estimator job: {estimator_job.result().values[0]}") - -.. _session_status: - -Query session status ---------------------- - - -You can query the status of a session using `session.status()`. You can also view a session's status on the Jobs page for your channel. - -Session status can be one of the following: - -- `Pending`: Session has not started or has been deactivated. The next session job needs to wait in the queue like other jobs. -- `In progress, accepting new jobs`: Session is active and accepting new jobs. -- `In progress, not accepting new jobs`: Session is active but not accepting new jobs. Job submission to the session will be rejected, but outstanding session jobs will run to completion. The session will be automatically closed once all jobs finish. -- `Closed`: Session maximum timeout value has been reached, or session was explicitly closed. - -.. _session_details: - -Determine session details --------------------------- - -You can find details about a session by using the `session.details()` method, from the `Quantum Platform Jobs page `__, or from the IBM Cloud Jobs page, which you access from your `Instances page `__. From the session details you can determine the `maximum <..sessions#max-ttl.html>`__ and `interactive <..sessions#ttl.html>`__ time to live (TTL) values, its status, whether it's currently accepting jobs, and more. - -Example: - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - - service = QiskitRuntimeService() - - with Session(service=service, backend="ibmq_qasm_simulator") as session: - estimator = Estimator() - job = estimator.run(circuit, observable) - print(session.details()) - -Output: - -.. code-block:: text - - { - 'id': 'cki5d18m3kt305s4pndg', - 'backend_name': 'ibm_algiers', - 'interactive_timeout': 300, # This is the interactive timeout, in seconds - 'max_time': 28800, # This is the maximum session timeout, in seconds - 'active_timeout': 28800, - 'state': 'closed', - 'accepting_jobs': True, - 'last_job_started': '2023-10-09T19:37:42.004Z', - 'last_job_completed': '2023-10-09T19:38:10.064Z', - 'started_at': '2023-10-09T19:37:42.004Z', - 'closed_at': '2023-10-09T19:38:39.406Z' - } - - -Full example ------------- - -In this example, we start a session, run an Estimator job, and output the result: - -.. code-block:: python - - from qiskit.circuit.random import random_circuit - from qiskit.quantum_info import SparsePauliOp - from qiskit_ibm_runtime import QiskitRuntimeService, Session, Estimator, Options - - circuit = random_circuit(2, 2, seed=1).decompose(reps=1) - observable = SparsePauliOp("IY") - - options = Options() - options.optimization_level = 2 - options.resilience_level = 2 - - service = QiskitRuntimeService() - with Session(service=service, backend="ibmq_qasm_simulator"): - estimator = Estimator(options=options) - job = estimator.run(circuit, observable) - result = job.result() - - display(circuit.draw("mpl")) - print(f" > Observable: {observable.paulis}") - print(f" > Expectation value: {result.values[0]}") - print(f" > Metadata: {result.metadata[0]}") \ No newline at end of file diff --git a/docs/images/Runtime_Accounting_Diagram.png b/docs/images/Runtime_Accounting_Diagram.png deleted file mode 100644 index 463eddb4b..000000000 Binary files a/docs/images/Runtime_Accounting_Diagram.png and /dev/null differ diff --git a/docs/images/batch.png b/docs/images/batch.png deleted file mode 100644 index 09ed05e83..000000000 Binary files a/docs/images/batch.png and /dev/null differ diff --git a/docs/images/check.png b/docs/images/check.png deleted file mode 100644 index 0cd006ab2..000000000 Binary files a/docs/images/check.png and /dev/null differ diff --git a/docs/images/clock.png b/docs/images/clock.png deleted file mode 100644 index 7dbc82ea3..000000000 Binary files a/docs/images/clock.png and /dev/null differ diff --git a/docs/images/close.png b/docs/images/close.png deleted file mode 100644 index 972705152..000000000 Binary files a/docs/images/close.png and /dev/null differ diff --git a/docs/images/compare-code.png b/docs/images/compare-code.png deleted file mode 100644 index e0339b3f1..000000000 Binary files a/docs/images/compare-code.png and /dev/null differ diff --git a/docs/images/compare-code.psd b/docs/images/compare-code.psd deleted file mode 100644 index 9221a95ea..000000000 Binary files a/docs/images/compare-code.psd and /dev/null differ diff --git a/docs/images/estimator.png b/docs/images/estimator.png deleted file mode 100644 index 4c9b0483c..000000000 Binary files a/docs/images/estimator.png and /dev/null differ diff --git a/docs/images/execution-paths.png b/docs/images/execution-paths.png deleted file mode 100644 index 415d53804..000000000 Binary files a/docs/images/execution-paths.png and /dev/null differ diff --git a/docs/images/ibm-quantum-logo.png b/docs/images/ibm-quantum-logo.png deleted file mode 100644 index 252c31b2f..000000000 Binary files a/docs/images/ibm-quantum-logo.png and /dev/null differ diff --git a/docs/images/instances.png b/docs/images/instances.png deleted file mode 100644 index 306d6d66a..000000000 Binary files a/docs/images/instances.png and /dev/null differ diff --git a/docs/images/iterative.png b/docs/images/iterative.png deleted file mode 100644 index 93f03e166..000000000 Binary files a/docs/images/iterative.png and /dev/null differ diff --git a/docs/images/jobs-failing.png b/docs/images/jobs-failing.png deleted file mode 100644 index 6c3da553e..000000000 Binary files a/docs/images/jobs-failing.png and /dev/null differ diff --git a/docs/images/logo.png b/docs/images/logo.png deleted file mode 100644 index c4f83672e..000000000 Binary files a/docs/images/logo.png and /dev/null differ diff --git a/docs/images/m3.png b/docs/images/m3.png deleted file mode 100644 index 0feed9313..000000000 Binary files a/docs/images/m3.png and /dev/null differ diff --git a/docs/images/noisy-sim-circuit.png b/docs/images/noisy-sim-circuit.png deleted file mode 100644 index 28d6fad02..000000000 Binary files a/docs/images/noisy-sim-circuit.png and /dev/null differ diff --git a/docs/images/noisy-sim-estimator-circuit.png b/docs/images/noisy-sim-estimator-circuit.png deleted file mode 100644 index 71797512c..000000000 Binary files a/docs/images/noisy-sim-estimator-circuit.png and /dev/null differ diff --git a/docs/images/noisy-sim-estimator-ideal.png b/docs/images/noisy-sim-estimator-ideal.png deleted file mode 100644 index e7bc8846a..000000000 Binary files a/docs/images/noisy-sim-estimator-ideal.png and /dev/null differ diff --git a/docs/images/noisy-sim-estimator-noisy.png b/docs/images/noisy-sim-estimator-noisy.png deleted file mode 100644 index 50c4df97f..000000000 Binary files a/docs/images/noisy-sim-estimator-noisy.png and /dev/null differ diff --git a/docs/images/noisy-sim-sampler-ideal.png b/docs/images/noisy-sim-sampler-ideal.png deleted file mode 100644 index e1ba79fbc..000000000 Binary files a/docs/images/noisy-sim-sampler-ideal.png and /dev/null differ diff --git a/docs/images/noisy-sim-sampler-noisy.png b/docs/images/noisy-sim-sampler-noisy.png deleted file mode 100644 index 8acc3151b..000000000 Binary files a/docs/images/noisy-sim-sampler-noisy.png and /dev/null differ diff --git a/docs/images/org-guide-audit-example.png b/docs/images/org-guide-audit-example.png deleted file mode 100644 index 7b4fd0d0f..000000000 Binary files a/docs/images/org-guide-audit-example.png and /dev/null differ diff --git a/docs/images/org-guide-create-access-group-1.png b/docs/images/org-guide-create-access-group-1.png deleted file mode 100644 index e44d2033b..000000000 Binary files a/docs/images/org-guide-create-access-group-1.png and /dev/null differ diff --git a/docs/images/org-guide-create-access-group-2.png b/docs/images/org-guide-create-access-group-2.png deleted file mode 100644 index d6b4fa7ea..000000000 Binary files a/docs/images/org-guide-create-access-group-2.png and /dev/null differ diff --git a/docs/images/org-guide-create-access-group-3.png b/docs/images/org-guide-create-access-group-3.png deleted file mode 100644 index 3aa56d46c..000000000 Binary files a/docs/images/org-guide-create-access-group-3.png and /dev/null differ diff --git a/docs/images/org-guide-create-appid.png b/docs/images/org-guide-create-appid.png deleted file mode 100644 index c36e0ebe6..000000000 Binary files a/docs/images/org-guide-create-appid.png and /dev/null differ diff --git a/docs/images/org-guide-create-custom-role.png b/docs/images/org-guide-create-custom-role.png deleted file mode 100644 index f0330abab..000000000 Binary files a/docs/images/org-guide-create-custom-role.png and /dev/null differ diff --git a/docs/images/org-guide-create-dynamic-rule1.png b/docs/images/org-guide-create-dynamic-rule1.png deleted file mode 100644 index 909ec479e..000000000 Binary files a/docs/images/org-guide-create-dynamic-rule1.png and /dev/null differ diff --git a/docs/images/org-guide-create-dynamic-rule2.png b/docs/images/org-guide-create-dynamic-rule2.png deleted file mode 100644 index 0d5abce29..000000000 Binary files a/docs/images/org-guide-create-dynamic-rule2.png and /dev/null differ diff --git a/docs/images/org-guide-custom-role-actions.png b/docs/images/org-guide-custom-role-actions.png deleted file mode 100644 index be3118b1d..000000000 Binary files a/docs/images/org-guide-custom-role-actions.png and /dev/null differ diff --git a/docs/images/org-guide-iam-settings.png b/docs/images/org-guide-iam-settings.png deleted file mode 100644 index f284b73da..000000000 Binary files a/docs/images/org-guide-iam-settings.png and /dev/null differ diff --git a/docs/images/org-guide-idp-reference.png b/docs/images/org-guide-idp-reference.png deleted file mode 100644 index ed7e4f8ed..000000000 Binary files a/docs/images/org-guide-idp-reference.png and /dev/null differ diff --git a/docs/images/org-guide-manage-user.png b/docs/images/org-guide-manage-user.png deleted file mode 100644 index 38f99e823..000000000 Binary files a/docs/images/org-guide-manage-user.png and /dev/null differ diff --git a/docs/images/resiliance-2.png b/docs/images/resiliance-2.png deleted file mode 100644 index 926a8a91f..000000000 Binary files a/docs/images/resiliance-2.png and /dev/null differ diff --git a/docs/images/runtime-architecture.png b/docs/images/runtime-architecture.png deleted file mode 100644 index 121840d34..000000000 Binary files a/docs/images/runtime-architecture.png and /dev/null differ diff --git a/docs/images/sampler.png b/docs/images/sampler.png deleted file mode 100644 index aa17084ff..000000000 Binary files a/docs/images/sampler.png and /dev/null differ diff --git a/docs/images/sampling-overhead.png b/docs/images/sampling-overhead.png deleted file mode 100644 index 8b893d029..000000000 Binary files a/docs/images/sampling-overhead.png and /dev/null differ diff --git a/docs/images/session-overview.png b/docs/images/session-overview.png deleted file mode 100644 index fbb6034fa..000000000 Binary files a/docs/images/session-overview.png and /dev/null differ diff --git a/docs/images/stratified.png b/docs/images/stratified.png deleted file mode 100644 index f61db7207..000000000 Binary files a/docs/images/stratified.png and /dev/null differ diff --git a/docs/images/table.png b/docs/images/table.png deleted file mode 100644 index 21b6e2bf4..000000000 Binary files a/docs/images/table.png and /dev/null differ diff --git a/docs/index.rst b/docs/index.rst index 341c1936e..1511a3162 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,50 +1,16 @@ ######################################### -Qiskit Runtime 0.16.1 documentation +Qiskit Runtime |version| API Docs Preview ######################################### -Overview -============== - -Qiskit Runtime is a cloud-based quantum computing service developed by IBM. It offers computational *primitives* to perform foundational quantum computing tasks that use built-in error suppression and mitigation techniques. Primitives can be executed inside of *sessions*, allowing collections of circuits to be jointly run on a quantum computer without being interrupted by other users’ jobs. The combination of primitives, error suppression / mitigation, and sessions paves the way to efficiently build and execute scalable quantum applications. - -The following figure illustrates how one can use Qiskit Runtime sessions and primitives. The first session request (job) waits through the regular `fair-share queue `__. When it starts to run, the session is started. After the first session job is finished processing, the next job in the session is run. This process continues until the session is paused (due to a lack of queued session jobs) or closed. - -.. figure:: images/runtime-architecture.png - :align: center - -Key concepts -============== - -**Primitives** - -Primitives are base level operations that serve as building blocks for many quantum algorithms and applications. Through these primitives, users can obtain high-fidelity results, without needing detailed hardware knowledge. This abstraction allows you to write code, using Qiskit algorithms or otherwise, that can run on different quantum hardware or simulators without having to explicitly manage aspects such as compilation, optimization, and error suppression / mitigation. The primitives offered by :mod:`qiskit_ibm_runtime` add additional options specific to IBM services. See `Introduction to primitives `__ for further details. - -There are currently two primitives defined in Qiskit: Estimator and Sampler. - - -**Estimator** - -The estimator primitive allows you to efficiently calculate and interpret expectation values of quantum operators; the values of interest for many near-term quantum algorithms. You specify circuits that prepare quantum states and then Pauli-basis observables to measure on those states. The estimator can use advanced error suppression and mitigation capabilities to improve the accuracy of the returned expectation values. - -**Sampler** - -This primitive takes circuits as input and returns a quasi-probability distribution over the measurement outcomes. This generalizes histograms from quantum circuits, allowing for mitigation of readout errors. - -**Error suppression / mitigation** - -While building a fault-tolerant quantum computation is the ultimate goal, at present, calculations performed on near-term quantum computers are susceptible to noise. Qiskit Runtime offers several methods for preventing errors before they occur (error suppression techniques) and dealing with those that do occur (error mitigation techniques). - -**Session** - -A session allows a collection of jobs to be grouped and jointly scheduled by the Qiskit Runtime service, facilitating iterative use of quantum computers without incurring queuing delays on each iteration. This eliminates artificial delays caused by other users’ jobs running on the same quantum device during the session. See `Introduction to sessions `__ for further details. - +Qiskit Runtime docs live at docs.quantum.ibm.com and come from https://github.com/Qiskit/documentation. +This site is only used to generate our API docs, which then get migrated to +https://github.com/Qiskit/documentation. +The tutorials are also pulled into learning.quantum.ibm.com. Next steps ================================= -`Getting started `_ - `Tutorials `_ .. toctree:: @@ -53,10 +19,6 @@ Next steps :caption: Get started Overview - Getting started - backend.run vs. Qiskit Runtime - Introduction to primitives - Introduction to sessions .. toctree:: :maxdepth: 1 @@ -73,48 +35,6 @@ Next steps Submit user-transpiled circuits using primitives All tutorials -.. toctree:: - :maxdepth: 1 - :hidden: - :caption: How to - - Run a primitive in a session - Run on quantum backends - Retrieve job results - Configure primitive options - Configure error mitigation options - Configure error suppression - Manage your account - Run noisy simulations - -.. toctree:: - :maxdepth: 1 - :hidden: - :caption: Migrate - - Migrate to using Qiskit Runtime primitives - Migrate your setup from qiskit-ibmq-provider - Use Estimator to design an algorithm - Use Sampler to design an algorithm - Update parameter values while running - Algorithm tuning options (shots, transpilation, error mitigation) - Migrate backend.run() from qiskit_ibm_provider to qiskit_ibm_runtime - -.. toctree:: - :maxdepth: 1 - :hidden: - :caption: Work with Qiskit Runtime in IBM Cloud - - Getting started - Pricing plans - Plan for an organization - Configure for an organization - Manage users in an organization - Manage the cost - Set up Terraform - Architecture and workload isolation - Securing your data - Audit events .. toctree:: :maxdepth: 1 @@ -122,12 +42,7 @@ Next steps :caption: Reference API Reference - Swagger API for building applications that use Qiskit Runtime - API error codes - FAQs - Retired systems Release Notes - GitHub .. Hiding - Indices and tables :ref:`genindex` diff --git a/docs/migrate/backend_run_migration_guide.rst b/docs/migrate/backend_run_migration_guide.rst deleted file mode 100644 index d62eebc26..000000000 --- a/docs/migrate/backend_run_migration_guide.rst +++ /dev/null @@ -1,96 +0,0 @@ -Migration guide: Migrate ``backend.run()`` from ``qiskit_ibm_provider`` to ``qiskit_ibm_runtime`` -================================================================================================= - -The Qiskit Runtime interface includes two packages: -Qiskit IBM Provider (the ``qiskit_ibm_provider`` package) and -Qiskit IBM Runtime (the ``qiskit_ibm_runtime`` package). Until now, -primitives (``Sampler`` and ``Estimator``) -were run in Runtime. Custom circuits that were manually transpiled and used ``IBMBackend.run()`` -were run in Provider. - -In this release, we add support for running custom circuits using ``IBMBackend.run()`` in Runtime, -so users can run all programs through Runtime. - -This guide describes how to migrate code that implemented ``IBMBackend.run()`` -using Qiskit IBM Provider to use Qiskit IBM Runtime instead. - -**Example 1: Straightforward execution of IBMBackend.run()** - -.. code-block:: python - - from qiskit import * - from qiskit.compiler import transpile, assemble - circuit = QuantumCircuit(2, 2) - circuit.h(0) - circuit.cx(0, 1) - circuit.measure_all() - -In Provider, the code is: - -.. code-block:: python - - from qiskit_ibm_provider import IBMProvider - provider = IBMProvider() - backend = provider.get_backend("ibmq_qasm_simulator") - transpiled_circuit = transpile(circuit, backend=backend) - job = backend.run(transpiled_circuit) - print(job.result()) - -In Runtime, the code is: - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - service = QiskitRuntimeService(channel="ibm_quantum") - backend = service.backend("ibmq_qasm_simulator") - transpiled_circuit = transpile(circuit, backend=backend) - job = backend.run(transpiled_circuit) - print(job.result()) - -**Example 2: Execution of backend.run() within a session:** - -This section of code is identical in Provider and in Runtime. - -.. code-block:: python - - with backend.open_session() as session: - job1 = backend.run(transpiled_circuit) - job2 = backend.run(transpiled_circuit) - print(job1.session_id) - print(job2.session_id) - backend.cancel_session() - -Sessions are implemented differently in ``IBMBackend`` than when using primitives. -Therefore, we cannot run a primitive and use backend.run() within a single session. If you specify both, one will be run outside of the session. - -**Example 3: Primitive session containing backend.run:** - -In this example, ``sampler`` is run within session, but ``backend`` is run independently -of the session. - -.. code-block:: python - - from qiskit_ibm_runtime import Session, Sampler - with Session(backend=backend) as session: - sampler = Sampler(session=session) - job1 = sampler.run(transpiled_circuit) - job2 = backend.run(transpiled_circuit) # runs outside the session - print(job1.session_id) - print(job2.session_id) # is None - -**Example 4: Backend session containing Sampler:** - -In this example, ``backend`` is run within a session, but ``sampler`` is run independently -of the session. - -.. code-block:: python - - with backend.open_session() as session: - sampler = Sampler(backend=backend) - job1 = sampler.run(transpiled_circuit) # runs outside the session - job2 = backend.run(transpiled_circuit) - session_id = session.session_id - print(job1.session_id) # is None - print(job2.session_id) - - diff --git a/docs/migrate/migrate-estimator.rst b/docs/migrate/migrate-estimator.rst deleted file mode 100644 index 96c91cc2a..000000000 --- a/docs/migrate/migrate-estimator.rst +++ /dev/null @@ -1,351 +0,0 @@ -Calculate expectation values in an algorithm -============================================== - -The Estimator primitive is used to design an algorithm that calculates expectation values. - -Background ----------- - -.. - vale IBMQuantum.Spelling = NO - -.. |qiskit.opflow| replace:: ``qiskit.opflow`` -.. _qiskit.opflow: https://qiskit.org/documentation/apidoc/opflow.html - -.. |BaseEstimator| replace:: ``BaseEstimator`` -.. _BaseEstimator: https://qiskit.org/documentation/stubs/qiskit.primitives.BaseEstimator.html - -.. |BaseSampler| replace:: ``BaseSampler`` -.. _BaseSampler: https://qiskit.org/documentation/stubs/qiskit.primitives.BaseSampler.html - -.. |qiskit_aer.primitives| replace:: ``qiskit_aer.primitives`` -.. _qiskit_aer.primitives: https://github.com/Qiskit/qiskit-aer/tree/main/qiskit_aer/primitives - -.. |qiskit.primitives| replace:: ``qiskit.primitives`` -.. _qiskit.primitives: https://qiskit.org/documentation/apidoc/primitives.html - - - -The role of the ``Estimator`` primitive is two-fold: it acts as an **entry point** to quantum devices or -simulators, replacing the ``Backend`` interface (commonly referred to as ``backend.run()``). Additionally, it is an -**algorithmic abstraction** for expectation -value calculations, so you don't have to manually construct the final expectation circuit. -This results in a considerable reduction of the code complexity and a more compact algorithm design. - -.. note:: - - **Backend.run() model:** In this model, you accessed real backends and remote simulators using the - ``qiskit-ibmq-provider`` module (now migrated to ``qiskit-ibm-provider``). To run - **local** simulations, you could import a specific backend from ``qiskit-aer``. All of them followed - the ``backend.run()`` interface. - - .. raw:: html - -
- Code example for qiskit-ibmq-provider & backend.run() -
- - .. code-block:: python - - from qiskit import IBMQ - - # Select provider - provider = IBMQ.get_provider(hub="ibm-q", group="open", project="main") - - # Get backend - backend = provider.get_backend("ibmq_qasm_simulator") # cloud simulator - - # Run - result = backend.run(expectation_circuits) - - .. raw:: html - -
-
- - .. raw:: html - -
- Code example for qiskit-aer & backend.run() -
- - .. code-block:: python - - from qiskit_aer import AerSimulator # former import: from qiskit import Aer - - # Get local simulator backend - backend = AerSimulator() - - # Run - result = backend.run(expectation_circuits) - - .. raw:: html - -
-
- - **Primitives model:** Access real backends and remote simulators through the ``qiskit-ibm-runtime`` - **primitives** (``Sampler`` and ``Estimator``). To run **local** simulations, you can import specific `local` primitives - from |qiskit_aer.primitives|_ and |qiskit.primitives|_. All of them follow the |BaseSampler|_ and |BaseEstimator|_ interfaces, but - **only the Runtime primitives offer access to the Runtime service, sessions, and built-in error mitigation**. - - .. raw:: html - -
- Code example for Runtime Estimator -
- - .. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Estimator - - # Define service - service = QiskitRuntimeService() - - # Get backend - backend = service.backend("ibmq_qasm_simulator") # cloud simulator - - # Define Estimator - estimator = Estimator(backend=backend) - - # Run Expectation value calculation - result = estimator.run(circuits, observables).result() - - .. raw:: html - -
-
- - .. raw:: html - -
- Code example for Aer Estimator -
- - .. code-block:: python - - from qiskit_aer import Estimator - - # Get local simulator Estimator - estimator = Estimator() - - # Run expectation value calculation - result = estimator.run(circuits, observables).result() - - .. raw:: html - -
-
- -If your code previously calculated expectation values using ``backend.run()``, you most likely used the |qiskit.opflow|_ -module to handle operators and state functions. To support this scenario, the following migration example shows how to replace -the (|qiskit.opflow|_ & ``backend.run()``) workflow with an ``Estimator``-based workflow. - -End-to-end example ------------------- - -1. Problem definition ----------------------- - -We want to compute the expectation value of a quantum state (circuit) with respect to a certain operator. -In this example, we are using the H2 molecule and an arbitrary circuit as the quantum state: - -.. code-block:: python - - from qiskit import QuantumCircuit - from qiskit.quantum_info import SparsePauliOp - - # Step 1: Define operator - op = SparsePauliOp.from_list( - [ - ("II", -1.052373245772859), - ("IZ", 0.39793742484318045), - ("ZI", -0.39793742484318045), - ("ZZ", -0.01128010425623538), - ("XX", 0.18093119978423156), - ] - ) - - # Step 2: Define quantum state - state = QuantumCircuit(2) - state.x(0) - state.x(1) - -.. _a-legacy-opflow: - -1.a. [Legacy] Convert problem to ``opflow`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -|qiskit.opflow|_ provided its own classes to represent both -operators and quantum states, so the problem defined previously would be wrapped as: - -.. code-block:: python - - from qiskit.opflow import CircuitStateFn, PauliSumOp - - opflow_op = PauliSumOp(op) - opflow_state = CircuitStateFn(state) - -This step is no longer necessary when using the primitives. - -.. note:: - - For instructions to migrate from |qiskit.opflow|_, see the `opflow migration guide `_ . - -2. Calculate expectation values on real device or cloud simulator -------------------------------------------------------------------- - - -2.a. [Legacy] Use ``opflow`` & ``backend.run()`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The legacy workflow required many steps to compute an expectation -value: - -.. note:: - - Replace ``ibmq_qasm_simulator`` with your device name to see the - complete workflow for a real device. - -.. code-block:: python - - from qiskit.opflow import StateFn, PauliExpectation, CircuitSampler - from qiskit import IBMQ - - # Define the state to sample - measurable_expression = StateFn(opflow_op, is_measurement=True).compose(opflow_state) - - # Convert to expectation value calculation object - expectation = PauliExpectation().convert(measurable_expression) - - # Define provider and backend - provider = IBMQ.get_provider(hub="ibm-q", group="open", project="main") - backend = provider.get_backend("ibmq_qasm_simulator") - - # Inject backend into circuit sampler - sampler = CircuitSampler(backend).convert(expectation) - - # Evaluate - expectation_value = sampler.eval().real - -.. code-block:: python - - >>> print("expectation: ", expectation_value) - expectation: -1.065734058826613 - -2.b. [New] Use the ``Estimator`` Runtime primitive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The ``Estimator`` simplifies the user-side syntax, making it a more -convenient tool for algorithm design. - -.. note:: - - Replace ``ibmq_qasm_simulator`` with your device name to see the - complete workflow for a real device. - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Estimator - - service = QiskitRuntimeService(channel="ibm_quantum") - backend = service.backend("ibmq_qasm_simulator") - - estimator = Estimator(backend=backend) - - expectation_value = estimator.run(state, op).result().values - -Note that the Estimator returns a list of values, as it can perform batched evaluations. - -.. code-block:: python - - >>> print("expectation: ", expectation_value) - expectation: [-1.06329149] - -The ``Estimator`` Runtime primitive offers a series of features and tuning options that do not have a legacy alternative -to migrate from, but can help improve your performance and results. For more information, refer to the following: - -- `Error mitigation tutorial `_ -- `Setting execution options topic `_ -- `Primitive execution options API reference `_ -- `How to run a session topic `_ - - -3. Other execution alternatives (non-Runtime) ----------------------------------------------- - -This section describes how to use non-Runtime primitives to test an algorithm using local simulation. Let's assume that we want to solve the problem defined previously with a local state vector simulation. - -3.a. [Legacy] Using the Qiskit Aer simulator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. code-block:: python - - from qiskit.opflow import StateFn, PauliExpectation, CircuitSampler - from qiskit_aer import AerSimulator - - # Define the state to sample - measurable_expression = StateFn(opflow_op, is_measurement=True).compose(opflow_state) - - # Convert to expectation value calculation object - expectation = PauliExpectation().convert(measurable_expression) - - # Define statevector simulator - simulator = AerSimulator(method="statevector", shots=100) - - # Inject backend into circuit sampler - circuit_sampler = CircuitSampler(simulator).convert(expectation) - - # Evaluate - expectation_value = circuit_sampler.eval().real - -.. code-block:: python - - >>> print("expectation: ", expectation_value) - expectation: -1.0636533500290943 - - -3.b. [New] Use the Reference ``Estimator`` or Aer ``Estimator`` primitive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The Reference ``Estimator`` lets you perform either an exact or a shot-based noisy simulation based -on the ``Statevector`` class in the ``qiskit.quantum_info`` module. - -.. code-block:: python - - from qiskit.primitives import Estimator - - estimator = Estimator() - - expectation_value = estimator.run(state, op).result().values - - # for shot-based simulation: - expectation_value = estimator.run(state, op, shots=100).result().values - -.. code-block:: python - - >>> print("expectation: ", expectation_value) - expectation: [-1.03134297] - -You can still access the Aer Simulator through its dedicated -``Estimator``. This can be handy for performing simulations with noise models. In this example, -the simulation method has been updated to match the result from 3.a. - -.. code-block:: python - - from qiskit_aer.primitives import Estimator # import change!!! - - estimator = Estimator(run_options= {"method": "statevector"}) - - expectation_value = estimator.run(state, op, shots=100).result().values - -.. code-block:: python - - >>> print("expectation: ", expectation_value) - expectation: [-1.06365335] - -For more information about using the Aer primitives, see the -`VQE tutorial `_ . - -For more information about running noisy simulations with the **Runtime primitives**, see this -`topic `_. diff --git a/docs/migrate/migrate-guide.rst b/docs/migrate/migrate-guide.rst deleted file mode 100644 index 7cd1610fb..000000000 --- a/docs/migrate/migrate-guide.rst +++ /dev/null @@ -1,205 +0,0 @@ -.. _migrate to primitives: - -Migrate to using Qiskit Runtime primitives -============================================ - -This guide describes key patterns of behavior and use cases with code examples to help you migrate code from -the legacy ``qiskit-ibmq-provider`` package to use the Qiskit Runtime primitives. - -Primitives are the recommended tool to write quantum algorithms, as they encapsulate common device queries -seen in application packages and allow for managed performance through the Qiskit Runtime service. -However, if your algorithm requires more granular information, such as pre-shot measurements, the primitives might -not provide the desired abstraction level. - -The Qiskit Runtime primitives implement the reference ``Sampler`` and ``Estimator`` interfaces found in -`qiskit.primitives `_. These interfaces let you -switch between primitive implementations with minimal code changes. Different primitive implementations -can be found in the ``qiskit``, ``qiskit_aer``, and ``qiskit_ibm_runtime`` libraries. -Each implementation serves a specific purpose: - -* The primitives in ``qiskit`` can perform local state vector simulations - useful for quickly prototyping algorithms. -* The primitives in ``qiskit_aer`` give access to the local Aer simulators for tasks such as noisy simulation. -* The primitives in ``qiskit_ibm_runtime`` provide access to cloud simulators and real hardware through the Qiskit - Runtime service. They include exclusive features such as built-in circuit optimization and error mitigation support. - -.. attention:: - - The **only primitives that provide access to the Qiskit Runtime service** are those imported - from ``qiskit_ibm_runtime`` (Qiskit Runtime Primitives). - -When migrating, the key to writing an equivalent algorithm using primitives is to first identify what is the minimal -unit of information your algorithm is based on: - -* If it uses an **expectation value**, you will need an ``Estimator``. -* If it uses a **probability distribution** (from sampling the device), you will need a ``Sampler``. - -After determining which primitive to use, identify where the algorithm accesses the backend. Look for the call to -``backend.run()``. -Next, you will replace this call with the respective primitive call, as shown in the following examples. - -.. - Add this in later when it's done and we have the link - For instructions to migrate code based on ``QuantumInstance``, refer to the `Quantum Instance migration guide `__. - -Algorithm developers who need to refactor algorithms to use primitives instead of `backend.run` should refer to these topics: - -* `Update code that performs circuit sampling `__ -* `Update code that calculates expectation values `__ - -The following topics are use cases with code migration examples: - - -* `Update parameter values while running `__ -* `Algorithm tuning options (shots, transpilation, error mitigation) `__ - -.. _why-migrate: - -Why use Qiskit Runtime? --------------------------------------------- - -.. list-table:: - :header-rows: 1 - - * - Function - - Backend.run - - Runtime Primitives - - * - Simplified algorithm building blocks - - :octicon:`x` - - :octicon:`check` - - * - Flexible interface - - :octicon:`check` - - :octicon:`check` - - * - Elastic compute integration - - :octicon:`check` - - :octicon:`check` - - * - Queuing efficiency - - :octicon:`x` - - :octicon:`check` - - * - Data caching - - :octicon:`x` - - :octicon:`clock` - - * - Error mitigation support - - :octicon:`x` - - :octicon:`check` - - * - SAAS enablement - - :octicon:`x` - - :octicon:`clock` - -Key: - -- :octicon:`x` Not supported -- :octicon:`check` Full support -- :octicon:`clock` Future support - - -**Benefits of using Qiskit Runtime**: - -* Simplify algorithm design and optimization. -* Run circuits faster by using sessions - a context manager designed to efficiently manage iterative workloads and minimize artificial latency between quantum and classical sub-components. -* Access our most powerful quantum systems with our latest performance and hardware optimization, including capabilities like error suppression and mitigation. -* Easily integrate Qiskit Runtime with your cloud or on-premise classical compute resources by using the quantum serverless toolkit. - -**Simplified interface**: - -Use primitives to write code more efficiently. For details, see the examples topics, such as `Using Estimator to design an algorithm `__. - - .. figure:: ../images/compare-code.png - :alt: Two code snippets, side by side - - Code without primitives, and the same code after being rewritten to use primitives. - - -.. _migfaqs: - -FAQs --------------------------------------------- - -Users might have the following questions when planning to migrate their -code to Qiskit Runtime: - -.. raw:: html - -
- Which channel should I use? - -After deciding to use Qiskit Runtime primitives, the user must determine whether to access Qiskit Runtime -through IBM Cloud or IBM Quantum Platform. Some information that might help you decide includes: - -* The available plans: - - * Qiskit Runtime is available in both the Open (free access) or Premium (contract-based paid access) plan on IBM Quantum Platform. See `IBM Quantum access plans `__ for details. - * Qiskit Runtime is accessible through the Lite (free access) or Standard (pay-as-you-go access) plan in IBM Cloud. See `Plans <../cloud/plans.html>`__ for details. - -* The use case requirements: - - * IBM Quantum Platform offers a visual circuit composer (Quantum Composer) and a Jupyter Notebook environment (Quantum Lab). - * IBM Cloud offers a cloud native service that is ideal if users need to integrate quantum capabilities with other cloud services. - -.. raw:: html - -
- -.. raw:: html - -
- How do I set up my channel? - -After deciding which channel to use to interact with Qiskit Runtime, you -can get set up on either platform using the instructions below: - -* To get started with Qiskit Runtime on IBM Quantum Platform, see `Experiment with Qiskit Runtime `__. -* To get started with Qiskit Runtime on IBM Cloud, see the `Getting Started guide <../cloud/get-started.html>`__. - -.. raw:: html - -
- -.. raw:: html - -
- Should I modify the Qiskit Terra algorithms? - -As of v0.22, `Qiskit Terra algorithms `__ use Qiskit Runtime primitives. Thus, there is no need for -users to modify amplitude estimators or any other Qiskit Terra algorithms. - -.. raw:: html - -
- -.. raw:: html - -
- Which primitive should I use? - -When choosing which primitive to use, you first need to understand -whether the algorithm uses a **quasi-probability distribution** sampled from a quantum state (a list of -quasi-probabilities), or an **expectation value** of a certain observable -with respect to a quantum state (a real number). - -A probability distribution is often of interest in optimization problems -that return a classical bit string, encoding a certain solution to a -problem at hand. In these cases, you might be interested in finding a bit -string that corresponds to a ket value with the largest probability of -being measured from a quantum state, for example. - -An expectation value of an observable could be the target quantity in -scenarios where knowing a quantum state is not relevant. This -often occurs in optimization problems or chemistry applications. For example, when trying to discover the extremal energy of a system. - -.. raw:: html - -
- -Related links -------------- - -* `Get started with Estimator <../tutorials/how-to-getting-started-with-estimator.ipynb>`__ -* `Get started with Sampler <../tutorials/how-to-getting-started-with-sampler.ipynb>`__ -* `Tutorial: Migrate from qiskit-ibmq-provider to qiskit-ibm-provider `__ diff --git a/docs/migrate/migrate-sampler.rst b/docs/migrate/migrate-sampler.rst deleted file mode 100644 index f1d3e8705..000000000 --- a/docs/migrate/migrate-sampler.rst +++ /dev/null @@ -1,427 +0,0 @@ -Circuit sampling in an algorithm -================================= - -The Sampler primitive is used to design an algorithm that samples circuits and extracts probability distributions. - -Background ----------- - -.. |qiskit.opflow| replace:: ``qiskit.opflow`` -.. _qiskit.opflow: https://qiskit.org/documentation/apidoc/opflow.html - -.. |BaseEstimator| replace:: ``BaseEstimator`` -.. _BaseEstimator: https://qiskit.org/documentation/stubs/qiskit.primitives.BaseEstimator.html - -.. |BaseSampler| replace:: ``BaseSampler`` -.. _BaseSampler: https://qiskit.org/documentation/stubs/qiskit.primitives.BaseSampler.html - -.. |qiskit_aer.primitives| replace:: ``qiskit_aer.primitives`` -.. _qiskit_aer.primitives: https://github.com/Qiskit/qiskit-aer/tree/main/qiskit_aer/primitives - -.. |qiskit.primitives| replace:: ``qiskit.primitives`` -.. _qiskit.primitives: https://qiskit.org/documentation/apidoc/primitives.html - -.. |QuasiDistribution.binary_probabilities| replace:: ``QuasiDistribution.binary_probabilities()`` -.. _QuasiDistribution.binary_probabilities: https://qiskit.org/documentation/stubs/qiskit.result.QuasiDistribution.binary_probabilities.html#qiskit.result.QuasiDistribution.binary_probabilities - - -The role of the ``Sampler`` primitive is two-fold: it acts as an **entry point** to quantum devices or -simulators, replacing ``backend.run()``. Additionally, it is an **algorithmic abstraction** to extract probability distributions from measurement counts. - -Both ``Sampler`` and ``backend.run()`` take in circuits as inputs. The main difference is the format of the -output: ``backend.run()`` outputs **counts**, while ``Sampler`` processes those counts and outputs -the **quasi-probability distribution** associated with them. - - -.. note:: - - **Backend.run() model:** In this model, you used the - ``qiskit-ibmq-provider`` (now migrated to ``qiskit-ibm-provider``) module to access real backends and remote simulators. - To run **local** simulations, you could import a specific backend from ``qiskit-aer``. All of them followed - the ``backend.run()`` interface. - - .. raw:: html - -
- Code example with qiskit-ibmq-provider & backend.run() -
- - .. code-block:: python - - from qiskit import IBMQ - - # Select provider - provider = IBMQ.load_account() - - # Get backend - backend = provider.get_backend("ibmq_qasm_simulator") # Use the cloud simulator - - # Run - result = backend.run(circuits) - - .. raw:: html - -
-
- - .. raw:: html - -
- Code example for qiskit-aer & backend.run() -
- - .. code-block:: python - - from qiskit_aer import AerSimulator # former import: from qiskit import Aer - - # Get local simulator backend - backend = AerSimulator() - - # Run - result = backend.run(circuits) - - .. raw:: html - -
-
- - **Primitives model:** Access real backends and remote simulators through the `qiskit-ibm-runtime` - **primitives** (`Sampler` and `Estimator`). To run **local** simulations, import specific `local` primitives - from |qiskit_aer.primitives|_ and |qiskit.primitives|_. All of them follow the |BaseSampler|_ and |BaseEstimator|_ interfaces, but - **only the Runtime primitives offer access to the Runtime service, sessions, and built-in error mitigation**. - - .. raw:: html - -
- Code example for Runtime Sampler -
- - .. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Sampler - - # Define service - service = QiskitRuntimeService() - - # Get backend - backend = service.backend("ibmq_qasm_simulator") # Use a cloud simulator - - # Define Sampler - sampler = Sampler(backend=backend) - - # Run Quasi-Probability calculation - result = sampler.run(circuits).result() - - .. raw:: html - -
-
- - .. raw:: html - -
- Code example for Aer Sampler -
- - .. code-block:: python - - from qiskit_aer import Sampler - - # Get local simulator Sampler - sampler = Sampler() - - # Run Quasi-Probability calculation - result = sampler.run(circuits).result() - - .. raw:: html - -
-
- -Next, we will show an end-to-end example of sampling a circuit: first, with ``backend.run()``, then by using the ``Sampler``. - -End-to-end example ------------------- - - -1. Problem definition ----------------------- - -We want to find the probability (or quasi-probability) distribution associated with a quantum state: - -.. attention:: - - Important: If you want to use the ``Sampler`` primitive, the circuit **must contain measurements**. - -.. code-block:: python - - from qiskit import QuantumCircuit - - circuit = QuantumCircuit(4) - circuit.h(range(2)) - circuit.cx(0,1) - circuit.measure_all() # measurement! - -2. Calculate probability distribution on a real device or cloud simulator -------------------------------------------------------------------------- - - -2.a. [Legacy] Use ``backend.run()`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The required steps to reach our goal with ``backend.run()`` are: - -1. Run circuits -2. Get counts from the result object -3. Use the counts and shots to calculate the probability distribution - - -.. raw:: html - -
- -First, we run the circuit in a cloud simulator and output the result object: - -.. note:: - - Replace ``ibmq_qasm_simulator`` with your device name to see the - complete workflow for a real device. - -.. code-block:: python - - from qiskit import IBMQ - - # Define provider and backend - provider = IBMQ.load_account() - backend = provider.get_backend("ibmq_qasm_simulator") - - # Run - result = backend.run(circuit, shots=1024).result() - -.. code-block:: python - - >>> print("result: ", result) - result: Result(backend_name='ibmq_qasm_simulator', backend_version='0.11.0', - qobj_id='65bb8a73-cced-40c1-995a-8961cc2badc4', job_id='63fc95612751d57b6639f777', - success=True, results=[ExperimentResult(shots=1024, success=True, meas_level=2, - data=ExperimentResultData(counts={'0x0': 255, '0x1': 258, '0x2': 243, '0x3': 268}), - header=QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1], ['meas', 2], ['meas', 3]], - creg_sizes=[['meas', 4]], global_phase=0.0, memory_slots=4, metadata={}, n_qubits=4, - name='circuit-930', qreg_sizes=[['q', 4]], qubit_labels=[['q', 0], ['q', 1], ['q', 2], ['q', 3]]), - status=DONE, metadata={'active_input_qubits': [0, 1, 2, 3], 'batched_shots_optimization': False, - 'device': 'CPU', 'fusion': {'enabled': False}, 'input_qubit_map': [[3, 3], [2, 2], [1, 1], [0, 0]], - 'measure_sampling': True, 'method': 'stabilizer', 'noise': 'ideal', 'num_clbits': 4, 'num_qubits': 4, - 'parallel_shots': 1, 'parallel_state_update': 16, 'remapped_qubits': False, - 'sample_measure_time': 0.001001096}, seed_simulator=2191402198, time_taken=0.002996865)], - date=2023-02-27 12:35:00.203255+01:00, status=COMPLETED, header=QobjHeader(backend_name='ibmq_qasm_simulator', - backend_version='0.1.547'), metadata={'max_gpu_memory_mb': 0, 'max_memory_mb': 386782, 'mpi_rank': 0, - 'num_mpi_processes': 1, 'num_processes_per_experiments': 1, 'omp_enabled': True, 'parallel_experiments': 1, - 'time_taken': 0.003215252, 'time_taken_execute': 0.00303248, 'time_taken_load_qobj': 0.000169435}, - time_taken=0.003215252, client_version={'qiskit': '0.39.5'}) - -Now we get the probability distribution from the output: - -.. code-block:: python - - counts = result.get_counts(circuit) - quasi_dists = {} - for key,count in counts.items(): - quasi_dists[key] = count/1024 - -.. code-block:: python - - >>> print("counts: ", counts) - >>> print("quasi_dists: ", quasi_dists) - counts: {'0000': 255, '0001': 258, '0010': 243, '0011': 268} - quasi_dists: {'0000': 0.2490234375, '0001': 0.251953125, '0010': 0.2373046875, '0011': 0.26171875} - - -2.b. [New] Use the ``Sampler`` Runtime primitive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -While the user-side syntax of the ``Sampler`` is very similar to ``backend.run()``, -notice that the workflow is now simplified, as the quasi-probability distribution is returned -**directly** (no need to perform post-processing), together with some key metadata. - -.. note:: - - Replace ``ibmq_qasm_simulator`` with your device name to see the - complete workflow for a real device. - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService, Sampler - - service = QiskitRuntimeService(channel="ibm_quantum") - backend = service.backend("ibmq_qasm_simulator") - - sampler = Sampler(backend=backend) - - result = sampler.run(circuit, shots=1024).result() - quasi_dists = result.quasi_dists - -.. code-block:: python - - >>> print("result: ", result) - >>> print("quasi_dists: ", quasi_dists) - result: SamplerResult(quasi_dists=[{0: 0.2802734375, 1: 0.2509765625, 2: 0.232421875, 3: 0.236328125}], - metadata=[{'header_metadata': {}, 'shots': 1024, 'readout_mitigation_overhead': 1.0, - 'readout_mitigation_time': 0.03801989182829857}]) - quasi_dists: [{0: 0.2802734375, 1: 0.2509765625, 2: 0.232421875, 3: 0.236328125}] - -.. attention:: - - Be careful with the output format. With ``Sampler``, the states are no longer represented - by bit strings, for example, ``"11"``, - but by integers, for example, ``3``. To convert the ``Sampler`` output to bit strings, - you can use the |QuasiDistribution.binary_probabilities|_ method, as shown below. - -.. code-block:: python - - >>> # convert the output to bit strings - >>> binary_quasi_dist = quasi_dists[0].binary_probabilities() - >>> print("binary_quasi_dist: ", binary_quasi_dist) - binary_quasi_dist: {'0000': 0.2802734375, '0001': 0.2509765625, '0010': 0.232421875, '0011': 0.236328125} - -The ``Sampler`` Runtime primitive offers several features and tuning options that do not have a legacy alternative -to migrate from, but can help improve your performance and results. For more information, refer to the following: - -- `Error mitigation tutorial `_ -- `Setting execution options topic `_ -- `How to run a session topic `_ - - -3. Other execution alternatives (non-Runtime) ---------------------------------------------- - -The following migration paths use non-Runtime primitives to use local simulation to test an algorithm. Let's assume that we want to use a local state vector simulation to solve the problem defined above. - -3.a. [Legacy] Use the Qiskit Aer simulator -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - -.. code-block:: python - - from qiskit_aer import AerSimulator - - # Define the statevector simulator - simulator = AerSimulator(method="statevector") - - # Run and get counts - result = simulator.run(circuit, shots=1024).result() - -.. code-block:: python - - >>> print("result: ", result) - result: Result(backend_name='aer_simulator_statevector', backend_version='0.11.2', - qobj_id='e51e51bc-96d8-4e10-aa4e-15ee6264f4a0', job_id='c603daa7-2c03-488c-8c75-8c6ea0381bbc', - success=True, results=[ExperimentResult(shots=1024, success=True, meas_level=2, - data=ExperimentResultData(counts={'0x2': 236, '0x0': 276, '0x3': 262, '0x1': 250}), - header=QobjExperimentHeader(clbit_labels=[['meas', 0], ['meas', 1], ['meas', 2], ['meas', 3]], - creg_sizes=[['meas', 4]], global_phase=0.0, memory_slots=4, metadata={}, n_qubits=4, name='circuit-930', - qreg_sizes=[['q', 4]], qubit_labels=[['q', 0], ['q', 1], ['q', 2], ['q', 3]]), status=DONE, - seed_simulator=3531074553, metadata={'parallel_state_update': 16, 'parallel_shots': 1, - 'sample_measure_time': 0.000405246, 'noise': 'ideal', 'batched_shots_optimization': False, - 'remapped_qubits': False, 'device': 'CPU', 'active_input_qubits': [0, 1, 2, 3], 'measure_sampling': True, - 'num_clbits': 4, 'input_qubit_map': [[3, 3], [2, 2], [1, 1], [0, 0]], 'num_qubits': 4, 'method': 'statevector', - 'fusion': {'applied': False, 'max_fused_qubits': 5, 'threshold': 14, 'enabled': True}}, time_taken=0.001981756)], - date=2023-02-27T12:38:18.580995, status=COMPLETED, header=QobjHeader(backend_name='aer_simulator_statevector', - backend_version='0.11.2'), metadata={'mpi_rank': 0, 'num_mpi_processes': 1, 'num_processes_per_experiments': 1, - 'time_taken': 0.002216379, 'max_gpu_memory_mb': 0, 'time_taken_execute': 0.002005713, 'max_memory_mb': 65536, - 'time_taken_load_qobj': 0.000200642, 'parallel_experiments': 1, 'omp_enabled': True}, - time_taken=0.0025920867919921875) - -Now let's get the probability distribution from the output: - -.. code-block:: python - - counts = result.get_counts(circuit) - quasi_dists = {} - for key,count in counts.items(): - quasi_dists[key] = count/1024 - -.. code-block:: python - - >>> print("counts: ", counts) - >>> print("quasi_dists: ", quasi_dists) - counts: {'0010': 236, '0000': 276, '0011': 262, '0001': 250} - quasi_dists: {'0010': 0.23046875, '0000': 0.26953125, '0011': 0.255859375, '0001': 0.244140625} - -3.b. [New] Use the Reference ``Sampler`` or Aer ``Sampler`` primitive -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The Reference ``Sampler`` lets you perform an exact or a shot-based noisy simulation based -on the ``Statevector`` class in the ``qiskit.quantum_info`` module. - -.. code-block:: python - - from qiskit.primitives import Sampler - - sampler = Sampler() - - result = sampler.run(circuit).result() - quasi_dists = result.quasi_dists - -.. code-block:: python - - >>> print("result: ", result) - >>> print("quasi_dists: ", quasi_dists) - result: SamplerResult(quasi_dists=[{0: 0.249999999999, 1: 0.249999999999, - 2: 0.249999999999, 3: 0.249999999999}], metadata=[{}]) - quasi_dists: [{0: 0.249999999999, 1: 0.249999999999, 2: 0.249999999999, - 3: 0.249999999999}] - -If shots are specified, this primitive outputs a shot-based simulation (no longer exact): - -.. code-block:: python - - from qiskit.primitives import Sampler - - sampler = Sampler() - - result = sampler.run(circuit, shots=1024).result() - quasi_dists = result.quasi_dists - -.. code-block:: python - - >>> print("result: ", result) - >>> print("quasi_dists: ", quasi_dists) - result: SamplerResult(quasi_dists=[{0: 0.2490234375, 1: 0.2578125, - 2: 0.2431640625, 3: 0.25}], metadata=[{'shots': 1024}]) - quasi_dists: [{0: 0.2490234375, 1: 0.2578125, 2: 0.2431640625, 3: 0.25}] - -You can still access the Aer simulator through its dedicated -``Sampler``. This can be handy for performing simulations with noise models. In this example, -the simulation method has been updated to match the result from 3.a. - -.. code-block:: python - - from qiskit_aer.primitives import Sampler as AerSampler # import change! - - sampler = AerSampler(run_options= {"method": "statevector"}) - - result = sampler.run(circuit, shots=1024).result() - quasi_dists = result.quasi_dists - -.. code-block:: python - - >>> print("result: ", result) - >>> print("quasi_dists: ", quasi_dists) - result: SamplerResult(quasi_dists=[{1: 0.2802734375, 2: 0.2412109375, 0: 0.2392578125, - 3: 0.2392578125}], metadata=[{'shots': 1024, 'simulator_metadata': - {'parallel_state_update': 16, 'parallel_shots': 1, 'sample_measure_time': 0.000409608, - 'noise': 'ideal', 'batched_shots_optimization': False, 'remapped_qubits': False, - 'device': 'CPU', 'active_input_qubits': [0, 1, 2, 3], 'measure_sampling': True, - 'num_clbits': 4, 'input_qubit_map': [[3, 3], [2, 2], [1, 1], [0, 0]], 'num_qubits': 4, - 'method': 'statevector', 'fusion': {'applied': False, 'max_fused_qubits': 5, - 'threshold': 14, 'enabled': True}}}]) - quasi_dists: [{1: 0.2802734375, 2: 0.2412109375, 0: 0.2392578125, 3: 0.2392578125}] - -.. code-block:: python - - >>> # Convert the output to bit strings - >>> binary_quasi_dist = quasi_dists[0].binary_probabilities() - >>> print("binary_quasi_dist: ", binary_quasi_dist) - binary_quasi_dist: {'0001': 0.2802734375, '0010': 0.2412109375, '0000': 0.2392578125, '0011': 0.2392578125} - -For information, see `Noisy simulators in Qiskit Runtime `_. diff --git a/docs/migrate/migrate-setup.rst b/docs/migrate/migrate-setup.rst deleted file mode 100644 index e71ba9606..000000000 --- a/docs/migrate/migrate-setup.rst +++ /dev/null @@ -1,161 +0,0 @@ -Migrate setup from ``qiskit-ibmq-provider`` -============================================== - -This guide describes how to migrate code from the legacy IBMQ provider (`qiskit-ibmq-provider`) package to use Qiskit Runtime (`qiskit-ibm-runtime`). This guide includes instructions to migrate legacy runtime programs to the new syntax. However, the ability to use custom uploaded programs is pending deprecation, so these should be migrated to use primitives instead. - -Changes in Class name and location ------------------------------------ - -The classes related to Qiskit Runtime that used to be included in ``qiskit-ibmq-provider`` are now part of ``qiskit-ibm-runtime``. Before, the provider used to populate the ``qiskit.providers.ibmq.runtime`` namespace with objects for Qiskit Runtime. These now live in the ``qiskit_ibm_runtime`` module. - -The module from which the classes are imported has changed. The following table contains example access patterns in ``qiskit.providers.ibmq.runtime`` and their new form in ``qiskit_ibm_runtime``: - -.. - I had to take the :class: formatting out of the following table because it was too wide for the page and was writing on top of the right-hand navigation menu when everything was in tags. When I took off one tag, sphinx put it in a scrollable table that didn't overrun the right-hand navigation, so I took them all out for consistency - - -.. list-table:: Migrate from ``qiskit.providers.ibmq.runtime`` in ``qiskit-ibmq-provider`` to ``qiskit-ibm-runtime`` - :header-rows: 1 - - * - class in ``qiskit-ibmq-provider`` - - class in ``qiskit-ibm-runtime`` - - Notes - * - ``qiskit.providers.ibmq.runtime.IBMRuntimeService`` - - ``qiskit_ibm_runtime.QiskitRuntimeService``` - - ``IBMRuntimeService`` class was removed from ``qiskit-ibm-runtime`` 0.6 and replaced by the new class in ``qiskit-ibm-runtime``. - * - ``qiskit.providers.ibmq.runtime.RuntimeJob`` - - ``qiskit_ibm_runtime.RuntimeJob`` - - - * - ``qiskit.providers.ibmq.runtime.ResultDecoder`` - - ``qiskit_ibm_runtime.utils.ResultDecoder`` - - Notice the new location, in ``qiskit_ibm_runtime.utils`` - * - ``qiskit.providers.ibmq.runtime.RuntimeEncoder`` - - ``qiskit_ibm_runtime.RuntimeEncoder`` - - - * - ``qiskit.providers.ibmq.runtime.RuntimeDecoder`` - - ``qiskit_ibm_runtime.RuntimeDecoder`` - - - * - ``qiskit.providers.ibmq.runtime.ParameterNamespace`` - - ``qiskit_ibm_runtime.ParameterNamespace`` - - - * - ``qiskit.providers.ibmq.runtime.RuntimeOptions`` - - ``qiskit_ibm_runtime.RuntimeOptions`` - - - -Import path -------------- - -The import path has changed as follows: - -**Legacy** - -.. code-block:: python - - from qiskit import IBMQ - -**Updated** - -.. code-block:: python - - from qiskit_ibm_runtime import QiskitRuntimeService - -Save and load accounts ------------------------------------- - -Use the updated code to work with accounts. - -**Legacy - Save accounts** - -.. code-block:: python - - IBMQ.save_account("", overwrite=True) - -**Updated - Save accounts** -The new syntax accepts credentials for Qiskit Runtime on IBM Cloud or IBM Quantum Platform. For more information about retrieving account credentials, see the `getting started guide `_. - -.. code-block:: python - - # IBM cloud channel - QiskitRuntimeService.save_account(channel="ibm_cloud", token="", instance="", overwrite=True) - - # IBM quantum channel - QiskitRuntimeService.save_account(channel="ibm_quantum", token="", overwrite=True) - -**Updated - Name saved credentials** -You can now name your saved credentials and load the credentials by name. - -**Example:** - -.. code-block:: python - - # Save different accounts for open and premium access - - QiskitRuntimeService.save_account(channel="ibm_quantum", token="", instance="h1/g1/p1", name="premium") - QiskitRuntimeService.save_account(channel="ibm_quantum", token="", instance="h2/g2/p2", name="open") - - # Load the "open" credentials - - service = QiskitRuntimeService(name="open") - -**Legacy - Load accounts** - -.. code-block:: python - - IBMQ.load_account() - -**Updated - Load accounts** - -The new syntax combines the functionality from ``load_account()`` and ``get_provider()`` in one statement. The ``channel`` input parameter is optional. If multiple accounts have been saved in one device and no ``channel`` is provided, the default is ``"ibm_cloud"``. - -.. code-block:: python - - # To access saved credentials for the IBM cloud channel - service = QiskitRuntimeService(channel="ibm_cloud") - - # To access saved credentials for the IBM quantum channel - service = QiskitRuntimeService(channel="ibm_quantum") - - -Channel selection (get a provider) ------------------------------------------- - -Use the updated code to select a channel. - -**Legacy** - -.. code-block:: python - - provider = IBMQ.get_provider(project="my_project", group="my_group", hub="my_hub") - -**Updated** - -The new syntax combines the functionality from ``load_account()`` and ``get_provider()`` in one statement. -When using the ``ibm_quantum`` channel, the ``hub``, ``group``, and ``project`` are specified through the new -``instance`` keyword. - -.. code-block:: python - - # To access saved credentials for the IBM cloud channel - service = QiskitRuntimeService(channel="ibm_cloud") - - # To access saved credentials for the IBM quantum channel and select an instance - service = QiskitRuntimeService(channel="ibm_quantum", instance="my_hub/my_group/my_project") - - -Get the backend ------------------- -Use the updated code to view backends. - -**Legacy** - -.. code-block:: python - - provider = IBMQ.get_provider(hub="h1", group="g1", project="p1") - backend = provider.get_backend("ibm_backend") - -**Updated** - -.. code-block:: python - - # You can specify the instance in service.backend() instead of initializing a new service - backend = service.backend("ibm_backend", instance="h1/g1/p1") diff --git a/docs/migrate/migrate-tuning.rst b/docs/migrate/migrate-tuning.rst deleted file mode 100644 index 40527d8e0..000000000 --- a/docs/migrate/migrate-tuning.rst +++ /dev/null @@ -1,132 +0,0 @@ -Guide on algorithm tuning options -================================= - -One of the advantages of the primitives is that they abstract away the circuit execution setup so that algorithm developers -can focus on the pure algorithmic components. However, sometimes, to get the most out of an algorithm, you might want -to tune certain primitive options. This section describes some of the common settings you might need. - -.. |qiskit.primitives| replace:: ``qiskit.primitives`` -.. _qiskit.primitives: https://qiskit.org/documentation/apidoc/primitives.html - -.. |qiskit_aer.primitives| replace:: ``qiskit_aer.primitives`` -.. _qiskit_aer.primitives: https://qiskit.org/documentation/locale/de_DE/apidoc/aer_primitives.html - -.. attention:: - - This section focuses on Qiskit Runtime primitive :class:`.Options` (imported from ``qiskit_ibm_runtime``). While - most of the `primitives` interface is common across implementations, most :class:`.Options` are not. Consult the - corresponding API references for information about the |qiskit.primitives|_ and |qiskit_aer.primitives|_ options. - -1. Shots -~~~~~~~~ - -For some algorithms, setting a specific number of shots is a core part of their routines. Previously, shots could be set during the call to `backend.run()`. For example, ``backend.run(shots=1024)``. Now, that setting is part of the execution -options ("second level option"). This can be done during the primitive setup: - -.. code-block:: python - - from qiskit_ibm_runtime import Estimator, Options - - options = Options() - options.execution.shots = 1024 - - estimator = Estimator(session=session, options=options) - - -If you need to modify the number of shots set between iterations (primitive calls), you can set the -shots directly in the ``run()`` method. This overwrites the initial ``shots`` setting. - -.. code-block:: python - - from qiskit_ibm_runtime import Estimator - - estimator = Estimator(session=session) - - estimator.run(circuits=circuits, observables=observables, shots=50) - - # other logic - - estimator.run(circuits=circuits, observables=observables, shots=100) - -For more information about the primitive options, refer to the -`Options class API reference `_. - - -2. Transpilation -~~~~~~~~~~~~~~~~ - -By default, the Qiskit Runtime primitives perform circuit transpilation. The optimization level you choose affects the transpilation strategy and might include additional error suppression mechanisms. Level 0 only involves basic transpilation. -To learn about each optimization level, view the Optimization level table in the -`Error suppression topic `__. - -.. note:: - When using primitives, optimization levels 2 and 3 behave like level 1. If you want to use more advanced optimization, use the Qiskit transpiler locally and then pass the transpiled circuits to the primitives. For instructions see the `Submitting user-transpiled circuits using primitives `__ tutorial. - -The optimization level option is a "first level option", and can be set as follows: - -.. code-block:: python - - from qiskit_ibm_runtime import Estimator, Options - - options = Options(optimization_level=1) - - # or.. - options = Options() - options.optimization_level = 1 - - estimator = Estimator(session=session, options=options) - - -You might want to configure your transpilation strategy further, and for this, there are advanced transpilation -options you can set up. These are "second level options", and can be set as follows: - -.. code-block:: python - - from qiskit_ibm_runtime import Estimator, Options - - options = Options() - options.transpilation.initial_layout = ... - options.transpilation.routing_method = ... - - estimator = Estimator(session=session, options=options) - -For more information, and a complete list of advanced transpilation options, see the Advanced transpilation options table in the -`Error suppression topic `__. - -To specify settings that are not available through the primitives interface or use custom transpiler passes, set ``skip_transpilation=True`` to submit user-transpiled circuits. This is described in the -`Submitting user-transpiled circuits using primitives tutorial `_. - -The ``skip_transpilation`` option is an advanced transpilation option, and is set as follows: - -.. code-block:: python - - from qiskit_ibm_runtime import Estimator, Options - - options = Options() - options.transpilation.skip_transpilation = True - - estimator = Estimator(session=session, options=options) - - -3. Error mitigation -~~~~~~~~~~~~~~~~~~~ - -You might want to use different error mitigation methods and see how these affect the performance of your -algorithm. These can also be set through the ``resilience_level`` option. The method selected for each level is -different for ``Sampler`` and ``Estimator``. You can find more information in the -`Configure error mitigation topic `_. - -The configuration is similar to the other options: - -.. code-block:: python - - from qiskit_ibm_runtime import Estimator, Options - - options = Options(resilience_level = ) - - # or... - - options = Options() - options.resilience_level = 2 - - estimator = Estimator(session=session, options=options) diff --git a/docs/migrate/migrate-update-parm.rst b/docs/migrate/migrate-update-parm.rst deleted file mode 100644 index e7084ee02..000000000 --- a/docs/migrate/migrate-update-parm.rst +++ /dev/null @@ -1,75 +0,0 @@ -Parametrized circuits with primitives -======================================= - - -Parametrized circuits are a commonly used tool for quantum algorithm design. -Because `backend.run()` did not accept parametrized circuits, the parameter binding step had to be integrated in the algorithm workflow. The primitives can perform the parameter binding step internally, which results in a simplification of the algorithm-side logic. - -The following example summarizes the new workflow for managing parametrized circuits. - -Example ---------- -Let's define a parametrized circuit: - -.. code-block:: python - - from qiskit.circuit import QuantumCircuit, ParameterVector - - n = 3 - thetas = ParameterVector('θ',n) - - qc = QuantumCircuit(n, 1) - qc.h(0) - - for i in range(n-1): - qc.cx(i, i+1) - - for i,t in enumerate(thetas): - qc.rz(t, i) - - for i in reversed(range(n-1)): - qc.cx(i, i+1) - - qc.h(0) - qc.measure(0, 0) - - qc.draw() - -We want to assign the following parameter values to the circuit: - -.. code-block:: python - - import numpy as np - theta_values = [np.pi/2, np.pi/2, np.pi/2] - - -Legacy ---------- -Previously, the parameter values had to be bound to their respective circuit parameters before calling `backend.run()`. - -.. code-block:: python - - from qiskit import Aer - - bound_circuit = qc.bind_parameters(theta_values) - bound_circuit.draw() - - backend = Aer.get_backend('aer_simulator') - job = backend.run(bound_circuit) - counts = job.result().get_counts() - print(counts) - -Primitives ------------- -Now, the primitives take in parametrized circuits directly, together with the parameter values, and the parameter assignment operation can be performed more efficiently on the server side of the primitive. - -This feature is particularly interesting when working with iterative algorithms because the parametrized circuit remains unchanged between calls while the parameter values change. The primitives can transpile once and then cache the unbound circuit, using classical resources more efficiently. Moreover, only the updated parameters are transferred to the cloud, saving additional bandwidth. - -.. code-block:: python - - from qiskit.primitives import Sampler - - sampler = Sampler() - job = sampler.run(qc, theta_values) - result = job.result().quasi_dists - print(result) diff --git a/docs/primitives.rst b/docs/primitives.rst deleted file mode 100644 index b7bfa0d3e..000000000 --- a/docs/primitives.rst +++ /dev/null @@ -1,67 +0,0 @@ -Introduction to primitives -============================= - -With Qiskit Runtime, we are introducing a new set of interfaces, in the form of primitives, to expand on how users run jobs on quantum computers. - -The existing Qiskit interface to backends (`backend.run()`) was originally designed to accept a list of circuits and return counts for every job. Over time, it became clear that users have diverse purposes for quantum computing, and therefore the ways in which they define the requirements for their computing jobs are expanding. Therefore, their results also look different. - -For example, an algorithm researcher and developer cares about information beyond counts; they are more focused on efficiently calculating quasi-probability distribution and expectation values of observables. - -Our primitives provide methods that make it easier to build modular algorithms and other higher-order programs. Rather than simply returning counts, they return more immediately meaningful information. Additionally, they provide a seamless way to access the latest optimizations in IBM Quantum hardware and software. - -The basic operations that one can perform with a probability distribution is to sample from it or to estimate quantities on it. Therefore, these operations form the fundamental building blocks of quantum algorithm development. Our first two Qiskit Runtime primitives (Sampler and Estimator) use these sampling and estimating operations as core interfaces to our quantum systems. - -Available primitives --------------------- - -The following primitives are available: - - -+-----------------------+-----------------------+------------------------------------+ -| Primitive | Description | Example output | -+=======================+=======================+====================================+ -| Estimator | Allows a user to | .. image:: images/estimator.png | -| | specify a list of | | -| | circuits and | | -| | observables and | | -| | selectively group | | -| | between the lists to | | -| | efficiently evaluate | | -| | expectation values | | -| | and variances for a | | -| | parameter input. It | | -| | is designed to enable | | -| | users to efficiently | | -| | calculate and | | -| | interpret expectation | | -| | values of quantum | | -| | operators that are | | -| | required for many | | -| | near-term quantum | | -| | algorithms. | | -+-----------------------+-----------------------+------------------------------------+ -| Sampler | Allows a user to | .. image:: images/sampler.png | -| | input a circuit and | | -| | then generate | | -| | quasi-probability | | -| | distribution. | | -| | This generation | | -| | enables users to more | | -| | efficiently evaluate | | -| | the possibility of | | -| | multiple relevant | | -| | data points in the | | -| | context of | | -| | destructive | | -| | interference. | | -+-----------------------+-----------------------+------------------------------------+ - - -How to use primitives ---------------------- - -Primitive interfaces vary based on the type of task that you want to run on the quantum computer and the corresponding data that you want returned as a result. After identifying the appropriate primitive for your program, you can use Qiskit to prepare inputs, such as circuits, observables (for Estimator), and customizable options to optimize your job. For more information, see the appropriate topic: - -* `Getting started with Estimator <./tutorials/how-to-getting-started-with-estimator.ipynb>`__ -* `Getting started with Sampler <./tutorials/how-to-getting-started-with-sampler.ipynb>`__ -* :ref:`Migrate from qiskit-ibmq-provider to qiskit-ibm-runtime ` diff --git a/docs/retired.rst b/docs/retired.rst deleted file mode 100644 index ce0d38382..000000000 --- a/docs/retired.rst +++ /dev/null @@ -1,74 +0,0 @@ -######################################### -Retired systems -######################################### - -The following systems have been retired. For the full list of available systems, run `service.backends()`. For more information, see `Run on quantum backends `__. - -To retrieve job results from retired backends, use `QiskitRuntimeService.jobs `_ for runtime jobs and `qiskit-ibm-provider `_ for everything else. - - -+-------------------------------+----------------------+ -| System name | Qubit count | -+===============================+======================+ -| ``ibm_washington`` | 127 | -+-------------------------------+----------------------+ -| ``ibmq_oslo`` | 7 | -+-------------------------------+----------------------+ -| ``ibmq_geneva`` | 27 | -+-------------------------------+----------------------+ -| ``ibmq_toronto`` | 27 | -+-------------------------------+----------------------+ -| ``ibmq_montreal`` | 27 | -+-------------------------------+----------------------+ -| ``ibmq_armonk`` | 1 | -+-------------------------------+----------------------+ -| ``ibmq_brooklyn`` | 65 | -+-------------------------------+----------------------+ -| ``ibmq_bogota`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_santiago`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_casablanca`` | 7 | -+-------------------------------+----------------------+ -| ``ibmq_sydney`` | 27 | -+-------------------------------+----------------------+ -| ``ibmq_dublin`` | 27 | -+-------------------------------+----------------------+ -| ``ibmq_manhattan`` | 65 | -+-------------------------------+----------------------+ -| ``ibmq_5_yorktown`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_16_melbourne`` | 15 | -+-------------------------------+----------------------+ -| ``ibmq_paris`` | 27 | -+-------------------------------+----------------------+ -| ``ibmq_rome`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_athens`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_berlin`` | 27 | -+-------------------------------+----------------------+ -| ``ibmq_boeblingen`` | 20 | -+-------------------------------+----------------------+ -| ``ibmq_ourense`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_vigo`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_valencia`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_almaden`` | 20 | -+-------------------------------+----------------------+ -| ``ibmq_singapore`` | 20 | -+-------------------------------+----------------------+ -| ``ibmq_johannesburg`` | 20 | -+-------------------------------+----------------------+ -| ``ibmq_essex`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_burlington`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_london`` | 5 | -+-------------------------------+----------------------+ -| ``ibmq_rochester`` | 53 | -+-------------------------------+----------------------+ -| ``ibmq_cambridge`` | 28 | -+-------------------------------+----------------------+ diff --git a/docs/sessions.rst b/docs/sessions.rst deleted file mode 100644 index 3a986d077..000000000 --- a/docs/sessions.rst +++ /dev/null @@ -1,199 +0,0 @@ -Introduction to sessions -============================= - -A session allows a collection of jobs to be grouped and jointly scheduled by the Qiskit Runtime service, facilitating iterative use of quantum computers without incurring queuing delays on each iteration. This eliminates artificial delays caused by other users’ jobs running on the same quantum device during the session. - -.. image:: images/session-overview.png - :width: 400 - -Compared with jobs that use the `fair-share scheduler `__, sessions become particularly beneficial when running programs that require iterative calls between classical and quantum resources, where a large number of jobs are submitted sequentially. This is the case, for example, when training a variational algorithm such as VQE or QAOA, or in device characterization experiments. - -Runtime sessions can be used in conjunction with Qiskit Runtime primitives. Primitive program interfaces vary based on the type of task that you want to run on the quantum computer and the corresponding data that you want returned as a result. After identifying the appropriate primitive for your program, you can use Qiskit to prepare inputs, such as circuits, observables (for Estimator), and customizable options to optimize your job. For more information, see the `Primitives `__ topic. - -Benefits of using sessions ---------------------------- - -There are several benefits to using sessions: - -* Jobs that belong to a single algorithm run are run together without interruption, increasing efficiency if your program submits multiple sequential jobs. - - .. note:: - * The queuing time does not decrease for the first job submitted within a session. Therefore, a session does not provide any benefits if you only need to run a single job. - * If the first session job is cancelled, subsequent session jobs will all fail. - -* When using sessions, the uncertainty around queuing time is significantly reduced. This allows better estimation of a workload's total runtime and better resource management. -* In a device characterization context, being able to run experiments closely together helps prevent device drifts and provide more accurate results. -* While the session is active, you can submit different jobs, inspect job results, and re-submit new jobs without opening a new session. -* You maintain the flexibility to deploy your programs either remotely (cloud / on-premises) or locally (your laptop). - -The mechanics of sessions (queuing) ----------------------------------------- - -For each backend, the first job in the session waits its turn in the queue normally, but while the session is active, subsequent jobs within the same session take priority over any other queued jobs. If no jobs that are part of the active session are ready, the session is deactivated (paused), and the next job from the regular fair-share queue is run. See :ref:`ttl` for more information. - -A quantum processor still executes one job at a time. Therefore, jobs that belong to a session still need to wait for their turn if one is already running. - -.. note:: - * Internal systems jobs such as calibration have priority over session jobs. - -Maximum session timeout -++++++++++++++++++++++++++++ - -When a session is started, it is assigned a *maximum session timeout* -value. You can set this value by using the ``max_time`` parameter, which -can be greater than the program's ``max_execution_time``. For -instructions, see `Run a primitive in a session `__. - -If you do not specify a timeout value, it is set to the system limit. - -To find the maximum session timeout value for a session, follow the instructions in `Determine session details `__. - - -.. _ttl: - -Interactive timeout value -+++++++++++++++++++++++++++++ - -Every session has an *interactive timeout value* (ITTL, or interactive time to live). If there are no session jobs queued within the -ITTL window, the session is temporarily deactivated and normal job -selection resumes. A deactivated session can be resumed if it has not -reached its maximum timeout value. The session is resumed when a -subsequent session job starts. Once a session is deactivated, its next -job waits in the queue like other jobs. - -After a session is deactivated, the next job in the queue is selected to -run. This newly selected job (which can belong to a different user) can -run as a singleton, but it can also start a different session. In other -words, a deactivated session does not block the creation of other -sessions. Jobs from this new session would then take priority until it -is deactivated or closed, at which point normal job selection resumes. - -To find the interactive timeout value for a session, follow the instructions in `Determine session details `__. - -.. _ends: - -What happens when a session ends -------------------------------------- - -A session ends by reaching its maximum timeout value, when it is `closed `__, or when it is canceled by using the `session.cancel()` method. What happens to unfinished session jobs when the session ends depends on how it ended: - - -.. note:: - Previously, `session.close()` **canceled** the session. Starting with `qiskit-ibm-runtime` 0.13, `session.close()` **closes** the session. The `session.cancel()` method was added in `qiskit-ibm-runtime` 0.13. - -If the maximum timeout value was reached: - - Any jobs that are already running continue to run. - - Any queued jobs remaining in the session are put into a failed state. - - No further jobs can be submitted to the session. - - The session cannot be reopened. - -If the maximum timeout value has not been reached: - -- When using `qiskit-ibm-runtime` 0.13 or later releases: - - If a session is closed: - - Session status becomes "In progress, not accepting new jobs". - - New job submissions to the session are rejected. - - Queued or running jobs continue to run. - - The session cannot be reopened. - - If a session is canceled: - - Session status becomes "Closed." - - Running jobs continue to run. - - Queued jobs are put into a failed state. - - The session cannot be reopened. - -- When using Qiskit Runtime releases before 0.13: - - Any jobs that are already running continue to run. - - Any queued jobs remaining in the session are put into a failed state. - - No further jobs can be submitted to the session. - - The session cannot be reopened. - -Different ways of using sessions ----------------------------------- - -Sessions can be used for iterative or batch execution. - -Iterative -+++++++++++++++++++++ - -Any session job submitted within the five-minute interactive timeout, also known as interactive time to live (ITTL), is processed immediately. This allows some time for variational algorithms, such as VQE, to perform classical post-processing. - -- When a session is active, its jobs get priority until ITTL or max timeout is reached. -- Post-processing could be done anywhere, such as a personal computer, cloud service, or an HPC environment. - -.. image:: images/iterative.png - -.. note:: - There might be a limit imposed on the ITTL value depending on whether your hub is Premium, Open, and so on. - -This is an example of running an iterative workload that uses the classical SciPy optimizer to minimize a cost function. In this model, SciPy uses the output of the cost function to calculate its next input. - -.. code-block:: python - - def cost_func(params, ansatz, hamiltonian, estimator): - # Return estimate of energy from estimator - - energy = estimator.run(ansatz, hamiltonian, parameter_values=params).result().values[0] - return energy - - x0 = 2 * np.pi * np.random.random(num_params) - - session = Session(backend=backend) - - estimator = Estimator(session=session, options={"shots": int(1e4)}) - res = minimize(cost_func, x0, args=(ansatz, hamiltonian, estimator), method="cobyla") - - # Close the session because we didn't use a context manager. - session.close() - - -Batch -+++++++++++++++++++++ - -Ideal for running experiments closely together to avoid device drifts, that is, to maintain device characterization. - -- Suitable for batching many jobs together. -- The classical computation, such as compilation, of the jobs is run in parallel. This means running multiple jobs in a batch would be significantly faster than running them serially. - - -.. note:: - When batching, jobs are not guaranteed to run in the order they are submitted. - -.. image:: images/batch.png - -The following example shows how you can divide up a long list of circuits into multiple jobs and run them as a batch to take advantage of the parallel processing. - -.. code-block:: python - - backend = service.backend("ibm_sherbrooke") - - with Session(backend=backend): - estimator = Estimator() - start_idx = 0 - jobs = [] - while start_idx < len(circuits): - end_idx = start_idx + backend.max_circuits - jobs.append(estimator.run(circuits[start_idx:end_idx], obs[start_idx:end_idx], params[start_idx:end_idx])) - start_idx = end_idx - -Sessions and reservations -------------------------- - -IBM Quantum Premium users can access both reservations and sessions on specific backends. Such users should plan ahead and decide whether to use a session or a reservation. You *can* use a session within a reservation. However, if you use a session within a reservation and some session jobs don’t finish during the reservation window, the remaining pending jobs might fail. If you use session inside a reservation, we suggest you set a realistic ``max_time`` value. - -.. image:: images/jobs-failing.png - -Summary ---------- - -- Jobs within an active session take priority over other queued jobs. -- A session becomes active when its first job starts running. -- A session stays active until one of the following happens: - - Its maximum timeout value is reached. In this case all queued jobs are canceled, but running jobs will finish. - - Its interactive timeout value is reached. In this case the session is deactivated but can be resumed if another session job starts running. - - The session is closed or cancelled. This can be done using the corresponding methods or upon exiting a session context. -- Sessions can be used for iterative or batch execution. - -Next steps ------------- - -`Run a primitive in a session `__ diff --git a/docs/tutorials/chsh_with_estimator.ipynb b/docs/tutorials/chsh_with_estimator.ipynb index c1c5f05aa..fe4e2e380 100644 --- a/docs/tutorials/chsh_with_estimator.ipynb +++ b/docs/tutorials/chsh_with_estimator.ipynb @@ -146,7 +146,7 @@ "id": "66d5e2e2", "metadata": {}, "source": [ - "## Map the problem to a quantum-native format" + "## Step 1: Map classical inputs to a quantum problem" ] }, { @@ -240,7 +240,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Optimize the circuits and operators\n", + "## Step 2: Optimize problem for quantum execution.\n", "\n", "For this example, the circuit and the operators are simple, so no optimizations are needed." ] @@ -250,7 +250,7 @@ "id": "b7194399", "metadata": {}, "source": [ - "## Execute using a quantum primitive function\n", + "## Step 3: Execute using Qiskit Primitives.\n", "\n", "In order to execute the entire experiment in one call to the [`Estimator`](https://docs.quantum-computing.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.Estimator#estimator) we need to batch the circuit and operators together, repeating each for the requested `number_of_phases` points." ] @@ -322,7 +322,7 @@ "id": "ace7dc90", "metadata": {}, "source": [ - "## Analyze the results\n", + "## Step 4: Post-process, return result in classical format.\n", "\n", "After running the circuits, we need to build the CHSH witness functions. We first build the quantities $\\langle AB \\rangle$, $\\langle Ab \\rangle$, $\\langle aB \\rangle$, and $\\langle ab \\rangle$, by looking at the parity of the outcomes for the four families of circuits we built (two measurement bases for each of the two qubits). Then we use those quantities to build the witness functions as defined previously." ] @@ -447,13 +447,6 @@ "\n", "qiskit.version.get_version_info()" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/docs/tutorials/grover_with_sampler.ipynb b/docs/tutorials/grover_with_sampler.ipynb index 3a98086ae..de017a145 100644 --- a/docs/tutorials/grover_with_sampler.ipynb +++ b/docs/tutorials/grover_with_sampler.ipynb @@ -82,7 +82,7 @@ "id": "d4845f4d", "metadata": {}, "source": [ - "## Map the problem to a quantum-native format\n", + "## Step 1: Map classical inputs to a quantum problem\n", "\n", "Grover's algorithm requires an [oracle](https://learning.quantum-computing.ibm.com/course/fundamentals-of-quantum-algorithms/grovers-algorithm) that specifies one or more marked computational basis states, where \"marked\" means a state with a phase of -1. A controlled-Z gate, or its multi-controlled generalization over $N$ qubits, marks the $2^{N}-1$ state (`'1'`*$N$ bit-string). Marking basis states with one or more `'0'` in the binary representation requires applying X-gates on the corresponding qubits before and after the controlled-Z gate; equivalent to having an open-control on that qubit. In the following code, we define an oracle that does just that, marking one or more input basis states defined through their bit-string representation. The `MCMT` gate is used to implement the multi-controlled Z-gate." ] @@ -212,7 +212,7 @@ "outputs": [], "source": [ "optimal_num_iterations = math.floor(\n", - " math.pi / 4 * math.sqrt(2**grover_op.num_qubits / len(marked_states))\n", + " math.pi / (4 * math.asin(math.sqrt(len(marked_states) / 2**grover_op.num_qubits)))\n", ")" ] }, @@ -260,7 +260,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Optimize the circuits and operators\n", + "## Step 2: Optimize problem for quantum execution.\n", "\n", "For this example, the circuit the operators are simple, so no optimizations are needed." ] @@ -271,7 +271,7 @@ "id": "c5edec73", "metadata": {}, "source": [ - "## Execute using a quantum primitive function\n", + "## Step 3: Execute using Qiskit Primitives.\n", "\n", "Amplitude amplification is a sampling problem that is suitable for execution with the [`Sampler`](https://docs.quantum-computing.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.Sampler#sampler) runtime primitive. Because we have a single circuit, we instantiate the [`Batch`](https://docs.quantum-computing.ibm.com/run/run-primitives-batch) context manager to run our non-iterative workload." ] @@ -295,7 +295,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Analyze the results" + "## Step 4: Post-process, return result in classical format." ] }, { diff --git a/docs/tutorials/how-to-getting-started-with-sampler.ipynb b/docs/tutorials/how-to-getting-started-with-sampler.ipynb index e939d73d7..750d37421 100644 --- a/docs/tutorials/how-to-getting-started-with-sampler.ipynb +++ b/docs/tutorials/how-to-getting-started-with-sampler.ipynb @@ -169,7 +169,7 @@ }, "source": [ "\n", - "The next step is to create an instance of an `Sampler` class, which can be any of the subclasses that comply with the base specification. For simplicity, we will use Qiskit Terra's `qiskit.primitives.Sampler` class, based on the [Statevector construct](https://qiskit.org/documentation/stubs/qiskit.quantum_info.Statevector.html?highlight=statevector#qiskit.quantum_info.Statevector) (that is, algebraic simulation).\n", + "The next step is to create an instance of an `Sampler` class, which can be any of the subclasses that comply with the base specification. For simplicity, we will use Qiskit Terra's `qiskit.primitives.Sampler` class, based on the [Statevector construct](https://docs.quantum.ibm.com/api/qiskit/qiskit.quantum_info.Statevector) (that is, algebraic simulation).\n", "" ] }, diff --git a/docs/tutorials/qaoa_with_primitives.ipynb b/docs/tutorials/qaoa_with_primitives.ipynb index 38ea936fa..835495c25 100644 --- a/docs/tutorials/qaoa_with_primitives.ipynb +++ b/docs/tutorials/qaoa_with_primitives.ipynb @@ -74,7 +74,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Map the problem to a quantum-native format\n", + "## Step 1: Map classical inputs to a quantum problem\n", "\n", "To demonstrate max-cut, we'll create a graph using the [rustworkx library](https://github.com/Qiskit/rustworkx), and create Pauli Hamiltonian that encodes the cost in a manner such that the minimum expectation value of the operator corresponds to the maximum number of edges between the nodes in two different groups." ] @@ -179,7 +179,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Optimize the circuits and operators\n", + "## Step 2: Optimize problem for quantum execution.\n", "\n", "We can schedule a series of [qiskit.transpiler](https://docs.quantum-computing.ibm.com/api/qiskit/transpiler) passes to optimize our circuit for a selected backend. This includes a few components:\n", "\n", @@ -273,7 +273,7 @@ "id": "b58c33dc", "metadata": {}, "source": [ - "## Execute using a quantum primitive function" + "## Step 3: Execute using Qiskit Primitives." ] }, { @@ -420,7 +420,7 @@ "id": "d162e690", "metadata": {}, "source": [ - "## Analyze the results" + "## Step 4: Post-process, return result in classical format." ] }, { diff --git a/docs/tutorials/user-transpiled-circuits.ipynb b/docs/tutorials/user-transpiled-circuits.ipynb index 5e9350ebe..fe78ab888 100644 --- a/docs/tutorials/user-transpiled-circuits.ipynb +++ b/docs/tutorials/user-transpiled-circuits.ipynb @@ -4,1089 +4,130 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# Submitting user-transpiled circuits using primitives\n", + "# Submit pre-transpiled circuits\n", + "\n", + "## Background\n", "\n", "To get the best performance from your circuits, the Qiskit Runtime service will pass all circuits through Qiskit's transpiler before running them. While this is usually a good thing, we might sometimes want to disable this by passing the argument `skip_transpilation=True` to the primitive we're using.\n", "\n", "For example, we may know better than the transpiler in some cases, or want to target a specific subset of qubits on a specific device. In this tutorial, we'll disable automatic transpilation to test the performance of different transpiler settings. This example will take you through the full process of creating, transpiling, and submitting circuits.\n", "\n", - "## Transpiling circuits for IBM Quantum devices\n", + "## Setup\n" + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": {}, + "outputs": [], + "source": [ + "# Create circuit to test transpiler on\n", + "from qiskit import QuantumCircuit, transpile\n", + "from qiskit.circuit.library import GroverOperator, Diagonal\n", + "\n", + "# Use Statevector object to calculate the ideal output\n", + "from qiskit.quantum_info import Statevector\n", + "from qiskit.visualization import plot_histogram\n", "\n", - "In the following code cell, we create a small circuit that our transpiler will try to optimize. In this example, we create a circuit that carries out Grover's algorithm, with an oracle that marks the state `111`. We then simulate the ideal distribution (what we'd expect to measure if we ran this on a perfect quantum computer, an infinite number of times) for comparison later. " + "# Qiskit Runtime\n", + "from qiskit_ibm_runtime import QiskitRuntimeService, Batch, Sampler" ] }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 43, "metadata": {}, "outputs": [ { "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " 2022-04-13T19:59:15.034547\n", - " image/svg+xml\n", - " \n", - " \n", - " Matplotlib v3.5.1, https://matplotlib.org/\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], "text/plain": [ - "
" + "'ibm_hanoi'" ] }, - "execution_count": 1, + "execution_count": 43, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# Create circuit to test transpiler on\n", - "from qiskit import QuantumCircuit\n", - "from qiskit.circuit.library import GroverOperator, Diagonal\n", - "\n", - "oracle = Diagonal([1] * 7 + [-1])\n", - "qc = QuantumCircuit(3)\n", - "qc.h([0, 1, 2])\n", - "qc = qc.compose(GroverOperator(oracle))\n", - "\n", - "# Use Statevector object to calculate the ideal output\n", - "from qiskit.quantum_info import Statevector\n", - "\n", - "ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()\n", - "\n", - "from qiskit.visualization import plot_histogram\n", - "\n", - "plot_histogram(ideal_distribution)" + "# To run on hardware, select the backend with the fewest number of jobs in the queue\n", + "service = QiskitRuntimeService(channel=\"ibm_quantum\")\n", + "backend = service.least_busy(operational=True, simulator=False)\n", + "backend.name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we need to choose a backend to transpile for. In the following cell, we create a service instance, which we'll use to start a session, and get the backend object, which contains information for the transpiler. Since the transpilation process depends on the device, we'll ask the runtime service for a specific device by name. In this example, we'll use `ibm_algiers`, which is only available through IBM Cloud." + "## Step 1: Map classical inputs to a quantum problem\n", + "\n", + "In the following code cell, we create a small circuit that our transpiler will try to optimize. In this example, we create a circuit that carries out Grover's algorithm, with an oracle that marks the state `111`. We then simulate the ideal distribution (what we'd expect to measure if we ran this on a perfect quantum computer, an infinite number of times) for comparison later.\n" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 48, "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "oracle = Diagonal([1] * 7 + [-1])\n", + "qc = QuantumCircuit(3)\n", + "qc.h([0, 1, 2])\n", + "qc = qc.compose(GroverOperator(oracle))\n", + "\n", + "qc.draw(output=\"mpl\", style=\"iqp\")" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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16JFi9S/f3998cUX8vZ2+2UAAACQB7ePzE2dOlVff/21brzxRv3www86d+6coqOjde7cOW3cuFEdO3bUwoUL9dJLLxVmvQAAADhPvm9NcqF69erJ19dXu3btyvHIW2pqqlq0aKHk5GQdOHDgkgv1NG5NAgBAycGtSf7h9pG56Oho9e3bN9evUH18fNS3b19FR0e7+xIAAAC4CLfDXO3atZWUlJTnOmfOnFGdOnXcfQkAAABchNth7oEHHtCCBQtyPfJ25MgRzZ8/Xw888IDbxQEAACBv+b7MNDIy0uXxwIED9dNPP6l169YaM2aMOnbsqKCgIB07dkw//PCD3nzzTXXs2FEDBgwo9KIBAACQqcBzs17IGJNre9bz0tLSLrFMz+MCCAAASg4ugPhHvo/MDR06NMfQBgAAAM/Jd5j75JNPirAMAAAAuIO5WQEAAGyMMAcAAGBjlzRp6unTp/XOO+9o9erVOnr0qJKTk7OtY1mWIiIiLuVlAAAAkAu3w1xsbKw6dOigiIgIBQQEOK+4SElJ0dmzZyVJNWrUkI+PT6EVCwAAAFduf8367LPPKiIiQp9++qlOnjwpSXr88cd15swZbd68WW3btlXdunX1+++/F1qxAAAAcOV2mFu6dKm6d++uu+++O9stS6677jotW7ZMBw8e1HPPPXfJRQIAACBnboe56OhotW7d2vnYy8vL+fWqJFWqVEk333yzFixYcGkVAgAAIFduh7nAwEClpqY6H1eqVEmHDx92WScgIEDHjh1zvzoAAADkye0wV69ePR08eND5uHXr1lq1apVOnDghSTp79qwWLVqkOnXqXHKRAAAAyJnbYa5Xr15as2aN/v77b0nSqFGjdPz4cbVs2VIDBgzQ1VdfrYiICA0fPrywagUAAMAF3A5zDz74oD788ENnmPvXv/6lV199VWfOnNHChQsVExOjsWPHavz48YVWLAAAAFxZxhhTmBtMT09XXFycqlWrlu0qVzvLuo9eQkKCAgICPF0OAACXtRHTPV2B9OGYot1+frPHJc0AkRMvLy8FBQUV9mYBAACQg0sOc9HR0Zo3b5527NihhIQEBQYGqnXr1ho8eLCqV69eGDUCAAAgF5cU5mbMmKHx48crOTlZ539bO3fuXD311FOaNm2aHn744UsuEgAAADlzO8zNmzdPjz76qK644go99dRTuvHGGxUU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AEB2bh+Zi46O1k033eR8vGfPHkVFRemVV15Rx44dJUlff/11ge5HBwAAgIJx+8hccnKyypQp43y8YcMGWZalXr16Odvq1aunI0eOXFqFAAAAyJXbYa5WrVratWuX8/HixYtVuXJltWjRwtl24sQJVahQ4dIqBAAAQK7c/pr15ptv1owZM/TEE0/I19dXy5cv19ChQ13W2b9/v+rUqXPJRQIAACBnboe5CRMmaNGiRXr99dclSdWrV9fzzz/vXH78+HH99NNPGj169KVXCQAAgBy5HeaCg4P1+++/a82aNZKkTp06KSAgwLk8Li5Or776qkJDQy+9SgAAAOTI7TAnSeXKldMtt9yS47KmTZuqadOml7J5AAAAXATTeQEAANjYJR2ZS09P14IFC7R69WodPXpUycnJ2daxLMv5VSwAAAAKl9th7syZM+rVq5c2bdokY4wsy3JO4yXJ+diyrEIpFAAAANm5/TXrf//7X4WFhem5555TXFycjDF69tlnFR0drfnz56tevXoaMGBAjkfrAAAAUDjcDnPffPON2rVrp6efflqVK1d2tgcFBWnAgAFat26dVq9ezdysAAAARcjtMBcZGal27dr9syGHw+UoXK1atdSnTx/NmTPn0ioEAABArtwOc35+fnI4/nl6YGCgoqOjXdYJDg5WZGSk+9UBAAAgT26HuZCQEJegdvXVV2vt2rXOo3PGGK1Zs0bVq1e/9CoBAACQI7fDXPfu3bVu3TqlpaVJkoYNG6bIyEi1b99e48ePV8eOHbVz507179+/0IoFAACAK7dvTTJixAhVqVJFsbGxql69uu677z7t2LFDM2fO1M6dOyVJ/fv317PPPltIpQIAAOBCljn/5nCFIDY2VgcOHFBISIiCg4MLc9MelZiYqMDAQCUkJLjMQQsAAIrfiOmerkD6cEzRbj+/2eOSZoDISdWqVVW1atXC3iwAAABywNysAAAANub2kbl69erlaz3LshQREeHuywAAACAPboe5jIyMHOddTUhI0KlTpyRJ1atXV5kyZdwuDgAAAHlzO8wdPHgwz2Vjx47VsWPHtGrVKndfAgAAABdRJOfM1a1bV/Pnz9fJkyf11FNPFcVLAAAAQEV4AYSPj4969uypBQsWFNVLAAAAXPaK9GrWv//+W/Hx8UX5EgAAAJe1IgtzP/zwg7788ks1atSoqF4CAADg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hykpKclMnz7dtG/f3nh7ezt3RIfDYcqUKWMsyzL33ntvjucFlAYXBrcsWZ8+s35xt2zZYsqXL29GjhxZnOUVm9TUVPP111+7fMps2bKleeSRR8zChQvNnj17zDfffGMGDRpkvLy8zH/+8x9Pl1yssoJd1v4QFhZWqveHl156yTgcDmNZlhk2bJhZsWKFOXv2rHN51vvI6tWrTc2aNc2IESM8VWqRSElJMb///rvzIjFjcj9CuX//fnP33Xcby7LMI488UpxlFpvPP//cBAUFOc8Vmz9/vst5oln7w5IlS0yNGjVK3f6QmJiY7cNtTn87jhw5YkaPHm0syzIPP/xwcZV3SbjPXCkTFxen/fv3a9OmTfrhhx+Unp6uhg0bqkmTJrr//vs9XV6hy7oprDFGGRkZF72v3KOPPqoZM2Zo69atpfo+a5L03XffadasWVqxYoXS09MlZU7vZoyRj4+PHnnkET355JOX9c1CR48erZkzZ5a6/cH8/3urnTp1Sh999JFefvllxcbGysvLS23atNENN9ygrl27KjAwUFu3btU777yj06dPa+3atWrevLmnyy82F75vPP/883r22WdL3f6QJTk5WQsXLtTrr7+u7du3S5KqVaumzp07q2fPnipbtqx2796t2bNnq2zZslq5cqWaNWvm4aqLx4X7wuTJk/XCCy/YZl8gzNnQ8ePH9dtvv2n//v1KSkpS27Zt1bhxY11xxRXZwkxycrLLDUHNZTxdS2JiokaNGqV169YpJibG0+UUiZxCbUxMjNatW6effvpJPj4+qlGjhho3bqy+fft6sFLPO3PmjB555BEtXbpUx48f93Q5herC3/Nz585pzpw5+vTTTxUWFpZt/aZNm2rChAkaMmRIcZZZ5LJukJ71oS8nWWO1f/9+9e3bV2lpaYqIiCjmSouXMUaLFi3SBx98oJUrVyotLc1lefv27fX000/r5ptv9lCFnnXgwAHddtttOn36tP766y9Pl5MvhDmbWbZsmf773/9me0OuXLmyunfvrkGDBqlv377y8fFxLiuNMz5IuYfaKlWqOO/yfuGbeHJyso4fP+4yW4TdFeTne+F4lKZw7+5+npiYqICAgCKoqGSKjIzU6tWrtXv3bgUHB6tatWrq2LGj6tev7+nSPGrfvn267bbb1LdvX73yyiueLqdImMxTq1x+TxISErR+/Xod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", + "text/plain": [ + "
" + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "from qiskit_ibm_runtime import QiskitRuntimeService\n", + "ideal_distribution = Statevector.from_instruction(qc).probabilities_dict()\n", "\n", - "service = QiskitRuntimeService()\n", - "backend = service.backend(\"ibm_algiers\")" + "plot_histogram(ideal_distribution)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Step 2: Optimize problem for quantum execution.\n", + "\n", "Next, we transpile the circuits for our backend. We're going to compare the performance of the transpiler with `optimization_level` set to `0` (lowest) against `3` (highest). The lowest optimization level just does the bare minimum needed to get the circuit running on the device; it maps the circuit qubits to the device qubits, and adds swaps gates to allow all 2-qubit operations. The highest optimization level is much smarter and uses lots of tricks to reduce the overall gate count. Since multi-qubit gates have high error rates, and qubits decohere over time, the shorter circuits should give better results.\n", "\n", - "In the following cell, we transpile `qc` for both values of `optimization_level`, print the number of CNOT gates, and add the transpiled circuits to a list. Some of the transpiler's algorithms are randomized, so we set a seed for reproducibility." + "In the following cell, we transpile `qc` for both values of `optimization_level`, print the number of CNOT gates, and add the transpiled circuits to a list. Some of the transpiler's algorithms are randomized, so we set a seed for reproducibility.\n" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 50, "metadata": {}, "outputs": [ { @@ -1094,7 +135,7 @@ "output_type": "stream", "text": [ "CNOTs (optimization_level=0): 27\n", - "CNOTs (optimization_level=3): 12\n" + "CNOTs (optimization_level=3): 14\n" ] } ], @@ -1102,8 +143,6 @@ "# Need to add measurements to the circuit\n", "qc.measure_all()\n", "\n", - "from qiskit import transpile\n", - "\n", "circuits = []\n", "for optimization_level in [0, 3]:\n", " t_qc = transpile(qc, backend, optimization_level=optimization_level, seed_transpiler=0)\n", @@ -1117,55 +156,75 @@ "source": [ "Since CNOTs usually have a high error rate, the circuit transpiled with `optimization_level=3` should perform much better.\n", "\n", - "Another way we can improve performance is through [dynamic decoupling](https://qiskit.org/documentation/stubs/qiskit.transpiler.passes.DynamicalDecoupling.html), where we apply a sequence of gates to idling qubits. This cancels out some unwanted interactions with the environment. In the following cell, we add dynamic decoupling to the circuit transpiled with `optimization_level=3`, and add it to our list." + "Another way we can improve performance is through [dynamic decoupling](https://docs.quantum-computing.ibm.com/api/qiskit-ibm-provider/qiskit_ibm_provider.transpiler.passes.scheduling.PadDynamicalDecoupling#paddynamicaldecoupling), where we apply a sequence of gates to idling qubits. This cancels out some unwanted interactions with the environment. In the following cell, we add dynamic decoupling to the circuit transpiled with `optimization_level=3`, and add it to our list.\n" ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 51, "metadata": {}, "outputs": [], "source": [ - "from qiskit.transpiler import PassManager, InstructionDurations\n", - "from qiskit.transpiler.passes import ASAPSchedule, DynamicalDecoupling\n", + "from qiskit.transpiler import PassManager\n", + "from qiskit_ibm_runtime.transpiler.passes.scheduling import (\n", + " ASAPScheduleAnalysis,\n", + " PadDynamicalDecoupling,\n", + ")\n", "from qiskit.circuit.library import XGate\n", "\n", "# Get gate durations so the transpiler knows how long each operation takes\n", - "durations = InstructionDurations.from_backend(backend)\n", + "durations = backend.target.durations()\n", "\n", "# This is the sequence we'll apply to idling qubits\n", "dd_sequence = [XGate(), XGate()]\n", "\n", "# Run scheduling and dynamic decoupling passes on circuit\n", - "pm = PassManager([ASAPSchedule(durations), DynamicalDecoupling(durations, dd_sequence)])\n", + "pm = PassManager([ASAPScheduleAnalysis(durations), PadDynamicalDecoupling(durations, dd_sequence)])\n", "circ_dd = pm.run(circuits[1])\n", "\n", "# Add this new circuit to our list\n", "circuits.append(circ_dd)" ] }, + { + "cell_type": "code", + "execution_count": 52, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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+DF+//13tGvXDkDhNIyKsLGxQe3atRUqK2s9rK2tMWzYMAwbNgyDBw9G9+7dkZycjKZNmyIuLg6GhobF3tsmr1atWuHo0aPFPjt16hRatWql8DaJiIiIiIiIiIiIiIhIdnJNHbl+/XqIRCL4+flh586dCAkJwePHj7F161Y8evQIBgYGUsuNHDkSpqamGDt2LB48eIBz585h2rRpGD16dLH3jOXl5WH8+PEIDg7G0aNHsWDBAkydOhVCoRBVqlSBvb09Nm7ciKdPn+Ls2bP47LPPKvbtFSBLPX788Ufs2LEDjx49QmhoKHbv3g1nZ2fY2tqic+fOaNWqFfr374+TJ08iPDwcV65cwZw5cxAYGAgAiI6Ohre3N27cuFFqPSZOnIhnz55h9uzZePToEdavX49du3Zh1qxZKv3+REREREREREREREREVEjmJ9oAoFatWrhz5w6WLl2Kr7/+GlFRUTAxMUG9evXwxRdfYPLkyVLLmZub48SJE5gxYwaaN28Oc3NzDBo0CD/++GOx9d5//314eXmhffv2yM3NxfDhw7Fw4UIAgFAoxL///ovp06ejfv36qFu3LtauXYsOHToo9MUVJUs9rKyssGLFCjx58gQGBgZo3rw5jh49WjRF5dGjRzFnzhx8+OGHSEhIgLOzM9q3b1806Jifn4/Hjx8jKyur1HrUqFEDR44cwaxZs7BmzRq4ubnhjz/+QLdu3VT6/YmIiIiIiIiIiIiIiKiQQCKRSDRdCSIiIiIiIiIiIiIiIiJtI9fUkURERERERERERERERERUiANtRERERERERERERERERArgQBsRERERERERERERERGRAjjQRkRERERERERERERERKQADrQRERERERERERERERERKYADbUREREREREREREREREQK4EAbERERERERERERERERkQI40EZERERERERERERERESkAA60ERERERERERERERERESmAA21ERERERERERERERERECuBAGxEREREREREREREREZECONBGREREREREREREREREpAAOtBEREREREREREREREREpgANtRERERERERERERERERArgQBsRERERERERERERERGRAjjQRkRERERERERERERERKQADrQRERERERERERERERERKYADbUREREREREREREREREQK4EAbERERERERERERERERkQI40EZERERERERERERERESkAA60ERERERERERERERERESmAA21ERERERERERERERERECuBAGxEREREREREREREREZECONBGREREREREREREREREpAAOtBEREREREREREREREREpgANtRERERERERERERERERArgQBsRERERERERERERERGRAjjQRkRERERERERERERERKQAQ01XgConiQTIKdB0LeRjagAIBMrZlkQigSg7VzkbUxNDMxMIFPwH0MbjLauy4kIbj7Oi5I0PiUSCrGyRCmukfOZmhnK3AcZA6RgDuqUi5wh9pY2xoezjrI35gbz5oDYeZ0UpEh/adi7geaBszAWk05cYYC4gP304DwL60wYAtgNFaFs7UOa9QUA724ci53tt+46K0od8GJA/H2IMKGnbKtkqab2cAqDdUU3XQj4BPQEzJUW0KDsX22qNUs7G1GRk2FYYmZsqVFYbj7esyooLbTzOipI3PrKyRbBsuUWFNVK+jGtjYGFuJFcZxkDpGAO6pSLnCH2ljbGh7OOsjfmBvPmgNh5nRSkSH9p2LuB5oGzMBaTTlxhgLiA/fTgPAvrTBgC2A0VoWztQ5r1BQDvbh7xxro3fUVH6kA8D8udDjAHl4NSRRERERERERERERERERArgQBsRERERERERERERERGRAjjQRkRERERERERERERERKQADrQRERERERERERERERERKUCJr4ckIiIiIiIiIiJ6Iz9fjIdhKQh8mIjgZynIyBJBIAAszY1Qv3YVNKvngHo1bWFoyN+Ck24qKBAjNOIVbgUn4l5oMtIy8iCRAOamhvCuYYNm9RzQsI4dTIwNNF1VIiJSEAfaiIjUzLmVL7r/twiXZqzD013nNV0d0gDGgH7j8Sci9gPEGNBv+nL8Ax8mYP3OEPx7/BmycwrKXNfS3Agje9XCpKHeaFTXXk01JE3Sh3bw+Hkqftv9CH8ffIKUV3llrmtsJMTA9z0xeZgP2jZ1gkAgUFMtiTRDH/oAKpuuxQAH2khp0u+fR+jcjsU+E5pawMS1Duw7jEbV3tMgMGDI6TJdiYHXHf1r4oIC5KdnIysuGUn3nuH5/kuIPhekuQqSyjEG9BuPP5HimAuQrmAM6Dce/4p58CQZny6+jCtB8TKXycjKx4bdj7Bh9yN0bO6C3+a1QR1PGxXWUnV4LqTIuAxMXXoVB8+/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WkY+LM2N0NHfVdNVI5IJr4npXbw3VhynjiS18V33EEa2VTVdDaJKITc1A02/GYlGs8pPUqTxmzcaDaYNQG5qhpJrRurCGCDGAJF+Yx9AOUmv0G7ddHiNeF+h8u1+mYGag9ohLz1byTUjdWE/oH94X+QNxj8RsR8gXYoBPtFGRKQB58b/UKHygYv/QeDif5RUG9IExgAxBoj0G/sAOtzjfxUqHzBljZJqQprCfoD0GeOfiNgPkC7FAJ9oIyIiIiIiIiIiIiIiIlIAB9qIiIiIiIiIiIiIiIiIFCCQSCTa8dZVUiuJBMgp0HQt5GNqAAgEytmWRCKBKDtXORtTE0MzEwgU/AfQxuMtq7LiQhuPs6LkjQ+JRIKsbJEKa6R85maGcrcBxkDpGAO6pSLnCGVz67wD0fFZqFbVHFGnh2u6OqXSxthQ9nHWxvxA3nxQG4+zohSJD207F/A8UDbmAtLpSwwwF5CfPpwHAf1pA0DlaQfa0gYA7WsHyrw3CGhn+1DkfK9t31FR+pAPA/LnQ/oeA5mZmbC0tAQAZGRkwMLCQrFtV7h2pJMEAsBMj6NDIBDAyNxU09VQG3093vp2nOUhEAhgYW6k6WqoHGOgdIwB0neMDf3ID3icy6YP5wLGQOn04fgDjAEqnT6cBwG2ASqbvrSD0uhD+9CH71gR+pAPMQaUg1NHEhERERERERERERERESmAA21ERERERERERERERERECuBAGxEREREREREREREREZECONBGREREREREREREREREpAAOtBEREREREREREREREREpgANtRERERERERERERERERArgQBsRERERERERERERERGRAjjQRkRERERERERERERERKQADrQRERERERERERERERERKYADbUREREREREREREREREQK4EAbERERERERERERERERkQI40EZERERERERERERERESkAA60ERERERERERERERERESmAA21ERERERERERERERERECuBAGxEREREREREREREREZECONBGREREREREREREREREpAAOtBEREREREREREREREREpgANtRERERERERERERERERAow1HQFqHKSSICcAk3XQj6mBoBAoJxtSSQSiLJzlbMxNTE0M4FAWf8AYAzoOx5/ItJ37AeJMUBEpN94X4BI+9qBMtuANuaCrzEnVB5tawMA24Gm4p8DbSRVTgHQ7qimayGfgJ6AmZIiWpSdi221RilnY2oyMmwrjMxNlbY9fY8BfcfjT0T6jv0gMQaIiPQb7wsQaV87UGYb0MZc8DXmhMqjbW0AYDvQVPxz6kgiIiIiIiIiIiIiIiIiBXCgjYiIiIiIiIiIiIiIiEgBHGgjIiIiIiIiIiIiIiIiUgAH2oiIiIiIiIiIiIiIiIgUwNciEhGpQV5+AZ5EvEJGVj4kEsDS3BC1q1vD1ITdMBHph+wcEZ6+eIW8/AIAQL5IjPx8MYyM+LsvfZHyKhfh0enIySuAoYEQ9rYmqFHNCgKBQNNVIyIiIiIiIlIY7/ASEamARCLBjfsJ+PvgE1y/n4D7T1KQLxIXW8fQUID6tavAv74jRvWqjbZNnXizkYh0hkQiwfmbsdh2JAw3HybiYVgKCgokRcvjk3Ng1WoLGtapgpYNq2JcXy80reegwRqTsuXmFWDPqefYfzYCt4KT8Dw6vcQ6NlbGaOptj/bNnDF+YB24O1tqoKZEREREREREiuNAGxGREkkkEmw7EobV/zzA7ZCkMtcViSQIepSMoEfJ2LjnMRp4VcGMkb74sH8dCIUccCMi7SQSifH73sdYu/0hHj1PK3Pd3LwC3HyQiJsPEvHz9mC0aOCIz8bUx5CuNfjDAy2W+ioXK/66jz/+e4yElJwy101Lz8O5m7E4dzMWizcGoV/H6vjfR43g38BRTbUlIiIiIiIiqhgOtJHSpN8/j9C5HYt9JjS1gIlrHdh3GI2qvadBYMCQ02X6HgMRMen4eOElnL4Wo1D5+09S8PHCS/j74BNs+rY9ale3VnINVU/fY4BI34U8S8W4uRdx40GCQuWv30/AsC/P4Z9DT7Fhfhu4VrVQcg1VT9/7waMBkfhk0SXExGfJXVYslmDfmQgcOPcCn4+pj2+nNNXKKZb1PQaIiIiI9BlzQSL9bAe69W2oUqjSfjhsmvUEJBLkp8Qh6fwWRG36DDlRIfCYslHT1SM10McY2H82HGPmXER6Zn6FtxVw+yUaDv4Pfy5sh+E9aymhduqnjzFApO/+/O8xpiy9ity8ggpv6/DFSPgO+A//ruiIbm3clFA79dO3frCgQIzp31/D+p0hFd6WWCzBD5vv49CFFzjyS1fUdNO+H54A+hcDRERERPQGc0Ei/WoHHGgjpTOv2RT2HUYV/e3YczIeTvZG4qk/4DpqCYxs9G8qoHGxe5S2rc0ug5W2LVXRtxjYcTQMo+dcKPbuoYrKzinAyK/PIyMrH58M9lbadtVF32KASN+t2foAM1dcV+o2U9Pz0GfaKexa2RH9O3kqddvqoE/9oEgkxqhvzmPn8edK3e6j52loO/YIzv3RA3Vr2Cp12+qgTzFARETl07f7AkTv0rc2wFyQ3qVvbQDQr3bAgTZSOQNTC1jUbYnUK3uQGxemUw1IFhbVHHBjwWYEbzys6apojC7HwLGASKUPsr0mkQCfLr6MKtYmGNy1htK3r066HANE+m7LwSdKH2R7LV8kxrAvz+HEb93RobmLSvahLrrcD05eckXpg2yvxSZkocunx3Ftax+tnEr0bbocA0REVDbeFyB9xzbAXFDfsQ0U0uV2INR0BUg/5MaFAQAMLe00XBP1c+/ih8iTgZquhsbpYgwkpuRg3LyLcg2y3dzRF5GnPsDNHX1lWl8iAT5edAnRLzMVrWaloYsxQKTvnkW9wqTvrshVRt5+MC9fjNHfXEBaep4iVaxUdLEf3HvqOX7f+1iuMvLGQGRcJiZ8exkSifJ/1KJuuhgDRERUPt4XIH3HNlCIuaD+Yht4Q1fbAQfaKiAxMRGzZ89G7dq1YWpqCnd3d8yYMQOZmZkYP348BAIB1q1bp+lqqp04NwuiV4nIT0tAdvh9vPhtCrKf3YG5lz9Mq9XRdPXUzrqmM9LD4zRdDbXSlxiYuuwK4pNz5Crj7GAONycLODuYy1wmLT0Pny7WrhuM+hIDRPpMLJZg/IJLyMoRyVVOkX4w6mUmvlilmqfmVEUf+sGE5Gy5B1oBxWLgyMVI/HPoqdz70iR9iAEiIpKNPt4XIHqbPrYB5oL0Nn1sA4B+tQNOHamgoKAg9OjRA3FxcbCwsEC9evUQExODtWvXIiwsDMnJyQCAxo0ba7aiGhC7YwFidywo9pltq4Go/ukvGqqR5hiamyI/Q76BGF2gDzFw/masyqbJkubIxUgcvhCJPh2qq22fFaEPMUCk73adeIbzN2PVtr8//gvFp0O84eerHVNL6EM/uGD9bSSkqC/PmfXDdQzuUgPmZtpxCaMPMUBEJI+09Dz8dyYcrzLzAQBZ2SJkZOXD0txIwzVTLX29L0Al5eSKcODcC6T/fxvIzBYhMSUHDlVMNVwz1dLXNsBcsKSCAjGOXYrCg6cpyMktgL2tCfp19EB1F0tNV02l9LUNAPrVDrTjKrWSSUxMRJ8+fRAXF4fPP/8cCxYsgJWVFQBgxYoV+Oqrr2BoaAiBQICGDRtquLbq59BtAqq0HgJJQT6yI+4j7r/lyEuMgsDoTeKQ/jAAT7/tUaKsRJQHibgAzfYVqLPKKuP6XkNEX7ir6WqonT7EwLodwWrf5y//BmvNQJs+xACRvlv3b4ja9/nLvyH4a7F2DLTpej+Ylp6HLWp+wiw5LRf/Hn+GjwZoxy8fdT0GiIhkFROfiW83BGHr4afIzH7zJHxKeh6qdd6BsX29MG9CYzjamWmwlqqjr/cF6I1XGXlY8nsQ/twXiqTU3KLPU9Pz4NblXwzrVgPzJzZBLXdrDdZSdfS1DTAXfCM/X4xVW+7j110heBFb/NUoM1dcR5/33DF3QmOt+VGlvPS1DQD61Q44daQCpk+fjqioKEydOhUrV64sGmQDgNmzZ6NRo0YQiUTw9PSEtbVuniTLYuLiBevGnWHTrAecB85G7TmHkPX0Jl78OrFoHSvfdmiyM6PYf77rQ2Fo5QDXEYs1WHvlqtrcGwk3i7+3pOnXIzAudg9qf9BJapnuexdhdPgO2NZ1V0cVVULXYyD6ZSb2n4tQ+35PXInG0xev1L5fReh6DBDpu7uPk3D5zku17/ff48+QlKodvwTU9X7wn3dulqrLL/8Ga81UyroeA0REsnj0PBUtRh7Cht2PpJ43XmXk4+ftwWg1+hCeR6VroIaqp6/3BahQfFI22n94BCv+ul9skO213LwCbDn0FC1GHsTNBwkaqKHq6WsbYC5YKCtbhN7TTuLrNYElBtmAwlcSHDj3Au3GHcFBDdxrUwd9bQOAfrUDDrTJKSQkBDt37oSDgwOWLVsmdZ1mzZoBABo1alT02euBOX9/f5iYmEAgEJS6j9OnT6Nly5YwNTVF1apVMXHiRKSlpSn3i6iRpU9r2HUYjZRLO5ERIv09HuL8XDz7fiAs67WFy5Bv1FxDFREIAAEgEYuLfRy0chdSQiLgv3AszF2Kv/Sx3oTecG7ti6CVO5H6OFKdtVUpXYuB/eciUFCgmZt8u0+qb7pKZdK1GCDSd5rqi3JyC3DowguN7LuidK0f1FQM3A5JwjMtvRGrazFARFSexJQcdJ90AlEvS95YfVdYZDp6TD6BtPQ8NdRMjXhfQK/l5hWg97STuPs4udx1k1Jz0XPKSYRHa2eeUyq2gSL6mAtKJBKMmXMBJ69El7tuTm4Bhn55FtfvxauhZmrENlCMLrcDDrTJaceOHRCLxRg5ciQsLaXPH2tmVjjdwdsDbU+fPsXevXvh7OyM5s2bl7r9CxcuoHv37qhWrRr27duHJUuWYM+ePejfv7/W/HpXGpdh8wChAWK2z5e6/MX6iRDn58Bzxmb1VkyFHJvURuKdklMqifNFCJixDobmJmjz4+Siz61ruaLp/4Yj4VYoHqw/qM6qqoUuxUDgw0SN7ftWsOb2XVG6FANE+k6z/WCSxvZdUbrSD4rFEtwO0dxx4LmQiEg7/PJvMCJiMmRe/3F4Gv7aH6rCGqkf7wvot72nwnHzgex5S2JKDlb+fV+FNVI/toHi9C0XvPkgEXtPh8u8fm6eGPPX31ZdhTSAbaAkXW0HHGiT09mzZwEAHTt2LHWdqKgoAMUH2tq3b4/Y2FgcPHgQnTt3LrXst99+Cy8vL+zevRs9evTAJ598go0bN+L8+fM4cuSIkr6F+pm61IZduw+Qfu8M0h8GFFsWf2gt0gIPo9bX+yE0MddQDSvGqaUPBAbFm1O1jk0QfS5I6vrJ95/j3s/7UK1DY9QZ1RkCoRDt1k4DAATMWFfiVw66QJdiQJM3+LT55qIuxQCRPpNIJLjFQRaF6Eo/GBqRhoysfI3tnzFARFT55eeLsXHv4/JXfMf6XSEQi7XzR8a8L0DvWr9L/ncabzn0FOmZ2vlkJ9tA+fQtF1y/U/42cPJKNJ5EaOfMbmwDstHVdsCBNjlFRBTOFevh4SF1uUgkwuXLlwEUH2gTCmX7p75+/To6d+5cbP2uXbsCAPbv369IlSsN5yFzAKGw2Gh1+r1ziNryFWrO3g0TJ0/NVa4CPPu2Rud/voFTC59inxtZmyM/PavUcndX70Hyg+fwmz8GLZZ8BMemXri9fAdehcWousoaoysxEBqhufekhcdkID9fe0+0uhIDRPosLT0PiSmae0/aEy15V2VpdKEfDA3X7IUvY4CIqPK7cvclYuJLvx4uzZOIV7j7WPueXud9AXpXTHymQu80Ts/Mx/HL5U+zV9mwDchOX3JBiUSCXSefKVRWG1+bwjYgH11sBwKJNs9HqAF2dnZISUnBlStX0KpVqxLLt23bhlGjRsHKygppaWlS38W2cOFCLFq0SOpUkDY2Nvj444+xatWqos9yc3NhZmaGFi1a4OrVq3LX2c/PD3FxcXKVERibwemnJ3LvSx65L8Px6IvmcPlgAar2mlrh7b2c6QVJXrYSagYYSYRYIPaXef2GMwbCxM4aNxdsBgBYVHOAR88WCP697KcQq9TzQO9j38PA2Agvr4fg2ID5gIJNcpHwBvIFyhuA0fcYKI0EAsTYLSx1+c0dfeHsUPovLpwdzGBoIISoQIy4xNLrGpeYhebDpT8i7pK8BEKo9hdu6jj+gHJjQB3Hn4iAAoEl4qp8WepyVfeDAkkuXFOWyldpBbAfLF2WsS9SLIdKXVbe8QcqHgMmeU/gkLFVvkorgDFARKS4bKN6SLYaplBZ+1d/w1Sk2M1ZZeJ9AaqIPANnJNhMUqisTeZhWObeVHKNFCNPO9C1NqCNueBrlSEnFMMIsXZzFSprkXMFtlknlFwjxWhbGwDYDuSNf7FYjNjYWABA48aNcefOHYX2a6hQKT3m7OyMlJQU3L59u8RAW2xsLL78svDGU8OGDaUOspWnTp06uH79erHPbt68CYlEguTk8l+eKk1cXByio+X7NYzQxBxOCu1NNuLcLIQt6w8b/75KO4nExMRAnCv/L+akMRYYQJ5/gIhjN/D+5q+KOlL3Ln6IPBlYbrn8V1kQ54lgYGyEqDO3K9SJxsTGIE9SoHD5d+l7DJSpihgQSH9K1dnBHG5OFuVuwtBAKNN60sTGRAMS1Q60qfr4A8qPAbUdfyJ9Z2AFVCl9sar7QYm4QO68RhHsB8tg7QxIf1WxzMcfUDwGcnOyGAOl4LmQiCoNKwfASrGiSYkvgUzNP9HD+wJUISZiwEaxomkpSUhL0XwbAORrB7rWBrQxF3ytcuSEBoCdYiUzX6Uh8yXbgKL0vR1UJP5fvpT/SeTXONAmp86dOyMkJATLly9Hly5dUKdOHQCFg2GjR49GYmLhOyMaN26s0PanT5+OMWPG4LvvvsPEiRMRFRWFyZMnw8DAQObpJ9/l7OwsdxmBsZlC+5JVypW9yH5+FznRoUi5tLPEct91wTB2rC7XNl1dXZX6RBvkGPhPC40CJIBtHTekhkbBqoYz0jeX3zDb/DQFQiNDpIZGouHMQQg/eAXpEYo1aFcXV6U/0aZKlT0GyhIjyYFEIP3X+nGJZXfk8vyKXyqJCK6uVSGAah9GVvXxB5QfA+o6/kT6TgJDxEhK/8GBqvtBA+TBuVo12SusIPaDpcsxNEdpk3qVd/yBiseAmQlgxxiQiudCIqos8oUSxAOFNwvl+RGyRAznKkIY2Kq+ny8P7wtQRYhhjDhJHiQCY9kL/X97cbAWwcRc820AkK8d6Fob0MZc8LXKkhPGFSSjwED+0TZb8xxYqCHfl4W2tQGA7UDe+H/7iTYnJ8WHFTnQJqfZs2dj+/btiIyMhK+vL7y9vZGTk4OnT5+iR48e8PT0xIkTJ4q9n00eo0aNwsOHD7F48WLMmzcPBgYGmDJlCoyNjWFtba3QNgMDyx85f1e2CGh3VKHdycS+42jYdxyt1G2Ghj6BmZIiOj8rB9tqjZKrTOSpQLh3a46MqETkZ5TfmH3G94RLm/q4tWw7Io/fQJ+TP6DN6sk4PnCBQnUOfRIKI3NThcpKo+8xUJZO44/i3M1YqctKm+7xtchTH8DNyQJxidlw7/Kv3Pv2q++Mmzsi5S4nL1Uff0D5MaCu409EgE+/PXj0XPp7ulTdD/bs1BAHf46Su5y82A+WLiE5G1U7bJe6rLzjD1Q8Bhb972N8+eFaucvJizFARFQxHT46gguB8r3Gom9HTxxYG6KiGsmH9wWooj5ZGIA//guVvYBAgHq1bPHgv8sKzZKlCvK2A11qA9qYC75WWXLC5Zvu4n8/yXdf2sbKGFHX9sHC3EhFtZKPtrUBgO1A3vjPzMyEpWXhlC2XLl1SeL+KPSKlx9zc3BAQEIBevXrB1NQU4eHhsLOzw4YNG3DkyBGEhhaeQBUdaBMIBPj++++RmJiIu3fv4uXLl1i1ahWePHmC1q1bK/OrkJJFngyEe1c/uHZohJiL98pc16qGM5p+MwIJd57gwbr9SA2NQtCqXXBu5Quf8T3VVGNSVLN6Dnq5byKi1zTbD9prbN9UyNHODNVdFJv+WBl4LiQi0g5TPqgnd5nJw3xUUBP14X0Bepsi8Tx5mE+lGWRTBNsAve2j/nVgYmwgV5kP+3lVmkE2RbAN6C8OtCnAx8cHhw8fRnp6OtLT03H9+nVMmDABmZmZCA8Ph1AoRP369Su0DysrKzRs2BD29vb466+/kJ2djQ8//FBJ34BU4eX1EFjXdIFHD38k3Hxc+ooCAdr+NBVCoRCXZqyDRFz4KO+DXw4gMegpmn4zAlYeqp79liqiTZOqGtt360aa2zcR0WttGmvuPNVag/umNzQVA8ZGQg60ERFpicFdPDG2r5fM608dXg9dW1eOqcIUxfsC9LYmPg74dkpTmdfv1d4dnw72VmGNVI9tgN7maGeGTYvaybx+Y287udpMZcQ2oL840KZEDx8+hEQigZeXF8zNS76/ac+ePdizZw+Cg4OL/f321I6BgYFYtmwZTpw4gSNHjmDWrFmYOHEili9fjlq1aqntu5D8JAViRJ+/W/j/xaXPg+s7sQ+c/L1x54edSHvy5sWeErEYl2asg9DAAG1WT1Z5fUlxPdu5w9lB9XMUv8vGyhiDu9RQ+36JiN41vEdNmJnK98tEZahRzQqd/F3Uvl8q6eOBdTWy36HdasDGSo53nRARkcYIBAL8sbAtPh1S/sDBZ2PqY81XLbX6SR6A9wWopLkTGmPpdL9y1xvcxRO7V3aCoaF236plG6B3jehVC1uXvQdjo7Jju00TJ5ze2ANWFtqd67MN6C/t7r0rmfv37wMofdrIIUOGYMiQIdi9e3exv9etW1e0jomJCQ4dOlS07MqVK9i5cydmzZql+i9AFfbi2A28OH6z1OU2XtXQdPYHiA98jIe/HSqxnI8IawdjIwON3GD8sJ8XzCvDJNtEpPdsrU0wsqf6fwA0cYg3DAyYvlYGHf1dUNfTRu371fYpxYiI9I2hoRC/zWuDmzv64qMBdWBq8uaHOhZmhpg4xBt39wzAqi9aQCjU7kG213hfgN4mEAjw9ceN8PjgYMwa7Qvbt34wZGQoxPAeNRGwuRd2rewEM1PduN5nG6B3jexVG8+ODsXCSU3gWrX4wyk927nh8LouuLCpJ+xtdeMdk2wD+kk3evBKoryBNolEUu42GjRogCtXrii1XqQ+EYevlrk87Uk0/qkxosx17v+8D/d/3qfMapEKTBrqjTXbHiI9M18t+zM1McC0EfK/44CISFVmja6PzQefQCQqP79RBjsbE4wfWEct+6LyCQQCzP6wIcYvCFDbPts2cULLhpxCmYhIG/n5OuLPRY74dW5rJKflQiAQwM7aBEblPOGgjXhfgKSp42mDH79siRWz/JGclgtRgRh2NiYwNdG9W7NsAyRNNScLLJjUFHMnNIZbl38Rl5gNF0czHPmlm6arpnRsA/pJ9zIaDSpvoI0KpVzeg4hfJ0Gcl4OnS/vjwaQ6CJ7RCKHzuyAn9qmmq0cKCl3QFcHTGyJ4ZmM8/rodsp7dkanc63h4W+Lpv3CrnwCp1/aroKbK4VrVAis/91fb/pZMa4aabtZq25+6vRsH2hADRPquXq0qmDehidr2t+7rVjrzC0dtPfe968P+Xujc0lUt+zI1McCfi9pp/ZRir+lKDBARycvYyADODuZwsjfTyUE2ovIYGgpR1d4MrlUtdHKQjYCcmCd4NLs1Hkyqg5DPmyP7xcMS64heJSF4ZuOi/x5MqoNbAwwhSk8GANz/xBMPJtUtWp4csLOorKL33yoLAwMhDP7/CWahjuT2pBhduyZij65EZ8+e1XQVtELqtX2w6zgGAODYdQKsm/WAQCBA/JF1iFj3MeouOa/ZCpJCan65C4aWtgCAlKv7EL5mHOqtuVtuubfjAQByX4Yj8eTvsKjbUlVVVZpPBtXF3tPhOHkluvyV/19cYlax/5VF68ZVMWOkr9z10yZvx4E2xQCRvvt6fCPsPxuBO4+SZC6jSD844H0PfNCjptz1q6y0+dz3ttfv3mkwaJ9cT3grEgNLp/uhjgamqlQVXYkBIiIiIiruxfpP4dBtAhzeH4eUy3sQvmYcfFYVn0bQ0Noe9X4KKvo7bt9KZDy8AEMru6LPan65E+Y1G5fYvqL334gqG127JuJAGymdKCMVwdPrQ5yXDWMHd0jyc5Eb9wx2HUbDY9KvyHh0GZ4zNkNgaAQbvzfzzFrUaYmX+1dqsOZUEa9P8gBQkJUG/P+vUmSNB6DwhZ8R6z6G+4SfEfXX5xr4FvIRCATYtqwD2o07jEfP02Qq03z4Qbn2UaOaFXav7KTV7yQqKwY8p/0BiSi/KA60LQaI9J2RkRD/rX4fbcceRnS8bIMm8vaDjb3tsEnLnmTS5XPfuzxcC89TfaadQr6o9Jd9v03eGBjb10vrfnCiTzFARERERIXyU+OR+TQQXotOAgBsWw/Ci41TkRP7FKYutUstl3T6T1QbvUymfZR2/42ostG3ayIOtJHSGVrawq79CBiYWcFl2Dyk3T6BuD1L4TntD7y6cxIW3q0hMDQqUS7+8BrY+vfTQI1JWZ6vHoP0++cAAF7zjwKQLx5eHvgRlj5tYFG7mca+g7wcqpjizO890OXT4wgOS1Xqtmu5W+HUhh5wrWqh1O2qW1kxAADp988VxUHcvpVaFwNE+s6zmhXO/tETnSccQ2RcplK33cTbHsd/7QZbaxOlblfVdP3c965ubdywZ1UnDP3yHHLzCpS67VG9a+GPhW0hFGrXDQR9iwEiIiIiAvISI2FUxQUCg8Jb7gKBAMaO1ZGX8KLUgbaMkCsQZaTApnnvYp+H/zQGEkhg4eWPamO+h5GNY9EyafffiCobfbsm0t5HJKhSy3oeBLOahe9tyQq7BfP///+p1/ejSssBJdaP3b0UubFPUW2MbL/eoMqpxqwtaLgpEtVGfYeoLV8VfS5LPGRHPEDq1b1wGTpX/RWvINeqFrj4Vy8M6uyptG32au+Oy1v6oIabldK2qSqPZrdC0CgHqf/lJUQCKD0GgDdxoM0xQKTv6nja4MqWPni/hfLe1zWqdy2c+7MnqtqbKW2b6qTr57539e3ogbN/9EDt6sp5n6iRoRDfTW2Gv797D4aG2nnJom8xQERERKTrZLn/Ia/E03/CvuOYosE5AKi79CLqrb2Hej/ehqG1A8LXjC1WprT7b0SVjT5dE/GJNlKJ7OdBRQ0nK+wWbP37QiKRIO3OCVQbu6LYunH7ViL16n/w+vY0hCbmmqguKZl9p7GI+HUiRK+SYGhtL1M8ZAQHIDc+HA8meQEA8lPiEBE5AfkpsXDsManUfVUW9ram2L2qE3affI4pS68iMSVHoe3YWBljzeyWGNO3ttZMk+a94mq560iLAQDF4iD5/D9aHQNE+s7N2QKnNnbHxj2P8cWqG8jIkv2dXW9zdjDDhnlt0Lejh5JrqF76cO57V+vGTri7ewDm/ByINdseQiJRbDvN6jlg8+J2qO9lV/7KlZg+xgARERGRLivv/ofAyAT5KbGQFIggMDCERCJBXsILGDtWl7p+QXYGUi7tKvEOt9frCwyN4NRnJh5MqiO1/Lv334gqG326JuJAGyldXlI0AAGM7asBALLD78FlyBxkhd6AmZsPDMwsi9Z9eeBHpATsgNe3p4vNMUzaRZSRCnFuFoztC59kSL22H4ZW9jCwspM5Hhx7TCrWeT6e0wFOfWbCtmV/dX8dhQkEAgztVhM92rph25Ew/PJvCB48TZGprHcNG0we5oMxfbxgY2Ws4pqqV2kxAKBYHOhCDBDpO4FAgE+HeGNI1xrYfCAUv+56hKcvXslUtom3PaZ84IPhPWrB3Ey7U1R9Ove9y9zMEKtnt8SUD+rht10h2LQ/FCmv8sotJxAAPdu5Y8owH3Rr46Z1U0W+S59jgIiIiEhfGdlWhXmtpkg6vxUO749D6pW9MLZ3K3XayJRLO2FWoxFM3byLPivIyYRElF90nzQ5YEfRQEVZ99+IKht9uybS7rsYVCllPbtTbFo4AwtbxB9dD0NrB9i26F/0eV5iFKI2fQ5j55oIndsRACAwNIHPyuvqrjJVUEFWGp6tGAJxXjYEAiEMrR1Re+5hCAQCmeNBl1hZGGPiUB98OsQbd0KScONBAm4FJ+LB0xQEPkyEqEACI0MhRvWuBb96Dmhe3xF+vg5a8wSbvEqLAc9pfyDl2j6djQMifWZnY4LPxjTAzFH1ceN+Am4+TMCt4CQEP0tBVrYIQqEAluZGaOBVBX71HNCiYVU08KqiM/2gPp773lW7ujVWftECi6c2Q8DtONwKTsKt4EQ8i0rH/SfJEBVIYGwkxLTh9dCsngPaNHFCdRfL8jesJRgDRERERPrJY9IGhK8dh7g9S2FgZg3P6X8VLQv/+WPY+veFbYvCWX4ST/0Jh66fFCsvSn2JsO8HAeICSCCBiVNNeM7cAqDs+29ElY2+XRMJJBJFJ3UhXZYtAtop+V2aD6f6os5352BkW1W5G/5/AT0BZf0APj8rB9tqjVLOxtRkZNhWGJmbKm17qoiBt6kiHpQZA6ri1nkHouOzUK2qOaJOD9d0dUql6uP/mjLjQBuOPxFpD33PhVRJn8+FjAEiIu3B+wJE2tcOlNkG1HVfRBUqe06oLdcDgPa1AaDyt4PKdk2UmZkJS8vCH31mZGTAwsJCof1W4iZHusZ33UNNV4EqEcYDAYwDItIv7POIMUBERERERPpMV6+JhJquABEREREREREREREREZE24kAbERERERERERERERERkQI40EZERERERERERERERESkAA60ERERERERERERERERESlAIJFIJJquBFU+EgmQU6DpWsjH1AAQCJSzLYlEAlF2rnI2piaGZiYQKOsfAIwBVXHrvAPR8VmoVtUcUaeHa7o6peLxJyJ9x35QdXguVB1tiQEiIm3A+wJE2tcOlNkGtDEXfK2y54Tacj0AaF8bANgO5I3/zMxMWFpaAgAyMjJgYWGh0H4NFSpFOk8gAMz0ODoEAgGMzE01XQ2N0vcY0Hc8/kSk79gPEmOAiEi/8b4AkX63A+aCBOh3GwDYDuTBqSOJiIiIiIiIiIiIiIiIFMCBNiIiIiIiIiIiIiIiIiIFcKCNiIiIiIiIiIiIiIiISAEcaCMiIiIiIiIiIiIiIiJSAAfaiIiIiIiIiIiIiIiIiBTAgTYiIiIiIiIiIiIiIiIiBXCgjYiIiIiIiIiIiIiIiEgBHGgjIiIiIiIiIiIiIiIiUgAH2oiIiIiIiIiIiIiIiIgUwIE2IiIiIiIiIiIiIiIiIgVwoI2IiIiIiIiIiIiIiIhIARxoIyIiIiIiIiIiIiIiIlIAB9qIiIiIiIiIiIiIiIiIFMCBNiIiIiIiIiIiIiIiIiIFcKCNiIiIiIiIiIiIiIiISAEcaCMiIiIiIiIiIiIiIiJSAAfaiIiIiIiIiIiIiIiIiBRgqOkKUOUkkQA5BZquhXxMDQCBQDnbkkgkEGXnKmdjamJoZgKBsv4BwBjQF9oY64pQpH1IJBJkZYtUVCPVMDczlOt76svxB/QjBuQ9/gBjgKgs2tg+9D0fVHYuqO8xoG3HH1AsBrTxOCtCH3IhgPlQWRgD0unL8QeYDxMpQl/yodJoYx+pqb6OA20kVU4B0O6opmshn4CegJmSIlqUnYtttUYpZ2NqMjJsK4zMTZW2PX2PAX2hjbGuCEXaR1a2CJYtt6ioRqqRcW0MLMyNZF5fX44/oB8xIO/xBxgDRGXRxvah7/mgsnNBfY8BbTv+gGIxoI3HWRH6kAsBzIfKwhiQTl+OP8B8mEgR+pIPlUYb+0hN9XWcOpKIiIiIiIiIiIiIiIhIARxoIyIiIiIiIiIiIiIiIlIAB9qIiIiIiIiIiIiIiIiIFMCBNiIiIiIiIiIiIiIiIiIFcKCNiIiIiIiIiIiIiIiISAGGmq4AERERERERERERERHpnrDIV7h0+yVuhSTiTkgSXiZlAwDik7Mx+LMzaFbPAc3q2aN9M2eYmnC4grQTn2gjIiK1cm7li3Gxe1B7aAdNV4U0hDFAjAEiItJnPA8SY4AYA6Tr8vPF2HvqOd7/+Chq99qNcfMu4uftwbh05yVEBZLCdUQS7D0djm/WBqLbxBNw6/Ivvlp9A8+iXmm49kTy4xAxKU36/fMIndux2GdCUwuYuNaBfYfRqNp7GgQGDDldxhjQDc6tfNH9v0VFf4sLCpCfno2suGQk3XuG5/svIfpckOYqSCrHGCDGABEpivkg6UIM8DxIjAFiDBAp7sb9BIybdxEhz1LlKpeUmosVf93Hyr8f4Iux9bFoclOtfMJNF3Ihkh+PKCldlfbDYdOsJyCRID8lDknntyBq02fIiQqBx5SNmq4eqQFjQDc8+y8AUWdvAwIBjCzMYFPbFdW7+6P20A6IuXAX5yesQt6rLE1Xk1SIMUCMASJSFPNB0oUY4HmQGAPEGCCSnUgkxrxfbmHFX/chFksU3o5YLMGKv+7j4PkX2LqsA5rVc1BiLdVHF3Ihkh0H2kjpzGs2hX2HUUV/O/acjIeTvZF46g+4jloCIxtHDdZOM8bF7lHatja7DFbatlSFMaAbku4/x7O9AcU+u7ngbzSbNwr1J/ZF+19n4fTIJRqqHakDY4AYA0TKoW+5IMB88F2MAe2MAZ4HiTFAjAEi2eTmFWDYl2dx4NwLpW3z0fM0tB93BPvXdEaXVtWUtl110YVcSJl0PR/mQBupnIGpBSzqtkTqlT3IjQvTu07EopoDbizYjOCNhzVdFY3R9xjQJRKxGIGLtsCxiRfcOjVBVX9vxN94BAAwsjJHw+kD4dGrBSxcHZCfkYWYi/dx+/vtyHgRX/aGBQI0nD4Arh0aw6amC4xtLZGdkIqo07dxZ/kO5KZkAABM7a0x5PYGhB++hoApa0pspsXSj+E9tiv2tpiCjKgEpX9/YgwQY4BIXswFC+lzPsgYKKQrMcDzIDEGiDFAVJxIJMYHs88pdZDttawcEfpOP4Xjv3bDe34uSt++OulKLqQIfciHhZquAOmH3LgwAIChpZ2Ga6J+7l38EHkyUNPV0Dh9jgFd9GT7GQCAW+emAAovJnoeWgLvcd0QdeY2rs/9EyGbjsOlbX30ProMFm5lP+ZvYGyI+pP64dWzWDxYfxA35v2FmAv34DW8E7rvXQShUeHvQnKSXiHyZCA8evjD2Nq8+DZMjFBzQFvEBNznxYQaMAaIMUAkG+aCb+hrPsgYeEOXYoDnQWIMEGOAqNDiDUHYfzZCZdvPyS3AwFlnEJeo/dO06lIuJA99yIf5RBspnTg3C6JXiZBIJBClxCHh+G/IfnYH5l7+MK1WR9PVUzvrms54tDlO09VQK8aA7ksOKUygrGu6AgCazB4Gq+pVcaT3N0gJfpNcPd11Dv3O/ogmXwzDpZm/lLq9gtx87Gz8CQpy8op9nhD4GG1+nIzq3Zsj/NBVAEDo1tPw7N0KNQa0w+O/TxSt69GrJUxsLfFk22mlfU8qHWOAGANEstHHXBBgPvg2xoBuxgDPg8QYIMYAEXAnJBFL/wySq8zNHX3h7GCOuMQsNB9+UKYyyWm5mPTdFfy3+n0IBAIFaqp+up4LyUMf8mE+0VYBiYmJmD17NmrXrg1TU1O4u7tjxowZyMzMxPjx4yEQCLBu3TpNV1PtYncswN3Rjrg3piqCZzREwrH1sG01ELXnHNB01dTO0NwU+Rk5mq6G2jEGpMvNK0BBgRgAIJEo/lLYyiA/PRsAYGxpBgCoObAdXl4PQVZcMkzsrIr+E2XlIuH2E7i+16jcbb6+mBAIhTC2NoeJnRViLz0AADg09SpaL+bCXaRHvITXiE7FynsN74Sc5Fd4cfymUr6jsiyZ3gySe+PxYX8vqcvP/dkTOYHj4Fu7ipprVjGMAdkxBhgD9MarjDyIdORcKAt9zQUB5oOvMQZ0NwZ4HpQdcyHGAGOAMUC6SSyWYNy8AIhE8uX1zg7mcHOygLODefkrv2X/2QjsORUuVxlN0vVcSFb6kg/ziTYFBQUFoUePHoiLi4OFhQXq1auHmJgYrF27FmFhYUhOTgYANG7cWLMV1QCHbhNQpfUQSArykR1xH3H/LUdeYhQERqZF66Q/DMDTb3uUKCsR5UEiLkCzfQXqrLLKuL7XENEX7mq6GmrHGCjudnAift0Vgu1HnyErRwQAiEnIxri5FzF5mA+a13fQml/jvGZkVXghkZeRDVN7a5jaWaNah8YY/vAvqeuLC8o/np59WsF3Yl/Y1feEgbFRsWUmNpbF/g7dfgbNvh4BO19PJD8Mh2X1qnBu7YvgP45CnC9S8FupxsL1d9Dnver48YsWOHk1GtEv30x1MHOULzo0d8H/frqJh09TNFhL+TEGZMcYKKTPMaDv8vPF2H8uAut3huD8zdiiz18m5WDxhjv4eGBduDjKd5GtLfQ1FwSYD77GGNDdGOB5UHbMhQoxBhgD+hwDpJtOXI7CvdBkte5zxV/3MKRrDbXuU1G6ngvJSl/yYQ60KSAxMRF9+vRBXFwcPv/8cyxYsABWVlYAgBUrVuCrr76CoaEhBAIBGjZsqOHaqp+JixesG3cGANg06wFLn7Z4/HVbvPh1Imp++S8AwMq3HZrszChWLi8pBo8+94Njr6lqr7OqVG3ujVvfbS32WdOvR6Dh9IG4NGs9nv57tkSZ7nsXwbFZHRzqNhupjyPVVVWlYgwUKigQ47MfrmPt9mCpy/8++AR/H3yCjwfWwfo5bWBkpD0PGdv5eAAAXoXFAP8/SBhz4S7u/7Jfoe1V79kCHTZ+joTbT3Bj3l/IjElCQW4eBAZCdN0xDwJh8YHIpzvOoskXQ+E1vBOuz90Er+GdIBAKK+X0GPkiMcbOvYjrW/viz4Xt0H1S4bQedTxtsGSaH67di8cPm+9ruJbyYwzIjjEgG12OAX0WE5+JXlNPIuhRyQvwArEE83+5jaV/3MXWpe9hUBftuGCWh77mggDzwdcYA7obAzwPyo65kGwYA4wBXY4B0k2/7nqk9n0GPkzEzQcJaF7fUe37lpeu50Ky0pd8mANtCpg+fTqioqIwdepUrFy5stiy2bNnY/v27bh79y5q1KgBa2trDdWy8rD0aQ27DqORfG4LMnpPh6VP6xLriPNz8ez7gbCs1xYuQ77RQC1VQCAABIBELC72cdDKXXDv0gz+C8ci5kIQsmLf3HiqN6E3nFv74taSrVrTichCH2NAIpFg2rKrMiUdf/wXitw8Mf5e0l5rnmzzGvE+ACDq9C3kJL1CbmoGjKzMERug2MVRrcHtIcrOxfHBC1CQ/WY+epvarlLXz05IReSpW6g5sB1uLdmG2kM7IuFWKFJDoxTav6rdCUnCsj/vYv7EJvhkUF38uS8UW5a0h0AAjJ17EWKx9k2fxhiQD2OgfLoeA/ooKTUHHccfQ2hEWpnr5eQWYMgXZ/Hf6vfRv5OneiqnDswFi9HHfJAxUJyuxQDPg/JhLlQ+xgBjQNdjgHRLbEIWDl98oZF9//HfY60YaHuXruVCMtGjfFh7Hp+oJEJCQrBz5044ODhg2bJlUtdp1qwZAKBRozdzL+/ZsweDBg2Ch4cHzM3N4e3tjTlz5iAjo/iItazraRuXYfMAoQFits+XuvzF+okQ5+fAc8Zm9VZMhRyb1EbinaclPhfnixAwYx0MzU3Q5sfJRZ9b13JF0/8NR8KtUDxYL9uLQLWJvsXAicvRcv2y55/DT7VinmmBUAi/+WPg1MIHkadvIf7mY0AiwbP/AuDY1AsevVpKLWdqX/aPDiT//74egaD4aanhzMGllgnddhomVazQasUEWLjaI3T7GTm/jXot3ngHQY+SsPJzf/z8dSu0aFAVc36+hdDwsm9AVzaMAcUxBhgD+ubLH2+UO8j2mkQCjJlzEemZeeWvrCWYC5akb/kgY6AkXYgBngcVx1yIMcAYYAyQ7rh2Lx6aeuXylaB4zexYCXQhF5KHPuXDfKJNTjt27IBYLMbIkSNhaWkpdR0zs8I5mt8eaFu5ciWqV6+OpUuXws3NDUFBQVi0aBEuXLiAixcvQigUyrWetjF1qQ27dh8g+cI2pD8MgJVvu6Jl8YfWIi3wMLxX3oTQRDvfz+HU0gfxNx8XJUUAUK1jEzzceFjq+sn3n+Pez/vQ+LMhqDOqM55sP4t2a6cBAAJmrCsxyq8LdD0G3vXLv9KniyzL+p0hlWqeafsGNVBzUOFxMrIwg01tV1Tv7g9L96qIPh+Ei5PXFK17+/sdqNrcGx02fobwg1eRcDsUBXkiWLo5wu39Jki69wyXZv5S6r4iDl+DZ+9W6LZnAcJ2X4DQyBDVuzeHoZlJqWWizwUhIzIetQa/h/yMbDzff1l5X14FRCIJxs69iJs7+mLyMB8E3I7DT1sfaLpaZWIMKBdjgDGgT5JSc7Dj2DO5yqRn5mPr4TBMGuajolqpDnNB2ehyPsgYkI22xQDPg8rFXIgxwBhgDJDuuBWcqLF9Bz9LRVa2COZm2je0oW25kDz0PR/WvmjUsLNnC+cM7dixY6nrREUVPqL99kDboUOH4Oj45pHW9957D46Ojhg5ciQuXbqE9u3by7WeNnIeMgfJATsQs30+6i45BwBIv3cOUVu+gtf8YzBx8tRsBRXk2bc12qyahDNjv0fclYdFnxtZmyM/PavUcndX70H1rn7wmz8Gdr6ecGzqhRsLNxfO7a2jdDUG3hUVl4kjAfI/2nz+ZiweP09F3Rq2yq+UAmoObIeaA9tBXFAAUWYOMmOTEXc1GM+/2ojoc0HF1s1Pz8LRfnPgO7EvavRpBfdufpAUiJEZm4T4G48Quq3sX9U9P3AZhpZm8J3QC83nj0FuWiYiTwXi1pJtGBGyWXohiQShO86i6ewP8PzgFYiycpTzxVUoLSMPuXkFMDYywNGASI39+ktWjAHlYwyUTl9iQF9sPfwUObnyv7h7w55HWjfQxlxQPrqYDzIG5KNNMcDzoPIxFyodY6ByYgwQSXf3ccl3MKuLWCzBg6cp8G+gfdNHAtqVC8mK+TAgkEgq+ymtcnF3d0dUVBTu3LmDxo0bl1guEong4uKCxMREhIWFoWbNmqVuKzQ0FHXr1sX27dsxfPjwCq9XGj8/P8TFxclVRmBsBqefnsi9L3nkvgzHoy+aw+WDBaiqhJc7vpzpBUlethJqBhhJhFgg9pd5/YYzBsLEzho3F2wGAFhUc4BHzxYI/v1ImeWq1PNA72Pfw8DYCC+vh+DYgPlQNMtcJLyBfIHyRvr1PQYqItfQE4nWHypU1i59G8zyQ5Vco9LJG+uVTf3J/eA3bzSO9P4GCbdK/3dTpH2IYYRYu7kVrWIxZ//ogdaNnRAW+QoerpZoOGgfnkWlK237LsnfQYh8mdfX9uMPMAbeJu/xBxgDpDqp5r2Radpc/oKSfFRL+U75FVKAPO2jMuSCAPNBZeeC+h4D6jj+gOZjQNvPhcyFitPHfIgxUByviZgPk3okWH2EPCMPqctu7ugLZ4fSn8hydjCDoYEQogIx4hLLPm/HJWah+fCSUwjav/obpiL5ZtBQhLblw4Dm7pFraz4sFosRGxsLAGjcuDHu3Lmj0H75RJucMjMzAQDZ2dKDdefOnUhMTISVlRVq1Ch7Crhz5wpHrH18yv7VrqzrlSYuLg7R0dFylRGamMNJob3JRpybhbBl/WHj31cpHQgAxMTEQJxb+gi5PIwFBpDnHyDi2A28v/mroo7EvYsfIk8Gllsu/1UWxHkiGBgbIerM7Qp1IjGxMciTyP/L8dLoewxUiKUNUPbU66VKTk4DXsnXXitC3livTAQGQtQZ3QXJwRFlXkwACrYPgTFgV4EKvmPaiHro6O+Kb9YG4sC5CNze2R+bvm2HDh8dVdo+YmNiAIns7zfS5uMPMAbeJe/xBxgDpELVcgBTRQoayJ23qoo87aMy5IIA80Fl54L6HgOqPv5A5YgBbT4XMhcqSd/yIcZASbwmKh3zYVKqmvmAkfRFzg7mcHOyKHcThgZCmdaTJikpGchQ/XWDtuXDgObuketCPvzy5UuF98uBNjk5OzsjJSUFt2/fRqtWrYoti42NxZdffgkAaNiwIQQCQanbiY6Oxrx589C9e3epT8bJu155dZaXwNhMoX3JKuXKXmQ/v4uc6FCkXNpZYrnvumAYO1aXa5uurq5KHa2HHD/ySQuNAiSAbR03pIZGwaqGM9I3l98w2/w0BUIjQ6SGRqLhzEEIP3gF6RGKNWhXF1el/4JZlSp7DFREnoEZEoDCE0MZ/YA0DlVMYWJVTSX1kkbeWK8MLN2rwtGvDqp3aw5rT2dcmLi63DKKtA8xjBCraCXfUbu6NZbN8MON+wlYvukexGIJFv56G8tmNMe0EfXw83b53+knjYurq9y/3tS24w8wBkoj7/EHGAOkOmlmQIYC5YSSTLhUU995sCzytI/KkAsCzAeVnQvqewyo+vgDlSMGtPFcyFyodPqSDzEGSsdrotIxHyZlSjQWILeUZXGJZQ/yyPtEmzQO9jYwsVH9dYO25cOA5u6Ra2s+/PYTbU5Oig+rcqBNTp07d0ZISAiWL1+OLl26oE6dOgCAmzdvYvTo0UhMLHwRZFmDYhkZGejXrx+MjY2xadOmCq9XnsDA8keO35UtAtop7wdFJdh3HA37jqOVus3Q0CdQ1jsw87NysK3WKLnKRJ4KhHu35siISkR+Rvmdmc/4nnBpUx+3lm1H5PEb6HPyB7RZPRnHBy5QqM6hT0JhZK7QT8el0vcYqAixWAKffnsRGpEmV7lqVc0RfjsAhoZCFdWsJEViXdOcW9VD2zVTkZOUhqBVu/D8QPkve1akfWRm5cOy5RZFq1lEIAA2L24PA6EAY+degFhc+MucFX/dx8D3PbFshh+OXIxUynQpT0JDYWFeyk/KpNDG4w8wBkoj7/EHGAOkOkGPktBk6H65y00d1QJr/hel/AopQN72oelcEGA+qOxcUN9jQNXHH6gcMaCN50LmQqXTl3yIMVA6XhOVjvkwKdOkxZfx2+5HUpdJm+rxbZGnPoCbkwXiErPh3uVfhfZ/7+YpuDiWPj2lsmhbPgxo9h65NubDmZmZsLS0BABcunRJ4f2q726ujpg9ezbs7e0RGRkJX19fNGjQAF5eXvD390fNmjXRqVMnAECjRo2kls/OzkafPn3w/PlznDx5Ei4uLhVajyqPyJOBcO/qB9cOjRBz8V6Z61rVcEbTb0Yg4c4TPFi3H6mhUQhatQvOrXzhM76nmmpMqiIUCjBxqLfc5T4d4q3WQTZt9XTXeWx2GYx/649H0Mpdmq5OuT4f2wBtmjhh/vrbePT8zeCrWCzBuHkXYWggxKZv22mwhtqHMUDaFgP6qLG3PVo2lP/l5JOGKTZVemXAXJAYA6Qu2nYeZC6kfIwB0rYYIN3TrJ6Dxvbt4miulkE2kp8+58O8oysnNzc3BAQEoFevXjA1NUV4eDjs7OywYcMGHDlyBKGhhfMhSxtoy8/Px+DBgxEYGIhjx46hXr16Uvch63pUuby8HgLrmi7w6OGPhJuPS19RIEDbn6ZCKBTi0ox1kIgLH2V98MsBJAY9RdNvRsDKQ4snCCcAwPgBdeBdw0bm9T1dLTFpqPbeXCTpvGvYYPGUprh6Nx6r/n5QYnlwWCoW/nob7/m5YNoI9vW6iDFA+mzpdD8YGsg+hXLhudNWdRVSMeaCxBggKom5EDEGiHSTn6/mBtqa+dhrbN9UNn3OhznQpgAfHx8cPnwY6enpSE9Px/Xr1zFhwgRkZmYiPDwcQqEQ9evXL1ZGLBZj5MiROHPmDA4cOAB/f3+p25Z1Pap8JAViRJ+/W/j/xaXPA+s7sQ+c/L1x54edSHvy5qWdErEYl2asg9DAAG1WT1Z5fUm1rC2NcWx9N9Subl3uutVdLHD8125wqMIpHHTNo+dpMGv+N1qPPlQ0Pcq7vv/zHgQN/1TaOwmocmEMkD7r6O+Krcs6wEiGp7UHvu+JX+e2UUOtVIe5IDEGiEpiLkSMASLd1LCOHep4yP4Dc2Ua2q2GRvZL5dPnfJgDbUr08OFDSCQSeHl5wdy8+OOrU6ZMwe7duzFr1iyYm5vj2rVrRf8lJCTIvR5VTi+O3cCL4zdLXW7jVQ1NZ3+A+MDHePjboRLLtf0RWSrOs5oVrmzpjZmjfGFjZVxiuZWFESYP88G1rX1RV4t/wU9ERFSaYd1r4sJfPdG7vTsEUh5u8/KwxpqvWmLXyo4wMtL+SxPmgsQYICIiIn0gFAowSYHXplSUQxVTDOnKgbbKTF/zYSW+Kpru378PQPq0kceOHQMAfP/99/j++++LLfvrr78wbtw4udajyini8NUyl6c9icY/NUaUuc79n/fh/s/7lFkt0iBHOzOsnt0S301thgPnXuBFXAbEYgncnS3Qv5MHrCxKDsARERHpklaNnHBoXVeER6fjSEAkktNyYWZiiMbedujk7wqhUPbpJSs75oLEGCAiIiJ9MbafF+auu4XMbJHa9vnxwDowNeGQRmWmr/kwo1KJyhpoCw8Pl2kbsq6nzVIu78Gre2fgPn41nq38ADmRwRAam8HQpiqqT/oVpi61NV1FUqHXx99j0q9FnyWe/gsRP3+EWl/vg23L/pqrnApZmBthRK9amq4GERGRxnhWs8KUD/juFSLiNSEREZEuqGJtgu9nNse0ZWUPrChLdRcLfD2+5H33ykKclyNzXhP33w9IOvc3IBbDtFpdeEz/C4aWtgCAsO8HI/PRFeSnxKLRtpSiz6ly40CbEpU10EZvpF7bB7uOYwAAjl0nwLpZDwgEAsQfWYeIdR+j7pLzmq0gqdTbxx8Acl+GI/Hk77Co21KDtaLydPl3HswcbQGxGPmZObg+dxPSnkThvd9mwcbLDQU5echJTMPV//2O9PA4AICpvTXa/TwNVh7OKMjLx7Wvf8fLayEAgME31qMgNx8Pfj2IJ9vPlNifW+dmqD+pL44PWgBLN0dU69QEj7ecLFre7pcZcG1bH88PXMaN+ZvV8U+g94TGhmi+YCyqdWiMgtw8JAdHIGDqWlTr1ARN/zccAoEAAkMDPFh/AGG7LwAAHBrXRovvPoLQ2AgGpkZ4+u85PFh/AADQfe8iWLg54Om/53B39Z4S+7Ot647O/3yNPf6TYWxtjrpjuuL+uv1Fy/3mjYZnv9ZIvv8cZz9coZZ/A33HfoCI5PVi43Sk3TyIvPgI+Ky+A/OajaWulxZ4FNHb5gISMSQFIjgP+BL2ncbKtQ3SLtp6TVhaPvT6nFaQkwcAuPfzPoQfvFJmGQAYF7sHKSERCPxuK6LP3imxP+8Pu8OhUS1cmvkL7Hw9YVO7Gp4fuFy0vNuehbCr54G7q/cg+PcjavgX0C/+iz9C9W5+sHSvioOdv0Dyw3AA0nOi5AfPi5WtPawj2v40BWc/XF5i+iznNvXRdec8BC7aUnTcmBtrB/YBRCVNHuaDvafDcf5mrMr39eeidrC2rNwzQ8mS17wKOoWkM3/B+4frMDC3Quyu7xCzdQ6qT/ylcBvdJ6L6xPW4N9ZJA9+AFMWBNiU6e/aspqtQKYgyUhE8vT7EedkwdnCHJD8XuXHPYNdhNDwm/YqMR5fhOWMzBIZGsPF7M8+qRZ2WeLl/pQZrTsog6/EHCl9wGbHuY7hP+BlRf32u2YpTmS5MWIW8V1kAgOo9/NH2pyk40utrPP7nVNEFgfeH3dFm1SQcH7QAANBszigk3HqCUyOWwL5RLXTaNBt7WkyGRFRQuM2Jq4suVsti6V4VdUd3KXaDPWDKGjT+fCiMbczLKEnK1GzOKEAiwX9tpgFA4c0FAO3XTcfxQQuREhIBSzdHDAhYg4ij1yHKzEHrHz7FnR92IvJkIIxtLTEgYA0iT99CWmgUAODmgs1lztv9mrG1BRpMHVDsZkLg4n+Q+jgS1Xv4K/27knTsB4hIXlXaDIbzwNl4/HXbUteRSCR4vnoU6iw5D3PPhsh9GY6HU7xh23IgDMytZNoGVT66ek1YWj4ElH5OK6sMABzrP6/o/FoWO19PVO/hX+wm+4nBC9H2pynyfxGSScSRq3iwfj96Hviu2OfScqKDnb8oWm7p5og6IzsjPvBxiW0aWZmj2ZyRiD5TclCFuXHlxz6AqCShUIC/vm2HlqMO4WVStkxl4hKziv2vLL4Y2wCdW1ZTqI7qIjQ2lSmvyXp+F5b12sLA3AoAYNOsJx7P6VA00GbduLN6KkxKxYE2UjpDS1vYtR8BAzMruAybh7TbJxC3Zyk8p/2BV3dOwsK7NQSGRiXKxR9eA1v/fhqoMSmTPMf/5YEfYenTBha1m2m41lSetxN/YytzQCJBQW5+sV/dJdx+gvqT+hb97dm3Ff5rVXgxkXQ3DFkvk+Hcqh5iA+6X2L7A0AAtFn8I1/aNkJuWgZfXQ4qWtVoxARbVHND31A/IiE7E2XHLVfEVqQyGZibwGt4Ju5t+WvRZdkIqAEAiQdFAh5GVOXJS0iHOE721zKJwG+YmEOeJkJeSUep+Gn8+FDUHtkNeRlax2Gq1YgIMLU3R99QPEBeIcbj7V8r+iiQD9gNEJC8r3/ayrSgQoCAzFQBQkP0Khlb2EBiZyLcNqlR08ZqwrHxIWWUMLUzRZtUk2Pl6Iif5FVIfRwIofEK88exhMLYyR99TPyDh9hNc/Wpjhb4Ple/1U/jvkpYTFREI0HrVJFyf+yeaLxhbomzLpeNx76e98OjZotz9MzeuXNgHEJXOs5oVTm3ojk6fHENiSk656zcfflCu7X8yqC5WfNZc0eppTGl5jUWtZkg4th75KXEwtHVC0oVtEGenQ5SeDEMrOw3UlJSBA22kElnPg1C19/TC/x92C+Y1mwAAUq/vR5WWA0qsH7t7KXJjn8Jjccmpo0j7yHL8syMeIPXqXtRdelFj9ST5tF07DS6tfQEAp0YtLbG83sc98eJE4S8wTapYQmhoWOwiIiMyARbVHKVuu+6oLrCuVQ37O8wCAHTZMbdo2dXZG+H/7Tgc7PKlsr4KycnK0xl5qRloOH0gXNo3REFOHoJW7kLspfu4MPFHdPzzS4iycmFsY4Fz43+AOL9woO3yrF/QafNXaPLVBzC1s8bVrzaWemHp9n5TePRphUPdZiM/Ixvt1k0vWnZ19kb0Pb2SMVAJsB8gImUTCASo+cVOhC0bCANTC4gyUlDrf/9BaFS5pwWi8unaNWFZ+RBQeI4UCICEO09xa+k25Ca9KrfMuxp/NgQFeSLsazcDRlbm6HVkKRJvP0FO0isErdiJ6j38OS1gJVFaTuT7aR/E33yEpHvPSpTx6NUSErEEkScDyx1oY25c+bAPICpbgzp2CNjcC72nnkRYZLrStvu/8Q2xdLofBAKB0rapDmXlNVYNO8Kp/xd4urg3IDQoyosEBhyq0WZCTVeAdFP286CiC6nXF1USiQRpd07AummPYuvG7VuJ1Kv/ofb8YxCacPonXSDL8c8IDkBufDgeTPLC/U88kfn4GiLWT0DCsV81WXUqw6XpP2O330TcXr4DfnNHFVvWYPpAWHk649bSbQpt26VdA4TtPg9xvgjifBGe/MupeCsTgaEQlu5VkfokCoe7f4XrczfhvQ2zYOpgg0YzB+Pc+B+wp/kknBiyCO1+ng4Tu8LpDxpMHYBbS7dhj98k7O8wC02/Gg6bOm5S9+HSrgHCD15BfkbhVBOP/zmltu9HsmM/QESvPZrdCkGjHKT+l5cQKfN2JAUixO7+DrW+/g8N/ohAncVn8Pyn0RC9SlRh7UkddO2asKx86NiA+Tj4/uc42HU2cpPT0W7N1HLLSOPSrgGe7Ci8IZefnoVn+y6p7fuRfKTlRLZ13eHRqwXu/rS3xPpmjrZoNHMQbszbJNP2mRtXPuwDiMrnXcMWd3cPwPQR9Sq8rZpuVji/qSeWzWiudYNssuQ1VXtOhs+PgfBZeR2WDTrAyN4NBubWaq4pKRMH2kjp8pKiAQhgbF84b252+D2YeTRAVugNmLn5wMDMsmjdlwd+RErADnh9ewqGlraaqTAplazH37HHJDTaHIsGv4ejwe/hsKjbEh6TN8KxxyQN1p5kEbb7Apxb+8KkSuGx9J3YFx49W+D0yCUoyC58+XNuSgbEBQXF5p+3dHdEZnSCbDt5e/oV0rjM6ESICwrwbG8AACD5wXNkvIhHrcHvwcypStG0Okl3w5AVmwS7+jVgYmdV+A6B/784zHgRj4TbT1C1ubdsO2UMVGrsB4jIe8VVNN6aKPU/Y0d3mbeT9SwI+ckxRVNEWng1h7G9G7KelXx/EWkPXbwmLC0fquJTHZnRhQPDElEBgn8/DKcWPuWWkQnPhZXe2zmRUwsfWLpXxaArP2PwjfVwbOqFVj9MRN0xXWHfsCbMnKqg7+mVGHxjPTx6t0SjWYPR5H/DZdsRY0Hj2AcQycbC3Ahr/tcKl/7ujb4dqkMolG+QrFpVc3w7pSnu7RmA9/xcVFRL1ZE1r8lPjgUAiHOzELN9PpwHzlZTDUlVONBGSpf17E7RLxcBwMDCFvFH1yPl2j7Ytuhf9HleYhSiNn0OUWYqQud2RPDMxgj5ovx5yqlyk/X4k/YwtjaHmVOVor+rd2+O3JQM5KZkoN6nvVFjQBucHPZtiRc4Rxy6irpjugIA7BvVgrmzHeKuBkvdR8zFe6g1qD0EhgYQGhmi9rBORcvyMrJgZF05f9msL3KT0xF76QFcOzQCAFi6V4Vl9aoIP3wF5k5VYONVeBPNytMZVh5OeBUWg7zUTIiycuDcpj4AwMTOCg5NayP10Qup+4i5eA+efVrB0MIUAFBnVJeiZXkZ2TAwNYbQiNMoaAr7ASJSFWNHd+QnxyI7svBHGzmxT5EbFwbTanU1XDOqCF28JiwtH0p7Eg3jt85RNQa0RdKD5+WWkSbm4j14DesIADCyNEON/m2LluVlZMPIiudCTSsrJ3q85SR2Nf4Ee/wnY4//5ML3aH35Gx5vOYmoM7exs+HHRcsiDl/D3dV7cOf7HVL3w9y48mEfQCSfNk2ccGBtFzw7OgTzP22CTv4usLEqOTW4QAB417DByF618N/q9xF+fBjmfdoEFuYl3+Va2ZWV18Rsm4+EY78VrRu6sCseTvVF8IxGsPRpC8deU4uWPfm2F+59VDgbUPA0Xzye00Gt34MUw7MyKZ1t896wbd676G+fVYXvank41RdO350r+tzYwQ3NDvDXObpG1uP/rrpLzqu6aqQgI2tzdNj4OQxNjSERS5CT9ApnxiyDuYsd/BeOw6vwOHTfsxAAUJAnwpFeXwMAAr/binbrpmPg5Z9RkC9CwNS1kIgKpO4jdNtpVPF2x4ALPyE3LQMvr4fAoWEtAEBKcARSH0eh37kfkR7xEmfHLVfL96birs7egDY/Tobf3FGQiCW4OnsDMqMSceXL39Bhw2eQiCUQCAW4NufPol90nv/0R/jNHw2hgQGERgYI/v0IEm6FSt1+9Nk7cGzihb4nf3jrhe+FvwTNS81A2J4L6Hd2FfIzc/jCdw1gP0BEiohY/ynSAo8gPyUOTxZ2g4GZFepveAoACP/5Y9j694Vti77wmLIRz34YCoFACIlEjOoT1sHYsXq526DKS1evCaXlQ0JjQ3TbsggCAyEEAiA9Ih6Xpv1cZpmsuGSp27+7eg/arJqEAQFrkJP8CvE3QmBgXHijMTbgPupP7Iu+Z1YhIfAxrn61US3fWZ+1WjEBbu83g1lVW3TZMRf5Gdk4MXSR1JxI2ZgbV07sA4jk5+FqhUVTmgIAJBIJwqMzkPIqF/kiMcxMDVGjmiWsLHTj3bxl5TWuI78t9rfvWunvagQAr/lHlFovUg8OtJHa+K57qOkqkAbx+GuvzKhEHOn5tdRlm10Gl1ouJzENpz5YLNM+JKICXPv6D+nLCsQquXgl+WS8iMeJwQtLfP58/2U8339ZapnYgPs43E32C/+gVbsQtGpX0d93lv9b9P+vfPGbtCKkJuwHiEgRHpM3lLrMc9qb9m7Xfjjs2kufPq2sbZD20fZrgtLyoUNdv5S7jDSizBxcmLha6rL89Cwc7TtHpu2QclydLX0go7Sc6F3HBy0oddmlmb+UW565ceXDPoCoYgQCAWq4WaEGrDRdFSKl49SRRESkdjlJr9Bu3XR4jXhfofLtfpmBmoPaIS89W8k1I3XJTc1A029GotGs0gdpyuI3bzQaTBuA3NQMJdeM1IX9ABER6bvs+BR0/+9bVOvUpPyVpei2ZyGcWtVDflaukmtG6sbcWD+xDyAi0h18oo2IiNTucI//Vah8wJQ1SqoJacq58T9UqHzg4n8QuPgfJdWGNIH9ABER6budjT6pUHlZn5Khyo+5sX5iH0BEpDv4RBsRERERERERERERERGRAjjQRkRERERERP/H3n3HN1W2fxz/pnuzWqBl772XgKgoGwEFUQEVJwIOVB5RUUFcOHCCqDwORAVREFRwAIJMZW8KhQKlE2hpge6R/P7ojz5UOpKQJk3zeb9evqQ560rPlfu+m+uc+wAAAAAArGAwmUwmRweB8sdkkjLzHB2FZXzcJYPBNvsymUzKzXCuOa49fL1lsNUvQOSAq3DGXLeGNZ8Pk8mk9IzcMoqobPj5elj0Pl3l/EuukQOWnn+JHABK4oyfD1cfD9p6LOjqOeBs51+yLgec8TxbwxXGQhLjoZKQA0VzlfMvMR4GrOEq46HiOGMbaWlbl5aWpoCAAElSamqq/P39rTouhTYAAAAAAAAAAAC4FFsV2pg6EgAAAAAAAAAAALAChTYAAAAAAAAAAADAChTaAAAAAAAAAAAAACtQaAMAAAAAAAAAAACsQKENAAAAAAAAAAAAsAKFNgAAAAAAAAAAAMAKFNoAAAAAAAAAAAAAK1BoAwAAAAAAAAAAAKxAoQ0AAAAAAAAAAACwAoU2AAAAAAAAAAAAwAoU2gAAAAAAAAAAAAArUGgDAAAAAAAAAAAArEChDQAAAAAAAAAAALAChTYAAAAAAAAAAADAChTaAAAAAAAAAAAAACtQaAMAAAAAAAAAAACsQKENAAAAAAAAAAAAsAKFNgAAAAAAAAAAAMAKFNoAAAAAAAAAAAAAK1BoAwAAAAAAAAAAAKxAoQ0AAAAAAAAAAACwAoU2SJK+/vprderUSVWqVJGvr69atGihd999VyaTydGhAQAAAAAAAAAAlEsejg4A5UP16tX14osvqlmzZvL29tbGjRs1ceJEubu7a9KkSY4ODwAAAAAAAAAAoNwxmLhlCcW49dZbJUnLli1zcCQAAAAAAAAAAAC2k5aWpoCAAElSamqq/P39rdoPU0fiCiaTSdu2bdPmzZvVu3dvR4cDAAAAAAAAAABQLjF1JAqcP39etWrVUnZ2toxGo6ZPn67HH3/c0WEBAAAAAAAAAACUSxTaUCAwMFB79uxRenq6tmzZoueee05hYWF64IEHHB0aAAAAAAAAAABAucMz2lCsmTNn6oMPPlBCQoKjQwEAAAAAAAAAALAZntGGMmc0GpWZmenoMAAAAAAAAAAAAMolpo6EJGn69Onq1auXGjZsqJycHG3YsEFvvvmm7rvvPkeHBgAAAAAAAAAAUC5RaIMk6cKFCxo/frxiY2Pl4+Ojhg0baubMmRo/fryjQwMAAAAAAAAAACiXeEYbAAAAAAAAAAAAXArPaIND5ObmKT0zy9FhAAAAAAAAAAAAOByFNlhk+77DevPjhdq884CjQwEAAAAAAAAAAHCoClNoMxgMMhgMkqRffvlFvXr1UlBQkIKDg3XbbbcpMjKyYN0VK1bo+uuvV+XKlRUUFKRhw4bp6NGjxe47KytLH3zwgXr06KHKlSvLx8dHzZo109NPP63ExMQit9m6daueeeYZdenSRTVr1pS3t7dq1aql22+/Xdu3by/2WL/++qsGDRqk6tWry9PTU9WqVVOLFi10//33a/PmzVb+dmwjJzdX6/7eo6zsHLm7VZjUAQAAAAAAAAAAsEqFeUbbpSLbnDlz9OijjyosLEw1atTQ4cOHlZGRoVq1amn37t369ttv9eSTTyo0NFQ1a9YsWF6zZk3t27dPISEhhfZ7+vRpDRw4ULt375abm5vq1KmjoKAgRUREKCsrS3Xr1tW6devUsGHDQts1btxYkZGRqlq1qkJDQ+Xl5aVTp04pKSlJHh4e+u677zRixIhC28ydO1ePPPKIJKlatWqqV6+eMjIyFB0drdTUVD388MP65JNPyvC3WLK/dx3UT6s3q1Kgv54ed6c8PNwdFgsAAAAAAAAAAIC1eEZbMaZMmaKvvvpKsbGx2rVrl2JiYtSlSxfFxsbq/vvv19SpU/XVV18pLi5Ou3btUnR0tDp16qSEhAS98847hfZlMpl0xx13aPfu3Ro8eLAiIyN18uRJ7du3T4mJibr//vt16tQp3XXXXVfEMW3aNB09elRJSUk6cOCAdu3apTNnzujHH3+Uj4+PHnzwQaWmphasn5ubqxdeeEFSfsHt9OnT2rlzpw4dOqQLFy5o/fr16t+/f9n+8kpw6W42SerdvQNFNgAAAAAAAAAA4PIq3B1tjz32mD788MNCy37//XcNHDiw2OW//fabBg0apLZt22rv3r0Fr//6668aPHiwWrdure3bt8vHx6fQdnl5eeratat27dqlTZs2qWfPnmbF+uKLL+rVV1/VokWLdOedd0qSEhISFBoaqipVqujcuXOWvflSzP7qR11MzbiqfWTn5CgzK1sGg0GB/r6SDLYJDgAAAAAAAAAAwM6yszI1Y/KDkqS3P/1W/xk32qr9eNgyqPLgwQcfvOK1jh07mrX8+PHjhV5funSpJGns2LFXFNkkyd3dXUOHDtWuXbv0119/XVFoO3r0qL777jvt3btXSUlJysnJkSSdOXNGkrRnz56CQltISIh8fHyUkpKi1atXq2/fvma/59JcTM3QhdQ0m+zLZDLpQmq6TfYFAAAAAAAAAADgCNnZWQX/Tr2Km5UqXKGtUaNGV7x2+XPXilpevXp1SSo0laMk7du3T5L05Zdfavny5UUe7/Tp05Kk2NjYQq+/8847evbZZ5Wbm1tsrElJSQX/dnd316RJk/Tmm2+qX79+6tixo/r06aNrr71W119/vYKCgordT2kCA3yt3lbibjYAAAAAAAAAAFCxZGf97xFZAVdRR6lwU0cW93asWd6kSRMdO3bMrOOPHTtW8+fPlyRt3rxZ1157rdzd3TVjxgwNGzZM9evXl7+/vwwGg7744gs98MADhbaRJKPRqLlz5+qjjz7S4cOHC1739vbW6NGjNWvWLFWtWtWseGwlJzdXb3+6WBdS03RLv2t1TYeWdj0+AAAAAAAAAACAraWlpSkgIEBS/o1Y/v7+Vu3HzZZBVTSXfsE///yzTCZTif9dXjD7+uuvJUmTJ0/W888/r9atWysgIKCgmHf5nWyXc3Nz06OPPqrw8HBFR0dr4cKFuu++++Th4aEvv/xSd9xxR9m+4SLs2HdEF1LTVCnQX53bNLP78QEAAAAAAAAAAMqrCjd1pC21atVKe/bs0YEDBzRkyBCztztx4oQk6dprry1y+T///FPqPmrXrq1Ro0Zp1KhRmjx5stq0aaM1a9boxIkTatCggdmxSNLsr37URavmFzXpYlr+dlnZOXrr0++s2AcAAAAAAAAAAED5YjKZNP2dzyRJn//wux6/d4RV+6HQVoLbbrtN3377rebNm6fHHnus4A630vj65s/lmZCQcMWyo0ePasWKFRbF0apVK1WqVEkpKSmKi4uzuNB2MTVDF1LTLNrm3zKzspWZlX1V+wAAAAAAAAAAAChvUtMyrd6WQlsJhg0bpuuvv17r169Xv379NG/ePLVu3bpgudFo1NatW/XVV19pypQpatiwoSSpV69e+umnnzRz5kzdeOONatSokSTp4MGDGjFihNzcrpyx89ChQ3rvvff0wAMPqFu3bgXTTObl5Wn27NlKSUmRj4+PWrVqZfH7CLTqIX75d7OZTCb5eHvJy9PTin0AAAAAAAAAAACUb9bVUfIZTCaTyYaxOMylwlRxb8fa5YmJiRo2bJi2bNkiSapXr55q1qypjIwMRUZGKi0t/06x8PBwNW/eXJJ08eJFdezYUceOHZOnp6eaNWsmo9Go8PBwhYaGauLEiXrhhRc0duzYgme77dmzRx06dJAkBQYGqlGjRnJ3d9fJkycLnuk2d+5cTZgwwarfj6X+3nVQP63erEqB/np63J3y8HC3y3EBAAAAAAAAAACcxZW3VqGQ4OBgrV+/XvPnz1ffvn2VlpamHTt26MSJE2rcuLEmTZqk9evXq2nTpgXbBAYGatOmTbr//vtVpUoVHTlyRKmpqXr44Ye1a9cu1apV64rjNG3aVJ999pnuuOMOhYaG6vjx49q7d698fHw0cuRIbdy40W5FtpzcXK37e48kqXf3DhTZAAAAAAAAAAAAilBh7miD7SScPacvvv9VBoOBu9kAAAAAAAAAAACKQaENRcrJzVVS8gXVDKnq6FAAAAAAAAAAAADKJQptAAAAAAAAAAAAgBV4RhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAAGAFCm0AAAAAAAAAAACAFSi0AQAAAAAAAAAAAFag0AYAAAAAAAAAAABYgUIbAAAAAAAAAAAAYAUKbQAAAAAAAAAAAIAVKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAAGAFCm0AAAAAAAAAAACAFSi0AQAAAAAAAAAAAFag0AYAAAAAAAAAAABYgUIbAAAAAAAAAAAAYAUKbQAAAAAAAAAAAIAVKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAAGAFCm0AAAAAAAAAAACAFSi0AQAAAAAAAAAAAFag0AYAAAAAAAAAAABYgUIbAAAAAAAAAAAAYAUKbQAAAAAAAAAAAIAVKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAAGAFCm0AAAAAAAAAAACAFSi0AQAAAAAAAAAAAFag0AYAAAAAAAAAAABYgUIbAAAAAAAAAAAAYAUKbQAAAAAAAAAAAIAVKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAAGAFCm0AAAAAAAAAAACAFSi0AQAAAAAAAAAAAFag0AYAAAAAAAAAAABYgUIbAAAAAAAAAAAAYAUKbQAAAAAAAAAAAIAVKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAAGAFCm0AAAAAAAAAAACAFSi0AQAAAAAAAAAAAFag0AYAAAAAAAAAAABYgUIbAAAAAAAAAAAAYAUKbQAAAAAAAAAAAIAVKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAAGAFCm0AAAAAAAAAAACAFSi0AQAAAAAAAAAAAFag0AYAAAAAAAAAAABYgUIbAAAAAAAAAAAAYAUKbQAAAAAAAAAAAIAVKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABW8HB0AAAAAAAAAAAAAM5o+/btFq2fmJioH3/8UcOHD1dwcLBZ23Tp0sWa0GAn3NEGAAAAAAAAAABgB4mJifrss8+UmJjo6FBgIxTaAAAAAAAAAAAAACtQaAMAAAAAAAAAAACsQKENAAAAAAAAAAAAsAKFNgAAAAAAAAAAADsIDAzUgAEDFBgY6OhQYCMGk8lkcnQQAAAAAAAAAAAAzmb79u1lfowuXbqU+TFgPe5oAwAAAAAAAAAAsIOsrCxFR0crKyvL0aHARii0AQAAAAAAAAAA2MGJEyc0YsQInThxwtGhwEY8HB0AyieTyaT0jFxHh2ERP18PGQwGs9c3mUzKzXCNqwY8fL0t+t24CnKgeK7QBkjkQEnIgYqFfgAomqu0AZJ17YCz9QX0AyVjLFA0V8kBxgKwhskkZeY5OgrL+LhLpLrtOFsbaeu2js8AXJ3JZFJennN9CNzd3R0y5qHQhiKlZ+Qq4JoFjg7DIqn/3CN/P0+z18/NyNK3je4qw4jKjzGR38jTz8fRYZQ75EDxXKENkMiBkpADFQv9AFA0V2kDJOvaAWfrC+gHSsZYoGiukgOMBWCNzDyp16+OjsIyGwdJvnzbaTPO1kbauq3jMwBXl5eXp6VLlzo6DIuMGDFCHh72/xAwdSQAAAAAAAAAAABgBQptAAAAAAAAAAAAgBW4kRQAAAAAAAAAAMAOmjdvrm3btjk6DNgQd7QBAAAAAAAAAAAAVqDQBgAAAAAAAAAAYAdRUVG6//77FRUV5ehQYCMU2gAAAAAAAAAAAOwgIyNDBw4cUEZGhqNDgY1QaAPsrGb3Vro3foka336Do0OBg5ADIAdcG+cfAO0AyAHXxvkHAACoWDwcHQBQ3tTs3koDfpxR8LMxL085FzOUnnBOSfuO68TyTYpdt8dxAaLMkQMgB1wb5x8A7QDIAdfG+Qesd3H/X4p4oXeh19x8/OUd1lTVbrhb1W9+TAZ3vo5ExcVnAHBNfKqBYhz/caNi1u6SDAZ5+vuqUuMw1R3QVY1vv0Fx6/fqr3HvKPtCuqPDRBkiB0AOuDbOPwDaAZADro3zD1ivynWjVKnTIMlkUk5ygpL+WqCYL55SZky46j0yz9HhAWWOzwDgWii0AcVI2n9Cx5duLPTa9ulfqdOLd6n1+KG67uMntWbMaw6KDvZADoAccG2cfwC0AyAHXBvnH7CeX8OOqnbDXQU/hwyaqIMTmytx9WcKu+s1eVYKcWB0QNnjM4CShIaGasaMGQoNDXV0KLARCm2ABUxGo3bMWKCQDk1U+8YOqt61uc5sOyxJ8gz0U9vHh6ve4G7yDwtWTmq64jbs1643Fir11JmSd2wwqO3jtyrshvaq1DBUXpUDlHE2RTFrdmn3m4uUlZwqSfKpFqSRuz7VyRX/aOMjH1yxm26vP6jmY/tpabdHlBpz1ubvH+QAyAFXx/kHQDsAcsC1cf4B67j7+Mu/2TVK2bJEWQmRFBlc0L3xS2y2r/mht9lsX/bCZwCXq1SpkgYOHOjoMJySyWSSwWBwdBhXoNAGWOHowj9Vo1sL1e7TUWe2HZZnoJ8G/fKaAmoF6+h3a5VyJFq+1auo+b39dfOvM/XLgGeUFpNY7P7cvTzUesIwnVz5j6J/367cjCxVa9dITUbdqBpdm+uX/s/ImJOrzKQLil61Q/UGdtXWIL9C05S4e3uq4a3XKm7jfv6gsgNyAOSAa+P8A6AdADng2jj/gOWyEiIlSR4BVR0cCezNv1awtk2fr0PzVjg6FIfiM4BLkpOTtWbNGvXp00dVqlRxdDh2kZOTo+joaEVHRyszM1Mmk0k+Pj6qU6eO6tSpIy8vr1L3kZaWpvfee08jR45Us2bN7BC1+Si0AVY4Fx4lSQpqGCZJ6jDlDgXWra6VN09V8qGogvWOfb9Ow9a+qw7/uUObnvio2P3lZeVocfuHlJeZXej1szuOqOe7E1V3QBed/OVvSVLEN2tU/+buanBrLx356o+CdesNvkbelQN09Ns1NnufKB45AHLAtXH+AdAOgBxwbZx/oGTGrHTlXkiUyWRSbnKCzv7+iTKO75Zfk67yqdXU0eHBzur07azoVTscHYZd8RlASU6fPq23335bbdq0qdCFtvT0dG3cuFEbNmzQyZMnlZeXV+R67u7uqlevnq699lpdf/318vf3v2KdtLQ0vf7664qMjNSxY8f03HPPlatim5ujA3BmiYmJmjJliho3blxQfZ00aZLS0tL0wAMPyGAwaM6cOY4O06Fee7yTTPse0H23NCly+brPBylzx71q1di5GpScixmSJK8AX0lSw+G9dHpruNITzsm7amDBf7npWTq766jCrm9X6j4v/UFlcHOTV5CfvKsGKn7TAUlScMf//f7i1u/VxajTajL6xkLbNxl1ozLPXdCp37fb5D2iZOSA+WgHyIGKmAOcfwC0A+ariP2ARA5YoiLmAOcfKFn8ounae3eI9t1TXYcmtdXZ3+aqcvfhavz8T44ODQ4Q1LCmLp5McHQYdsVnAK4sPT1dX331lSZMmKAvv/xSkZGRxRbZJCkvL0/Hjx/XggULNHHiRH3xxRdKS0srWH55kU2SPD095efnV+bvwxLc0WalPXv2aODAgUpISJC/v79atmypuLg4ffjhh4qMjNS5c+ckSe3bt3dsoA720tzdGnJ9Xb37n25a9XesYk//b1qLJ+5qpRu6hOrZ97fr4LFkB0ZpOc/A/D+mslMz5FMtSD5Vg1TrhvYadfDLItc3ltCQXFJ/SHe1Gj9UVVvXl7uXZ6Fl3pUCCv0csfBPdXputKq2qq9zB08qoG511ezRSoc++1XGnFwr31XZ2X7grOYuDtfKDdFKuZgtP18PdWpRTRNub6FhvevJ09P5av7kgPloB/KRAxUrBzj/gGV2hydq7uJw/fzXKSVfyJavj7vaNa2qCbe30Ii+9eXl6e7oEC1GO2C+itgPSOSAJSpiDnD+YYmcHKN+Whelj78P187wJKVn5KpyoJcGX1dHE+9ooS6tK96zmoL7j1OVHiNlystRRtR+Jfz4prITY2Tw9ClY5+LBjTr28pXPKDLlZstkzFOnZaV/bpxFyoUsLfjlmD5fFqFjpy4oz2hSzWBfjR7YSA+PbKZ6YYGODrHMePj5KCc109Fh2B2fAbiqvXv3at68eUpKSir0eq1atdSwYUPVr19fgYH5bV5qaqpOnjyp48ePKyYmRpKUlZWlVatWafv27Ro3bpyaNm1aqMgWGBioadOmqU6dOvZ9Y6Wg0GaFxMREDRkyRAkJCZo8ebKmT59ekBxvvfWWnnnmGXl4eMhgMKht27YOjtaxcnKNGvvCBm39Zqg+f6mXBkzIn9aiaf1Keu2xzvpn3xm9PX+/g6O0XNUW9SRJFyLjpP9/+GLc+r3a/9Fyq/ZXd1A33TBvss7uOqptL36ptLgk5WVly+Dupn6LXpTBrfADHo8tWqsO/7ldTUbdqK0vfKEmo26Uwc2t3E0Rcv5itu6csk6/b4654vW12+K1dlu86ob666cP+qp982oOitI65ID5aAfMQw44Vw5w/gHzpKbn6K7n/tJP604Vej0n1aiNu05r467TCnvXT8ve66OubZzrS0baAfNVxH5AIgcsURFzgPMPc+05nKRhk1brVHxaodfPJmdq/k9HNf+noxrQs7a+e6u3KgWW/nwaZ+Ed2kRB7ftIkip1GqiAFtfqyHPX6tTH49Xw6e8kSYGteqnD4tRC22Unxenw5M4KGfyo3WMuKwtXRuqhGZuUnlm4CB4Vl6qZn+/Vm1/u0+R7WuuNJ7rI7V+f9Yog7Pq2il2/19Fh2B2fAbgak8mkJUuWaOnSpQWveXt7q1evXurXr5/q1q1b4vYxMTFavXq11q9fr8zMTCUnJ+vNN99U5cqVlZKSIqn8FtkkCm1WefzxxxUTE6NHH31Us2bNKrRsypQpWrhwofbu3asGDRooKCjIQVGWH7vDkzTz872aNr6DHhrRTJ8vi9CC166TwSCNfWGDjEaTo0O0WJPRN0mSYtbsVGbSBWWlpMoz0E/xG63747DRbdcpNyNLv982XXkZ/5uTv1LjsCLXzziboujVO9VweC/tfO1bNb69t87ujFBKREyR6ztCanqO+oz7TTsOFv/Ab0k6FZ+m6+5bqQ1fDnaqYhs5YBnagdKRA86VA5x/oHQZmbkaOOEPbdp9usT14s6kq/cDv2rtZwPVrW11O0V39WgHLFPR+gGJHLBURcsBzj/Msedwkq67b6UupuWUuN7vm2PUZ9xv+uvzQfL38yxxXWcV0KKHqt5wt86tW6DUmx9XQIseV6xjzMnS8TeGK6DltQodOdUBUdreVz8d1b0vbihxHaPRpLfn79f51Gx98mJPGQwVq9hWvUtz7Xz1m0KvdXxutNo+PlybnpyrY9+tvWKbAUtnKKRTU/3Sf4pSjkTbK9Qy5aqfARTNz89P3bp1K3fTH1rLZDJp4cKF+uWXXwpea926tcaNG6fq1c37G6927dq67777NGTIEP33v//V3r35BXpnKLJJPKPNYuHh4Vq8eLGCg4M1c+bMItfp1KmTJKldu//NwX6pMNe1a1d5e3uX2mkuW7ZMPXr0kL+/vypVqqSePXvq4MGDtnsjdvbKvN3aczhJsyZ31eznuqtbm+p6fvZORZw87+jQLGJwc1PnafeoRrcWil6zU2e2H5FMJh3/caNCOjZRvcHXFLmdT7WSC66mPGP+/g2FP5Jtn7it2G0ivl0j7yqB6v7WOPmHVVPEwj8tfDdl6+l3tpVaZLvkYlqObn1ijXJzjWUc1dUjB6xHO0AOVIQc4PwD5ntxzs5Si2yXpGfm6tYn/1RWdvmfHod2wHoVoR+QyIGrURFygPMPc+XmGnXrE2tKLbJdsuNgov7zzrYyjsqxQu94UXJzV9zCaUUuPzV3vIw5mao/ab59AysjR6PO68GXNpq9/rwlR7To1+NlGJEDGAySQTIZC3/fs2fW90oOj1LXl8bKL7RqoWUtx92smj1aac+sxRWmyHaJq30GULy6detq9uzZpd7l5Sx+++23QkW2MWPG6Pnnnze7yHa54OBgPfbYYwoODi70es+ePcttkU3ijjaLLVq0SEajUWPGjFFAQECR6/j65s/Vfnmh7dixY1q6dKm6dOkiLy8vbd68udhjfPjhh5o8ebKefPJJvfLKK8rKytLWrVuVkZFh2zdjR7m5Jo19YYO2LxqqiXe00MZdCXr/mwOODqtE1do0UMMRvSRJnv6+qtQ4THUHdFVAneqK/WuPNkz8oGDdXW8sUvUuzXXDvKd08ue/dXZXhPKycxVQO0S1b+qgpH3HtemJj4o9VtSKf1T/5u7qv2S6In9YLzdPD9Ud0EUevt7FbhO7bo9So8+o0W3XKyc1QyeWF59T9pZ8IUtf/XLUom1OxqVq5cZoDetdr4yishw5YFu0A+SAs+UA5x+wXlp6jj5bFmHRNvFn07Vk9QmNGdy4jKKyHO2AbTlbPyCRA7bmbDnA+cfVWLHhlE7GpZa+4mUW/HJMMyd1VuWg4s+7M/MJbayqve7UufXf6uLBjQps1atg2ZlfPtT5HSvUfNZ2uXlXjDs8PvnhsHLzLLtbd/aigxo9uFEZRWR/IR0aK3H3sSteN+bkauOkObp55Uz1fHeiVo96VZIU1ChMHZ8dpbM7I3Rg7s/2DrfMudpnAMXLy8tTRkaGfH195e7ufM+rvlxMTIwWLlxY8PODDz6oPn36WL2/tLQ0zZw5U4mJhW/gWL16tW644QbVr1/f6n2XJQptFlq7Nv925t69exe7zqUH911eaLvuuusUHx8vSXrppZeKLbRFRkbq6aef1nvvvadHH/3fXLyDBg2yOFaTyaT09PyHTPv5+Tn81vPzqdnKys6Tl6e7ft0YLVM5nxmk4fBeaji8l4x5ecpNy1Ra/Dkl/H1IJ56Zp9h1ewqtm3MxXb8Oe16txg9VgyHdVad/Z5nyjEqLT9KZbYcV8W3JVxae+GmzPAJ81WrcYHWZdo+yzqcpevUO7XztW40On1/0RiaTIhatVccpd+rEz1uUm15+Hiy74Oejysi0/Ir0uYvDy1WhjRywPdqB4pED5Q/nH7Ded78f1/mL2aWv+C9zF4eXq0Ib7YDtOVM/IJEDZcGZcoDzj6vx8feHLd4mPTNXX/18VJPual0GEZUPNUc+r3MbFylu4TQ1e22dJOnivnWKWfCMmkz7Td416js2QBvJzMrVl8stu+hIkv7Zd1a7wxPVoUVw6SuXMzWuaaEz248U3KErSbV6d9DBeSuKXP/c/hPaN3uZ2j81Uk3v6qOjC9eq14ePSZI2TppzxV1wFYWrfAZQsqNHj+qee+7RggUL1Lx5c0eHY7W8vDx9/PHHys3Nfwbl4MGDr7rI9vrrrysyMlJS/nSRXbt21Z9//llwrNdee00eHrYra9mqhmIwmcrzsLb8qVOnjmJiYrR79261b9/+iuW5ubkKDQ1VYmKiIiMj1bBhwyvWeemllzRjxgwV9at//vnn9cEHHygpKUne3ld3BVNaWlrBXXehoaFyczN/plCjPBVf9YWrOv6/rf1soHq0r6HI6AuqFxagtiOW6XjMRZvtP/Tcq3KTeVMySJKnyU3TjV1tdnxHaD1xmDq/eLdW3jxVZ3cWP4Cb4bZNOQb7DVCS/W9RuncHi7dzM6YqNOXtMoioaORA8cqiDZDKth2wtA2QyIGSkAPOobz2A0CK3yCl+XSzeDuDKVthya+VQURFc/Y2QCrbdsDZ/iZwxX5AYixwOVfMAcYC5Vd85adldCt6JqSS+GXtVpW05bYPyAoGL1/VeN+y2WoslXX6pA7/p4tC75yu6oMfLX2DUpx+oolM2Y6fESrHrZrOVH7cqm0rpy6Tf/Ye2wZkJXPbyPpDe6jnOxP059g3lLDlf4++6TLjXm2fPr/Y7Qwe7hry2xsKqFdDx5duUPN7B2jbS/N16NOii3OlsXVbx2cA5rrttuKnei7KmTNntGjRIo0aNcrs6RWXLFliTWhXxcvLq9jHZ0nS5s2bNXv2bElSWFiY3njjDXl5eVl1rKKKbNOmTVNoaKimTp2qU6dOSZIefvjhEm+Ceu6555Sdbf5Fn0ajseAGqfbt22v37t1Wxc8dbRZKS0uTpGKncVy8eLESExMVGBioBg0aWLz/LVu2qFmzZvrmm2/06quvKjo6Wk2aNNG0adM0atQoq+O+lCxmM3hJVUtfzVyPjW6p3l3DNPXDHfppXZR2Lb5FX7zcSzfc/6vNjhEfFyeZzP8QeRncpRo2O7zdGdzd1PTuvjp3KKrEP6gkKS4+TtkmOz7zpE6uZEWd2GhyV2xsrO3jKQY5UNLObdsGSGXfDljaBkjkQMk7JwfKu3LdDwC1ciQfyzczyYOxgAXKvB1wsr8JXK0fkBgL/Jur5QBjgXKuknVfuaVn5Cjdjn1hSdy8/cr042HMSlfkzFtUqetQmxQYJCkuLk7GrHSb7Ouq+LhJla3bNOV8ulLOlY8cMLeNPPnzFgU1qKk6/bsUFNr8awUrLeZsiduZcvPyp5D87Q01v3eATm8N16F5K62O19ZtHZ8BmOtSzcBcl2oLGRkZZm9rz7+TLintRqBVq1YV/Pu+++6zeZHt0jPZHnjgAU2fPl1S/hSSJRXa4uLilJWVZVUcp0+b94zxolBos1DNmjWVnJysXbt2qXv37oWWxcfH6+mnn5YktW3b1qrbDOPj4xUbG6vnnntOb775purUqaPPP/9co0ePVkhIiNW3Xlp1R5tVR7pS47pBmjmps7btP6s3v9gno9Gklz7epZmTuuix0S01e+EhmxwnNCzM4jva5IQX9AXUqa6Qzk1Vt38XBdWvqfXj3yt1m7DQMPve0eYjWdOluytLNWvVsnk8xSEHimfLNkCyTztgaRsgkQMlIQfKL2foB4Dzvm6y7Kk0+QymTIUxFiiVvdoBZ/ubwFX6AYmxQHFcJQcYCziHBGUpz4qrTvx8DKpix76wJAYv3zLdf/KWpco4sVeZsRFK3rT4iuWt5hySV0hdi/YZFhZWLu7myXUL0mlJMpkkC78frFLJW36+5SMHLGkjo37bppvmP1NwB1udvp0VvWpHqdvlXEiXMTtX7l6eivlzl65mTmFbt3V8BmAuf39/i9a/VFzz9fU1e9taDugbSiqcnTp1SkeOHJEk1a5dW61bWzftcWlFNklq2rSpGjRooBMnTuj48eOKjIxUo0ZFP88yLCzM6jvaatSwvrROoc1Cffr0UXh4uN5880317dtXTZs2lSRt375dd999d8FD+oqaVtIcRqNRqamp+vrrr3XLLbdIkm666SYdOnRIr7zyitWFtqNHj1r0gU9Lz1HANQusOtblDAZp/ivXyd3NoLEvrJfRmN9ZvvXlfg2/qb5mTuqslRuibTJVyNGICPn7eZq9fk56pr5tdNdVH9feanZvqWs/eFSZSee1553vdeKn0h94HXE0Qp5+VlxWbqWVG07p5kdXW7zd+DHdNWfq5DKIqGjkQPFs1QZI9msHLG0DJHKgJORA+eUM/QCwfke8VXeo3HNLO81/NaYMIiqaM7YBkv3aAWf7m8BV+gGJsUBxXCUHGAs4h0de26K5i8Mt3u6Hz5/XoF6flEFElsvIlXrZbiKiK1Trfbeq9b7bpvuMiDgq33LwbafJZFK725Zp/9Fki7bz9nJXxM5lCq5SPj6vlrSR5yNiJJNUuWltpUTEKLBBTV2cX/rdIT3ff0Runh5KiYhW2ydG6OTPW3Qxyrq7Smzd1vEZgLm2b99u0fqHDx/WokWLNHDgQLOf0fb+++9bEdnVyc3N1dKlS4tcdvkUi3369LHqpiNzimySZDAY1LdvX82bN0+StGvXrmILbRERERY9w+3yx29t2rTJ4vdwifm3OEGSNGXKFFWrVk3R0dFq1aqV2rRpoyZNmqhr165q2LChbrzxRklSu3btrNp/1ar5c3NcXlAzGAzq06ePDhw4cPVvwM4mj22jnh1qaNrcXTp84nzB60ajSfe+uEEe7m764uVeDozQ+Rz7/i/ND71N37V+QHtmfe/ocIo0oGdt1Q+zfC76Cbe3KINoKh5nyIHL0Q7YHjng2pzt/MM1Xdepplo2qmzxdhPvYCxgDmdrB+gHbI8ccG3Odv5d1YTbzfvi9HINagWqf4/ycScTro7BYLBqXHN7vwblpshmjejVO1Snfxd5+PkoJ7X0u6paPDBIoT1ba8+7P+ivh96Rm7u7er430Q6RAo7VuHFj/fHHH2rcuLGjQ7Ha8ePHC/5tzd1s5hbZLmnVqlXBv0+cOGHx8coahTYL1a5dWxs3btTgwYPl4+OjkydPqmrVqvr000+1cuVKRUTkz4tubaHt8oT5t8zMTKv26SjNG1TSK4901N97z+idr64sEh6KTNFLH+/S9Z1D9djolg6IEGXF3d1N0yd0sGib2/rWV6vGVcooIjgK7QDIAcA1GQwGTR9v2Vhg4LW11aV1cBlFBEehHwA5AFfVuklV3da3vkXbTBvfXu7ufFVXUYwZ3EiN6gSavb6vt7uevrdNGUZU9qJX7VCdfp0VdkM7xW3YV+K6gQ1qquPU0Tq7+6gOzFmulIgY7Xnne9Xs3kotHhhkp4gBx/Dw8FCVKlUsuvOqvLlUaPP29lZYWJhF21paZJOk6tWrF8zYd3mRr7xw3jPpQC1atNCKFSuueD01NVUnT56Um5ub1XOSDhs2TF988YVWrVql4cOHS8qfTnL16tXq0qXLVcVtb4dPnJdvl69KXOeNz/fpjc9L7njhnO4d1lQnYlL18qe7S133uk41Nf+V6+wQFeyNdgDkAOC6bu/fUCdiL+rZ90t/NkfX1iH67q3eVk03gvKNfgDkAFzZ/Feu0+mkDG3cVfo0eNMe7qB7hzW1Q1Swl0B/L/02t796P/CrYs+U/CR7by83fT/rRrVpWtVO0ZWN01vDFdQwVPUGdtWmSR8Vv6LBoGvff1Rubm7aNGmOTMb856od+Ogn1RvUTR2njlbMmp1WTyEJlHcxMTF677339OSTT6p27dqODscqSUlJkqTQ0FC5uZl/kYg1RTYp/2LOsLAwHT16VCkpKcrLy5O7u7v1b8DGKLTZ0MGDB2UymdS0aVP5+fldsXzJkiWSpEOHDhX6uX79+urcubMkaciQIerVq5fGjRunpKQk1a1bV5999pkOHjyo1astf+YV4EgzHumoRnUC9cq8PTp26sIVyysFeunBW5vq1cc6yceb5ggAgIrmmfvbqX5YoF76uPB0cZcE+Hnovlua6o1JXeTHwyQAABWMv5+nVn06QC/M3qn//nhEF1Jzrlincd0gvTiuve4Z2sQBEaKsNalXSf98M1RPzdqqZX+eVG6e6Yp1enWsoTef7KLu7Wo4IELbMuUZFfvX3vx//3/xrCitxg9Rja7NteOVr3X+aOz/tjcatWnSHA1Z9bZ6vjdRvw+fXuYxA46QmpqqjRs36qGHHnJ0KFYxGo3q27evsrOzFRxs2awkH3/8scVFtks6deqk2rVry9PTUybTle2pI/HXrA3t379fUvHTRo4cObLIn8eOHav58+dLyq/M/vzzz3rmmWc0depUXbhwQe3atdOvv/5a8Pw3wJncM7SJ7rq5sdb8E6sVG6L12Y9HlJGZp8qBXopZfafFDysHAADO5Y4BDXV7/wb6a3u8flp3SvOWHlZGZp4qBXgqevWdCvT3cnSIAACUGR9vD836TzfNmNhRC3+N1JNvb1VaRq78fT3043s3qc81teTmxh3dFVntmv76ftaNijuTpq9+PqZX5+1WemaeAvw8tGXBEKe/i+3fTv22TSrhC/BKTWqp45Q7dWbHER385Jcrll+aQrLT1DFq8cAghX/+a1mGC8AKbm5uuu+++6zadvTo0Tp27Jjy8vIsKrJJ0i233GLVMe2BQpsNlVZoM7fKWrlyZX366af69NNPbRYb4Ehubgb161Fb/XrU1o9rTio2M13+vh4U2QAAcBEGg0G9u4apd9cwLVl9QrGZ6Qrw86TIBgBwGf5+nnrotuaa8clupWXkqnKgl/r1cM7pwooTMb2fcpMTJDc3ufsGqs5DH8qvYenPbE3evEQX9v2pehM+Lngtcc2Xipp9vxo9t0yVr7mlDKO2n7Dq/nruwXb66LtDSs9MV6UArwpXZJOkqBV/l7j8/NFYfd1gdInr7J+9TPtnL7NlWE4hefMSnd+xUrlpycqMPiQ3L195VKquuhM+lk9oY0eHB9hEWFiYpk2bptzcXIuKbOUdT1i1odIKbXBO7t6euvHLKbp104caumaW+n33ogLr1yy0Ts2erXVPzGK1fGjw/7bz9dJ1c5/Q8C2zdeumD1Vv8DUFywYsnaERWz9SuydvK/KYlZvV0W3b5kqSvIL81ObRWwot7/zi3bptx8e68cspNnqXKAk5gMs1vqO37o1foroD8p+bWa1dIw386VUNXTNLQ1e/rZo9Cz+js9nY/rplw/satvYdDV0zS+7e+QVmcsC50A4AqHVjBw1Z9ZaGrn5bw9a9q0Yjry9Y1n7y7bp104catvYd9V/y0hXbFtU+0AaUf11fuV+3bZure+OXqGqr+pIk7yoBGrr67YL/bt30oe6JXiyvygGSpMErZxYsG7buXd0bv0RVWtQr2CfjAudGOwCUrOHT36vlh/vU8v09qj70KZ384F6ztkv5Z5kqd7ul4Oes0yeVuOq/8m92TfEbARVQyj/5heWQfuPUau4Rtfxgryp3G6aoOQ86OjTApsLCwlS3bl1Hh2FT3NFmQ2vXrnV0CCgjR75erdi1uyVJze8boJ7vTNDvI/LnifYM9FOn58co9s/dhbZpPX6ojNk5+rHHYwqoU12Df52phC0HlJWcKknaPn2+Tv2+vdRjewX5q82jt2r/nOUFr+145WulHIlW3YFdbfQOURpyAJIUUDtETcf00ZkdRwpeu/GLKdr0xBzFb9yvoIah6vf9NC27dpLyMrNVp38XNRzeSysHT1XOxXR5VwuSMSevYFtywLnQDgCu7bo5j+v3ES8pOTxKAbVDdOvGDxT161Y1GXWjqrSsp596PyVjTq58QyoX2q649kGiDSjvolb+rQNzl2vQT68WvJaVnKqf+z5d8HOr8UNVs3tLZafkt+srBz9XsKze4GvUfvJIJYdHSRLjggqAdgAomUdA5YJ/56Wflwz5U2Lmpqbo0OOtZczOkFdwHZlyspSVcFxVb7hb9SZ8rNTDm1V/0nxJ+c/oiprzoOqMm62YLyc74F0AZaOkz0H9xz6TKTen4LNg8PjfDFD+Ta/R6eWzHBg5ykJISIgmTZqkkJAQR4cCG6HQBpQiLyun4ItVSTq766haTxha8PM1rz+gfe8vVb1B3QptV39YT215Kv/qw9ToM0rYclB1B3bT0YV/Fnmc9pNvV8PhvZSdml7oeN3fGiePAB8NXf22jHlGrRjwjC3fHsxADkCSZDCoxzsTtPWFz9Vl+lhJknfVQPlUC1L8xvw7mi8cj1f2+XTVurGDTv26Va0nDtXed39QzsV0SVJW0oUSD0EOlF+0AwBMJsmrkp+k/C/NM5Mvypidq9YThumPkS/JmJMrSco4m1Jou+Lah6LQBpQvp/8JL3WdJqNv1K7Xvy1m2U06uuh/F2MyLnB+tANA6U68d48u7l8nSWoyLf/ZWh4BlVX1utFy9w1U6B0v6vyuP5Sw5HXVf+wzXdi9Sv7NexQUFk7/9K4CWvSUf+NODnsPQFko6XMgSRf3ryv0WbjkzIoPVLnrMEeEjDJUrVo1jRkzxtFhwIYotAEWavngIJ36I/+Kw3qDr5HJaFL0qh1X/NEUUCtYqTFnC35OjT4j/1rBRe6z9k0dVW9Id/3Sf4pyUjPUa87jBcv+njJPQ9fMKnTlLByLHHBNrR4eojPbDytp3/GC17LOXVTGmWTVH9JdJ3/5W9XaNVKlRmEKqJN/RVLlJrVVrV0jtXtqpNy9PRX5w/piH+RMDjgX2gHA9awf/656f/60ctOz5FXJX+seeFvu3p7yDamkOv27qP7N3SVJBz/9RSd/3iKp5Pbh32gDnE9I52byruSv6NU7r1jmF1ZNNbu31MbHPix4jXGB86MdAErX4MkFkqSktV8pZsEzBcW29BN7VP3m/JxOj9xZ8Oy2lK3LVeWaWyVJGVEHlPL3UjV7fYMDIgeuzuEp3ZUZd7TIZS3f2y2vkDrFfg6kwp+FS+J/eF1Z8cdU75WiL9SE87pw4YK2bdumrl27KigoyNHhwAYotAEWaPP4cAXWr6ktt8+Qb0hltXtiRMG0YVcjtFcbnfx5i3JSMyTlT09Wo2vzq94vbI8ccE2Vm9VRvcHd9Nut065Y9ue9b6rzC3epzWPDlRIRrdPbDsuUa5QkGTzcFVi3un6/dZq8KvtrwI8v62LUacWsufILOXLAedAOAK7H4O6mdk/cpnUPvK3T/4SrWrtGuumrZ/XzTZPl5ukhDx8vrRz8nAJqh2jQL6/p/LFYZZ49b1H7QBvgfJqMulHHflgvU57ximWN7+it6NU7lXXuYsFrjAucG+0AYJlqN45V1MfjlXshSR5B1ZRxYk9BUSE9cqcqdx0qk8mk87v/UK2xb0mSUg9tVNaZkzowoYkkKSc5QVHR45STHK+QgRMc9l4AczR/6+9S1ynqcyDpis+CJCUsm6WUv39Uk5fXyM3br2yChsPExcVp6tSpWrBgAYW2CoJCG2CmVuOHqt6gblp1+wzlZWSrWo/W8q1RRUPX5M+T7F01UHX6dZZ3tSDtfmORUmMTFVA7RBlnUiRJAXWqK279XvMOZjKV0bvA1SAHXFeNbi0UUKe6RmyZLUnyDams7m+Pl2/1KjqyYJVWj36tYN1bNryvlCPRkqS02EQdX7ZJJqNRWecuKvbPXQrp1KTIL9SuQA6US7QDgGuq2rqBfGtUKZhKMGlvpNLjk1SlVX3lpGYocmn+lfepMWd1ZvsRBbdvrIzTySW2D6WiDSjXPPx81GBoD60YWPT0fU3u6K2/n51X6DXGBc6NdgAoWW5qioxZ6fKqFiZJSvlnuTwCq8k9sKqyk2IlGeRVrZYkKePkPoWOfF7pEdvkW7uF3H0DJEkhAycUKqgdef4G1RjyhCpfc4u93w5gc8V9DiRd8Vk4/dO7St64SE1eXlPo2YcAyi83RwcAOIOWD9+sBrf21Ko7Xlb2hfxnKsT8uUuL2z6oJV0naknXiYpa8Y/2vrek4A+mqF/+VrN7+knK/2K1Zo9WOvX7tiL3H7dhn+oP6S4Pfx9JUtO7+hYsy07NkLuPl9w8qYs7Ejng2o4sWKXv2z9UcK7P7jqqv5/+REcWrJJv9coF6zUZ00e56VmK35T/zLbjyzapVu/2kiR3Hy/V7NFK5w5GFXkMcqD8ox0AXFdabKL8alRRpSb5X4wE1q+pwHo1dCEyTseXb1Kt3vlXJntVDlBwh8ZKPhRVavvwb7QBzqXBsB46d+ikzh+Lu2JZ6LVtZPBwU9z6fYVeZ1zg3GgHgJLlpZ9X5MxbdPDxNjo0qZ3OrJyjxi+skMFgUPrx3YWmyHP3r6wzv85V8j/LVLnbLY4LGrCj4j4Hkgp9FrITYxTzxWTlpqUo4oXeOvREe4X/p/RnfAJwLEZoQCn8Qquq60v36sLJBA1Y8pIkKS87VysHP1fidgfm/qSe703U8L/nyJRn1NapnxWaOuZysWt3K6RDEw1d9fZlD71uIUnKTklV5JL1Grb2HeWkZfLQawcgB1CSpnf1VcPhvWQwGJRyNEZr7//fVA+HPv1F3d96WLesf08mkxS18h9FrSh6OglyoHyjHQBcW2bieW15+hPd8OlTMhlNMrgZ9M/znystNlG7Xv9WPd97RM3v7S9J2j9nuRL3HLP4GLQB5U/3t8ap9k2d5Fu9svouekE5qRn6scdjkqQmo25SxLdrityuyagbdey7dVfcjcS4wLnRDgAl865eTy1mFX1BWeUuN6tyl5sLfm7xTv6zjg8+2ko1Xl1X7D6bvfaXTWMEHKm4z4Eknd/+S8FnwSu4tjr9xB3NgLOh0AaUIj3+nOaH3lbqepue+KjQz7kZWVo//j2zj7Pnne+1553vC37e/eZ3Bf/e8p9PzN4PbI8cwL9d/pyNve/+oL3v/lDkenlZOdo0aY7Z+yUHyi/aAQAnlm/WieWbr3g9KzlVa+99s9Tt/90+FIU2oHz5e8q8Ypf9OvT5YpdteOSDIl9nXOD8aAcA22o156CjQwDKBT4Lrsfb21vNmjWTt7e3o0OBjTB1JOAAWSmp6jh1jNo9WfqXtkXp/OLdavPYrcpKSbVxZLAXcgDkAMgBwLXRBuBy5INr4rwDAOCaGjRooK+//loNGjRwdCiwEe5oAxxg3QNvX9X2O175Wjte+dpG0cARyAGQAyAHANdGG4DLkQ+uifMOAABQMXBHGwAAAAAAAAAAgB0cOXJEPXv21JEjRxwdCmyEQhsAAAAAAAAAAIAdmEwm5eTkyGQyOToU2AhTR6JIfr4eSv3nHkeHYRE/X8vS2cPXW2MivymjaMoXD18erFkUcqB4rtAGSORASciBioV+ACiaq7QBknXtgLP1BfQDJWMsUDRXyQHGArCGj7u0cZCjo7CMj7ujI6hYnK2NtHVbx2cArs7d3V0jRoyw2f7e/nSxLqalKdDfX08/fMcVP9uCu7tjPgQU2lAkg8Egfz9PR4dRpgwGgzz9fBwdBhyIHCieK7QBEjlQEnIAgCugDSiZK/QF5EDxXOH8S+QAUBKDQbKifo0KxNXbSD4DcHUGg0EeHrb7EJgkGU35//fw8LjiZ2fG1JEAAAAAAAAAAACAFZy7TAgAAAAAAAAAAOAk6tevr0WLFqlWrVqODgU2QqENAAAAAAAAAADADnx8fNSoUSNHhwEbYupIAAAAAAAAAAAAO4iPj9err76q+Ph4R4cCG6HQBgAAAAAAAAAAYAfnz5/Xzz//rPPnzzs6FNgIhTYAAAAAAAAAAADAChTaAAAAAAAAAAAAACtQaAMAAAAAAAAAAACsQKENAAAAAAAAAADADqpWraqxY8eqatWqjg4FNkKhDQAAAAAAAAAAwA7c3Nzk6ekpNzfKMxUFZxIAAAAAAAAAAMAOEhMT9dlnnykxMdHRocBGKLQBAAAAAAAAAAAAVqDQBgAAAAAAAAAAAFiBQhsAAAAAAAAAAABgBQptAAAAAAAAAAAAdhAYGKgBAwYoMDDQ0aHARjwcHQAAAAAAAAAAAIArqFWrll5++WVHhwEb4o42AAAAAAAAAAAAO8jKylJ0dLSysrIcHQpshEIbAAAAAAAAAACAHZw4cUIjRozQiRMnHB0KbISpIwGgCCaTlJnn6Cgs4+MuGQyOjgIAKgb6AZADkslkUm6G81xl6+HrLYMNfwHkAABXRzsIZ8sBVx8LSbYdDznb+ZdoA+A4FNoAoAiZeVKvXx0dhWU2DpJ8adUBwCboB0AOSLkZWfq20V2222EZGxP5jTz9fGy2P3IAgKujHYSz5YCrj4Uk246HnO38S7QBcBymjgQAAAAAAAAAAACsQKENAAAAAAAAAAAAsAI3UgIAAAAAAAAAANhB8+bNtW3bNkeHARvijjYAAAAAAAAAAADACtzRBqDM5eYadfhEiqLiU5WRmStJysrJU1p6jvz9PB0cHQCgrJlMJsWcTlP48RSlZeTKYJAC/bzUpkkVVa/m6+jwYAd5eUYdOXleJ+MuGwtk5yk1PUcBjAUAl5ByIUv7Is4p+WK28vJM8vV2V5N6ldSoTqAMBoOjw0MZM5lMijuTrkPHU5SaniODQQrw81TrxlVUM9jP0eHZRVp6jvYdPaeMrPx+MDMrTweOnlPzBpXl4cF18K4gMTlT+/+VA1FxF1U3NIB2EHAxUVFRmjFjhqZPn6569eo5OhzYAIU2AGXiTFKGvlgeoZ//OqU9R5KUkZlXaHlicpaCenyt5g0q6YbOoRo/srnaNK3qoGgBALaWlZ2npatP6ttfj2n7gUSdTc4scr3aNfx1TdsQ3TusiQb0rC13d75oqiiSUjL15fKjWr4uSnsOJyktI7fQ8sSULAV1X6Bm9Svpuk419fBtzdWxZbCDogVgayaTSZt3n9Z/lx7R5j2nFRl9scj1KgV6qWPzahrZr4HuurmRAv297BwpykpOjlHL1p7UNysite3AWZ1OyihyvbDqfuraOkRjhzbWzdfVrVBFp30R5/TpD4f11454HT5xXkajqWBZ0vkstRmxTL4+7mrfrJqG3lBXD9zaVCFVuQipojAaTVr9d6y+/ClCf+89o1PxaYWWJ53PUv0B36taZW91bhmsUQMb6fb+DeTrw9e1QEWXkZGhAwcOKCOj6L4RzoeWG4BNnYi5qBc/2qkfVp1Qdo6xxHWNRpMORaboUGSK5i4O17Udamja+A7q272WnaIFANhaekau3vhirz75/nCxxbXLxZxO05LVaVqy+qTqhwXoibta6ZE7W1aoL9lcTXRCqqZ9tEvf/X5cmVl5Ja5rMkmHT5zX4RPnNW/JEV3TNkTPP9ReN19f107RArA1k8mkhb9G6s0v9mn/0eRS1z9/MVvrtsdr3fZ4TXlvu8YObayXJnRUcBUfO0SLspCZlatZX+3XR9+FKyGx9C8Q486ka/naKC1fG6XaNfz1+OiWeuKu1vL0dN6xwKotMXrl0z3atPt0qetmZObp771n9PfeM5o+d5dG9mugVx7ppAa1A+0QKcqC0WjSpz8c1rtfH9CxUxdKXT8pJUt/bInVH1ti9dSsrXpoRDM9/1A7LjwAACdCoQ2ATRiNJn3yfbimvLf9iivWzbVp92n1e/h3PTi8qWZN7qZKgc43qLy4/y9FvNC70GtuPv7yDmuqajfcreo3PyaDO00vgIpp064E3Tdto1lfKBTlZFyqnnhrqxb9dlxfvnKdWjSsbNsA7cCV+wGTyaQvlkXoqVlbdSE1x6p9/LPvrIY8tlp33dxIHzzTXVUreds4yrLnyjkAzn/cmTSNe3mzVm6Itmr71PQcffRduH5YdUKfvNhTt95U37YBosxt239W903boEORKVZtH3M6TVPe266Fv0Vq/ivXqV2zarYNsIydv5itybO26vNlEVZtn51j1LcrI7V8bZTefqqrHh7ZXG5uzjWloKu3g8dOXdB9L24wq8halHPns/TmF/u0+Pfj+nxGL93YLczGEZY9V88BkANwTWQ0gKuWmp6j2/+zVr9tirHJ/j77MUKr/o7Vb3P7q2WjKjbZp71VuW6UKnUaJJlMyklOUNJfCxTzxVPKjAlXvUfmOTo8ALApk8mk1/+7Vy9+tFMmU+nrl2br/rPqcPtyffbStbrr5sZXv0MHcLV+IDMrV6Of/UvL/oyyyf6+WRGpP7fGa+WcvurQwjmnk3S1HEBhrnj+122L0/An/1TKxeyr3teZc5ka/uSfemhEM338Qg+mFXYS7319QP95Z1uh6RGttefwOXUe9ZPmTu2hh25rboPoyt7BY8kaOPEPRSeklb5yKdIycjXxtS36Zf0p/TDrRqd8trkrtoNLV5/Q3c+vv+LRGdY4GZeqmx76TVMfbKdXH+vklM9wc8UcQGHkAFwJhTYAVyU1PUf9x/+uLXvO2HS/p+LTdN19K7X2s0Fq64TPbvNr2FHVbrir4OeQQRN1cGJzJa7+TGF3vSbPSiEOjA4AbMdkMum5D3bozS/22XS/Wdl5unvqemVk5jrNF2yXc6V+ICMzV0MeW60/t8bZdL/xZ9N1wwO/as28gerS2vl+X66UA+a4N36JzfY1P/Q2m+2rrLja+f99U4xueWKNsrKv/svly/136RFdSMvWtzNvoNhWzr06b7denLPLpvvMzTVp3MublZ6Zq0l3tbbpvm1tX8Q59X7gV507n2XT/f62KUb9J/yhPz7u73TFNldrB79deUz3PL/BJoXmy73+2V5dTM/RB89c43TFNlfLgdK42lhIIgdKEhoaqhkzZig0NNTRocBGKLQBsJrRaNKIp/60eZHtkqSULPV7+HdtXzRUdWoGlMkx7MXdx1/+za5RypYlykqIdOnBBICK5b2vD9i8yHa5h1/ZrJCqPrrlxvpldgx7qKj9gMlk0pjn/rJ5ke2SC6k5GjDhD239dqga1w0qk2PYS0XNAXP41wrWtunzdWjeCkeH4jAV+fzvOHhWw5+yfZHtksW/n1C1Sj766PkeZbJ/XL1Pfzhs8yLb5Z54a6tCqvhq9OBGZXaMqxGdkKp+D/9u8yLbJZt3n9aIp/7Ur3P7O900kperyO3gqi0xGvuC7Ytsl8xeeEjVq/rohXEdymT/9lKRc6A0jIXyuXIO/FulSpU0cOBAR4cBG+KSMABWm73woFZtibVom+2Lhip69Z3avmioWeufTsrQQy9tkskWc5E5WFZCpCTJI8D57tADgKLsjzinZ9/fYdE2lvYDJpP04EubdCYpw5oQy5WK2A98/mOExdNFWpoD585n6b4Xy+7LK3uqiDlgjjp9Oyt6lWVtRUVUEc9/Zlbu/999bH6RzdI2QJLmLg7Xyg2nrAkRZexo1Hk98dY/Fm1jTQ5MeG2zYmwwJaOtmUwmPTB9o05bME6x5v3/sSVWcxYdsibEcqUitoPJF7J074sblZdn/jjFmhyYPne3th84a02I5UpFzAFzMBb6H1fNgX9LTk7WDz/8oOTkZEeHAhuh0HYVEhMTNWXKFDVu3Fg+Pj6qU6eOJk2apLS0ND3wwAMyGAyaM2eOo8MEysTRqPN67kPLBwk1g/1Uu4a/agb7mb3NH1ti9fmP1j1M2lGMWenKvZConPNnlXFyv0598ogyju+WX5Ou8qnV1NHhAcBVy8kx6t4XNygn12jRdtb0A0kpWZr42hanuujCFfqBU/GpemrWVou3syYHNu0+rdkLD1p8LEdyhRwwV1DDmrp4MsHRYdiVq5z/6XN36fCJ8xZtY00bIEnjXt6s5Atlc8cQrJOXZ9R90zYqM8uyuxmtyYELqTl6aEb5uwDzs6VHtPpvy+7qtvYz8OwH23Xs1AWLtnEkV2kHJ73xj+LPplu0jTU5YDSadO+LG5SZlWtpiA7jKjlgDlccC0nkQElOnz6tt99+W6dPn3Z0KLARpo600p49ezRw4EAlJCTI399fLVu2VFxcnD788ENFRkbq3LlzkqT27ds7NlCgjDz7/g6bPODXXFPe26bRgxrJz9c5mq34RdMVv2h6odcqdx+uug9/5KCIAMC2vvr5qHaFJ9nteEvXnNSGnQm6vrNzzGHvCv3AC7N36mJajt2O9/zsnbp3WFNVCvSy2zGvhivkgDk8/HyUk5rp6DDszhXOf1TcRc366oDdjhd3Jl1vfrFPbzzRxW7HtCWTyaQ/t8Zp1ZZYnU/Nlp+Ph65pW1233lRPXp7ujg7PKt//cUKbd9vvC8LfN8fot00xGtSrjt2OWZL0jFw98/52ux0vIzNPz76/XUvevclux7wartAObj9wVl+vOGa34x2KTNGnPxwu988svMQVcsAcrjoWksgBuBbn+Ma6nElMTNSQIUOUkJCgyZMna/r06QoMDJQkvfXWW3rmmWfk4eEhg8Ggtm3bOjhawPZiEtL001+WTRN1tZIvZGvxH8d13y3OccVLcP9xqtJjpEx5OcqI2q+EH99UdmKMDJ4+BetcPLhRx16+cj5mU262TMY8dVpmv0ImAFjCZDLpo8X2n77oo+/CnabQVtH7gcTkTC3+47hdj5mWkasFvxzVY6Nb2fW41qroOWCusOvbKnb9XkeHYXeucP4/+eGw3ad0/XxZhF6a0EE+3s71VcZXPx3V65/tVUTUv+/+O6ga1Xz16KgWeu6BdnJ3d65Jhz5aHG7/Y353qNwU2r77/biSL2Tb9ZjL10UpJiFNtWv62/W41nCFdnCuAz4Dcxcf1uNjWslgKP/P63OFHDCHq46FJHIArsW5RqflxOOPP66YmBg9+uijmjVrVqFlU6ZM0cKFC7V37141aNBAQUHO/dB2oCjzlh62aP5xW5m7ONxpCm3eoU0U1L6PJKlSp4EKaHGtjjx3rU59PF4Nn/5OkhTYqpc6LE4ttF12UpwOT+6skMGP2j1mADDXP/vOaM/hc3Y/7rK1JxV3Jk1h1cv/l0sVvR/4YlmEsnMsmzbUFuYuDtejo1o6xZdLFT0HzFW9S3PtfPWbQq91fG602j4+XJuenKtj3629YpsBS2copFNT/dJ/ilKORNsrVJuq6Oc/KztPnzlgavfE5EwtWX1Sd93c2O7HttbzH+7Q658V/wXr6aQMvThnl3YcTNQPs26Sp6dzFNv2Hkmy691sl/y2KUbHYy6oYW3HftdiMpn00Xf2v+goL8+k/y49ohmPdLT7sS1V0dvBpJRMffe7fS86kqSIqPNauzVeN10TZvdjW6qi54C5XHUsJJEDcC3OMYIrR8LDw7V48WIFBwdr5syZRa7TqVMnSVK7du0KXrtUmOvatau8vb1L/HJgzZo1uuaaa+Tj46Pq1atr/PjxOn/esnnvgbK07E/73s12yY6DieXyAdjmCGjRQ1VvuFvJmxYrNXxLkesYc7J0/I3hCmh5rUJHTrVzhABgPkf1A7m5Jq3c4Jx/aFa0fmDZ2pMOOe7hE+d15KRzjosrWg6YxWCQDJLJWLgou2fW90oOj1LXl8bKL7RqoWUtx92smj1aac+sxU79xdK/VbTzv3n3aSUmO2YaLEf1Qdb45PvwEotsl/tp3Sk9/sbfZRyR7Sxf65jzYDJJP6875ZBjXy7mdJpdp9C+nKP64KtV0drBPzbHWvx8QlshB5wIY6FCXDIHiuHn56du3brJz8+y53Wi/KLQZqFFixbJaDRqzJgxCggIKHIdX19fSYULbceOHdPSpUtVs2ZNdelS/Jzy69ev14ABA1SrVi0tW7ZMr732mpYsWaJbbrml3D30F64pPSNXh46nOOz4O8MTHXbsqxV6x4uSm7viFk4rcvmpueNlzMlU/Unz7RsYAFho5yHHtcU7HfSlli1UlH4gN9eovRH2v6PxEkfm39WqKDlgrpAOjZW4+8pn1xhzcrVx0hx5+Hmr57sTC14PahSmjs+O0tmdETow92d7hmoXFen8O7YfcI42ICfHqBmf7LZom3lLjyg6IbX0FcuBnYcc1x+Xhxxw5Gfg0PEUZWTmOuz4V6NCtYMOzEPGQs6DsdCVXC0HilO3bl3Nnj1bdevWdXQosBEKbRZauzb/dt7evXsXu05MTIykwoW26667TvHx8fr555/Vp0+fYrd9+eWX1aRJE/3www8aOHCgHnroIc2bN09//fWXVq5caaN3AVhvb0SS3Z/FcDlnHlD6hDZW1V536uK+P3Xx4MZCy8788qHO71ihRs8tl5s3V7MAKL9MJpPDruCW6AfKg/DjKcrIdNyzEsiB8qnGNS1k+NfzpWr17qDYdXuKXP/c/hPaN3uZat3QXk3v6iODm5t6ffiYJGnjpDlXXPldEVSk8+/IL5ij4lKVlOKYu+kssXxdlBISMyzaxmg0ad6SI2UUkW05tsjg+ItuHBlDXp5Je4847oKXq1Gh2kEHjkf2RpxTbq5z9pMVKQf+jbGQeSpyDlgiLy9PqampysvjGXQVBYU2C0VF5U+PUK9evSKX5+bmavPmzZIKF9rc3Mz7VW/dulV9+vQptH6/fv0kScuXL7cmZMCmTsQ49grL4zEXHXr8q1Vz5POSm1uhK3cu7lunmAXPqOGUH+Rdo77jggMAMyRfyFbKxWyHHf9ELP2Aox138Dk4Eescd3sUpyLkwL/VH9pDfb6eqhrdWhR63TPITzkX04vdbu97S3TuwAl1nnaPur12v0I6NtGuNxfpQmRcWYfsMBXl/PM3Qel+Wmfd1IrL/jxp20DKQHZOnuLOFP/ZLmvl4fw7OgZnHg9VlHbQkTmQkZlncSG/PKkoOXA5xkKWqYg5YKmjR4/qxhtv1NGjRx0dCmzEYGI+QotUrVpVycnJ2rJli7p3737F8m+//VZ33XWXAgMDdf78+SKfxfbSSy9pxowZRU4FWalSJT344IN65513Cl7LysqSr6+vunXrpr//Nn/O9rS0tILpLUNDQ80u9gElSfPqoJSAW4pctn3RUNUMLvmKk5rBvvJwd1NunrHEgWFCYrq6jLryNnmf7EOqlrrYopitYfDyVY33y76zyzp9Uof/00Whd05X9at8yOvpJ5rIlO28g20AziHPEKCEKk8Xu7y0vuBq+wGDKVthya9ZFrQV6AeKl+7VSskBtxe5zB5jAe/sowpO/aaILWyLHJA8TW6abuxq1rptJw2Xd9UgbZ8+X5LkXytY9QZ106H/ljwrR5WW9XTzb2/I3ctTp7eG67dbp+U/hMkKM9y2Kcdgu6u/7ZEDtjz/kv3agdNBE5TrUbPIZbbqB6Ti24HgC1/IO7d8P6stMWCMsryaWrydm/GCQlPeKX1FBzLKW/FVi39uTlmPBSQp7Nx0Xflti/0kBdyhTK+WRS6zx2egcupy+WdbNjWpNWgHixdf+WkZ3Yp+pIw9PgM1Uj6Qh7Hs72x0thxw9bGQZNvxEONh8912220WrX/mzBktWrRIo0aNUvXq1c3aZsmSJdaEVq7cet8T8g8IUlrqBS378v0rfnYEo9Go+Ph4SVL79u21e7d1/auHLYNyBTVr1lRycrJ27dp1RaEtPj5eTz+d/8VT27ZtiyyylaZp06baunVrode2b98uk8mkc+es70AvJQtw1SrXlYoeS6pmsJ9q1/A3azce7m5mr3u5zPRUxcbGWrydpdy8/VSjjI9hzEpX5MxbVKnrUJv8QREXFydjluOuLAXgItwDpCrFLza3L7C2HzAZc+kHimG3fiCohkPHAlmZ6eRAMWydA14Gd5n7S4j6bZtumv9MwZdLdfp2VvSqHaVul3MhXcbsXLl7eSrmz11X9cVSXHycsk22m36nrHPA1udfsmM74JtV7LcJZd0PSFLi2QQpvezbgatSN1XysnwzY06mXdq4q2LwkqoWv7jMc8CUqzhH/47qpBV7fu3xGUhJTlRKivP3hU7dDgbmFjtPmD1y4HRCnJRT9lOYOlsOuPpYSLLteIjxsPnS0tIsWj8jI6Pg/+ZuW+7HB2Yw/v9Umca8PMXGxl7xs6OdPn3a6m0ptFmoT58+Cg8P15tvvqm+ffuqadP8K9S2b9+uu+++W4mJ+XM0t2/f3qr9P/7447rnnnv06quvavz48YqJidHEiRPl7u5+VXekcUcbbCXT01vFDeUSEkvvyCy5cqso/j5S5Vq1zAn1qhi8fMv8GMlblirjxF5lxkYoedOVd+m1mnNIXiHmPxQ1LCyMO9oAlDmT3BVnypMM7kUuL60vuNp+wF2Zqkk/UCR79QNZHr4q7qkk9hgL+HqbVJUcKJKtc8DT5CaZeUH0+YgYySRVblpbKRExCmxQUxfnl/6Has/3H5Gbp4dSIqLV9okROvnzFl2Msu4P3LDQMJvf0VaWbH3+Jfu1A4keOcoqZpmt+oGS9lW9WoA8q5R9O3A1LnielzUTy/kYElXNDm3c1TDJoHhTtkyGoitNZT0WcDNlKtTBv6MUH5OK+1rUHp+BapV95OPv/H2hM7eDpw1Zyi1mWVl/BiQptEYluZl8zAn1qjhbDrj6WEiy7XiI8bD5/P0tK5pfKq75+vqavW2tcj4+MIebu3vB/2vVqnXFz45w+R1tNWpYX1pm6kgLxcTEqH379kpKSpKHh4eaN2+uzMxMHTt2TAMHDpTRaNQff/yhefPm6aGHHipyHyVNHWkymfTcc8/pvffeU3Z2ttzd3fXII49o8+bNCgoK0tq1a82O9fKpI1NTUy3+wANFiT2dptp9v7N6++jVd6p2DX/FnE5THSv289/p1+rBEc2sPr65MnKlXr+W+WFsauMgyZfLJwDYQdsRP2r/0WSrtr3afmD4TfW19L2brDq2JegHipd8IUtVr7V+6sarzYH3nu6mJ+5ubfXxzUUOSDnpmfq20V1mr9/lpbHKTLqg8M9/U+tHhmnP2yVP993igUHq9ur92jlzoaJ/36Yhq97W2V0R+n34dKviHRP5jTz9bPelIzlQvCnvbtPb8/dbte3VtgEBfp5K2XyX3N3L94WkMQlpqj9wsfLyLPvKZdWnA9S3e/n/Iq37XT/rn31nrdr2anOgf49a+v2TAVYd21b+u+Swxr282aptr/b9S1LsmjsVVr3sv+OhHSzePVPX6+sVx6za9mpzoFGdQB1bWfQ03rbmbDng6mMhybbjIWc7/5Ljvhvbvn27ResfPnxY99xzjxYsWKDmzZubtU2XLl2sCa1cef2jb3UhNU1BAf6a+siYK352BFvVUMr3yLQcql27tjZu3KjBgwfLx8dHJ0+eVNWqVfXpp59q5cqVioiIkCS1a9fOqv0bDAa98cYbSkxM1N69e3X69Gm98847Onr0qHr06GHLtwJYJay6n2pUK/srWorTqWU1hx0bAJCvU8tgBx6bfsDRqgR5q1GdQIcd35H5h5JFr9qhOv06K+yGdorbsK/EdQMb1FTHqaN1dvdRHZizXCkRMdrzzveq2b2VWjwwyE4Rw1qO/Bx2aF613BfZJKl2TX8Nv6m+Rds0b1BJN3ULK5uAbMyxYwHH9wOOjKFmsK9dimwomSPHpOXhM4CiMRaCuRo3bqw//vhDjRs3dnQosJHyPzoth1q0aKEVK1bo4sWLunjxorZu3apx48YpLS1NJ0+elJubm1q3vrqrbAMDA9W2bVtVq1ZNX375pTIyMnTffffZ6B0A1jMYDLquU9EPPi9rVYK81KpxCQ8GAgDYhaP6AUnq1dFxx8b/OCoH/Hw81LEFxdby6vTWcAU1DFW9gV11dvuR4lc0GHTt+4/Kzc1NmybNkcmYP73RgY9+UuKeY+o4dbQC65X1E0FwNXq2ryErHkluE87UD8x9voea1qtk1rqVAry05J2b5ObmoF+shVx9LNC6SRVVDrTiIXw2UB7eP/gMoGiMhWAuDw8PValSRR4eTE1VUVBos6GDBw/KZDKpSZMm8vPzu2L5kiVLtGTJEh06dKjQzzt2/O/BmDt27NDMmTP1xx9/aOXKlXryySc1fvx4vfnmm2rUqJHd3gtQknG3lf3UjUW575am8vIs+plAAAD7ub1fAwUFeNr9uC0aVta1HfmDszwYN8K86U1s7a6bG8nfz/65B/OY8oyK/Wtv/r+NxT8bpNX4IarRtbl2v71Y54/+76HnJqNRmybNkZu7u3q+N7HM44X1atf01+Bedex+XINBenC4Y/4WsUZwFR9tmD9Y3dqElLhe3VB/bZw/2KkuKhzWu55CqpT986H+rX5YgPp2d/xdf16e7rrvliYOOfbDtzmmD0ZhHVoEq3Mr+99Z5uvjrrsG8/1gecVYCOaKiYnR5MmTFRMT4+hQYCMU2mxo//78OeqLmzZy5MiRGjlypH744YdCP8+ZM6dgHW9vb/3yyy8Fy7Zs2aLFixfrySefLPs3AJjpxq5hZl+ZaUvjR/IHBQCUB/5+nrp3qP2/XJp4RwsZHHULBQrp1jZEHZrb/86yCbe3sPsxYZlTv23Tqd+Lf0ZFpSa11HHKnTqz44gOfvLLFcuZNsl5TLzD/p/HQb3qqEFtx01da40a1Xz19zdDtGbeQA2/qb78L3twjJenmxa/3VtHV4xUm6ZVHRil5by93B1S9Bx/e/NyM3Xo+JH2/ww0q19JN3YLtftxUTRHtINjBjVS5SBvux8X5mMsBHOkpqZq48aNSk1NdXQosBHuTbSh0gptJlPpD0Fu06aNtmzZYtO4AFtzczNo6oPtdO+LG+x2zNv7N1ATBxT3AABFe+Ku1vrsxwilZ+ba5Xi1qvvpniHMX19eGAwGPf9QO902ea3djjn4ujpq74DiHiwTteLvEpefPxqrrxuMLnGd/bOXaf/sZbYMC2Wgf8/a6tQyWDsPJdrtmM/e39Zux7Ilg8Ggm64J003X5N+JVeumhYo7m6GQKj66vX9DB0dnvUdHtdBHiw/pQmqOXY4XUsWnXN3R2LR+Jd3ev4G+/+OE3Y459cF2XHRUjtw5oKFe+XSPTsRetMvxvDzdNHlsG7scC9ZjLAS4pvJxGVAFUVqhDahI7hnaWAOvrW2XYwVX8dHsZ7vb5Vj2kLx5iaI+nlDotcQ1X2rnMINS/lnumKAAwEINagdq5qTOdjvef6dfq6AAxzwLxR7+3Tc4Q78wom8D3da3vl2OVSnQS5+80NMux7JWxPR+OvR4Wx16or2OPNdL6cd3m7Ud4wJckrx5iU5+cJ+OvX6LDkxoqkOT2iliWl9lxh9zdGhFcnMz6MuXe8nTwz5fKzw+uqWurSDPJaoohZKw6v569z/d7Ha8uc/3ULXK9p+usiSzn+2uYDtNoTmoV23dXcEvOnK28ZCvj4e+eLmX3Y43Y2JHNW9Q2W7HK0uXzrUxO9Np+j3Yh7ONh4BLKLTZ0Nq1a2UymTR48GBHhwKUOYPBoHnTrlUlCx8AnZCYrpjTaUpITDd7m7nP91D1ar6WhlhupfyzTJW73VLwc9bpk0pc9V/5N7vGcUEBgBUeHdXS4gfBW9MP3HdLEw10wLOA7OnyvsGZ+oWPpvaw+Bk91uTAe093U+2a/paGZ1cNn/5eLT/cp5bv71H1oU/p5Af3mrUd4wJckvLPMlW+5haF9BunVnOPqOUHe1W52zBFzXnQ0aEVq03TqnppQgeLtrGmDWhUJ1CvP26/iztgvvtvbaoBPS27ANOaHLi9fwPd1q+BpeGVuerVfPXRVMsuCrXm/VcK9NK8addWmCJtcZxxPHRDl1A9NrqlRdtYkwNdWgfrPxXobrbLz7Uz9Xsoe844HgIkpo4EcBVq1/TXTx/00YAJfygzK8+sbbqM+tmiY7w0oYNGlsM/qEqSm5qiQ4+3ljE7Q17BdWTKyVJWwnFVveFu1ZvwsVIPb1b9SfMl5T/oNmrOg6ozbrZivpzs2MABwEJubgb9MOtGXXffSh05ed6sbSztB67vXFMfTe1hTXjlRkn9Qv3HPpMpN6egb3C2fqF6NV/9MruvbnroN6VlmDeNqKU58J+xbXTvMPs/E9BSHgGVC/6dl35e+v8vQxkXQLKsHTB4eBZs59/0Gp1ePsuBkZfu2QfaKfxEir5ZEWnW+pa2ASFVfLRyTj/5+3mWvjLszmAwaOGbN+j6+1Zq/9Fks7axNAe6tQnR5zPsd9eQpW7v31CHIlM04xPz7mS29P37eLvr5w/6qFaN8n3BSWkq8nho1uSuOhp1Qb9vjjFrfUtzoEGtQC1/v4887HQHsS2YO/4xeHiqUuf/PYfMGfo9WK8ij4csFRISokmTJikkJMTRocBGnKeFBlAuXd85VCtm9yv0UG9bmT6+g6aNt+wK2fLAI6Cyql43WjWGPKGW7+9R7Qfel3+za1T/sc90cf86+TfvUTBgOP3Tuwpo0VP+jTs5OGoAsE71ar5a+9lAtWpU2eb7vrFrqH6Z3Ve+Ps59bVhJ/YKkQn2DM/YL3dpW1x+fDLD4Lndz/GdsG731VBenuYL/xHv3aN/9dRT37Ytq8MTXkhgXIJ8l7cDlzqz4QJW7DnNEyGbLn0LyujJ5jmZoiJ/WfT5IzSrIVGkVVZUgb/3534Hq2ML2z9Hs2aGGfv+4vwLKeaF1+oQOmvaw7f929ff10Mo5/XRd51Cb79veKvJ4yMvTXT++d5MG9bL94zWa1AvSus8HKqy6cxVaLRn/XM4Z+j1YryKPhyxVrVo1jRkzRtWq8QzqisK5v7UAUC7cdE2Ytnw9RGNfWK89h89d9f6qVvLW7Ge7a/TgRjaIzjHST+xR9Zsfz/935E75Ncz/oytl63JVueZWSVJG1AGl/L1UzV7f4LA4AcAWwqr7a+NXN+vxN/42+46Gkri5GTT5ntZ6+ZGO8vEu/8PVw1O6KzPuaJHLWr63W14hdYrtF6T/9Q3O3C/07FBD/3wzRGOf36BtB85e9f6CAjz1/pRrdO+wJk5TZJOkBk8ukCQlrf1KMQueUZNpv0piXOAKbNUOXC7+h9eVFX9M9V75s+wCtxEPDzd9+cp1atu0ql6Ys9Ps2S5K0rd7mD57qZfqhgbYIEKUtZCqvlr3+SBNnrVVn/0YYZN9Pja6pd6Y1EV+ZXBRp60ZDAbNeKSjmtWvpEdnblHyheyr3meH5tX01avXqU3TqjaIsOy5+njI18dDy97vo1c+3aOZn+9VXp7pqvc5sl8DfTS1u0KqOuejNMwZ/1zOmfo9FM3Vx0OWuHDhgrZt26auXbsqKCjI0eHABsr/aAWAU2jbtKq2fTtMMz/fq9f+u0fZOUar9nPLjfX08Qs9VDPYz8YR2lfGiT0Fg4X0yJ2q3HWoTCaTzu/+Q7XGviVJSj20UVlnTurAhPzpsHKSExQVPU45yfEKGTih2H0DQHlUJchbX79+g0b2baAJr21R3BnznzlxuRYNK+uLGb10TbvqNo6w7DR/6+9S1ymqX5BUqG8499fXTt0vNG9QWZsX3Kx3FxzQ9I93Wf1F+6BetfXpi9eW+2eylaTajWMV9fF45V5IkkdQNcYFLsBW7cAlCctmKeXvH9Xk5TVy83aOcbGbm0GTx7bRzdfV0QMvbdLm3aet2k9QgKdmPdVVD45o5lSFdkhBAV7670u9dFvfBhr38iadik+zaj+N6wbps5eu1fVOeBfX6MGNdGO3UE14dYuWr42yah9enm56YVx7PXt/O3l6Os9EVIyH8u9se+XRTrrlxnq6f9pG7Yuw7kLk6lV9NGdqD6d7jMa/mTP+ucQZ+z1cifGQ+eLi4jR16lQtWLCAQlsFQaENgM14erpp2vgOmnB7c32+LEKf/HBYUXGppW4X4Oepu29upAm3t3Caq/VKkp0UK8kgr2q1JEkZJ/cpdOTzSo/YJt/aLeTum39VbsjACYX+UDjy/A2qMeQJVb7mFgdEDQC2MbR3PfXvWVs/rjmpuYvDtcmML1oNBmlQrzqaeEcL9e9RS+7uzvOlkjmK6xckFeobKkK/4OHhpin3t9UDw5tq/k9H9fH34YqMvljqdr4+7hozKH8s0LFlsB0ita3c1BQZs9LlVS1MkpTyz3J5BFaTe2BVxgWQZH47IOVPIZq8cZGavLym0LP/nEWzBpW1cf5gbdlzRh99d0hLVp9UTm7pF+G1blxFj9zZQmMGN1Kgv+2nooX99O9ZW8dW3K7l66I0d3G4/toeb9Z2/XrU0sQ7WmhwrzpO9Syqf6sZ7Kdl7/fR/ohzmrs4XF+vOGbWc0zrhQVowu3Ndf8tTZ32DqaSuNJ4qFPLYO3+/hat+SdWcxeH65f10TIaS7/DrVubEE28o4Vu79/AKWZ1KIm54x/J+fs9mM+VxkNwPc7dagMol0Kq+urZB9rp6Xvb6MCxZO08lKSdhxJ1Mu6isrKN8vRwU3AVb3VsEaxOLaqpU8vgCvVw8/Tjuwvd+u7uX1lnfp0rj6BgVe52i+MCAwA78fZy16hBjTRqUCNFxV3U9gOJ2hmeqEORKfpjc4yycozy8XbXM/e1VedWwerSOkQ1qlW8L5QuKa5fqP/YZ0r+Z1mF7BuqVfbR5LFt9OTdrXXwWLJ2hidq56EkHY+5UDAWqFrJWx3/fxzQqWU1p/5iPS/9vI6/NVLG7AwZDG7yCApR4xdWyGAwMC6AJPPbgezEGMV8MVleNRsq4oXekiSDh7dazNrqiLCtZjAY1LNDDfXsUEMfPZ+lHQcTtfNQonYfTtJP66KUlW2Uj5ebJtzR4v/bgGA1q1+JO9gqEE9PN43s10Aj+zVQdEKqdhxM1I6DiToYmazfN/1vLPD0vW3UqWWwurQKdrpnUJWmTdOq+vjFnpo1uat2HkrUzvAk7QpPVGJylnJyjfL2clP9sMCCfrB14yoV7mKjy7naeMjNzaB+PWqrX4/aOnsuQ9v/vx3ce+ScLqbnyGg0yc/HQ80bVFKnlsHq3CpYDWtXnLtazB3/VJR+D+ZxtfEQXIvBZDJd/aTBKJfS0tIUEJB/FUBqaqr8/SvWoBUoSxm5Uq9fbbvPg4+2UtNX18mzctlMh7ZxkOQEjy8A4OJq91mk2DPpqlXdTzFrRjk6nGKVRT9QFFv2DfQDtlXWOVAW4wJb50BOeqa+bXSX7XZYxsZEfiNPPx+b7Y92oGw4Sz9Qllz9d+Dq79+Z0A7C1jngbN+LONtYSLLteMhebYBku9xwVBuwfft2i9Y/fPiw7rnnHi1YsEDNmzc3a5suXbpYE1q58vpH3+pCapqCAvw19ZExV/zsCLaqodD1AICdtJpz0NEhAADKGfoG18W5xyXkAgBXRzvoOjjXKI6r5Ya3t7eaNWsmb29vR4cCG6HQBgAAAAAAAAAAYAcNGjTQ119/7egwYEMVd/JnAAAAAAAAAAAAoAxRaAMAAAAAAAAAALCDI0eOqGfPnjpy5IijQ4GNUGgDAAAAAAAAAACwA5PJpJycHJlMJkeHAhvhGW0AUAQfd2njIEdHYRkfd0dHAAAVB/0AyAHJw9dbYyK/se1Oy5CHr20fJk8OAHB1tINwthxw9bGQZNvxkLOdf4k2AI5DoQ0AimAwSL60kADgsugHQA5IBoNBnn4+jg7DYcgBAK6OdhCungOMhVz7/AOWYOpIAAAAAAAAAAAAwArUpAEAAAAAAAAAAOygfv36WrRokWrVquXoUGAjFNoAAAAAAAAAAADswMfHR40aNXJ0GLAhpo4EAAAAAAAAAACwg/j4eL366quKj493dCiwEQptAAAAAAAAAAAAdnD+/Hn9/PPPOn/+vKNDgY1QaAMAAAAAAAAAAACsQKENAAAAAAAAAAAAsAKFNgAAAAAAAAAAAMAKFNoAAAAAAAAAAADswM3NTR06dJCbG+WZioIzCQAAAAAAAAAAYAdGo1G7d++W0Wh0dCiwEQptAAAAAAAAAAAAgBUotAEAAAAAAAAAAABWoNAGAAAAAAAAAAAAWIFCGwAAAAAAAAAAgB0EBgZqwIABCgwMdHQosBEPRwcAAAAAAAAAAADgCmrVqqWXX37Z0WHAhrijDQAAAAAAAAAAwA6ysrIUHR2trKwsR4cCG6HQBgAAAAAAAAAAYAcnTpzQiBEjdOLECUeHAhth6kgUyWSSMvMcHYVlfNwlg8E2+zKZTMrNcK4rCjx8vWWw1S9ArpEDznierWVpfphMJqVn5JZhRLbn5+th8WeAHCgeOVCx2LqPqChc5fxL1uWAK7QD5EDJnC0H6AdKRl8AFM1V2gFX6AckxgIlcYUcYCxQMsYCQNmh0IYiZeZJvX51dBSW2ThI8rVRRudmZOnbRnfZZmd2MibyG3n6+dhsf66QA854nq1laX6kZ+Qq4JoFZRiR7aX+c4/8/Twt2oYcKB45ULHYuo+oKFzl/EvW5YArtAPkQMmcLQfoB0pGXwAUzVXaAVfoByTGAiVxhRxgLFAyxgJA2WHqSAAAAAAAAAAAAMAKFNoAAAAAAAAAAAAAKzB1JAAAAAAAAAAAgB00b95c27Ztc3QYsCHuaAMAAAAAAAAAAACsQKENAAAAAAAAAADADqKionT//fcrKirK0aHARpg6EgAAAAAAoAxExV3U75tjtfNQonYeSlR0QpoSUzIlSaeTMnT31L/UqUWwbuwWprZNqzo4WgAAYA8ZGRk6cOCAMjIyHB0KbIQ72gDAzmp2b6V745eo8e03ODoUOAg54No4/yAHQA6AHKjYTCaTftsYraGPrVaDgd9r/Cub9d+lR7QrPElnkzNlMuWvl5tn0jcrIvXk21vV7rZl6nH3L/pmxTFlZec59g2gzNEGgBwAOQBULNzRBpu5uP8vRbzQu9Brbj7+8g5rqmo33K3qNz8mgzspV5FVlByo2b2VBvw4o+BnY16eci5mKD3hnJL2HdeJ5ZsUu26P4wJEmSMHXBvnH+QAyAGQA7BWVNxFPTB9k/7cGmfxtn/vPaO/957RzM/3av4r16lL65AyiBDmoA0AOQByAIAlyv833nA6Va4bpUqdBkkmk3KSE5T01wLFfPGUMmPCVe+ReY4OD3ZQUXLg+I8bFbN2l2QwyNPfV5Uah6nugK5qfPsNilu/V3+Ne0fZF9IdHSbKEDng2jj/IAdADoAcgCW+XB6hx9/4R6npOVe1n0ORKep+9y969v62evmRTnJzM9goQliKNgDkAMgBAOag0Aab82vYUdVuuKvg55BBE3VwYnMlrv5MYXe9Js9KrndV3r3xS2y2r/mht9lsX2WlouRA0v4TOr50Y6HXtk//Sp1evEutxw/VdR8/qTVjXnNQdLAHcsC1cf5BDoAcADkAc702b49emLPTZvvLyzPptf/u1fGYi1rw2vXy8ODJH45AGwByAOQAykJoaKhmzJih0NBQR4cCG6HQhjLn7uMv/2bXKGXLEmUlRDpNkcVW/GsFa9v0+To0b4WjQ3GYipQDJqNRO2YsUEiHJqp9YwdV79pcZ7YdliR5Bvqp7ePDVW9wN/mHBSsnNV1xG/Zr1xsLlXrqTMk7NhjU9vFbFXZDe1VqGCqvygHKOJuimDW7tPvNRcpKTpUk+VQL0shdn+rkin+08ZEPrthNt9cfVPOx/bS02yNKjTlr8/cPcsDVcf5BDoAcADmAf3vnq/02LbJdbtFvx+Xh7qb5r17HnW3lBG0AyAGQA7halSpV0sCBAx0dBmyIS6JgF1kJkZIkj4CqDo7E/ur07azoVTscHYbDVbQcOLrwT0lS7T4dJeUPpAb98pqa39tfMX/u0tYXPlf4F78r9NrWuvnXmfKvHVzi/ty9PNR6wjBdOB6vA3N/1rYXv1Tc+n1qMupGDVg6Q26e+ddFZCZdUPSqHao3sKu8gvwK78PbUw1vvVZxG/czkLIDcsC1cf5BDoAcADkASdqwI17/eWdbmR7j6xXH9PH34WV6DFiONgDkAMgBWCs5OVk//PCDkpOTHR0KbIQ72mBzxqx05V5IlMlkUm5ygs7+/okyju+WX5Ou8qnV1NHh2V1Qw5o6PD/B0WHYlSvkwLnwKElSUMMwSVKHKXcosG51rbx5qpIPRRWsd+z7dRq29l11+M8d2vTER8XuLy8rR4vbP6S8zOxCr5/dcUQ9352ougO66OQvf0uSIr5Zo/o3d1eDW3vpyFd/FKxbb/A18q4coKPfrrHZ+0TxyAHXxvkHOQByAOQA0tJzdP/0jaWv+C/bFw1VzWA/JSSmq8uon83a5pn3tmvgtbXVsHaQxcdD2aANADkAcgDWOn36tN5++221adNGVapUcXQ4sAHuaLsKiYmJmjJliho3biwfHx/VqVNHkyZNUlpamh544AEZDAbNmTPH0WHaXfyi6dp7d4j23VNdhya11dnf5qpy9+Fq/PxPjg7N7jz8fJSTmunoMOzOFXIg52KGJMkrwFeS1HB4L53eGq70hHPyrhpY8F9uepbO7jqqsOvblbrPSwMpg5ubvIL85F01UPGbDkiSE9pcSwAAaFJJREFUgjs2KVgvbv1eXYw6rSajbyy0fZNRNyrz3AWd+n27Td6jrbz2eCeZ9j2g+25pUuTydZ8PUuaOe9WqsXMNLMgB81XEHOD8Wyf2dJqyc/IkSXlGk4OjuTrkgPkqYhsgkQOWIAfIgUtSLmQpO8coScrNM8pkcu6+4OVPdysy+qLF29UM9lPtGv6qGexX+sr/Ly0jVxNe3WLxscqbuDOMBUpSUdsA+gFygBwgB4CKjjvarLRnzx4NHDhQCQkJ8vf3V8uWLRUXF6cPP/xQkZGROnfunCSpffv2jg3UAYL7j1OVHiNlystRRtR+Jfz4prITY2Tw9ClY5+LBjTr28pXz0Jpys2Uy5qnTsjx7hlxmwq5vq9j1ex0dht25Qg54BuYPorJTM+RTLUg+VYNU64b2GnXwyyLXN+aV/n7qD+muVuOHqmrr+nL38iy0zLtSQKGfIxb+qU7PjVbVVvV17uBJBdStrpo9WunQZ7/KmJNr5bsqGy/N3a0h19fVu//pplV/xyr2dHrBsifuaqUbuoTq2fe36+Ax57pdnhwwX0XMAc6/+XJzjVq2NkofLw7Xuu3xBa8nJGbo1ifWaOIdLdTnmjAZDM713BlywHwVsQ2QyAFLkAP5XDkHtu0/q7mLw/Xd78eVlZ3/ezidlKnOd/6kiXe00KiBjeTn61xfT6Sm5+iTHw7b9ZirtsTqwNFzat3Euabjz8sz6pf1pzR3cbhW/x1X8HpCYoZufnSVJt7RQgN61na6Z9DRBpiPfiAfOUAOuHIOABWdc41ky4nExEQNGTJECQkJmjx5sqZPn67AwEBJ0ltvvaVnnnlGHh4eMhgMatu2rYOjtT/v0CYKat9HklSp00AFtLhWR567Vqc+Hq+GT38nSQps1UsdFqcW2i47KU6HJ3dWyOBH7R5zWanepbl2vvpNodc6PjdabR8frk1PztWx79Zesc2ApTMU0qmpfuk/RSlHou0Vqk25Qg5UbVFPknQhMk76/y+H49bv1f6Pllu1v7qDuumGeZN1dtdRbXvxS6XFJSkvK1sGdzf1W/SiDP/6o/PYorXq8J/b1WTUjdr6whdqMupGGdzcyuXUADm5Ro19YYO2fjNUn7/USwMm5E9p0LR+Jb32WGf9s++M3p6/38FRWo4cMF9FzAHOv3kupGZrxFN/as0/cUUuX742SsvXRuneYU00b9q18vR0nskWyAHzVcQ2QCIHLEEOmKci5oDJZNKMj3drxie7i1y+KzxJD760SR8uPKSVc/qpdk1/O0dovW9XRupCao7djzt3cbjmvtDT7se1Vlp6jkY985d+WX+qyOUrN0Rr5YZo3TGggb569Xp5e7nbOULr0QaYj37APOQAOVCRcwCo6Ci0WeHxxx9XTEyMHn30Uc2aNavQsilTpmjhwoXau3evGjRooKAg5k8PaNFDVW+4W+fWLVDqzY8roEWPK9Yx5mTp+BvDFdDyWoWOnOqAKMuAwSAZJJPRWOjlPbO+V52+ndT1pbGKW79H6fHnCpa1HHezavZopZ2vfeO0RbaiVMQcaDL6JklSzJqdyky6oKyUVHkG+il+o3UDw0a3XafcjCz9ftt05WX8by7uSo3Dilw/42yKolfvVMPhvbTztW/V+PbeOrszQikRMVYdv6ztDk/SzM/3atr4DnpoRDN9vixCC167TgaDNPaFDTI64bQx5IBlKloOcP5Ll5Wdp2GT1uivy+5iK878n45Kkr54uZfT3NlGDlimorUBEjlgKXKgdBUxB177755ii2yX2xdxTn0f/k1bvh6iKkHedojs6n25PMIhx/16RaQ+eKa7U1yckptr1O1Pr9WvG0vPycW/n5DJJC16s7fT3NlGG2AZ+oHSkQPkQEXPAfyPn5+funXrJj8/86eRRvlW/kdm5Ux4eLgWL16s4OBgzZw5s8h1OnXqJElq1+5/8+4uWbJEI0aMUL169eTn56fmzZvr+eefV2pq4Tt6zF3P2YTe8aLk5q64hdOKXH5q7ngZczJVf9J8+wZWhkI6NFbi7mNXvG7MydXGSXPk4eetnu9OLHg9qFGYOj47Smd3RujAXPMeiO1MKkoOGNzc1HnaParRrYWi1+zUme1HJJNJx3/cqJCOTVRv8DVFbudTreSiuykvvyBrMBRults+cVux20R8u0beVQLV/a1x8g+rpoiFf1r4buzrlXm7tedwkmZN7qrZz3VXtzbV9fzsnYo4ed7RoVmEHLBeRcgBzr/55i05bFaR7ZL5Px3Vqi2xZRiRbZAD1qsIbYBEDlwNcsC1ciDi5Hm9OGeX2esfPnFeMz4uvShXHmRl52lXeJJDjp2anqPwEykOObalvl5xzKwi2yXf/3FCy/48WXYB2QhtgPXoB8gBcoAcQL66detq9uzZqlu3rqNDgY1wR5uFFi1aJKPRqDFjxiggIKDIdXx98+fnvbzQNmvWLNWtW1evv/66ateurT179mjGjBlav369NmzYIDc3N4vWczY+oY1VtdedOrf+W108uFGBrXoVLDvzy4c6v2OFms/aLjdv56zi17imhc5sP1LQIUpSrd4ddHDeiiLXP7f/hPbNXqb2T41U07v66OjCter14WOSpI2T5lxxF1xF4Iw5UK1NAzUckR+np7+vKjUOU90BXRVQp7pi/9qjDRM/KFh31xuLVL1Lc90w7ymd/Plvnd0VobzsXAXUDlHtmzooad9xbXrio2KPFbXiH9W/ubv6L5muyB/Wy83TQ3UHdJGHb/FX9Mau26PU6DNqdNv1yknN0Inlm2335stAbq5JY1/YoO2LhmriHS20cVeC3v/mgKPDKhE5YFvOlgOcf+uZTCZ9/L3lz62Zuzhc/XvWLoOIrEMO2JaztQESOWBr5IBr5cAnP4RbvM38n4/qtcc6yd/Ps/SVHejAsWTl5Drub7adhxLVtmn5f07b3MWW58DH3x/WiL4NyiAa69AG2Bb9ADlADpADyJeXl6eMjAz5+vrK3d15pk1G8Si0WWjt2vxnavXu3bvYdWJi8q/YurzQ9ssvvygkJKTg5+uvv14hISEaM2aMNm3apOuuu86i9ZxRzZHP69zGRYpbOE3NXlsnSbq4b51iFjyjJtN+k3eN+o4N0Er1h/ZQz3cm6M+xbyhhy8GC1z2D/JRzMb3Y7fa+t0R1+3VW52n3qGqr+grp2ETbXpqfP69zBeVsOdBweC81HN5Lxrw85aZlKi3+nBL+PqQTz8xT7Lo9hdbNuZiuX4c9r1bjh6rBkO6q07+zTHlGpcUn6cy2w4r4tuQrik78tFkeAb5qNW6wuky7R1nn0xS9eod2vvatRofPL3ojk0kRi9aq45Q7deLnLcpNz7TNGy9D51OzlZWdJy9Pd/26MVqmcj4zBDlge86UA5x/6/2z74zCj6dYvN2KDdFKSExXzeDycdEFOWB7ztQGSORAWSAHileRciAvz6gvlx+1eLvzF7P1458ndfeQJmUQle3sP3qu9JXK0L4Ixx7fHPsizmnHwUSLt/tza5xOxFxUg9qBZRCV5WgDbI9+oHjkQPlEDqAsHD16VPfcc48WLFig5s2bOzoc2IDBZCrvzVn5UqdOHcXExGj37t1q3779Fctzc3MVGhqqxMRERUZGqmHDhsXuKyIiQs2aNdPChQs1atSoq17v39LS0gruugsNDbXobjiDl69qvG/5H0aWyDp9Uof/00Whd05X9cGPXvX+Tj/RRKbsDBtEJnma3DTd2NXs9dtOGi7vqkHaPn2+JMm/VrDqDeqmQ/9dWeJ2VVrW082/vSF3L0+d3hqu326dJmtHGDPctinHYLurKl0hByw9z+VR64nD1PnFu7Xy5qk6u7P450RYmh9GeSq+6gu2CLGQtZ8NVI/2NRQZfUH1wgLUdsQyHY+5aJN9h557VW6y7IH05EDxyAHnUFbn/2qle7VRckDx05uUJOT8PHnl2WcKSWc//1LZ5kBZtANl2QZIlrcD5EDJnC0HXLEfkMpnX5Bn8FVClWet2jYwfa2CMtfbOCLbSvXupvP+g4pctn3R0FIvGKkZ7CsPdzfl5hmVkFj83y8JienqMurKxwr4Ze5UlfTy/biBDM/mOhdo/vcXlwu+8KW8c0/aNqASOHs7QD9QGGMB184BxgLlZyzgam67zbK/f8+cOaNFixZp1KhRql69ulnbLFmyxJrQypVb73tC/gFBSku9oGVfvn/Fz45gNBoVH5//2Iv27dtr927rpjLnjjYLpaWlSZIyMooeDC9evFiJiYkKDAxUgwYlT3ewbl3+HT0tWrSwyXoluZQs5nLz9lMNq49WOmNWuiJn3qJKXYfapMAiSXFxcTJmFX8HmSW8DO6y5BcQ9ds23TT/mYJCW52+nRW9akep2+VcSJcxO1fuXp6K+XOX1UU2SYqLj1O2Kc/q7f/NFXLA0vNc3hjc3dT07r46dyiqxIGUZEV+GLwkG89G89jolurdNUxTP9yhn9ZFadfiW/TFy710w/2/2mT/8XFxkim79BUvQw6UtHNyoLwr0/N/tSrXlYqeYbtUZxMTpXT7FNqc+fxLdsgBG7cDZd0GSJa3A+RAaQdwrhxwtX5AKsd9gXugVMW6TS9eTNPFM+X8mZ3Vzkv+RS+qGeyn2jWKWfgvHu5uZq97ufT0DKXHlvPfUVANycqb0hITz0lp9nt/ztwO0A9cibFA8VwhBxgLlKOxgIu5VDMw16XaQkZGhtnbxpb3vt8Mxry8gv/HxsZe8bOjnT592uptKbRZqGbNmkpOTtauXbvUvXv3Qsvi4+P19NNPS5Latm0rg8FQ7H5iY2P14osvasCAAUXeGWfpeqWx5o62spS8ZakyTuxVZmyEkjctvmJ5qzmH5BVi2cMgw8LCbHpHmyy4wON8RIxkkio3ra2UiBgFNvi/9u48Tsuy3h/4ZxZgWGUXBGURUMRdsVTU9OCCplaalUsadkoTI38qtrh19LjbombpKZdjhlaWuxlmmku5IW64AIKyqqgoww4zvz/IOSIDzNwODMv7/XrxYp77upfvPc891/PMfJ7rurtk9g2r/sHc/WcnpbRJeWa9Njnbfu+wTLrz8cx+o9gP9CZdN2nwEW2r09pwDdT3eV5btNq0czrt3C+b7T8wbXp2ycMn/HSV29T3+qhKk9Qvnl+5Ppu1yYXDd86TL7yTi697PlVV1Tn3l6Nz4fCBOfnIrXLl78Z+6mN03WSTQp9ccw3UzjWw9loTz/+nNb+8PO/Wd6Pq6qSkJBt3aJ7ydt1WR1nLWRef/2TNXQMN2Q+siT4gqX8/4BpYuXXtGthQXgeStf+1oDqlmVa9KCmp/73W2rZKWjZZM68DRc1p1iKzVtA2Y+aqP/hXnxFttWnZoknadlu7v0cLypum3hNH/vu9QOcOzdKk7Zo7v3WxH/A6sGLeC6zYhnANeC+wcmv698INScuW9fvgzEfhWvPmzeu8bbe1/LW/Lkr/fT+60rKydOvWbbnHjeHjI9o23rh46i5oq6fBgwfn5ZdfzsUXX5x99903/fr1S5I89dRTOeaYYzJz5tK3kisLxSorK3PooYemadOmue666z71enUxbty4ev3Az1uc7NFwHyhaToe9j0mHvY9p0H2+9tq4NG+gK3rR3Pm5efOj67XN5FFPZ9P9B6Zyyswsqlx12NP/+APTdfet88yFv8vkvzyZg/96aXb/6Xfyly+dU6jm18a9liYtKgptW5sN4Roo8jyvDbrsulUG/XxY5r/7QcZc/vtMvGPVN7qt7/UxZ+6itPrs/36aMmuUlCQ3nLdnykpLcuyZD6eqaunIzUuufyFf+o+euXD4zrnnH5M/9VQR4157LS1b1O+POa6BFXMNrL3WxPP/aS1aVJWeQ27NtLfrMdK8pCS777BxHr1xzd0MfV18/pM1dw00VD+wpvqApP79gGtg5da1a2BDeR1I1o3XgmN/9HD+967x9dqmWdOyjHv4lnRst+bqLOLR0TOyx3G13yagtqkeP2nyqK+m+8YtM2PmvGy67y31Pv4l//W9fOer19Z7uzWpqqo6fT//h/r9XJeUZPst22f0raNX+sHlhrYu9gNeB1bMe4EV2xCuAe8FVm5NvxfYkDz11FP1Wv+VV17JyJEjM2TIkDrfo+1nP/tZgcrWLhf84uZ8WDknXbt0zZQpU5Z73Bg+fvutRx99tPB+BG31NGLEiPzud7/L5MmTM2DAgGy55ZaZP39+xo8fnyFDhqRnz565//77s91229W6/bx583LwwQdn4sSJeeSRR9K1a9dPtR5rj8l/fTo7/uDIfDBhWqb94/mVrtu6V5fs+MMj886z4/LiVbenuqoqYy7/fXb64VHpf/yBefk3qzHhYp03/vcPZfzvH2rsMurs1GO3ye47bJwRP30yr0z8oGZ5VVV1jjvrH6tlupD1nWtgw7YuPP9NmpTmW4dtkXN/Wb+5zb9zRPFpsjck68I18HH6gIbnGmBduAa+85X+9Q7ajtiv11ofsiXJ9lt2SEnJp5r5/1PZaauOjXPgeigtLcmJR/TP6T95sl7bfeeI/ms0ZFtXrQt9wMd5HWh4rgHWtWuAZfXp0yf3339/WrcuOM8ya526zyVIkqR79+555JFHctBBB6WioiKTJk1K+/btc8011+See+7Ja68tnQu3tqBt0aJFOfzww/P000/nvvvuy1ZbbVXrMeq6HmuXt554OW16d02PIbvknadeXfGKJSUZ9LNhKS0tzaPDr0p11dIh2y/+4o7MHDM+O/7wyLTusQ5PDg0fs2WvjXLeSTvmn8+9nctrGaUydsKsnPvL0dlr5645+Uh93frINbDhOvnIAenXY6M6r7/Xzl1y+H49V19BNAp9AK6BDdcu23TKMZ/vU+f1O7aryDkn7rAaK2o4rVo0yVa92zbKsZuUl2bbfg18I93V5Ntf3iLb9K37zfo+s02nHF2Pa4Z1g9cBXAOwvPLy8rRr1y7l5cZBrS88kwX0798/d99993LLKysrM2nSpJSWlmbrrbdepq2qqipHHXVU/va3v+Xee+/NLrvsUuu+67oea5/qJVWZ+tBzS7+uWvF8xwNOODgb77Jlnj7vpnww7v9u8lhdVZVHh1/1qaeQhLXJKxM/SPOBN650nYt+83wu+s3KR4Gy7nINbLjab9Qs9/9q/+x/wv157Y0PVrru7jtsnD//bHCaNilbQ9WxpugDcA1suEpKSvLrHw/KvAWL88dRk1a6bqd2Fbn36v2y+aZt1kxxDeCog/rkh1c8vcaP+6XBPdK8Yt34U07rlk1z39X754AT78+L499f6bo7D+iYu67cd505N+rO6wCuAVjelClT8tOf/jSnnHJKunfv3tjl0ACMaGtAL730Uqqrq9O3b9+0aNFimbaTTjopf/jDH3LKKaekRYsW+de//lXz75133qn3eqyd3rzvybz5lxXPybtR327ZccRX8/bTr+alX921XPus16ZkzOW/T5ddB6T/8QeuzlIBYLXr2a11/vnbg3P2t3dIl47Nl2vvs1mbXH7aLnng2gPSrk2zRqgQgNWpaZOy3HrpPvnNj/fIDlt2WK59o9ZN872jB+TpWw7NzgM6NUKFxR3/xX5pUr7m/6Syrk2z3G3jlnn0xs/nvGE7pvvGy983vucmrXLx9wbmod8cmE7tl3+vAADro8rKyjzyyCOprKxs7FJoID4q1IBeeOGFJLVPG3nfffclSS666KJcdNFFy7Rdf/31Oe644+q1HmunN+7+50rbPxg3NTf1OnKl67xw5Z/zwpV/bsiyAKDRtN+oWX580o4581vb529PTMu0d+amrLQkPbu1yh47dklpqfuwAKzPSktLMvSL/fKNL/TN0y/NzCsTZ2XBwqp0al+RwZ/ZJC1bNGnsEgvp3KF5vjakd73vQ/dpbLdF++yxU5c1dryGslHrpjnzWzvk+0O3y9+fmp4pb81JSUmyWZdW2WvnLikr8xlwAGDdJmhrQCsL2iZNmlSnfdR1PVibVC2cn9cv+2rmTx6b0qbNU75R52x24i9T0XX5+fUXvvNm3rzmpMyf+lpKSsvSaciJ6fz5k5MkM/50ad79+41JVVUqum2RHt+9PuWt2q7hswFgdWjSpDQHDDIlBsCGqqSkJAO37pSBW69bI9dW5uJTBubuf0zOex8sWO3HKi0tyTVn7Z6SknX3Ayrl5aXZd9dujV0GAECDE7Q1oJUFbRuS+oQur52zXxa/PyMpLU1Z89bZ9D+vSIveO9S5nbVHp/2+lTY7DUlJSUnevueqvHHVN7PFfz+0zDrV1dWZcOEX0+Ww76fd7l9Okiya9VaS5MMxo/Lu367Plpc+kbIWrTP99+dn2m9/lM1O+MWaPpXllDVrkr1+dUo26ts9S+YvzPyZH+Sf3/+fzJ40Ix2375PPnD80pU2bpKyiScbf8ve8ePUdSZLWvbpkt0tPSLONWqasomkmP/BMnv6vm5Lq6gz62Unpuue2mTLqmfzzjGuXO2Z5i4ocPeG3uaHr4UmS7U89Ii9c9ecsWbAoSbLVtz6fLY/bP4vnzM+d+56+5r4ZG7DSpuUZeM6x6fa57bNkwcK8N/aNPDLsirTu1SV7/PzkNGvfOotmz82jw6/KrNemJEm67bNDdvz+11JSUpKS8rK8ePUdmfCHh5Mkhz95dZYsWJQXf3lnxv3ub8sdr/vgnbL1iYfkL4edk1bdO6XbPjvk1f/9a037Hr8Ynk0GbZ2JdzyWJ8++YY18DzZk+gH2veWsNO/UNqmqyqI58/PEmdflvRcnrrBvSPQB65Nm7Vpl/9//3/1zy5o3S+seG+eWbY7PwHO+ng7bbp5UVaVq8ZI88983Z/qjL9Ssu/2pR6TXFwelauGizH9vdu4//NwkroF1kX6AT+rSsUWu/P6uOeoHD9Vruxkz5y7zf12cduzW+cy2net1HBrOyl4HFs6qXGFfv7L3iQfc9uO07N4x42/5e5776R+XO2bbLTbN4Jt+kD/u8p00bdMiW3x9v7xw1e017TufdUx6Hrpb3nthYh78xiWr9fw3VLucNzSb7b9zWm3aOXcOPi3vvTQpyf/130vmL0ySPH/lnzPpzsdX2ray3yc+vp3XhLWXfgBYGUFbA3rwwQcbu4S1Rl1ClyTpffrva0Ysvf/PP2fSz4/LVj9/rs7trB1Km1Zko53/755yLft9Nm/dftly681+7m8pKW9WE7IlSZO2GydJ5k58Lq22GpSyFq2TJBvtdGBe/dHn1oqgLUlevWlUpj74bJJky28ckN0vPzF/Oeyc7Hbpt/Pspbdm8l+fTtO2rfLFR36eyQ88kw9em5KBZ389b973ZF7+zb0pa9Ykn7/vokzf+4Wa/bz0yzsz9n/uqdPxtz/tiIz9n7tr/sA+9tq7894LE7PLfx23Ws6X5e30o6OT6ur8afelIzCbd2qbJNntkm/ntd+OyvjfP5QeB302g34+LHcP+X6SZM+rvpu/HHZu3n/5jbTq3ilffOTneePeJ7J4zvwkycMn/LTml7WVabVp52xxzL7L/CL1yEk/z/anHpGmG7VYyZY0JP3Ahu3hb12ehR8u/YPoZkN2yaCfnZQ7B5+2wr4h0QesTxa8X7lMoD3ghEPSZdetsnBWZZ4654aaa6P91r2y/+/PzsgBQ5Pq6vT/5oFpt1WP3LH3/0vVosXLXB+Ja2Bdox+gNl87sHcefHJafvPn1+q8zcCv3VmvYwzaYeP8+Ds71rc0GtDKXgdW1tev7H1ikjx1zg0rvc/7R5q2aZlthn1xmT+wP33eTZn16uRsNmSXBjtPlvXGPf/Mi1ffngPvOH+5tpX13ytqW9HvE3XZ58d5TWgc+gEaUqdOnTJ8+PB06rT+jPTf0AnaaHB1DV2SLDMt4JK5HySfmAZjVe2snd6+++dpu8uhyy2fP3lsyjfqlNcv/WrmT301zTr3TPehl6dZl95puflOeee+q7Po/Rkpb7tx3n345lTNm53Fs99Leev2jXAW/2fJgkU1b4aT5J3R47L1iYckSaqrk6YbLb2pd3mLZqlauDgL36/8d1t1mrRZ+ia3rKJpSpuUZ97bs1Z4nH7H7JutTzgki+bMzxv3PVGzfNeLv5UkGXL7ealeUpW/fvW8zH/3wwY9R1auvHmz9P3aPvnDjt+uWTbvnVmp6NAmHbbbPH/96nlJkjfu+Vc+e8Hxad2zS2ZPmvHv62PpNdCkdYvMf392qhYurvUYJeVl+cx538gme26XBR9U5q0nXq5p2/WSb6Vlt445ZNSlqZw6Mw8ed/FqPFtqox/goz+uJ0nT1i2S6uoV9g0f0Qesv/oeuU9GX3BzklqujY/Z+sRDc/+Xz03VoqXP+8evj09yDaz99APUpqSkJNecvXvmzl+ckfe93uD732XrTrn7qv1S0cyfb9YmH38dWFlfv7L3ibXZ/tQj0vtLe2Rh5dxl3nvuesm3Ut6qIoeMujRVS6py9wFnrIaz4pPe+tfLq16pjlb2+0RtvCas/fQDfBodOnTIUUcd1dhl0IC8U2O1W1Ho8pGJP/16Zr/w9yRJ37PvrXc7a5fpf7ggC6aPT4/zlp/qoLpqcWY//2C2vPRfab7ZgLxz36/y+iVHpP9Pnk7rbffOxl84LePP+3xSWpZ2n/1ikqSkbO3rprb65oF58/6lnzZ67JRfZJ8bzsgOZ3w1Fe3b5J9nXFvzhurJs6/Pf9z4g2z59f3SdKOWee5nt+W9FyfWus+2W2yaHU47Infue3rmvT0rO/7gyJq2f55xbbb4+n657wtnLfMHHtac1j27ZOGsymz73S+l657bZsn8hRlz2e+z8MM5mffW+6leUlWzbuXUmWnZrWNmT5qRh0/4Sfb+zelZPHdBmm7UMn8//tKaN96ftMXR+6bN5t1y++dOSZLsO/LMmrZ/jrg2u/zXcaYHXIvoBzZMg644OV13G5AkGXX0BSvsGz6aNlAfsH7qtPMWabZRy0we9UzNsp1+eFR6HLxrmm3UMn//5mVJdXWatGqe5p02yqb7D0zPz++aJHnpmrtqppb6JNfAukE/QG3Kykpz0wV7pUvH5vnpTS812H4P+dxm+e2Fe6V1y6YNtk8+vY+/Dqyqr1/Z+8RP6v4fO6bHwbvmrv1HZFHlvOxx1Xdr2v454toc8sBl+oC1yKArTk5JSfLOs+PzzAU3Z8HHPgS3sraPfPz3idp4TVi76Qf4tD788MM8+eST2WWXXdKmTZvGLocGUNrYBbDueWXErhlzdMda/y18Z/Iy634UunT7+oUr3F+vU/432143Od2OPj9T/nf5T2Osqp21x4w/X5ZZ//xT+px9X0qbLT9dQdOOm6VF7x3SfLOlf5xov/cxmfv66FQvXjoNWucDv5P+P3k6/S97Iq22+VyadOieshZr14vNNt/9Ulr37JJn/v2ppW2GfTHPXHBz/rjzibn9c6dkxzO+lo36dU+SbHnsAZl4+6P5/Q7fyh8HnpjeX9ojXffcttb9dh20TaY8+GzNSJdXbrx/jZwPdVNSXppWm3bOrHFTcvcBZ+SJM6/LXteckpKyshVvU1aa7b53eP5+/KX548ATc/+Xf5w9rvxumrVvXev6XffYJhP+8FCqFi1O1aLFGXeL6YjXVvqBDdej370yf9j5hIy+eGR2PvPoFfYNFR030gesx/p+bZ+M/8PDy3zI4pkLbs6fdh2Wh779k+x81jEpbVKekvKylDYpT3lF09xz0A/y8Ld/kl1+fFzabdWj1v26BtYN+gFWpKysND85/bN58NdD0qtb7c9xXbVt3TT/+9975vafDxayrYU+/jqwqr5+Ze8TP6nrHttk0p2PZ1HlvCRLpxlk7XTfF8/Onf9xau7cb0QWvDc7e/x8WJ3aPvLJ3ydq4zVh7aYf4NOaNm1afvjDH2batGmNXQoNRNBGvW15yT+z/W9n1vqvaadNa9ZbVejySR32OTazX/h7Fn/4bqF2Gtdbd/wk7z8yMn3/a9QyU35+XJudhmThu1Oy8N2pSZIPn7k3Fd37p6S8SZJk0XvTkyRVC+Zm2u/OTpcvjVgjtdfVgBMOSY8DP5MHjvrvLJm3MM3at85mQ3bJxD8/miSpfPPtvDN6XDoP3DJJsuU39s/43z+UJJn/7oeZ+rfR6fLvT0CvUnX16jgFCpozdWaqlizJ67c9kiR578WJqXzz7bTq3jHNN26XkrL/ezlt1a1j5kydmfZb90rzjdvVTDfy7nMTMnf6u2m/da+6HdQ1sFbSD5AkE/7wcLrsNiBzp79Xa9/Qrv9m+oD1VHmLivQ6ZLeMX8Efu6Y/8kKatGqedv03y8JZlVlUOS8TbvtHkqRyyjt5+6lX03H7PnU7mGtgraYfYEX23mWTPH/bF3Px9wbWO3Dr0LZZzhi6bcbefliOObhvStw6Ya3zydeBlfX1q3qfuEr6gLXWnKkzkyTVi5dk7P/cnY0/079Obcnyv0/UmethraEfAGojaGO1qEvosrhyVha++3+p/ax/3Z7y1h1S9u/7ca2qnbXHwplTMuW6U7N4zqy8dubeGfu97fPyaZ9Jkky7+ey8c9+vkiRlFS3T48RfZfx5B2Xs8O3y9t1Xpvdpt9Ts57Vz98tLwwZk7PDt0qr/oHQ6aPlPfjWWrb79+fT64u7561f+q2batoWz5mTx3PnpsvvWSZJm7Vun4459MuuVN5Mks994O9323iHJ0nt8ddl965q2T5r+6AvptvcONTfM3eLr+y3TvnD23Jr7PLHmLXhvdqY/+mI2+dx2SZbefLrVZp3z9lOv5r0XJmbzw/ZMkvQ46LOZM/29zJ40I3OmzkyLjdtlo77dkiydfrJ1j43z4YTaP6007R/PZ/PD9qz5NFyfr+xT07aw0vO/NtAPbLiatmmR5hu3q3m82QEDs+D9ysyf+UGtfcMH46bqA9ZTvQ7dLe+NnZQPxi99HkvKy9K6Z5ea9o7b90lFhzaZ/cZbSZLXb3+0pg9o2rZVOu7QJ++PfaPWfbsG1m76AeqjVYsmGTF024y/58u59xf7ZdjXtspnt+2U5hXLzoZQXl6S7bZon6Ff7JffXrhXpoz6ai763sB07eS5Xlt98nUgWXFfv6r3iZ807R/Pp+fBu6a8ZUWSpN/R+9a0LaycV3O/XxpXefNmafqx/rjXFwfl3X9PDb+ytqT23ydWxGvC2ks/ANTGTyYN7qPQpWmX3nntzL2TJCXlzdL/sieSJJOu/Gba7nJImvfaLq9f8uVULZyXkpLSlLfplD5n3l3zqb0lcz9YaTtrj6Ydu2enO2r/lM0mR/3XMo/b7LBfttphv1rXHXDFCw1eW0No0bV9djn3uHw4aUYO+OO5SZIlCxfnnoN+sHSKqLOPSWlZWUqblGXs/9yTd555LUny6PCr8pn/Pj5bffPAlDYtz+T7n87E2x+r9RizXp2cMZf/PkNuPy+L5szPG/c9sUz7S7+6K/vdcnaWzFuQv371vMyvZY53Vq9/jrgmu//kO9n5zKNTXVWdf464JnNnvJfHR1yTQT87Kdt890tZVDkvj37vF0mS+TM/yOOn/yqfu+b/pbqqOiWlJfnXj35T8wnHT3rt5gfSbstN88WHf1Zzs+uO226eJHl/7BuZ9eqUHPr3n2T2G2+52XUj0A9s2Jq0aZHPXXtqyiuaprqqOvPf/TB/+/e02CvqG5LoA9ZDfb/2H3nt5gdqHpc2Kcugnw9L0zYtUr14SRbNXZC//+flWfjBnCTJ6Atuzu4/PSlbHrd/kuSFq27PzDHja923a2Dtph+giNLSkgzZY9MM2WPpzC+LF1flvQ8WZP7CJWnapDTt2jRLs6Yrnoqctc8nXweSlff1K3uf+ElTH3w2nXbom0P+emkWVs7N1AefTbJ0NNTCWZWZ8MeHc+iDl2fRnPm5+wC31VgTdr3kW+n+Hzuleee22XfkmVlUOS9//ep52fvXp6ekrDQlJUs/WPfoyVcmSSo6bbTCtpX9PlEbrwlrL/0AUBtBGw1uZaFLkvQ8+dc1X/e/7MkVrtesc4+VtsOaMnf6e7mh6+G1tk1/5IXcvX/tb27ee3Fi7jv0zFrbavPaTaPy2sfm337+p3+s+fq5n/whz/3kD3XeFw2v8s23c//h5y63/MMJ03LvwT+qdZuJtz+2wlDlk6oXL8m/fvDr2tuWVNX8MY/GoR/YsM2ZMjP3HFj7H0FW1Dck+oD10b2HLNvfL5m3cKU/4wver6zzH79cA2s3/QANoby8NJ07NG/sMvgUPvk6kKy8r1/Z+8TajLn89xlz+e9rHj978f/NAPP4ab+qR6U0hH+OuLbW5Xftd3qtyyvffHuFbSv7faI2XhPWXvoBGkKzZs2yxRZbpFmzZo1dCg3E1JEAjWDh7LnZ4tj9s+vF3yq0/Vbf+nw+e9E3M/+92Q1cGWvK/Hc/zB5XfTd9j/yPQtvv8Yvh6X3YHlk4e14DV8aaoh/YsOkDcA3gGoAN24JZldnxh0dlu1PqHr583M5nHZNtTv5iFsyqbODKaAxeEzZM+oENV69evXLTTTelV6863ruXtZ4RbQCN4Mmzrk9yfeHtx157d8Zee3fDFcQad/eQ73+q7R856ecNVAmNRT+wYdMH4BrANQAbtr8ff+mn2v7p827K0+fd1EDV0Ni8JmyY9AOw/jCiDQAAAAAAYA149dVXs/vuu+fVV19t7FJoIII2AAAAAACANaC6ujqLFi1KdXV1Y5dCAymp9myut+bMmZNWrVolSSorK9OyZcs6b1tdncxfsroqWz0qypKSkobZV3V1dRbPW9AwO1tDyps3S0lDfQOyYVwD6+LzXFR9r4/q6urMnbd4NVbU8Fo0L6/3z4BrYMVcA+uXhn6NWF9sKM9/Uuwa2BD6AdfAyq1r14DXgZXzWgC121D6gQ3hdSDxXmBlNoRrwHuBlfNeYPV56qmn6rX+K6+8kq9//ev53//932y55ZZ12mbgwIFFSlurXPCLm/Nh5Zy0adUyPzzpqOUeN4ZPk6F8nHu0UauSkqT5Bnx1lJSUpEmLisYuo1FtCNeA53nFSkpK0rJFk8YuY7VzDayYa4ANged/5TaEfsA1sHKuAWBDoB9YMa8DuAYA6sbUkQAAAAAAAFDAej5eBQAAAAAAYO3Qs2fPjBw5Mt26dWvsUmgggjYAAAAAAIA1oKKiIptvvnljl0EDMnUkAAAAAADAGjB9+vScf/75mT59emOXQgMRtAEAAAAAAKwBH3zwQe6888588MEHjV0KDUTQBgAAAAAAAAUI2gAAAAAAAKAAQRsAAAAAAAAUUN7YBQAAAAAAAKyLBg4cWK/1u3fvnnPOOSeDBw9O165dV1NVrEmCNgAAAAAAgDWga9euOffccxu7DBqQqSMBAAAAAACgAEEbAAAAAAAAFCBoAwAAAAAAgAIEbQAAAAAAAFCAoA0AAAAAAAAKELQBAAAAAABAAYI2AAAAAAAAKEDQBgAAAAAAAAUI2gAAAAAAAKAAQRsAAAAAAAAUIGgDAAAAAACAAgRtJEluuumm7LTTTmnXrl2aN2+e/v375yc/+Umqq6sbuzQAAAAAAIBl3Hvvvdl+++3TrFmz9OzZMz/5yU8apY7yRjkqa53OnTvnrLPOyhZbbJFmzZrlkUceyXe+852UlZVl+PDhjV0eAAAAAABAkuTpp5/OoYcemtNOOy0jR47ME088kRNOOCEtWrTICSecsEZrEbSRJNl///2Xedy7d+/cfvvteeihhwRtAAAAAADAWuMnP/lJBg4cmAsvvDBJ0r9//7z00ku56KKL1njQZupIllNdXZ0nn3wyjz32WPbee+/GLgcAAAAAAKDGY489lgMOOGCZZQcccEDeeOONTJkyZY3WYkQbNT744IN069YtCxcuTFVVVc4555x897vfbeyyAAAAAACAdcD8BQsz68PK5ZZXVVXV/D/jnfeWe/xxbdu0SkWzpis9zvTp09OlS5dlln30ePr06enevXvhc6gvQRs1WrdunTFjxmTu3Ll5/PHH84Mf/CCbbLJJjj/++MYuDQAAAAAAWMuVlpbmt7ePysz3Pqi1vXLuvPzsuj+u8HH7jVpn+DcOW+11NiRBGzVKS0vTp0+fJMm2226b999/Pz/60Y8EbQAAAAAAwCo1bVKerxy0d3752ztSVV1dr21Lkhzx+b3TbBWj2ZKka9eumTFjxjLL3nrrrZq2Nck92lihqqqqzJ8/v7HLAAAAAAAA1hGbbtI5e++2Q7232/Mz26Vn9y6rXjHJ7rvvnvvvv3+ZZX/5y1/So0ePNTptZGJEG/92zjnnZI899kjv3r2zaNGi/OMf/8jFF1+cb3zjG41dGgAAAAAAsA7ZZ9cd8+rrkzNl+jt1Wr9r5w7Zd9DOdd7/Kaeckt122y0/+tGPcswxx+SJJ57IlVdemZ/+9KdFSy6spLq6nmP3WGfMmTMnrVq1SpJUVlamZcuWK1z3lFNOyV133ZWpU6emoqIivXv3ztChQ3PCCSekrKxsTZUMAAAAAACsB955d1auuOG2LFq8ZKXrlZWV5uRjv5QundrXa//33HNPfvjDH+aVV15Jly5dMnz48Py///f/6rx9fTKUlRG0rcca6iIBAAAAAACor3+Ofil3jHpspesc+LnPZM/PbLeGKvo/DZWhuEcb9bJg4aK898Hsxi4DAAAAAABYy312h63Sr9eK75nWa9OuGTRwmzVYUcMTtFEv/xz9Ui679pb87bHRjV0KAAAAAACwFispKcnhQ/ZK84pmy7U1a9okRxz0uZSWrttR1bpd/ceUlJSkpKQkSXLXXXdljz32SJs2bdKxY8ccfvjhmTBhQs26d999d/baa6+0bds2bdq0yaGHHppx48atcN8LFizIz3/+8+y2225p27ZtKioqssUWW+T000/PzJkza93miSeeyBlnnJGBAwemS5cuadasWbp165YjjjgiTz311AqPde+99+bAAw9M586d06RJk3To0CH9+/fP0KFD89hjKx9eubotWLAw/3jyuVRVVafdRq0atRYAAAAAAGDt16Z1y3xxv0HLLT9k8O5pt1HrRqioYa0392j7KGS76qqrMmzYsGyyySbZeOON88orr2TevHnp1q1bnn322dx888055ZRT0rVr13Tp0qWmvUuXLnn++efTqVOnZfb71ltvZciQIXn22WdTWlqaTTfdNG3atMlrr72WBQsWZLPNNsvf//739O7de5nt+vTpkwkTJqR9+/bp2rVrmjZtmjfffDPvvvtuysvLc8stt+Swww5bZpurr746J510UpKkQ4cO6dGjR+bNm5fJkyensrIy3/72t/OrX/2qzt+Thr5H20P/GpO/PPxkOrbbKKd888spW8dTZgAAAAAAYM245a4HM2bs+CTJgH49c/QX9q3JdhqDe7StwIgRI3LjjTdm6tSpGT16dKZMmZKBAwdm6tSpGTp0aH74wx/mxhtvzLRp0zJ69OhMnjw5O+20U2bMmJHLL798mX1VV1fnK1/5Sp599tkcdNBBmTBhQiZNmpTnn38+M2fOzNChQ/Pmm2/m6KOPXq6Os88+O+PGjcu7776bF198MaNHj87bb7+dP/3pT6moqMg3v/nNVFZW1qy/ePHinHnmmUmWBm5vvfVWnnnmmYwdOzYffvhhHn744ey///6r95u3Eh+NZkuSfXbbQcgGAAAAAADU2SH77p6NWrdMq5bN88X992jUkK0hrXcj2k4++eRcccUVy7T95S9/yZAhQ1bYft999+XAAw/Mtttum+eee65m+b333puDDjooW2+9dZ566qlUVFQss92SJUuyyy67ZPTo0Xn00Uez++6716nWs846K+eff35GjhyZr371q0mSGTNmpGvXrmnXrl3ee++9+p38Cnw8jT3n8l+nabOKVWyxYgsWLsyChYtSWlKSVi2bJ1k/fgAAAAAAAIA1Y/HiJalOdZqUlzd2KVm4YH5+fOo3kySXXnNzTvvWkYX20/hn0sC++c1vLrdsxx13rFP766+/vszy2267LUly7LHHLheyJUlZWVkOOeSQjB49Og899NByQdu4ceNyyy235Lnnnsu7776bRYsWJUnefvvtJMmYMWNqgrZOnTqloqIis2bNyqhRo7LvvvvW+Zzr4sM5c9N00ZJPvZ+q6up8WDm3ASoCAAAAAAA2RPOyoLFLyMKF/1dDZeW8wvtZ74K2zTfffLllH7/vWm3tnTt3TpJlpnJMkueffz5Jcv311+f222+v9XhvvfVWkmTq1KnLLL/88svz/e9/P4sXL15hre+++27N12VlZRk+fHguvvji7Lffftlxxx0zePDgDBo0KHvttVfatGmzwv3URZuWLQqPaDOaDQAAAAAAWJ8sXFBW83WrVs0L72e9mzpyRadTpL1v374ZP358nY5/7LHH5oYbbkiSPPbYYxk0aFDKysry4x//OIceemh69uyZli1bpqSkJNddd12OP/74ZbZJkqqqqlx99dX5xS9+kVdeeaVmebNmzXLkkUfmsssuS/v27etUT9IwN/JbsGBhLr5mZObOW5AjDvpcdty6X733AQAAAAAAsDZpiAwlWQ9HtDWkj77Bd955Zw4++OA6b3fTTTclSU499dT86Ec/Wq794yPZPq60tDTDhg3LsGHDMmXKlDzyyCMZNWpUfv/73+f666/P5MmTM2rUqDrX0aJFi5pRei1atKjzdh/3z2fHZu68BenYbqNst1WfQvsAAAAAAABYmzREhpII2lZqwIABGTNmTF588cV6BW0TJ05MkgwaNKjW9n/961+r3Ef37t3zta99LV/72tdy6qmnZptttskDDzyQiRMnplevXnWqo6SkJC1btsyVN/4pswvNL1qd2f++H9ucefNy8S9HFtgHAAAAAADA2qt1q+Y5+dgvFdpW0LYShx9+eG6++eZce+21Ofnkk2tGuK1K8+ZL5/KcMWPGcm3jxo3L3XffXa86BgwYkI022iizZs3KtGnT6hy0fWR25bx8WDmnXtt80rz5CzMvCz/VPgAAAAAAANYngraVOPTQQ7PXXnvl4Ycfzn777Zdrr702W2+9dU17VVVVnnjiidx4440ZMWJEevfunSTZY489cscdd+TCCy/MPvvsk8033zxJ8tJLL+Wwww5LaWnpcscaO3ZsfvrTn+b444/PZz7zmZp7xi1ZsiRXXnllZs2alYqKigwYMKDe59G60E38lo5mq07SvKJpmpQ3KbAPAAAAAACAtVuxHGWpkurq6uoGrKXRfBRMreh0irbPnDkzhx56aB5//PEkSY8ePdKlS5fMmzcvEyZMyJw5S0eKvfzyy9lyyy2TJLNnz86OO+6Y8ePHp0mTJtliiy1SVVWVl19+OV27ds13vvOdnHnmmTn22GNz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AAAAgFUpqa6urm7sItY1Rx55ZEaOHJlhw4blyiuvXK59++23z3PPPZdevXrl9ddfb4QKl5ozZ05atWqVJKmsrEzLli3rvO3ixYtz22231WndBx54IL/+9a9rHnfo0CH/+Z//me222y4lJSV1Pt4DDzyQkSNHLhPWJXUP2Q477LCUl8uOAQAAAACANcM92urp5Zdfzq233pqOHTvmwgsvrHWdnXbaKUmWCYamTJmSYcOGZZdddkmzZs1WGkA98MAD+exnP5uKiop07tw5J5xwQj744IOGPZEG8vjjjy8Tsu2555659NJLs/3229c5ZEuS8vLyHHDAAbnggguWCwSPPfZYI9kAAAAAAIC1jqCtnkaOHJmqqqocddRRNaPFPql58+ZJlg3axo8fn9tuuy1dunTJwIEDV7j/hx9+OAcccEC6deuWP//5z/nv//7v/PGPf8wXvvCFrG2DD99+++1ce+21NY8PPvjgnHjiiWnRokWh/S1evDi33npr5syZs8zyv/71r1m8ePGnqhUAAAAAAKChmWevnh588MEkyd57773CdaZMmZJk2aBtzz33zPTp05Mk5557bh577LFat/2v//qv9O3bN3/4wx9SWro0B+3QoUMOO+yw3HPPPfn85z/fIOfxaVVVVeWaa67J/Pnzkyw9vyOPPLJeo9g+bvHixbniiivy5JNPJlk6XWTbtm3zzjvv5M0338yf/vSnHHHEEQ1WPwAAAAAAwKdlRFs9vfHGG0mSHj161Nq+ePHimhDt40HbR6HZqjzxxBMZPHjwMuvvt99+SZLbb7+9SMmrxeOPP56XXnopSdKxY8ccd9xxDRqynXbaaTn11FNTVlaWZOm5z5gxo2GKBwAAAAAAaABGtNXTR9Mazps3r9b2W2+9NTNnzkzr1q3Tq1eveu+/rKwsTZs2XWZZkyZNUlJSUhNsFdG3b986h31J0rRp0xXegy5J7r///pqvjz/++E81XWRtIdtHIeUhhxySP//5z6mqqsoDDzyQo48+eoX76tevXxYuXFioDgAAAAAAYMPUpUuXPP3004W2FbTVU5cuXfL+++9n9OjR2XXXXZdpmz59ek4//fQkybbbbltohFe/fv3yxBNPLLPsqaeeSnV1dd57773CdX80bWVdNWvWbIVtEydOzLhx45Ikm222WbbffvtCNa0qZEuSIUOG5K677srixYvz0EMP5YgjjlguiPzItGnTsmDBgkK1AAAAAAAA1JegrZ4GDx6cl19+ORdffHH23Xff9OvXL8nSMOyYY47JzJkzk6Rw+PTd7343X//613P++efnhBNOyJQpU/Kd73wnZWVl9RqR9kldu3at94i2FXn88cdrvt5vv/0KBYp1CdmSpE2bNvnsZz+bRx99NJWVlXn++eez884717rPTTbZxIg2AAAAAACgXrp06VJ4W0FbPY0YMSK/+93vMnny5AwYMCBbbrll5s+fn/Hjx2fIkCHp2bNn7r///uUCo7o6+uij89JLL+W8887LWWedlbKyspx00klp2rRp2rRpU7jucePGpWXLlnVef/HixbnttttqbZswYULN1ysKvVa177qEbB8ZOHBgHn300Zpjr+iYr732WsrLXdIAAAAAAMCaUXyI1Aaqe/fueeSRR3LQQQeloqIikyZNSvv27XPNNdfknnvuyWuvvZYkhYO2kpKSXHTRRZk5c2aee+65vPXWW7n88sszbty47Lbbbg15KoVUVVVl4sSJSZL27dunbdu29dq+viFbkmXudffRsQEAAAAAABqb4T8F9O/fP3ffffdyyysrKzNp0qSUlpZm6623/lTHaN26dbbddtskyf/8z/9k3rx5+cY3vvGp9tkQZs2alXnz5iVJevToUa9ti4RsSdKpU6e0aNEic+fOzdSpU4sVDgAAAAAA0MAEbQ3opZdeSnV1dfr165cWLVos1/7HP/4xSTJ27NhlHvfs2bNmOsSnn346o0aNyo477pjFixfngQceyBVXXJHLLrssm2+++Ro6kxWrqqrKxhtvnIULF6ZDhw712q5IyJYsHeXXuXPnfPjhh/UeQQcAAAAAALC6CNoa0AsvvJBkxdNGfvnLX6718bHHHpsbbrghSdKsWbPcddddufDCC7N48eJss802ufXWW3P44YevvsLroWPHjvn5z39e7+1KS0vTu3fvPPnkk/UK2T5y0UUX1fuYAAAAAAAAq5OgrQGtKmirrq5e5T622WabPP744w1a19riC1/4QsrKyrLZZpsVvocdAAAAAADA2kLQ1oBWFbSRHHzwwY1dAgAAAAAAQIMQtDWgBx98sLFLAAAAAAAAYA0pbewCAAAAAAAAYF0kaAMAAAAAAIACBG0AAAAAAABQgKANAAAAAAAAChC0AQAAAAAAQAGCNgAAAAAAAChA0AYAAAAAAAAFCNoAAAAAAACgAEEbAAAAAAAAFCBoAwAAAAAAgAIEbQAAAAAAAFCAoA0AAAAAAAAKKKmurq5u7CJYPebMmZNWrVolSSorK9OyZcs6b1tdXZ0lS5Y0WC2XXnNrZs+Zk9YtW+b0b39luccNoaysLCUlJQ2yLwAAAAAAgFUpb+wCWDuVlJSkvLzhLo/qJFXVS/8vLy9f7jEAAAAAAMC6xtSRAAAAAAAAUICgDQAAAAAAAAoQtAEAAAAAAEABgjYAAAAAAAAoQNAGAAAAAAAABQjaAAAAAAAAoABBGwAAAAAAABQgaAMAAAAAAIACBG0AAAAAAABQgKANAAAAAAAAChC0AQAAAAAAQAGCNgAAAAAAAChA0AYAAAAAAAAFCNoAAAAAAACgAEEbAAAAAAAAFCBoAwAAAAAAgAIEbQAAAAAAAFBAeWMXAGuj6urqLFmypLHLqJeysrKUlJQ0dhkAAAAAALDBELRBLZYsWZLbbrutscuol8MOOyzl5X6kAQAAAABgTTF1JAAAAAAAABQgaAMAAAAAAIACBG0AAAAAAABQgKANAAAAAAAAChC0AQAAAAAAQAGCNgAAAAAAAChA0AYAAAAAAAAFCNoAAAAAAACgAEEbAAAAAAAAFCBoAwAAAAAAgAIEbbCO+OCDD7Jw4cLGLgMAAAAAAPi38sYuANZns2fPzvjx4/P666/njTfeSGVlZZYsWZKmTZtm4403Tq9evdK7d+/06NEjpaUrzr3ff//9nHfeeenYsWNOO+20NG3adA2eBQAAAAAAUBtBGzSw6urqvPLKKxk1alSeeOKJLFmypNb1XnjhhZqvO3funH333Td77bVX2rRps8x6H4Vs06ZNy7Rp03LdddflhBNOWK3nAAAAAAAArJqpIz+FmTNnZsSIEenTp08qKiqy6aabZvjw4ZkzZ06OP/74lJSU5KqrrmrsMlmDpk2blnPPPTc//vGP8/jjj68wZPukt99+OzfffHNOOumk3HnnnamqqkqybMiWJB07dsyXvvSl1VY/AAAAAABQd0a0FTRmzJgMGTIkM2bMSMuWLbPVVltl2rRpueKKKzJhwoS89957SZLtt9++cQtljaiurs59992XkSNHZtGiRTXL27Rpk9122y19+/ZNr1690rFjx5SWlmb+/PmZPHlyXn/99YwZM6ZmdNuiRYvyu9/9Lk8++WSOPvroXHvttcuEbGeffXY6d+7cKOcIAAAAAAAsS9BWwMyZM3PwwQdnxowZOfXUU3POOeekdevWSZJLLrkkZ5xxRsrLy1NSUpJtt922katldauqqsr//M//5O9//3vNss6dO+eII47IZz7zmTRp0mS5bVq1apX+/funf//+OeiggzJ9+vTcd999GTVqVKqrqzN+/Pj8+Mc/TnV1dRIhGwAAAAAArI1MHVnAd7/73UyZMiXDhg3LZZddVhOyJcmIESOy3XbbZfHixenZs+dy99ti/VJdXb1cyHbAAQfkkksuyaBBg2oN2WrTtWvXDB06NOeee25NmPZRyNa2bVshGwAAAAAArIUEbfX08ssv59Zbb03Hjh1z4YUX1rrOTjvtlCTZbrvtapb98Y9/zGGHHZYePXqkRYsW2XLLLfOjH/0olZWVy2xb1/VYO9x77701IVtZWVmGDx+e4447LhUVFYX217lz55SVlS2zbMmSJWnRosWnrhUAAAAAAGhYgrZ6GjlyZKqqqnLUUUelVatWta7TvHnzJMsGbZdddlnKyspywQUX5L777suJJ56YX/7ylznggANSVVVV7/VofNOmTcstt9xS83jYsGHZddddC+/v/fffz3nnnZfp06cnScrLl87sOnv27Nxwww2fqlYAAAAAAKDhuUdbPT344INJkr333nuF60yZMiXJskHbXXfdlU6dOtU83muvvdKpU6ccddRRefTRR7PnnnvWaz0aV3V1da655posWrQoydLpIhsiZJs2bVqSpfdkGz58eC688MLMnTs3jz76aHbbbbfsuOOODVI/AAAAAADw6Qna6umNN95IkvTo0aPW9sWLF+exxx5LsmzQ9vHw7CM777xzkmTq1Kn1Xq+++vbtm9LSxhvA+MVvfC8tW7XJ9BnT07179+Uer22aNm26wqlBk+SVV17Jq6++miTZeOON89WvfrXwsWoL2T66J9uxxx6bX/7yl0mSO+64Y6VBW79+/bJw4cLCdQAAAAAAwIaoS5cuefrppwttK2irpzlz5iRJ5s2bV2v7rbfempkzZ6Z169bp1avXSvf10b29+vfv3yDrrcxH0xE2lqolS2r+nzp16nKP1zbNmjVbafuoUaNqvj788MML35NtZSFbkuy555656667MmXKlLz66qt54403VhjyTps2LQsWLChUBwAAAAAAUH+Ctnrq0qVL3n///YwePXq5qQKnT5+e008/PUmy7bbbpqSkZIX7mTp1as4666wccMAB2X777T/1eqvStWvXRh3RVlpWVvN/t27dlnu8tmnatOkK22bPnp0nnngiSdK6det89rOfLXSMVYVsSVJSUpL99tsv1113XZLkb3/7W4YOHVrr/jbZZBMj2gAAAAAAoJ66dOlSeFtBWz0NHjw4L7/8ci6++OLsu+++6devX5LkqaeeyjHHHJOZM2cmyUpDscrKyhx66KFp2rRpTYDyadari3HjxqVly5afah+fxgW/uDkfVs5J1y5dM2XKlOUer20WL16c2267rda28ePHZ8m/R+TtvvvuadKkSb33X5eQ7SODBg3KDTfckKqqqrzyyisr3Odrr72W8nI/0gAAAAAAsKY03hCnddSIESPSoUOHTJ48OQMGDMg222yTvn37Zpdddknv3r2zzz77JFn2/mwfN2/evBx88MGZOHFi/vrXv6Zr166faj3WvNdff73m6759+9Z7+/qEbEnSokWLmvvYTZkyxag1AAAAAABYSwja6ql79+555JFHctBBB6WioiKTJk1K+/btc8011+See+7Ja6+9lqT2oG3RokU5/PDD8/TTT+e+++7LVlttVesx6roejWPSpEk1X6/qPnyfVN+Q7SO9e/dOklRVVWXy5Mn1KxgAAAAAAFgtzDNXQP/+/XP33Xcvt7yysjKTJk1KaWlptt5662XaqqqqctRRR+Vvf/tb7r333uyyyy617ruu69F4Kisra77u2LFjnbcrGrIlSYcOHWo9PgAAAAAA0HgEbQ3opZdeSnV1dfr165cWLVos03bSSSflD3/4Q77//e+nRYsW+de//lXTtvnmm6dTp071Wo/Gc9xxx+XDDz/MokWL6nV/tmeeeaZQyJYsvRdc375907Rp02y22WaF6gYAAAAAABqWoK0BvfDCC0lqnzbyvvvuS5JcdNFFueiii5Zpu/7663PcccfVaz0aT48ePQptN3jw4FRWVuaBBx6oV8iWJJtsskk22WSTQscFAAAAAABWD0FbA1pZ0Pbx+3qtTF3XY930hS98Ifvtt99yIx4BAAAAAIB1T2ljF7A+WVnQBh8RsgEAAAAAwPrBiLYG9OCDDzZ2CQAAAAAAAKwhRrQBAAAAAABAAYI2AAAAAAAAKEDQBgAAAAAAAAUI2gAAAAAAAKAAQRsA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CgAAAAAA+LTWm3u0fRSyXXXVVRk2bFg22WSTbLzxxnnllVcyb968dOvWLc8++2xuvvnmnHLKKenatWu6dOlS096lS5c8//zz6dSp0zL7feuttzJkyJA8++yzKS0tzaabbpo2bdrktddey4IFC7LZZpvl73//e3r37r3Mdn369MmECRPSvn37dO3aNU2bNs2bb76Zd999N+Xl5bnlllty2GGHLbPN1VdfnZNOOilJ0qFDh/To0SPz5s3L5MmTU1lZmW9/+9v51a9+VefvSUPfo+3+fzyVv//z2XTt3CHfPe5LNd/zuqiurs4Nf/xLXn19cpJk/z0HZu9dd/hU9axrnn/l9fzujgeSJBt3bJdhx35xgwgaAQAAAABgfbXejGj7yIgRI3LjjTdm6tSpGT16dKZMmZKBAwdm6tSpGTp0aH74wx/mxhtvzLRp0zJ69OhMnjw5O+20U2bMmJHLL798mX1VV1fnK1/5Sp599tkcdNBBmTBhQiZNmpTnn38+M2fOzNChQ/Pmm2/m6KOPXq6Os88+O+PGjcu7776bF198MaNHj87bb7+dP/3pT6moqMg3v/nNVFZW1qy/ePHinHnmmUmWBm5vvfVWnnnmmYwdOzYffvhhHn744ey///6r95u3Eh8fzTZ4953qFbIlS4PQw4bslRbNm6VHt42z52e2Wx1lrtW23bJ3dhjQZ+lovs9vGKP5AAAAAABgfbbejWg7+eSTc8UVVyzT9pe//CVDhgxZYft9992XAw88MNtuu22ee+65muX33ntvDjrooGy99dZ56qmnUlFRscx2S5YsyS677JLRo0fn0Ucfze67716nWs8666ycf/75GTlyZL761a8mSWbMmJGuXbumXbt2ee+99+p38ivw8RFt51z+6zRtVrGKLVZswcKFWbBwUUpLS9OqRfPC+1m0eHHKSktTWrreZbx1Ul1dnUWLF6dpkyaNXQoAAAAAAJCkdavmOfnYLxXadr0bUvPNb35zuWU77rhjndpff/31ZZbfdtttSZJjjz12uZAtScrKynLIIYdk9OjReeihh5YL2saNG5dbbrklzz33XN59990sWrQoSfL2228nScaMGVMTtHXq1CkVFRWZNWtWRo0alX333bfO51wXH86Zm6aLlnzq/VRVVeXDyjkNUNGGbf6ChY1dAgAAAAAA8Cmtd0Hb5ptvvtyyj993rbb2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", + "text/plain": [ + "
" + ] + }, + "execution_count": 52, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "circ_dd.draw(output=\"mpl\", style=\"iqp\", idle_wires=False)" + ] + }, { "cell_type": "markdown", "metadata": {}, "source": [ + "## Step 3: Execute using Qiskit Primitives.\n", "\n", + "At this point, we have a list of circuits transpiled for our system. In the following cell, we create an instance of the sampler primitive, and start a batched job using the context manager (`with ...:`), which automatically opens and closes the Batch for us. This is where we pass the `skip_transpilation=True` argument.\n", "\n", - "## Run user-transpiled circuits using Qiskit Runtime\n", - "\n", - "At this point, we have a list of circuits (named `circuits`) transpiled for `ibm_algiers`. In the following cell, we create an instance of the sampler primitive, and start a session using the context manager (`with ...:`), which automatically opens and closes the session for us. This is where we pass the `skip_transpilation=True` argument.\n", - "\n", - "Within the context manager, we sample the circuits and store the results to `result`." + "Within the context manager, we sample the circuits and store the results to `result`.\n" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 53, "metadata": {}, "outputs": [], "source": [ - "from qiskit_ibm_runtime import Sampler, Session\n", - "\n", - "with Session(service=service, backend=backend):\n", + "with Batch(service=service, backend=backend):\n", " sampler = Sampler()\n", " job = sampler.run(\n", " circuits=circuits, # sample all three circuits\n", @@ -1179,1500 +238,29 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Finally, we can plot the results from the device runs against the ideal distribution. You can see the results with `optimization_level=3` are closer to the ideal distribution due to the lower gate count, and `optimization_level=3 + dd` is even closer due to the dynamic decoupling we applied." + "## Step 4: Post-process, return result in classical format.\n", + "\n", + "Finally, we can plot the results from the device runs against the ideal distribution. You can see the results with `optimization_level=3` are closer to the ideal distribution due to the lower gate count, and `optimization_level=3 + dd` is even closer due to the dynamic decoupling we applied.\n" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 54, "metadata": {}, "outputs": [ { "data": { - "image/svg+xml": [ - "\n", - "\n", - "\n", - " \n", - " \n", - " \n", - " \n", - " 2022-04-13T19:59:47.404379\n", - " image/svg+xml\n", - " \n", - " \n", - " Matplotlib v3.5.1, https://matplotlib.org/\n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - " \n", - "\n" - ], + "image/png": "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6nQuJPv27Ys6depg1qxZyMnJwUcffYSEhAQcOnQI/fv3L/ISjY4dO75woRw/Pz94enpiy5YtahPVMzMzERkZCXNzc4wcOVJuT0lJwZUrVzT2O378eADAggULcP/+fbk9NTUVy5cvh0KhwBtvvKFD5kRERERERJWbzhffHzx4EN27d0dYWBhef/31El3H369fP9SoUeP5AZmaYu3atQgICECXLl0wePBg2NrayqOcS5YsURtJnDZtGjZu3Ij169dj9OjRcnvHjh3x4YcfYunSpWjevDn69euH/Px87N27F2lpaYiMjESDBg10TZ+IiIiIiKjS0rmQvHr1apGrqj5P06ZN0bRp0xf28/X1xalTpzBr1ixs27YN+fn5aNasGRYuXIiQkJASH+/TTz9Fs2bN8OWXX2LDhg2QJAmtWrXCV199hddff12r2ImIiIiIiOgJnW//8eabb6J///5FropaaP/+/fjhhx/wzTff6BygscjKyoK9vT2X4CYiInoJ45Zp/xxjvf0HXtPbHdSKxM8eRGTMdJ4juWHDBrU5jEX5448/sHHjRl0PQUREREREREaoVG/o9PjxY94DiYiIiIiIqIJ5qSpPkqQi24UQSE5OxqFDh164uA4RERERERGVL1qNSCoUCpiYmMi39pg9e7b8+Ok/pqamqFu3Ln7//XcMHjy4VAInIiIiIiIiw9BqRLJLly7yKGRsbCxq166tdiuOQiYmJnByckL37t0xbtw4vQRKRERERERExkGrQvL48ePyvxUKBcaMGYOZM2fqOyYiIiIiIiIyYjrPkVSpVPqMg4iIiIiIiMqJUl21lYiIiIiIiCqeEo9Ivvnmm5AkCZGRkXB1dcWbb75ZoudJkoR169bpHCAREREREREZlxIXkhs2bIAkSfjoo4/g6uqKDRs2lOh5LCSJiIiIiIgqlhIXktevXwcAuLu7qz0mIiIiIiKiyqXEhaSHh8dzHxMREREREVHlwMV2iIiIiIiISCslHpG8ceOGzgepXbu2zs8lIiIiIiIi41LiQrJOnTqQJEnrA0iShIKCAq2fR0RERERERMapxIXkyJEjdSokiYiIiIiIqGLR6vYfRERERERERFxsh4iIiIiIiLTCQpKIiIiIiIi0UuJLW998801IkoTIyEi4urrizTffLNHzJEnCunXrdA6QiIiIiIiIjItWcyQlScJHH30E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rl1T6/PHH39oPE+SJF0PSUREREREREZA50Ly6fsdERERERERUeVRsW7oRERERERERKVOb4XkjRs3EBsbq6/dERERERERkZHSWyG5fv16+Pr66mt3REREREREZKR4aSsRERERERFphYUkERERERERaYWFJBEREREREWlFb4Wkvb09ateura/dERERERERkZHSWyE5ceJEXL9+XV+7IyIiIiIiIiPFS1uJiIiIiIhIK6Yl7Vh4j8i2bdvC0tJSq3tGdunSRfvIiIiIiIiIyCiVuJDs1q0bJEnC5cuX0aBBA/lxSSiVSp0DJCIiIiIiIuNS4kJy5syZkCQJ1apVU3tMRERERERElUuJC8nZs2c/9zERERERERFVDlxsh4iIiIiIiLSicyH54MEDXLt2Dfn5+Wrt27Ztw7Bhw/DWW2/h999/f+kAiYiIiIiIyLiU+NLWZ02dOhWbNm3CnTt3YGZmBgBYtWoVwsPDIYQAAGzduhW//fYbGjZsqJ9oiYiIiIiIyOB0HpE8ceIEevToASsrK7ltwYIFcHd3R2xsLLZv3w4hBBYvXqyXQImIiIiIiMg46DwimZKSgsDAQPnx5cuXkZycjEWLFsHHxwcAsHPnTq3uN0lERERERETGT+cRydzcXJibm8uPT5w4AUmS4O/vL7d5enri1q1bLxchERERERERGRWdC8maNWvi4sWL8uP9+/fDyckJzZs3l9v+/fdf2NjYvFyEREREREREZFR0vrS1V69e+PLLLzF58mRYWlrip59+wsiRI9X6XL16FbVr137pIImIiIiIiMh46FxITps2Dfv27cPSpUsBAG5ubvjkk0/k7WlpaTh9+jTCw8NfPkoiIiIiIiIyGjoXktWrV8elS5dw9OhRAECXLl1gZ2cnb8/IyMDixYsREBDw8lESERERERGR0dC5kASAKlWqoG/fvkVua9y4MRo3bvwyuyciIiIiIiIjpPNiO0RERERERFQ5vdSIpFKpxPbt2xEdHY3bt28jNzdXo48kSfLlr0RERERERFT+6VxIPnz4EP7+/vj5558hhIAkSRBCyNsLH0uSpJdAiYiIiIiIyDjofGnrvHnzcPbsWcyZMwcZGRkQQmD27NlISUnBtm3b4OnpiUGDBhU5SklERERERETll86F5A8//ID27dtjxowZcHJykttdXV0xaNAgxMTEIDo6GosXL9ZLoERERERERGQcdC4kb9y4gfbt2//fjhQKtdHHmjVrok+fPti4cePLRUhERERERERGRedC0traGgrF/z3d3t4eKSkpan2qV6+OGzdu6B4dERERERERGR2dC0kPDw+1IrFp06Y4duyYPCophMDRo0fh5ub28lESERERERGR0dC5kPTz80NMTAwKCgoAAKNGjcKNGzfQoUMHTJkyBT4+Prhw4QLeeOMNvQVLREREREREhqfz7T/GjRuHqlWrIj09HW5ubnjzzTdx/vx5rFy5EhcuXAAAvPHGG5g9e7aeQiUiIiIiIiJjoHMhWb9+fXz00UdqbV988QVmzpyJa9euwcPDA9WrV3/pAImIiIiIiMi46FxIFsfZ2RnOzs763i0REREREREZCZ3nSBIREREREVHlpPOIpKenZ4n6SZKEhIQEXQ9DRERERERERkbnQlKlUkGSJI32zMxM3L9/HwDg5uYGc3NznYMjIiIiIiIi46NzIZmYmPjcbR9++CHu3LmDI0eO6HoIIiIiIiIiMkKlMkeyTp062LZtG+7du4fp06eXxiGIiIiIiIjIQEptsR0zMzP07NkT27dvL61DEBERERERkQGU6qqtjx49wt27d0vzEERERERERFTGSq2QPHnyJL7//nt4e3uX1iGIiIiIiIjIAHRebKd79+5FthcUFODWrVvyYjwzZ87U9RBERERERERkhHQuJI8fP15kuyRJcHR0hL+/Pz788EP07NlT10MQERERERGREXqp+0gSERERERFR5aNzIVkoLS0Nt27dgkqlgru7O6pXr66PuIiIiIiIiMhI6bTYTm5uLhYtWoT69evDzc0Nr776Ktq2bQt3d3dUq1YNH3zwgTxHkoiIiIiIiCoWrQvJ5ORktGnTBtOmTUNCQgLc3NzQtm1btG3bFm5ubrh79y6WL1+OV199FdHR0fLzUlJSeE9JIiIiIiKiCkCrQjI/Px+9e/fGX3/9hSFDhuDy5cu4efMmzp49i7Nnz+LmzZu4fPkyhg0bhrt376J///5ITExEQkICfHx8cOXKldLKg4iIiIiIiMqIVnMkV69ejUuXLmHWrFmYNWtWkX28vb3x3XffoUGDBpg1axaGDRuGxMREZGRkoHXr1noJmoiIiIiIiAxHqxHJ7du3w8vLq0T3hpwxYwbq16+Ps2fP4vHjx4iKikKfPn10DpSIiIiIiIiMg1aF5N9//w1/f39IkvTCvpIkyX1/+eUXdOvWTdcYiYiIiIiIyIhoVUhmZ2fD3t6+xP3t7OxgamoKLy8vrQMjIiIiIiIi46RVIeni4oJ//vmnxP0TEhLg4uKidVBERERERERkvLQqJDt06IBDhw4hNTX1hX1TU1Nx4MAB+Pj46BwcERERERERGR+tCsnx48cjOzsbr7/+OjIyMort9++//+L111/Ho0ePEBYW9tJBEhERERERkfHQ6vYfvr6+GDduHNasWYNGjRohLCwM3bt3R61atQAAycnJOHr0KNasWYOMjAyEhoZykR0iIiIiIqIKRqtCEgBWrlwJOzs7fPbZZ5g/fz7mz5+vtl0IAYVCgcmTJ2tsIyIiIiIiovJP60LSxMQEixcvRmhoKDZs2ICzZ8/KcyarV6+Ojh07YtSoUahfv77egyUiIiIiIiLD07qQLFS/fn3897//1WcsREREREREVA5otdgOEREREREREQtJIiIiIiIi0goLSSIiIiIiItIKC0kiIiIiIiLSCgtJIiIiIiIi0goLSSIiIiIiItIKC0kiIiIiIiLSCgtJIiIiIiIi0goLSSIiIiIiItIKC0kiIiIiIiLSCgtJIiIiIiIi0orRFpLnzp1D79694eDgAGtra7Rv3x7bt28v0XOFEDh06BDefvttNG/eHPb29rCyskKLFi0QGRmJx48fl3L0REREREREFZepoQMoSkxMDAICAmBpaYnBgwfD1tYWu3btQkhICJKTkzFp0qTnPj83Nxe9e/eGhYUFunXrhoCAADx+/BhRUVGYPn069uzZg+PHj8PKyqqMMiIiIiIiIqo4jK6QLCgowLhx46BQKBAbG4uWLVsCAGbOnIm2bdsiIiICAwcOhIeHR7H7MDExwbx58/DOO+/A0dFRbs/Pz8cbb7yBffv24csvv8SUKVNKOx0iIiIiIqIKx+gubT127BgSEhIwdOhQuYgEAHt7e0RERCAvLw8bN2587j7MzMwwffp0tSKysH3atGkAgBMnTug9diIiIiIiosrA6ArJ48ePAwD8/f01tgUEBAB4uSLQzMwMAGBqanSDsUREREREROWC0VVT8fHxAID69etrbKtevTpsbGzkPrr45ptvABRdqD4tNzcXubm58uOsrCwATy6Pzc/PBwAoFAqYmJhAqVRCpVLJfQvbCwoKIISQ201MTKBQKIptL9xvocJit6CgoETtZmZmUKlUUCqVcpskSTA1NS22vbjYmRNzYk7MiTkxp9LJyQwVRVmcJyIiY2V0hWRmZiaAJ5eyFsXOzk7uo61Dhw5h9erVaNSoEd56663n9p0/fz7mzJmj0X748GF5kZ7atWujVatWuHjxIm7cuCH38fb2RsOGDfHrr78iPT1dbm/ZsiU8PDwQGxuLBw8eyO0dOnSAi4sLDh8+rPZLw9fXF1WqVMHBgwfVYujduzdycnIQExMjt5mamqJPnz7IyMjA2bNn5XZbW1t0794dycnJuHDhgtzu7OyMjh07Ij4+HnFxcXI7c2JOzIk5MSfmVLo5vYaKorTP02+//VYmeRAR6UIST3/1aAT8/f1x5MgRxMfHw8vLS2O7u7s7srOztS4mz507Bz8/P5iamuLkyZNo0qTJc/sXNSJZq1YtZGRkwM7ODoCxf+P7RMX6Fps5MSfmxJyYU3nP6Z0vtR+R/HjVbK2fs7b9B1r1nz/AQetjqPopS/U83b17F1WrVkVmZqb82YOIyFgY3Yhk4UhkcYViVlaWxiI6L/K///0P/v7+UCgUiIqKemERCQAWFhawsLDQaDczM5PnWRYyMTGBiYmJRt/i5mEW1/7sfnVpVygUUCg0p74W115c7MyJOWnbzpyYE8CciotR2/bKlFN5ZqjzRERkDIzuHb1wbmRR8yBTU1ORnZ1d5PzJ4vzvf/9Dz549oVKpEBUVhTZt2ugtViIiIiIiosrI6ArJrl27AngyF/FZUVFRan1epLCIVCqV+Omnn9CuXTv9BUpERERERFRJGV0h6efnB09PT2zZskVtonpmZiYiIyNhbm6OkSNHyu0pKSm4cuWKxqWwv/32G3r27ImCggIcOnQIHTp0KKsUiIiIiIiIKjSju/je1NQUa9euRUBAALp06YLBgwfD1tYWu3btQlJSEpYsWYI6derI/adNm4aNGzdi/fr1GD16NADg7t276NmzJ+7fv4/AwEAcOXIER44cUTuOg4MDJk6cWHaJERERERERVRBGV0gCT5YVP3XqFGbNmoVt27YhPz8fzZo1w8KFCxESEvLC52dlZeHevXsAgJ9++gk//fSTRh8PDw8WkkRERERERDowykISANq2bYtDhw69sN+GDRuwYcMGtbY6derAyO5qQkREREREVGEY3RxJIiIiIiIiMm4sJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrLCSJiIiIiIhIKywkiYiIiIiISCssJImIiIiIiEgrpoYOgIiIqKQSvGdr1b9enHb9iYiIqGQ4IklERERERERaYSFJREREREREWmEhSURERERERFrhHEkiIjKIccu0f87Heo+CiIiIdMFCshKaNipT6+fM32hfCpEQkbbF1JqJpREFlQdcaIiIiIwJC0kjww+VRET6wy/OiIiISgfnSBIREREREZFWWEgSERERERGRVnhpKxERURkri4WGeFkvERGVJo5IEhERERERkVZYSBIREREREZFWeGkrEVE5ou0tIABgbfsPtH4OL3EkIiKi5+GIJBEREREREWmFI5JUrvEG3drRdvENjkoRERERUVE4IklERERERERa4YhkOafLfCnoMF+KyJjo8v++so9GExEREekTC0mqVHhfNSIiIiKil8dCkogMqixuzE5ERERE+sU5kkRERERERKQVjkgSGQFdRuXWTNR3FEREREREJcNCkowGL3EkIiIiIiofWEgSlVNcsZeIiIiIDIWFJJXMXkm7/q+J0onDELTNHag4+TN37VSU3AH+zGurouRfmXMnIiKtcLEdIiIiIiIi0goLSSIiIiIiItIKC0kiIiIiIiLSCgtJIiIiIiIi0goLSSIiIiIiItIKV20lokph2qhMrfrPH1BKgRARERFVAByRJCIi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", "text/plain": [ - "
" + "
" ] }, - "execution_count": 6, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "from qiskit.visualization import plot_histogram\n", - "\n", "binary_prob = [quasi_dist.binary_probabilities() for quasi_dist in result.quasi_dists]\n", "plot_histogram(\n", " binary_prob + [ideal_distribution],\n", @@ -2690,21 +278,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We can confirm this by computing the [Hellinger fidelity](https://qiskit.org/documentation/stubs/qiskit.quantum_info.hellinger_fidelity.html) between each set of results and the ideal distribution (higher is better, and 1 is perfect fidelity)." + "We can confirm this by computing the [Hellinger fidelity](https://docs.quantum-computing.ibm.com/api/qiskit/quantum_info) between each set of results and the ideal distribution (higher is better, and 1 is perfect fidelity).\n" ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 57, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "0.927\n", - "0.938\n", - "0.951\n" + "0.958\n", + "0.961\n", + "0.963\n" ] } ], @@ -2717,16 +305,16 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'0.4.0'" + "'0.15.0'" ] }, - "execution_count": 8, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -2739,47 +327,32 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 30, "metadata": {}, "outputs": [ { "data": { - "text/html": [ - "

Version Information

Qiskit SoftwareVersion
qiskit-terra0.20.0
qiskit-aer0.9.1
qiskit-ignis0.7.0
qiskit-ibmq-provider0.18.3
qiskit-aqua0.9.5
qiskit0.34.0
qiskit-nature0.3.0
qiskit-finance0.2.1
qiskit-optimization0.2.3
qiskit-machine-learning0.2.1
System information
Python version3.9.10
Python compilerClang 13.0.0 (clang-1300.0.29.3)
Python buildmain, Jan 15 2022 11:48:00
OSDarwin
CPUs8
Memory (Gb)32.0
Wed Apr 13 19:59:49 2022 BST
" - ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "

This code is a part of Qiskit

© Copyright IBM 2017, 2022.

This code is licensed under the Apache License, Version 2.0. You may
obtain a copy of this license in the LICENSE.txt file in the root directory
of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.

Any modifications or derivative works of this code must retain this
copyright notice, and modified files need to carry a notice indicating
that they have been altered from the originals.

" - ], "text/plain": [ - "" + "'0.45.0'" ] }, + "execution_count": 30, "metadata": {}, - "output_type": "display_data" + "output_type": "execute_result" } ], "source": [ - "from qiskit.tools.jupyter import *\n", + "import qiskit\n", "\n", - "%qiskit_version_table\n", - "%qiskit_copyright" + "qiskit.version.get_version_info()" ] } ], "metadata": { "kernelspec": { - "display_name": "primitives", + "display_name": "env", "language": "python", - "name": "primitives" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -2791,7 +364,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.7" + "version": "3.9.6" } }, "nbformat": 4, diff --git a/docs/tutorials/vqe_with_estimator.ipynb b/docs/tutorials/vqe_with_estimator.ipynb index 6dc49c243..57987e522 100644 --- a/docs/tutorials/vqe_with_estimator.ipynb +++ b/docs/tutorials/vqe_with_estimator.ipynb @@ -94,7 +94,7 @@ "id": "988ee237", "metadata": {}, "source": [ - "## Map the problem to a quantum-native format\n", + "## Step 1: Map classical inputs to a quantum problem\n", "\n", "Here we define the problem instance for our VQE algorithm. Although the problem in question can come from a variety of domains, the form for execution through Qiskit Runtime is the same. Qiskit provides a convenience class for expressing Hamiltonians in Pauli form, and a collection of widely used ansatz circuits in the [`qiskit.circuit.library`](https://docs.quantum-computing.ibm.com/api/qiskit/circuit_library).\n", "\n", @@ -180,7 +180,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Optimize the circuits and operators\n", + "## Step 2: Optimize problem for quantum execution.\n", "\n", "We can schedule a series of [`qiskit.transpiler`](https://docs.quantum-computing.ibm.com/api/qiskit/transpiler) passes to optimize our circuit for a selected backend. This includes a few components:\n", "\n", @@ -274,7 +274,7 @@ "id": "b4d480b3", "metadata": {}, "source": [ - "## Execute using a quantum primitive function\n", + "## Step 3: Execute using Qiskit Primitives.\n", "\n", "Like many classical optimization problems, the solution to a VQE problem can be formulated as minimization of a scalar cost function. By definition, VQE looks to find the ground state solution to a Hamiltonian by optimizing the ansatz circuit parameters to minimize the expectation value (energy) of the Hamiltonian. With the Qiskit Runtime [`Estimator`](https://docs.quantum-computing.ibm.com/api/qiskit-ibm-runtime/qiskit_ibm_runtime.Estimator) directly taking a Hamiltonian and parameterized ansatz, and returning the necessary energy, the cost function for a VQE instance is quite simple:" ] @@ -486,7 +486,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Analyze the results" + "## Step 4: Post-process, return result in classical format." ] }, { diff --git a/program_source/circuit_runner/__init__.py b/program_source/circuit_runner/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/qiskit_ibm_runtime/VERSION.txt b/qiskit_ibm_runtime/VERSION.txt index c5523bd09..249afd517 100644 --- a/qiskit_ibm_runtime/VERSION.txt +++ b/qiskit_ibm_runtime/VERSION.txt @@ -1 +1 @@ -0.17.0 +0.18.1 diff --git a/qiskit_ibm_runtime/__init__.py b/qiskit_ibm_runtime/__init__.py index 5980bcdd6..b9d32ba92 100644 --- a/qiskit_ibm_runtime/__init__.py +++ b/qiskit_ibm_runtime/__init__.py @@ -24,7 +24,7 @@ execute significantly faster within its improved hybrid quantum/classical process. Primitives and sessions ------------------------ +======================= Qiskit Runtime has two predefined primitives: ``Sampler`` and ``Estimator``. These primitives provide a simplified interface for performing foundational quantum @@ -38,8 +38,8 @@ Below is an example of using primitives within a session:: from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler, Estimator, Options - from qiskit.test.reference_circuits import ReferenceCircuits from qiskit.circuit.library import RealAmplitudes + from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.quantum_info import SparsePauliOp # Initialize account. @@ -49,15 +49,21 @@ options = Options(optimization_level=3) # Prepare inputs. - bell = ReferenceCircuits.bell() psi = RealAmplitudes(num_qubits=2, reps=2) H1 = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)]) theta = [0, 1, 1, 2, 3, 5] + # Bell Circuit + qr = QuantumRegister(2, name="qr") + cr = ClassicalRegister(2, name="qc") + qc = QuantumCircuit(qr, cr, name="bell") + qc.h(qr[0]) + qc.cx(qr[0], qr[1]) + qc.measure(qr, cr) with Session(service=service, backend="ibmq_qasm_simulator") as session: # Submit a request to the Sampler primitive within the session. sampler = Sampler(session=session, options=options) - job = sampler.run(circuits=bell) + job = sampler.run(circuits=qc) print(f"Sampler results: {job.result()}") # Submit a request to the Estimator primitive within the session. @@ -68,7 +74,7 @@ print(f"Estimator results: {job.result()}") Backend data ------------- +============ :class:`QiskitRuntimeService` also has methods, such as :meth:`backend`, :meth:`backends`, and :meth:`least_busy`, that allow you to query for a target @@ -76,76 +82,84 @@ that contains methods and attributes describing the backend. Supplementary Information +========================= + +Account initialization ------------------------- -.. dropdown:: Account initialization - :animate: fade-in-slide-down +You need to initialize your account before you can start using the Qiskit Runtime service. +This is done by initializing a :class:`QiskitRuntimeService` instance with your +account credentials. If you don't want to pass in the credentials each time, you +can use the :meth:`QiskitRuntimeService.save_account` method to save the credentials +on disk. - You need to initialize your account before you can start using the Qiskit Runtime service. - This is done by initializing a :class:`QiskitRuntimeService` instance with your - account credentials. If you don't want to pass in the credentials each time, you - can use the :meth:`QiskitRuntimeService.save_account` method to save the credentials - on disk. +Qiskit Runtime is available on both IBM Cloud and IBM Quantum, and you can specify +``channel="ibm_cloud"`` for IBM Cloud and ``channel="ibm_quantum"`` for IBM Quantum. The default +is IBM Cloud. - Qiskit Runtime is available on both IBM Cloud and IBM Quantum, and you can specify - ``channel="ibm_cloud"`` for IBM Cloud and ``channel="ibm_quantum"`` for IBM Quantum. The default - is IBM Cloud. +Runtime Jobs +------------ -.. dropdown:: Runtime Jobs - :animate: fade-in-slide-down +When you use the ``run()`` method of the :class:`Sampler` or :class:`Estimator` +to invoke the primitive, a +:class:`RuntimeJob` instance is returned. This class has all the basic job +methods, such as :meth:`RuntimeJob.status`, :meth:`RuntimeJob.result`, and +:meth:`RuntimeJob.cancel`. - When you use the ``run()`` method of the :class:`Sampler` or :class:`Estimator` - to invoke the primitive, a - :class:`RuntimeJob` instance is returned. This class has all the basic job - methods, such as :meth:`RuntimeJob.status`, :meth:`RuntimeJob.result`, and - :meth:`RuntimeJob.cancel`. +Logging +------- -.. dropdown:: Logging - :animate: fade-in-slide-down +``qiskit-ibm-runtime`` uses the ``qiskit_ibm_runtime`` logger. - ``qiskit-ibm-runtime`` uses the ``qiskit_ibm_runtime`` logger. +Two environment variables can be used to control the logging: - Two environment variables can be used to control the logging: + * ``QISKIT_IBM_RUNTIME_LOG_LEVEL``: Specifies the log level to use. + If an invalid level is set, the log level defaults to ``WARNING``. + The valid log levels are ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``, and ``CRITICAL`` + (case-insensitive). If the environment variable is not set, then the parent logger's level + is used, which also defaults to ``WARNING``. + * ``QISKIT_IBM_RUNTIME_LOG_FILE``: Specifies the name of the log file to use. If specified, + messages will be logged to the file only. Otherwise messages will be logged to the standard + error (usually the screen). - * ``QISKIT_IBM_RUNTIME_LOG_LEVEL``: Specifies the log level to use. - If an invalid level is set, the log level defaults to ``WARNING``. - The valid log levels are ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``, and ``CRITICAL`` - (case-insensitive). If the environment variable is not set, then the parent logger's level - is used, which also defaults to ``WARNING``. - * ``QISKIT_IBM_RUNTIME_LOG_FILE``: Specifies the name of the log file to use. If specified, - messages will be logged to the file only. Otherwise messages will be logged to the standard - error (usually the screen). +For more advanced use, you can modify the logger itself. For example, to manually set the level +to ``WARNING``:: - For more advanced use, you can modify the logger itself. For example, to manually set the level - to ``WARNING``:: + import logging + logging.getLogger('qiskit_ibm_runtime').setLevel(logging.WARNING) - import logging - logging.getLogger('qiskit_ibm_runtime').setLevel(logging.WARNING) +Interim and final results +------------------------- -.. dropdown:: Interim and final results - :animate: fade-in-slide-down +Some runtime primitives provide interim results that inform you about the +progress of your job. You can choose to stream the interim results and final result when you run the +program by passing in the ``callback`` parameter, or at a later time using +the :meth:`RuntimeJob.stream_results` method. For example:: - Some runtime programs provide interim results that inform you about program - progress. You can choose to stream the interim results and final result when you run the - program by passing in the ``callback`` parameter, or at a later time using - the :meth:`RuntimeJob.stream_results` method. For example:: + from qiskit_ibm_runtime import QiskitRuntimeService, Sampler + from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister - from qiskit.test.reference_circuits import ReferenceCircuits - from qiskit_ibm_runtime import QiskitRuntimeService, Sampler + service = QiskitRuntimeService() + backend = service.backend("ibmq_qasm_simulator") - service = QiskitRuntimeService() - backend = service.backend("ibmq_qasm_simulator") + # Bell Circuit + qr = QuantumRegister(2, name="qr") + cr = ClassicalRegister(2, name="qc") + qc = QuantumCircuit(qr, cr, name="bell") + qc.h(qr[0]) + qc.cx(qr[0], qr[1]) + qc.measure(qr, cr) - def result_callback(job_id, result): - print(result) + def result_callback(job_id, result): + print(result) - # Stream results as soon as the job starts running. - job = Sampler(backend).run(ReferenceCircuits.bell(), callback=result_callback) - print(job.result()) + # Stream results as soon as the job starts running. + job = Sampler(backend).run(qc, callback=result_callback) + print(job.result()) Classes -========================== +======= .. autosummary:: :toctree: ../stubs/ diff --git a/qiskit_ibm_runtime/accounts/management.py b/qiskit_ibm_runtime/accounts/management.py index f230e40a5..4992998a6 100644 --- a/qiskit_ibm_runtime/accounts/management.py +++ b/qiskit_ibm_runtime/accounts/management.py @@ -13,20 +13,17 @@ """Account management related classes and functions.""" import os -import ast from typing import Optional, Dict from qiskit_ibm_provider.proxies import ProxyConfiguration -from qiskit_ibm_runtime.utils.deprecation import issue_deprecation_msg from .exceptions import AccountNotFoundError from .account import Account, ChannelType -from .storage import save_config, read_config, delete_config, read_qiskitrc +from .storage import save_config, read_config, delete_config _DEFAULT_ACCOUNT_CONFIG_JSON_FILE = os.path.join( os.path.expanduser("~"), ".qiskit", "qiskit-ibm.json" ) -_QISKITRC_CONFIG_FILE = os.path.join(os.path.expanduser("~"), ".qiskit", "qiskitrc") _DEFAULT_ACCOUNT_NAME = "default" _DEFAULT_ACCOUNT_NAME_IBM_QUANTUM = "default-ibm-quantum" _DEFAULT_ACCOUNT_NAME_IBM_CLOUD = "default-ibm-cloud" @@ -196,15 +193,6 @@ def get( if account_name in all_config: return Account.from_saved_format(all_config[account_name]) - if os.path.isfile(_QISKITRC_CONFIG_FILE): - issue_deprecation_msg( - msg="Use of the ~/.qiskit/qiskitrc.json file is deprecated.", - version="0.15.0", - remedy="Please use the ~/.qiskit/qiskit-ibm.json file instead.", - period="1 month", - ) - return cls._from_qiskitrc_file() - raise AccountNotFoundError("Unable to find account.") @classmethod @@ -272,30 +260,3 @@ def _get_default_account_name(cls, channel: ChannelType) -> str: if channel == "ibm_quantum" else _DEFAULT_ACCOUNT_NAME_IBM_CLOUD ) - - @classmethod - def _from_qiskitrc_file(cls) -> Optional[Account]: - """Read account from qiskitrc file.""" - qiskitrc_data = read_qiskitrc(_QISKITRC_CONFIG_FILE) - proxies = ( - ProxyConfiguration(ast.literal_eval(qiskitrc_data["proxies"])) - if "proxies" in qiskitrc_data - else None - ) - save_config( - filename=_DEFAULT_ACCOUNT_CONFIG_JSON_FILE, - name=_DEFAULT_ACCOUNT_NAME_IBM_QUANTUM, - overwrite=False, - config=Account.create_account( - token=qiskitrc_data.get("token", None), - url=qiskitrc_data.get("url", None), - instance=qiskitrc_data.get("default_provider", None), - verify=bool(qiskitrc_data.get("verify", None)), - proxies=proxies, - channel="ibm_quantum", - ) - .validate() - .to_saved_format(), - ) - default_config = read_config(filename=_DEFAULT_ACCOUNT_CONFIG_JSON_FILE) - return Account.from_saved_format(default_config[_DEFAULT_ACCOUNT_NAME_IBM_QUANTUM]) diff --git a/qiskit_ibm_runtime/accounts/storage.py b/qiskit_ibm_runtime/accounts/storage.py index 256432997..127d92a81 100644 --- a/qiskit_ibm_runtime/accounts/storage.py +++ b/qiskit_ibm_runtime/accounts/storage.py @@ -16,7 +16,6 @@ import logging import os from typing import Optional, Dict -from configparser import ConfigParser from .exceptions import AccountAlreadyExistsError logger = logging.getLogger(__name__) @@ -58,16 +57,6 @@ def save_config( json.dump(data, json_out, sort_keys=True, indent=4) -def read_qiskitrc(qiskitrc_config_file: str) -> Dict[str, str]: - """Read credentials from a qiskitrc config and return as a dictionary.""" - config_parser = ConfigParser() - config_parser.read(qiskitrc_config_file) - account_data = {} - for name in config_parser.sections(): - account_data = dict(config_parser.items(name)) - return account_data - - def read_config( filename: str, name: Optional[str] = None, diff --git a/qiskit_ibm_runtime/api/clients/runtime.py b/qiskit_ibm_runtime/api/clients/runtime.py index b895b8644..e1e08c499 100644 --- a/qiskit_ibm_runtime/api/clients/runtime.py +++ b/qiskit_ibm_runtime/api/clients/runtime.py @@ -100,7 +100,7 @@ def program_run( **hgp_dict, ) - def job_get(self, job_id: str, exclude_params: bool = None) -> Dict: + def job_get(self, job_id: str, exclude_params: bool = True) -> Dict: """Get job data. Args: diff --git a/qiskit_ibm_runtime/fake_provider/backends/almaden/fake_almaden.py b/qiskit_ibm_runtime/fake_provider/backends/almaden/fake_almaden.py index d946722d9..920c53448 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/almaden/fake_almaden.py +++ b/qiskit_ibm_runtime/fake_provider/backends/almaden/fake_almaden.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeAlmadenV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/armonk/fake_armonk.py b/qiskit_ibm_runtime/fake_provider/backends/armonk/fake_armonk.py index 1765c6d28..4598f19b8 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/armonk/fake_armonk.py +++ b/qiskit_ibm_runtime/fake_provider/backends/armonk/fake_armonk.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeArmonkV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/athens/fake_athens.py b/qiskit_ibm_runtime/fake_provider/backends/athens/fake_athens.py index 2116b7f83..e3f7dcdca 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/athens/fake_athens.py +++ b/qiskit_ibm_runtime/fake_provider/backends/athens/fake_athens.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeAthensV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/auckland/fake_auckland.py b/qiskit_ibm_runtime/fake_provider/backends/auckland/fake_auckland.py index 5e360ce6b..bb852c6c2 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/auckland/fake_auckland.py +++ b/qiskit_ibm_runtime/fake_provider/backends/auckland/fake_auckland.py @@ -16,7 +16,7 @@ """ import os -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakeAuckland(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/belem/fake_belem.py b/qiskit_ibm_runtime/fake_provider/backends/belem/fake_belem.py index 11a24cce1..f5e40b8d3 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/belem/fake_belem.py +++ b/qiskit_ibm_runtime/fake_provider/backends/belem/fake_belem.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeBelemV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/boeblingen/fake_boeblingen.py b/qiskit_ibm_runtime/fake_provider/backends/boeblingen/fake_boeblingen.py index a2893c45a..83032992a 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/boeblingen/fake_boeblingen.py +++ b/qiskit_ibm_runtime/fake_provider/backends/boeblingen/fake_boeblingen.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeBoeblingenV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/bogota/fake_bogota.py b/qiskit_ibm_runtime/fake_provider/backends/bogota/fake_bogota.py index 7878eb24b..c90610c8d 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/bogota/fake_bogota.py +++ b/qiskit_ibm_runtime/fake_provider/backends/bogota/fake_bogota.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeBogotaV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/brooklyn/fake_brooklyn.py b/qiskit_ibm_runtime/fake_provider/backends/brooklyn/fake_brooklyn.py index 0be78ab70..4739b9c3f 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/brooklyn/fake_brooklyn.py +++ b/qiskit_ibm_runtime/fake_provider/backends/brooklyn/fake_brooklyn.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeBrooklynV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/burlington/fake_burlington.py b/qiskit_ibm_runtime/fake_provider/backends/burlington/fake_burlington.py index a04144d03..60a47c78a 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/burlington/fake_burlington.py +++ b/qiskit_ibm_runtime/fake_provider/backends/burlington/fake_burlington.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeBurlingtonV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/cairo/fake_cairo.py b/qiskit_ibm_runtime/fake_provider/backends/cairo/fake_cairo.py index 5f541baa2..e35320fd9 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/cairo/fake_cairo.py +++ b/qiskit_ibm_runtime/fake_provider/backends/cairo/fake_cairo.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeCairoV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/cambridge/fake_cambridge.py b/qiskit_ibm_runtime/fake_provider/backends/cambridge/fake_cambridge.py index e84118f26..cd4b70b28 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/cambridge/fake_cambridge.py +++ b/qiskit_ibm_runtime/fake_provider/backends/cambridge/fake_cambridge.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeCambridgeV2(fake_backend.FakeBackendV2): @@ -68,5 +68,5 @@ class FakeCambridgeAlternativeBasis(FakeCambridge): props_filename = "props_cambridge_alt.json" # type: ignore def __init__(self) -> None: - super().__init__() + super().__init__() # type: ignore self._configuration.basis_gates = ["u", "sx", "p", "cx", "id"] diff --git a/qiskit_ibm_runtime/fake_provider/backends/casablanca/fake_casablanca.py b/qiskit_ibm_runtime/fake_provider/backends/casablanca/fake_casablanca.py index 9f5f63b6c..15e00e183 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/casablanca/fake_casablanca.py +++ b/qiskit_ibm_runtime/fake_provider/backends/casablanca/fake_casablanca.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeCasablancaV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/essex/fake_essex.py b/qiskit_ibm_runtime/fake_provider/backends/essex/fake_essex.py index 9eaea225c..84d416578 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/essex/fake_essex.py +++ b/qiskit_ibm_runtime/fake_provider/backends/essex/fake_essex.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeEssexV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/geneva/fake_geneva.py b/qiskit_ibm_runtime/fake_provider/backends/geneva/fake_geneva.py index da59a6641..b237179c2 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/geneva/fake_geneva.py +++ b/qiskit_ibm_runtime/fake_provider/backends/geneva/fake_geneva.py @@ -16,7 +16,7 @@ """ import os -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakeGeneva(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/guadalupe/fake_guadalupe.py b/qiskit_ibm_runtime/fake_provider/backends/guadalupe/fake_guadalupe.py index ede18c92f..3d644e6e0 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/guadalupe/fake_guadalupe.py +++ b/qiskit_ibm_runtime/fake_provider/backends/guadalupe/fake_guadalupe.py @@ -16,7 +16,7 @@ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeGuadalupeV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/hanoi/fake_hanoi.py b/qiskit_ibm_runtime/fake_provider/backends/hanoi/fake_hanoi.py index d27209d45..0ee547d48 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/hanoi/fake_hanoi.py +++ b/qiskit_ibm_runtime/fake_provider/backends/hanoi/fake_hanoi.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeHanoiV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/jakarta/fake_jakarta.py b/qiskit_ibm_runtime/fake_provider/backends/jakarta/fake_jakarta.py index 81b676384..f1db79f18 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/jakarta/fake_jakarta.py +++ b/qiskit_ibm_runtime/fake_provider/backends/jakarta/fake_jakarta.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeJakartaV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/johannesburg/fake_johannesburg.py b/qiskit_ibm_runtime/fake_provider/backends/johannesburg/fake_johannesburg.py index bd0d0d7b7..2a1c9bbcd 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/johannesburg/fake_johannesburg.py +++ b/qiskit_ibm_runtime/fake_provider/backends/johannesburg/fake_johannesburg.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeJohannesburgV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/kolkata/fake_kolkata.py b/qiskit_ibm_runtime/fake_provider/backends/kolkata/fake_kolkata.py index 44d15387f..61320a400 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/kolkata/fake_kolkata.py +++ b/qiskit_ibm_runtime/fake_provider/backends/kolkata/fake_kolkata.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeKolkataV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/lagos/fake_lagos.py b/qiskit_ibm_runtime/fake_provider/backends/lagos/fake_lagos.py index 31e922553..a599f484a 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/lagos/fake_lagos.py +++ b/qiskit_ibm_runtime/fake_provider/backends/lagos/fake_lagos.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeLagosV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/lima/fake_lima.py b/qiskit_ibm_runtime/fake_provider/backends/lima/fake_lima.py index 08e9a661c..c74680e5f 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/lima/fake_lima.py +++ b/qiskit_ibm_runtime/fake_provider/backends/lima/fake_lima.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeLimaV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/london/fake_london.py b/qiskit_ibm_runtime/fake_provider/backends/london/fake_london.py index 69c183ef9..f3bfb6159 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/london/fake_london.py +++ b/qiskit_ibm_runtime/fake_provider/backends/london/fake_london.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeLondonV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/manhattan/fake_manhattan.py b/qiskit_ibm_runtime/fake_provider/backends/manhattan/fake_manhattan.py index 2d35b6751..89e03cfe7 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/manhattan/fake_manhattan.py +++ b/qiskit_ibm_runtime/fake_provider/backends/manhattan/fake_manhattan.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeManhattanV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/manila/fake_manila.py b/qiskit_ibm_runtime/fake_provider/backends/manila/fake_manila.py index 9fa70bf7d..43f180f6a 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/manila/fake_manila.py +++ b/qiskit_ibm_runtime/fake_provider/backends/manila/fake_manila.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeManilaV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/melbourne/fake_melbourne.py b/qiskit_ibm_runtime/fake_provider/backends/melbourne/fake_melbourne.py index d217a0c6c..2451d16dd 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/melbourne/fake_melbourne.py +++ b/qiskit_ibm_runtime/fake_provider/backends/melbourne/fake_melbourne.py @@ -23,7 +23,7 @@ BackendProperties, ) from qiskit.providers.fake_provider.fake_backend import FakeBackend -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakeMelbourneV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/montreal/fake_montreal.py b/qiskit_ibm_runtime/fake_provider/backends/montreal/fake_montreal.py index 1d25370b1..dedbb059a 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/montreal/fake_montreal.py +++ b/qiskit_ibm_runtime/fake_provider/backends/montreal/fake_montreal.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeMontrealV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/mumbai/fake_mumbai.py b/qiskit_ibm_runtime/fake_provider/backends/mumbai/fake_mumbai.py index 45769be54..70f3691f9 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/mumbai/fake_mumbai.py +++ b/qiskit_ibm_runtime/fake_provider/backends/mumbai/fake_mumbai.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeMumbaiV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/nairobi/fake_nairobi.py b/qiskit_ibm_runtime/fake_provider/backends/nairobi/fake_nairobi.py index 98d3ee02a..c2034ff28 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/nairobi/fake_nairobi.py +++ b/qiskit_ibm_runtime/fake_provider/backends/nairobi/fake_nairobi.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeNairobiV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/oslo/fake_oslo.py b/qiskit_ibm_runtime/fake_provider/backends/oslo/fake_oslo.py index 4c671d4ca..c3aae11bb 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/oslo/fake_oslo.py +++ b/qiskit_ibm_runtime/fake_provider/backends/oslo/fake_oslo.py @@ -16,7 +16,7 @@ """ import os -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakeOslo(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/ourense/fake_ourense.py b/qiskit_ibm_runtime/fake_provider/backends/ourense/fake_ourense.py index 19ca24b70..f76efc5c8 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/ourense/fake_ourense.py +++ b/qiskit_ibm_runtime/fake_provider/backends/ourense/fake_ourense.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeOurenseV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/paris/fake_paris.py b/qiskit_ibm_runtime/fake_provider/backends/paris/fake_paris.py index 239cc0d1d..25a8fedbc 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/paris/fake_paris.py +++ b/qiskit_ibm_runtime/fake_provider/backends/paris/fake_paris.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeParisV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/perth/fake_perth.py b/qiskit_ibm_runtime/fake_provider/backends/perth/fake_perth.py index fde49419c..e530f0a20 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/perth/fake_perth.py +++ b/qiskit_ibm_runtime/fake_provider/backends/perth/fake_perth.py @@ -16,7 +16,7 @@ """ import os -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakePerth(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/poughkeepsie/fake_poughkeepsie.py b/qiskit_ibm_runtime/fake_provider/backends/poughkeepsie/fake_poughkeepsie.py index 0d1d6da7d..a385d3af5 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/poughkeepsie/fake_poughkeepsie.py +++ b/qiskit_ibm_runtime/fake_provider/backends/poughkeepsie/fake_poughkeepsie.py @@ -23,7 +23,7 @@ BackendProperties, ) from qiskit.providers.fake_provider.fake_backend import FakeBackend -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakePoughkeepsieV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/prague/fake_prague.py b/qiskit_ibm_runtime/fake_provider/backends/prague/fake_prague.py index ebca5701a..d55f5feec 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/prague/fake_prague.py +++ b/qiskit_ibm_runtime/fake_provider/backends/prague/fake_prague.py @@ -16,7 +16,7 @@ """ import os -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakePrague(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/quito/fake_quito.py b/qiskit_ibm_runtime/fake_provider/backends/quito/fake_quito.py index 6f160e480..2c6c33edb 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/quito/fake_quito.py +++ b/qiskit_ibm_runtime/fake_provider/backends/quito/fake_quito.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeQuitoV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/rochester/fake_rochester.py b/qiskit_ibm_runtime/fake_provider/backends/rochester/fake_rochester.py index 166cda4a6..172ad59d6 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/rochester/fake_rochester.py +++ b/qiskit_ibm_runtime/fake_provider/backends/rochester/fake_rochester.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeRochesterV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/rome/fake_rome.py b/qiskit_ibm_runtime/fake_provider/backends/rome/fake_rome.py index 1bed17a5f..f49474126 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/rome/fake_rome.py +++ b/qiskit_ibm_runtime/fake_provider/backends/rome/fake_rome.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeRomeV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/santiago/fake_santiago.py b/qiskit_ibm_runtime/fake_provider/backends/santiago/fake_santiago.py index bf80342d6..255191e1a 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/santiago/fake_santiago.py +++ b/qiskit_ibm_runtime/fake_provider/backends/santiago/fake_santiago.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeSantiagoV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/sherbrooke/fake_sherbrooke.py b/qiskit_ibm_runtime/fake_provider/backends/sherbrooke/fake_sherbrooke.py index c2e672fd3..37de24a67 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/sherbrooke/fake_sherbrooke.py +++ b/qiskit_ibm_runtime/fake_provider/backends/sherbrooke/fake_sherbrooke.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_backend +from qiskit_ibm_runtime.fake_provider import fake_backend class FakeSherbrooke(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/singapore/fake_singapore.py b/qiskit_ibm_runtime/fake_provider/backends/singapore/fake_singapore.py index d56fda962..61d2d0526 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/singapore/fake_singapore.py +++ b/qiskit_ibm_runtime/fake_provider/backends/singapore/fake_singapore.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeSingaporeV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/sydney/fake_sydney.py b/qiskit_ibm_runtime/fake_provider/backends/sydney/fake_sydney.py index b60c30a7f..fed6a1729 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/sydney/fake_sydney.py +++ b/qiskit_ibm_runtime/fake_provider/backends/sydney/fake_sydney.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeSydneyV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/toronto/fake_toronto.py b/qiskit_ibm_runtime/fake_provider/backends/toronto/fake_toronto.py index 1d9b6310a..d55d65e22 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/toronto/fake_toronto.py +++ b/qiskit_ibm_runtime/fake_provider/backends/toronto/fake_toronto.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeTorontoV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/valencia/fake_valencia.py b/qiskit_ibm_runtime/fake_provider/backends/valencia/fake_valencia.py index fc7f1d634..c84165cc7 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/valencia/fake_valencia.py +++ b/qiskit_ibm_runtime/fake_provider/backends/valencia/fake_valencia.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeValenciaV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/vigo/fake_vigo.py b/qiskit_ibm_runtime/fake_provider/backends/vigo/fake_vigo.py index bf0828b9e..2f87fe2c8 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/vigo/fake_vigo.py +++ b/qiskit_ibm_runtime/fake_provider/backends/vigo/fake_vigo.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeVigoV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/washington/fake_washington.py b/qiskit_ibm_runtime/fake_provider/backends/washington/fake_washington.py index cf1a72eef..35c46f266 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/washington/fake_washington.py +++ b/qiskit_ibm_runtime/fake_provider/backends/washington/fake_washington.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_pulse_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_pulse_backend, fake_backend class FakeWashingtonV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/backends/yorktown/fake_yorktown.py b/qiskit_ibm_runtime/fake_provider/backends/yorktown/fake_yorktown.py index 8a38f36d0..ff0586310 100644 --- a/qiskit_ibm_runtime/fake_provider/backends/yorktown/fake_yorktown.py +++ b/qiskit_ibm_runtime/fake_provider/backends/yorktown/fake_yorktown.py @@ -15,7 +15,7 @@ """ import os -from qiskit.providers.fake_provider import fake_qasm_backend, fake_backend +from qiskit_ibm_runtime.fake_provider import fake_qasm_backend, fake_backend class FakeYorktownV2(fake_backend.FakeBackendV2): diff --git a/qiskit_ibm_runtime/fake_provider/fake_backend.py b/qiskit_ibm_runtime/fake_provider/fake_backend.py new file mode 100644 index 000000000..023d4beee --- /dev/null +++ b/qiskit_ibm_runtime/fake_provider/fake_backend.py @@ -0,0 +1,570 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2019, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +# pylint: disable=no-name-in-module +""" +Base class for dummy backends. +""" + +import warnings +import collections +import json +import os +import re + +from typing import List, Iterable + +from qiskit import circuit +from qiskit.providers.models import BackendProperties, BackendConfiguration, PulseDefaults +from qiskit.providers import BackendV2, BackendV1 +from qiskit import pulse +from qiskit.exceptions import QiskitError +from qiskit.utils import optionals as _optionals +from qiskit.providers import basicaer +from qiskit.transpiler import Target +from qiskit.providers import Options +from qiskit.providers.backend_compat import convert_to_target +from qiskit.providers.fake_provider.utils.json_decoder import ( + decode_backend_configuration, + decode_backend_properties, + decode_pulse_defaults, +) + + +class _Credentials: + def __init__(self, token: str = "123456", url: str = "https://") -> None: + self.token = token + self.url = url + self.hub = "hub" + self.group = "group" + self.project = "project" + + +class FakeBackendV2(BackendV2): + """A fake backend class for testing and noisy simulation using real backend + snapshots. + + The class inherits :class:`~qiskit.providers.BackendV2` class. This version + differs from earlier :class:`~qiskit.providers.fake_provider.FakeBackend` (V1) class in a + few aspects. Firstly, configuration attribute no longer exsists. Instead, + attributes exposing equivalent required immutable properties of the backend + device are added. For example ``fake_backend.configuration().n_qubits`` is + accessible from ``fake_backend.num_qubits`` now. Secondly, this version + removes extra abstractions :class:`~qiskit.providers.fake_provider.FakeQasmBackend` and + :class:`~qiskit.providers.fake_provider.FakePulseBackend` that were present in V1. + """ + + # directory and file names for real backend snapshots. + dirname = None + conf_filename = None + props_filename = None + defs_filename = None + backend_name = None + + def __init__(self) -> None: + """FakeBackendV2 initializer.""" + self._conf_dict = self._get_conf_dict_from_json() + self._props_dict = None + self._defs_dict = None + super().__init__( + provider=None, + name=self._conf_dict.get("backend_name"), + description=self._conf_dict.get("description"), + online_date=self._conf_dict.get("online_date"), + backend_version=self._conf_dict.get("backend_version"), + ) + self._target = None + self.sim = None + + if "channels" in self._conf_dict: + self._parse_channels(self._conf_dict["channels"]) + + def _parse_channels(self, channels: dict) -> None: + type_map = { + "acquire": pulse.AcquireChannel, + "drive": pulse.DriveChannel, + "measure": pulse.MeasureChannel, + "control": pulse.ControlChannel, + } + identifier_pattern = re.compile(r"\D+(?P\d+)") + + channels_map = { # type: ignore + "acquire": collections.defaultdict(list), + "drive": collections.defaultdict(list), + "measure": collections.defaultdict(list), + "control": collections.defaultdict(list), + } + for identifier, spec in channels.items(): + channel_type = spec["type"] + out = re.match(identifier_pattern, identifier) + if out is None: + # Identifier is not a valid channel name format + continue + channel_index = int(out.groupdict()["index"]) + qubit_index = tuple(spec["operates"]["qubits"]) + chan_obj = type_map[channel_type](channel_index) + channels_map[channel_type][qubit_index].append(chan_obj) + setattr(self, "channels_map", channels_map) + + def _setup_sim(self) -> None: + if _optionals.HAS_AER: + from qiskit_aer import AerSimulator # pylint: disable=import-outside-toplevel + + self.sim = AerSimulator() + if self.target and self._props_dict: + noise_model = self._get_noise_model_from_backend_v2() # type: ignore + self.sim.set_options(noise_model=noise_model) + # Update fake backend default too to avoid overwriting + # it when run() is called + self.set_options(noise_model=noise_model) + + else: + self.sim = basicaer.QasmSimulatorPy() + + def _get_conf_dict_from_json(self) -> dict: + if not self.conf_filename: + return None + conf_dict = self._load_json(self.conf_filename) # type: ignore + decode_backend_configuration(conf_dict) + conf_dict["backend_name"] = self.backend_name + return conf_dict + + def _set_props_dict_from_json(self) -> None: + if self.props_filename: + props_dict = self._load_json(self.props_filename) # type: ignore + decode_backend_properties(props_dict) + self._props_dict = props_dict + + def _set_defs_dict_from_json(self) -> None: + if self.defs_filename: + defs_dict = self._load_json(self.defs_filename) # type: ignore + decode_pulse_defaults(defs_dict) + self._defs_dict = defs_dict + + def _load_json(self, filename: str) -> dict: + with open( # pylint: disable=unspecified-encoding + os.path.join(self.dirname, filename) + ) as f_json: + the_json = json.load(f_json) + return the_json + + @property + def target(self) -> Target: + """A :class:`qiskit.transpiler.Target` object for the backend. + + :rtype: Target + """ + if self._target is None: + self._get_conf_dict_from_json() + if self._props_dict is None: + self._set_props_dict_from_json() + if self._defs_dict is None: + self._set_defs_dict_from_json() + conf = BackendConfiguration.from_dict(self._conf_dict) + props = None + if self._props_dict is not None: + props = BackendProperties.from_dict(self._props_dict) # type: ignore + defaults = None + if self._defs_dict is not None: + defaults = PulseDefaults.from_dict(self._defs_dict) # type: ignore + + self._target = convert_to_target( + conf, props, defaults, add_delay=True, filter_faulty=True + ) + + return self._target + + @property + def max_circuits(self) -> None: + return None + + @classmethod + def _default_options(cls) -> Options: + """Return the default options + + This method will return a :class:`qiskit.providers.Options` + subclass object that will be used for the default options. These + should be the default parameters to use for the options of the + backend. + + Returns: + qiskit.providers.Options: A options object with + default values set + """ + if _optionals.HAS_AER: + from qiskit_aer import AerSimulator # pylint: disable=import-outside-toplevel + + return AerSimulator._default_options() + else: + return basicaer.QasmSimulatorPy._default_options() + + @property + def dtm(self) -> float: + """Return the system time resolution of output signals + + Returns: + The output signal timestep in seconds. + """ + dtm = self._conf_dict.get("dtm") + if dtm is not None: + # converting `dtm` in nanoseconds in configuration file to seconds + return dtm * 1e-9 + else: + return None + + @property + def meas_map(self) -> List[List[int]]: + """Return the grouping of measurements which are multiplexed + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + The grouping of measurements which are multiplexed + """ + return self._conf_dict.get("meas_map") + + def drive_channel(self, qubit: int): # type: ignore + """Return the drive channel for the given qubit. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + DriveChannel: The Qubit drive channel + """ + drive_channels_map = getattr(self, "channels_map", {}).get("drive", {}) + qubits = (qubit,) + if qubits in drive_channels_map: + return drive_channels_map[qubits][0] + return None + + def measure_channel(self, qubit: int): # type: ignore + """Return the measure stimulus channel for the given qubit. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + MeasureChannel: The Qubit measurement stimulus line + """ + measure_channels_map = getattr(self, "channels_map", {}).get("measure", {}) + qubits = (qubit,) + if qubits in measure_channels_map: + return measure_channels_map[qubits][0] + return None + + def acquire_channel(self, qubit: int): # type: ignore + """Return the acquisition channel for the given qubit. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Returns: + AcquireChannel: The Qubit measurement acquisition line. + """ + acquire_channels_map = getattr(self, "channels_map", {}).get("acquire", {}) + qubits = (qubit,) + if qubits in acquire_channels_map: + return acquire_channels_map[qubits][0] + return None + + def control_channel(self, qubits: Iterable[int]): # type: ignore + """Return the secondary drive channel for the given qubit + + This is typically utilized for controlling multiqubit interactions. + This channel is derived from other channels. + + This is required to be implemented if the backend supports Pulse + scheduling. + + Args: + qubits: Tuple or list of qubits of the form + ``(control_qubit, target_qubit)``. + + Returns: + List[ControlChannel]: The multi qubit control line. + """ + control_channels_map = getattr(self, "channels_map", {}).get("control", {}) + qubits = tuple(qubits) + if qubits in control_channels_map: + return control_channels_map[qubits] + return [] + + def run(self, run_input, **options): # type: ignore + """Run on the fake backend using a simulator. + + This method runs circuit jobs (an individual or a list of QuantumCircuit + ) and pulse jobs (an individual or a list of Schedule or ScheduleBlock) + using BasicAer or Aer simulator and returns a + :class:`~qiskit.providers.Job` object. + + If qiskit-aer is installed, jobs will be run using AerSimulator with + noise model of the fake backend. Otherwise, jobs will be run using + BasicAer simulator without noise. + + Currently noisy simulation of a pulse job is not supported yet in + FakeBackendV2. + + Args: + run_input (QuantumCircuit or Schedule or ScheduleBlock or list): An + individual or a list of + :class:`~qiskit.circuit.QuantumCircuit`, + :class:`~qiskit.pulse.ScheduleBlock`, or + :class:`~qiskit.pulse.Schedule` objects to run on the backend. + options: Any kwarg options to pass to the backend for running the + config. If a key is also present in the options + attribute/object then the expectation is that the value + specified will be used instead of what's set in the options + object. + + Returns: + Job: The job object for the run + + Raises: + QiskitError: If a pulse job is supplied and qiskit-aer is not installed. + """ + circuits = run_input + pulse_job = None + if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)): + pulse_job = True + elif isinstance(circuits, circuit.QuantumCircuit): + pulse_job = False + elif isinstance(circuits, list): + if circuits: + if all(isinstance(x, (pulse.Schedule, pulse.ScheduleBlock)) for x in circuits): + pulse_job = True + elif all(isinstance(x, circuit.QuantumCircuit) for x in circuits): + pulse_job = False + if pulse_job is None: # submitted job is invalid + raise QiskitError( + "Invalid input object %s, must be either a " + "QuantumCircuit, Schedule, or a list of either" % circuits + ) + if pulse_job: # pulse job + raise QiskitError("Pulse simulation is currently not supported for V2 fake backends.") + # circuit job + if not _optionals.HAS_AER: + warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) + if self.sim is None: + self._setup_sim() + self.sim._options = self._options + job = self.sim.run(circuits, **options) + return job + + def _get_noise_model_from_backend_v2( # type: ignore + self, + gate_error=True, + readout_error=True, + thermal_relaxation=True, + temperature=0, + gate_lengths=None, + gate_length_units="ns", + ): + """Build noise model from BackendV2. + + This is a temporary fix until qiskit-aer supports building noise model + from a BackendV2 object. + """ + + from qiskit.circuit import Delay # pylint: disable=import-outside-toplevel + from qiskit.providers.exceptions import ( # pylint: disable=import-outside-toplevel + BackendPropertyError, + ) + from qiskit_aer.noise import NoiseModel # pylint: disable=import-outside-toplevel + from qiskit_aer.noise.device.models import ( # pylint: disable=import-outside-toplevel + _excited_population, + basic_device_gate_errors, + basic_device_readout_errors, + ) + from qiskit_aer.noise.passes import ( # pylint: disable=import-outside-toplevel + RelaxationNoisePass, + ) + + if self._props_dict is None: + self._set_props_dict_from_json() + + properties = BackendProperties.from_dict(self._props_dict) + basis_gates = self.operation_names + num_qubits = self.num_qubits + dt = self.dt # pylint: disable=invalid-name + + noise_model = NoiseModel(basis_gates=basis_gates) + + # Add single-qubit readout errors + if readout_error: + for qubits, error in basic_device_readout_errors(properties): + noise_model.add_readout_error(error, qubits) + + # Add gate errors + with warnings.catch_warnings(): + warnings.filterwarnings( + "ignore", + module="qiskit_aer.noise.device.models", + ) + gate_errors = basic_device_gate_errors( + properties, + gate_error=gate_error, + thermal_relaxation=thermal_relaxation, + gate_lengths=gate_lengths, + gate_length_units=gate_length_units, + temperature=temperature, + ) + for name, qubits, error in gate_errors: + noise_model.add_quantum_error(error, name, qubits) + + if thermal_relaxation: + # Add delay errors via RelaxationNiose pass + try: + excited_state_populations = [ + _excited_population(freq=properties.frequency(q), temperature=temperature) + for q in range(num_qubits) + ] + except BackendPropertyError: + excited_state_populations = None + try: + delay_pass = RelaxationNoisePass( + t1s=[properties.t1(q) for q in range(num_qubits)], + t2s=[properties.t2(q) for q in range(num_qubits)], + dt=dt, + op_types=Delay, + excited_state_populations=excited_state_populations, + ) + noise_model._custom_noise_passes.append(delay_pass) + except BackendPropertyError: + # Device does not have the required T1 or T2 information + # in its properties + pass + + return noise_model + + +class FakeBackend(BackendV1): + """This is a dummy backend just for testing purposes.""" + + def __init__(self, configuration, time_alive=10): # type: ignore + """FakeBackend initializer. + + Args: + configuration (BackendConfiguration): backend configuration + time_alive (int): time to wait before returning result + """ + super().__init__(configuration) + self.time_alive = time_alive + self._credentials = _Credentials() + self.sim = None + + def _setup_sim(self) -> None: + if _optionals.HAS_AER: + from qiskit_aer import AerSimulator # pylint: disable=import-outside-toplevel + from qiskit_aer.noise import NoiseModel # pylint: disable=import-outside-toplevel + + self.sim = AerSimulator() + if self.properties(): + noise_model = NoiseModel.from_backend(self) + self.sim.set_options(noise_model=noise_model) + # Update fake backend default options too to avoid overwriting + # it when run() is called + self.set_options(noise_model=noise_model) + else: + self.sim = basicaer.QasmSimulatorPy() + + def properties(self) -> BackendProperties: + """Return backend properties""" + coupling_map = self.configuration().coupling_map + if coupling_map is None: + return None + unique_qubits = list(set().union(*coupling_map)) + + properties = { + "backend_name": self.name(), + "backend_version": self.configuration().backend_version, + "last_update_date": "2000-01-01 00:00:00Z", + "qubits": [ + [ + {"date": "2000-01-01 00:00:00Z", "name": "T1", "unit": "\u00b5s", "value": 0.0}, + {"date": "2000-01-01 00:00:00Z", "name": "T2", "unit": "\u00b5s", "value": 0.0}, + { + "date": "2000-01-01 00:00:00Z", + "name": "frequency", + "unit": "GHz", + "value": 0.0, + }, + { + "date": "2000-01-01 00:00:00Z", + "name": "readout_error", + "unit": "", + "value": 0.0, + }, + {"date": "2000-01-01 00:00:00Z", "name": "operational", "unit": "", "value": 1}, + ] + for _ in range(len(unique_qubits)) + ], + "gates": [ + { + "gate": "cx", + "name": "CX" + str(pair[0]) + "_" + str(pair[1]), + "parameters": [ + { + "date": "2000-01-01 00:00:00Z", + "name": "gate_error", + "unit": "", + "value": 0.0, + } + ], + "qubits": [pair[0], pair[1]], + } + for pair in coupling_map + ], + "general": [], + } + + return BackendProperties.from_dict(properties) + + @classmethod + def _default_options(cls) -> Options: + if _optionals.HAS_AER: + from qiskit_aer import QasmSimulator # pylint: disable=import-outside-toplevel + + return QasmSimulator._default_options() + else: + return basicaer.QasmSimulatorPy._default_options() + + def run(self, run_input, **kwargs): # type: ignore + """Main job in simulator""" + circuits = run_input + pulse_job = None + if isinstance(circuits, (pulse.Schedule, pulse.ScheduleBlock)): + pulse_job = True + elif isinstance(circuits, circuit.QuantumCircuit): + pulse_job = False + elif isinstance(circuits, list): + if circuits: + if all(isinstance(x, (pulse.Schedule, pulse.ScheduleBlock)) for x in circuits): + pulse_job = True + elif all(isinstance(x, circuit.QuantumCircuit) for x in circuits): + pulse_job = False + if pulse_job is None: + raise QiskitError( + "Invalid input object %s, must be either a " + "QuantumCircuit, Schedule, or a list of either" % circuits + ) + if pulse_job: # pulse job + raise QiskitError("Pulse simulation is currently not supported for V1 fake backends.") + + if self.sim is None: + self._setup_sim() + if not _optionals.HAS_AER: + warnings.warn("Aer not found using BasicAer and no noise", RuntimeWarning) + self.sim._options = self._options + job = self.sim.run(circuits, **kwargs) + + return job diff --git a/qiskit_ibm_runtime/fake_provider/fake_provider.py b/qiskit_ibm_runtime/fake_provider/fake_provider.py index ccd1d704b..4c3becb49 100644 --- a/qiskit_ibm_runtime/fake_provider/fake_provider.py +++ b/qiskit_ibm_runtime/fake_provider/fake_provider.py @@ -69,7 +69,10 @@ class FakeProviderForBackendV2(ProviderV1): available in the :mod:`qiskit_ibm_runtime.fake_provider`. """ - def get_backend(self, name=None, **kwargs): # type: ignore + def backend(self, name=None, **kwargs): # type: ignore + """ + Filter backends in provider by name. + """ backend = self._backends[0] if name: filtered_backends = [backend for backend in self._backends if backend.name() == name] diff --git a/qiskit_ibm_runtime/fake_provider/fake_pulse_backend.py b/qiskit_ibm_runtime/fake_provider/fake_pulse_backend.py new file mode 100644 index 000000000..97b7f0b8e --- /dev/null +++ b/qiskit_ibm_runtime/fake_provider/fake_pulse_backend.py @@ -0,0 +1,43 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Fake backend abstract class for mock backends supporting OpenPulse. +""" + +from qiskit.exceptions import QiskitError +from qiskit.providers.models import PulseBackendConfiguration, PulseDefaults + +from qiskit.providers.fake_provider.utils.json_decoder import decode_pulse_defaults +from .fake_qasm_backend import FakeQasmBackend + + +class FakePulseBackend(FakeQasmBackend): + """A fake pulse backend.""" + + defs_filename = None + + def defaults(self) -> PulseDefaults: + """Returns a snapshot of device defaults""" + if not self._defaults: + self._set_defaults_from_json() + return self._defaults + + def _set_defaults_from_json(self) -> None: + if not self.props_filename: + raise QiskitError("No properties file has been defined") + defs = self._load_json(self.defs_filename) # type: ignore + decode_pulse_defaults(defs) + self._defaults = PulseDefaults.from_dict(defs) + + def _get_config_from_dict(self, conf: dict) -> PulseBackendConfiguration: + return PulseBackendConfiguration.from_dict(conf) diff --git a/qiskit_ibm_runtime/fake_provider/fake_qasm_backend.py b/qiskit_ibm_runtime/fake_provider/fake_qasm_backend.py new file mode 100644 index 000000000..70473168e --- /dev/null +++ b/qiskit_ibm_runtime/fake_provider/fake_qasm_backend.py @@ -0,0 +1,74 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2020, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" +Fake backend abstract class for mock backends. +""" + +import json +import os + +from qiskit.exceptions import QiskitError +from qiskit.providers.models import BackendProperties, QasmBackendConfiguration + +from qiskit.providers.fake_provider.utils.json_decoder import ( + decode_backend_configuration, + decode_backend_properties, +) +from .fake_backend import FakeBackend + + +class FakeQasmBackend(FakeBackend): + """A fake OpenQASM backend.""" + + dirname = None + conf_filename = None + props_filename = None + backend_name = None + + def __init__(self): # type: ignore + configuration = self._get_conf_from_json() + self._defaults = None + self._properties = None + super().__init__(configuration) + + def properties(self) -> BackendProperties: + """Returns a snapshot of device properties""" + if not self._properties: + self._set_props_from_json() + return self._properties + + def _get_conf_from_json(self) -> QasmBackendConfiguration: + if not self.conf_filename: + raise QiskitError("No configuration file has been defined") + conf = self._load_json(self.conf_filename) # type: ignore + decode_backend_configuration(conf) + configuration = self._get_config_from_dict(conf) + configuration.backend_name = self.backend_name + return configuration + + def _set_props_from_json(self) -> None: + if not self.props_filename: + raise QiskitError("No properties file has been defined") + props = self._load_json(self.props_filename) # type: ignore + decode_backend_properties(props) + self._properties = BackendProperties.from_dict(props) + + def _load_json(self, filename: str) -> dict: + with open( # pylint: disable=unspecified-encoding + os.path.join(self.dirname, filename) + ) as f_json: + the_json = json.load(f_json) + return the_json + + def _get_config_from_dict(self, conf: dict) -> QasmBackendConfiguration: + return QasmBackendConfiguration.from_dict(conf) diff --git a/qiskit_ibm_runtime/ibm_backend.py b/qiskit_ibm_runtime/ibm_backend.py index 1fb60dbd6..77374df2d 100644 --- a/qiskit_ibm_runtime/ibm_backend.py +++ b/qiskit_ibm_runtime/ibm_backend.py @@ -21,7 +21,6 @@ from qiskit import QuantumCircuit from qiskit.qobj.utils import MeasLevel, MeasReturnType -from qiskit.tools.events.pubsub import Publisher from qiskit.providers.backend import BackendV2 as Backend from qiskit.providers.options import Options @@ -67,6 +66,14 @@ ) from .utils.default_session import get_cm_session as get_cm_primitive_session +# If using a new-enough version of the IBM Provider, access the pub/sub +# mechanism from it as a broker, but fall back to Qiskit if we're using +# an old version (in which case it will also be falling back to Qiskit). +try: + from qiskit_ibm_provider.utils.pubsub import Publisher +except ImportError: + from qiskit.tools.events.pubsub import Publisher # pylint: disable=ungrouped-imports + logger = logging.getLogger(__name__) QOBJRUNNERPROGRAMID = "circuit-runner" @@ -762,9 +769,11 @@ def _runtime_run( raise RuntimeError(f"The session {self._session.session_id} is closed.") session_id = self._session.session_id start_session = session_id is None + max_session_time = self._session._max_time else: session_id = None start_session = False + max_session_time = None log_level = getattr(self.options, "log_level", None) # temporary try: @@ -777,6 +786,7 @@ def _runtime_run( job_tags=job_tags, session_id=session_id, start_session=start_session, + session_time=max_session_time, image=image, ) except RequestsApiError as ex: @@ -827,9 +837,9 @@ def _get_run_config(self, program_id: str, **kwargs: Any) -> Dict: run_config_dict[key] = backend_options[key] return run_config_dict - def open_session(self) -> ProviderSession: + def open_session(self, max_time: Optional[Union[int, str]] = None) -> ProviderSession: """Open session""" - self._session = ProviderSession() + self._session = ProviderSession(max_time=max_time) return self._session @property diff --git a/qiskit_ibm_runtime/qiskit_runtime_service.py b/qiskit_ibm_runtime/qiskit_runtime_service.py index f7d7c5dad..f3cbdda4c 100644 --- a/qiskit_ibm_runtime/qiskit_runtime_service.py +++ b/qiskit_ibm_runtime/qiskit_runtime_service.py @@ -53,6 +53,7 @@ from .api.client_parameters import ClientParameters from .runtime_options import RuntimeOptions from .ibm_backend import IBMBackend +from .utils.deprecation import issue_deprecation_msg logger = logging.getLogger(__name__) @@ -70,8 +71,8 @@ class QiskitRuntimeService(Provider): A sample workflow of using the runtime service:: from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler, Estimator, Options - from qiskit.test.reference_circuits import ReferenceCircuits from qiskit.circuit.library import RealAmplitudes + from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.quantum_info import SparsePauliOp # Initialize account. @@ -81,15 +82,22 @@ class QiskitRuntimeService(Provider): options = Options(optimization_level=1) # Prepare inputs. - bell = ReferenceCircuits.bell() psi = RealAmplitudes(num_qubits=2, reps=2) H1 = SparsePauliOp.from_list([("II", 1), ("IZ", 2), ("XI", 3)]) theta = [0, 1, 1, 2, 3, 5] + # Bell Circuit + qr = QuantumRegister(2, name="qr") + cr = ClassicalRegister(2, name="cr") + qc = QuantumCircuit(qr, cr, name="bell") + qc.h(qr[0]) + qc.cx(qr[0], qr[1]) + qc.measure(qr, cr) + with Session(service=service, backend="ibmq_qasm_simulator") as session: # Submit a request to the Sampler primitive within the session. sampler = Sampler(session=session, options=options) - job = sampler.run(circuits=bell) + job = sampler.run(circuits=qc) print(f"Sampler results: {job.result()}") # Submit a request to the Estimator primitive within the session. @@ -537,6 +545,7 @@ def backends( name: Optional[str] = None, min_num_qubits: Optional[int] = None, instance: Optional[str] = None, + dynamic_circuits: Optional[bool] = None, filters: Optional[Callable[[List["ibm_backend.IBMBackend"]], bool]] = None, **kwargs: Any, ) -> List["ibm_backend.IBMBackend"]: @@ -547,6 +556,7 @@ def backends( min_num_qubits: Minimum number of qubits the backend has to have. instance: This is only supported for ``ibm_quantum`` runtime and is in the hub/group/project format. + dynamic_circuits: Filter by whether the backend supports dynamic circuits. filters: More complex filters, such as lambda functions. For example:: @@ -585,11 +595,11 @@ def backends( if name: if name not in self._backends: raise QiskitBackendNotFoundError("No backend matches the criteria.") - if not self._backends[name] or instance != self._backends[name]._instance: + if not self._backends[name] or instance_filter != self._backends[name]._instance: self._set_backend_config(name) self._backends[name] = self._create_backend_obj( self._backend_configs[name], - instance, + instance_filter, ) if self._backends[name]: backends.append(self._backends[name]) @@ -629,6 +639,15 @@ def backends( backends = list( filter(lambda b: b.configuration().n_qubits >= min_num_qubits, backends) ) + + if dynamic_circuits is not None: + backends = list( + filter( + lambda b: ("qasm3" in getattr(b.configuration(), "supported_features", [])) + == dynamic_circuits, + backends, + ) + ) return filter_backends(backends, filters=filters, **kwargs) def _set_backend_config(self, backend_name: str, instance: Optional[str] = None) -> None: @@ -664,9 +683,17 @@ def _create_backend_obj( break elif config.backend_name not in self._get_hgp(instance=instance).backends: + hgps_with_backend = [] + for hgp in list(self._hgps.values()): + if config.backend_name in hgp.backends: + hgps_with_backend.append( + to_instance_format(hgp._hub, hgp._group, hgp._project) + ) raise QiskitBackendNotFoundError( f"Backend {config.backend_name} is not in " - f"{instance}: please try a different hub/group/project." + f"{instance}. Please try a different instance. " + f"{config.backend_name} is in the following instances you have access to: " + f"{hgps_with_backend}" ) return ibm_backend.IBMBackend( @@ -1182,6 +1209,12 @@ def runtime(self): # type:ignore Returns: self """ + issue_deprecation_msg( + msg="The runtime property is deprecated", + version="0.18.0", + remedy="", + period="1 month", + ) return self def __repr__(self) -> str: diff --git a/qiskit_ibm_runtime/runtime_job.py b/qiskit_ibm_runtime/runtime_job.py index f306ce4b8..e14843fb4 100644 --- a/qiskit_ibm_runtime/runtime_job.py +++ b/qiskit_ibm_runtime/runtime_job.py @@ -24,6 +24,7 @@ from qiskit.providers.backend import Backend from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES +from qiskit.providers.models import BackendProperties from qiskit.providers.job import JobV1 as Job # pylint: disable=unused-import,cyclic-import @@ -32,6 +33,7 @@ from .utils.utils import validate_job_tags from .utils.estimator_result_decoder import EstimatorResultDecoder +from .utils.queueinfo import QueueInfo from .constants import API_TO_JOB_ERROR_MESSAGE, API_TO_JOB_STATUS, DEFAULT_DECODERS from .exceptions import ( IBMApiError, @@ -139,6 +141,7 @@ def __init__( self._tags = tags self._usage_estimation: Dict[str, Any] = {} self._version = version + self._queue_info: QueueInfo = None decoder = result_decoder or DEFAULT_DECODERS.get(program_id, None) or ResultDecoder if isinstance(decoder, Sequence): @@ -440,6 +443,20 @@ def update_tags(self, new_tags: List[str]) -> List[str]: "the job.".format(self.job_id()) ) + def properties(self, refresh: bool = False) -> Optional[BackendProperties]: + """Return the backend properties for this job. + + Args: + refresh: If ``True``, re-query the server for the backend properties. + Otherwise, return a cached version. + + Returns: + The backend properties used for this job, at the time the job was run, + or ``None`` if properties are not available. + """ + + return self._backend.properties(refresh, self.creation_date) + def _set_status_and_error_message(self) -> None: """Fetch and set status and error message.""" if self._status not in JOB_FINAL_STATES: @@ -615,7 +632,7 @@ def inputs(self) -> Dict: Input parameters used in this job. """ if not self._params: - response = self._api_client.job_get(job_id=self.job_id()) + response = self._api_client.job_get(job_id=self.job_id(), exclude_params=False) self._params = response.get("params", {}) return self._params @@ -682,3 +699,64 @@ def usage_estimation(self) -> Dict[str, Any]: } return self._usage_estimation + + def queue_position(self, refresh: bool = False) -> Optional[int]: + """Return the position of the job in the server queue. + + Note: + The position returned is within the scope of the provider + and may differ from the global queue position. + + Args: + refresh: If ``True``, re-query the server to get the latest value. + Otherwise return the cached value. + + Returns: + Position in the queue or ``None`` if position is unknown or not applicable. + """ + if refresh: + api_metadata = self._api_client.job_metadata(self.job_id()) + self._queue_info = QueueInfo( + position_in_queue=api_metadata.get("position_in_queue"), + status=self.status(), + estimated_start_time=api_metadata.get("estimated_start_time"), + estimated_completion_time=api_metadata.get("estimated_completion_time"), + ) + + if self._queue_info: + return self._queue_info.position + return None + + def queue_info(self) -> Optional[QueueInfo]: + """Return queue information for this job. + + The queue information may include queue position, estimated start and + end time, and dynamic priorities for the hub, group, and project. See + :class:`QueueInfo` for more information. + + Note: + The queue information is calculated after the job enters the queue. + Therefore, some or all of the information may not be immediately + available, and this method may return ``None``. + + Returns: + A :class:`QueueInfo` instance that contains queue information for + this job, or ``None`` if queue information is unknown or not + applicable. + """ + # Get latest queue information. + api_metadata = self._api_client.job_metadata(self.job_id()) + self._queue_info = QueueInfo( + position_in_queue=api_metadata.get("position_in_queue"), + status=self.status(), + estimated_start_time=api_metadata.get("estimated_start_time"), + estimated_completion_time=api_metadata.get("estimated_completion_time"), + ) + # Return queue information only if it has any useful information. + if self._queue_info and any( + value is not None + for attr, value in self._queue_info.__dict__.items() + if not attr.startswith("_") and attr != "job_id" + ): + return self._queue_info + return None diff --git a/qiskit_ibm_runtime/sampler.py b/qiskit_ibm_runtime/sampler.py index e165bb8ff..04c92c203 100644 --- a/qiskit_ibm_runtime/sampler.py +++ b/qiskit_ibm_runtime/sampler.py @@ -123,16 +123,23 @@ class SamplerV1(BasePrimitiveV1, Sampler, BaseSampler): Example:: - from qiskit.test.reference_circuits import ReferenceCircuits + from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit_ibm_runtime import QiskitRuntimeService, Session, Sampler service = QiskitRuntimeService(channel="ibm_cloud") - bell = ReferenceCircuits.bell() + + # Bell Circuit + qr = QuantumRegister(2, name="qr") + cr = ClassicalRegister(2, name="cr") + qc = QuantumCircuit(qr, cr, name="bell") + qc.h(qr[0]) + qc.cx(qr[0], qr[1]) + qc.measure(qr, cr) with Session(service, backend="ibmq_qasm_simulator") as session: sampler = Sampler(session=session) - job = sampler.run(bell, shots=1024) + job = sampler.run(qc, shots=1024) print(f"Job ID: {job.job_id()}") print(f"Job result: {job.result()}") diff --git a/qiskit_ibm_runtime/session.py b/qiskit_ibm_runtime/session.py index 7b9905ac7..cd9d66502 100644 --- a/qiskit_ibm_runtime/session.py +++ b/qiskit_ibm_runtime/session.py @@ -53,14 +53,22 @@ class Session: For example:: - from qiskit.test.reference_circuits import ReferenceCircuits + from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit_ibm_runtime import Sampler, Session, Options + # Bell Circuit + qr = QuantumRegister(2, name="qr") + cr = ClassicalRegister(2, name="cr") + qc = QuantumCircuit(qr, cr, name="bell") + qc.h(qr[0]) + qc.cx(qr[0], qr[1]) + qc.measure(qr, cr) + options = Options(optimization_level=3) with Session(backend="ibmq_qasm_simulator") as session: sampler = Sampler(session=session, options=options) - job = sampler.run(ReferenceCircuits.bell()) + job = sampler.run(qc) print(f"Sampler job ID: {job.job_id()}") print(f"Sampler job result: {job.result()}") @@ -249,6 +257,7 @@ def details(self) -> Optional[Dict[str, Any]]: last_job_completed: Timestamp of when the last job in the session completed. started_at: Timestamp of when the session was started. closed_at: Timestamp of when the session was closed. + activated_at: Timestamp of when the session state was changed to active. """ if self._session_id: response = self._service._api_client.session_details(self._session_id) @@ -265,6 +274,7 @@ def details(self) -> Optional[Dict[str, Any]]: "last_job_completed": response.get("last_job_completed"), "started_at": response.get("started_at"), "closed_at": response.get("closed_at"), + "activated_at": response.get("activated_at"), } return None @@ -273,7 +283,7 @@ def session_id(self) -> str: """Return the session ID. Returns: - Session ID. None until a job runs in the session. + Session ID. None until a job is submitted. """ return self._session_id diff --git a/qiskit_ibm_runtime/test/ibm_runtime_service_mock.py b/qiskit_ibm_runtime/test/ibm_runtime_service_mock.py deleted file mode 100644 index 19bf24303..000000000 --- a/qiskit_ibm_runtime/test/ibm_runtime_service_mock.py +++ /dev/null @@ -1,44 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Mock for qiskit_ibm_runtime.QiskitRuntimeService.""" - -from unittest.mock import MagicMock -from qiskit.test import mock as backend_mocks -import qiskit_ibm_runtime - - -def mock_get_backend(backend): - """Mock for QiskitRuntimeService. - - Replace qiskit_ibm_runtime.QiskitRuntimeService with a mock that returns a single backend. - Note this will set the value of qiskit_ibm_runtime.QiskitRuntimeService to a MagicMock object. It - is intended to be run as part of docstrings with jupyter-example in a hidden - cell so that later examples which rely on ibm quantum devices so that the docs can - be built without requiring configured accounts. If used outside of this - context be aware that you will have to manually restore qiskit_ibm_runtime.QiskitRuntimeService - the value to qiskit_ibm_runtime.QiskitRuntimeService after you finish using your mock. - - Args: - backend (str): The class name as a string for the fake device to - return. For example, FakeVigo. - - Raises: - NameError: If the specified value of backend - """ - mock_qiskit_runtime_service = MagicMock() - if not hasattr(backend_mocks, backend): - raise NameError("The specified backend name is not a valid mock from qiskit.test.mock") - fake_backend = getattr(backend_mocks, backend)() - mock_qiskit_runtime_service.backend.return_value = fake_backend - mock_qiskit_runtime_service.return_value = mock_qiskit_runtime_service - qiskit_ibm_runtime.QiskitRuntimeService = mock_qiskit_runtime_service diff --git a/qiskit_ibm_runtime/utils/backend_converter.py b/qiskit_ibm_runtime/utils/backend_converter.py index d889b11b6..1bf390bfc 100644 --- a/qiskit_ibm_runtime/utils/backend_converter.py +++ b/qiskit_ibm_runtime/utils/backend_converter.py @@ -151,11 +151,11 @@ def convert_to_target( if any(qubit in faulty_qubits for qubit in qarg): continue target[inst][qarg].calibration = sched - if "delay" not in target: - target.add_instruction( - Delay(Parameter("t")), - {(bit,): None for bit in range(target.num_qubits) if bit not in faulty_qubits}, - ) + if "delay" not in target: + target.add_instruction( + Delay(Parameter("t")), + {(bit,): None for bit in range(target.num_qubits) if bit not in faulty_qubits}, + ) return target diff --git a/qiskit_ibm_runtime/utils/queueinfo.py b/qiskit_ibm_runtime/utils/queueinfo.py new file mode 100644 index 000000000..1fc9e19a4 --- /dev/null +++ b/qiskit_ibm_runtime/utils/queueinfo.py @@ -0,0 +1,166 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Queue information for a job.""" + +import warnings +from datetime import datetime +from typing import Any, Optional, Union, Dict +import dateutil.parser + +from ..utils import utc_to_local, duration_difference + + +class QueueInfo: + """Queue information for a job.""" + + _data = {} # type: Dict + + def __init__( + self, + position_in_queue: Optional[int] = None, + status: Optional[str] = None, + estimated_start_time: Optional[Union[str, datetime]] = None, + estimated_completion_time: Optional[Union[str, datetime]] = None, + hub_priority: Optional[float] = None, + group_priority: Optional[float] = None, + project_priority: Optional[float] = None, + job_id: Optional[str] = None, + **kwargs: Any + ) -> None: + """QueueInfo constructor. + + Args: + position: Position in the queue. + status: Job status. + estimated_start_time: Estimated start time for the job, in UTC. + estimated_complete_time: Estimated complete time for the job, in UTC. + hub_priority: Dynamic priority for the hub. + group_priority: Dynamic priority for the group. + project_priority: Dynamic priority for the project. + job_id: Job ID. + kwargs: Additional attributes. + """ + self.position = int(position_in_queue) if position_in_queue else None + self._status = status + if isinstance(estimated_start_time, str): + estimated_start_time = dateutil.parser.isoparse(estimated_start_time) + if isinstance(estimated_completion_time, str): + estimated_completion_time = dateutil.parser.isoparse(estimated_completion_time) + self._estimated_start_time_utc = estimated_start_time + self._estimated_complete_time_utc = estimated_completion_time + self.hub_priority = hub_priority + self.group_priority = group_priority + self.project_priority = project_priority + self.job_id = job_id + + self._data = kwargs + + def __repr__(self) -> str: + """Return the string representation of ``QueueInfo``. + + Note: + The estimated start and end time are displayed in local time + for convenience. + + Returns: + A string representation of ``QueueInfo``. + + Raises: + TypeError: If the `estimated_start_time` or `estimated_end_time` + value is not valid. + """ + status = self._get_value(self._status) + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + est_start_time = ( + self.estimated_start_time.isoformat() if self.estimated_start_time else None + ) + est_complete_time = ( + self.estimated_complete_time.isoformat() if self.estimated_complete_time else None + ) + + queue_info = [ + "job_id='{}'".format(self.job_id), + "_status='{}'".format(status), + "estimated_start_time='{}'".format(est_start_time), + "estimated_complete_time='{}'".format(est_complete_time), + "position={}".format(self.position), + "hub_priority={}".format(self.hub_priority), + "group_priority={}".format(self.group_priority), + "project_priority={}".format(self.project_priority), + ] + + return "<{}({})>".format(self.__class__.__name__, ", ".join(queue_info)) + + def __getattr__(self, name: str) -> Any: + try: + return self._data[name] + except KeyError: + raise AttributeError("Attribute {} is not defined.".format(name)) from None + + def format(self) -> str: + """Build a user-friendly report for the job queue information. + + Returns: + The job queue information report. + """ + status = self._status + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + est_start_time = ( + duration_difference(self.estimated_start_time) + if self.estimated_start_time + else self._get_value(self.estimated_start_time) + ) + est_complete_time = ( + duration_difference(self.estimated_complete_time) + if self.estimated_complete_time + else self._get_value(self.estimated_complete_time) + ) + + queue_info = [ + "Job {} queue information:".format(self._get_value(self.job_id)), + " queue position: {}".format(self._get_value(self.position)), + " status: {}".format(status), + " estimated start time: {}".format(est_start_time), + " estimated completion time: {}".format(est_complete_time), + " hub priority: {}".format(self._get_value(self.hub_priority)), + " group priority: {}".format(self._get_value(self.group_priority)), + " project priority: {}".format(self._get_value(self.project_priority)), + ] + + return "\n".join(queue_info) + + def _get_value(self, value: Optional[Any], default_value: str = "unknown") -> Optional[Any]: + """Return the input value if it exists or the default. + + Returns: + The input value if it is not ``None``, else the input default value. + """ + return value or default_value + + @property + def estimated_start_time(self) -> Optional[datetime]: + """Return estimated start time in local time.""" + if self._estimated_start_time_utc is None: + return None + return utc_to_local(self._estimated_start_time_utc) + + @property + def estimated_complete_time(self) -> Optional[datetime]: + """Return estimated complete time in local time.""" + if self._estimated_complete_time_utc is None: + return None + return utc_to_local(self._estimated_complete_time_utc) diff --git a/releasenotes/notes/0.17/backend-instance-filter-20d69b3951437f19.yaml b/releasenotes/notes/0.17/backend-instance-filter-20d69b3951437f19.yaml new file mode 100644 index 000000000..b23198477 --- /dev/null +++ b/releasenotes/notes/0.17/backend-instance-filter-20d69b3951437f19.yaml @@ -0,0 +1,16 @@ +--- +fixes: + - | + When a single backend is retrieved with the ``instance`` parameter, + + .. code-block:: + + service.backend('ibm_torino', instance='ibm-q/open/main') + # raises error if torino is not in ibm-q/open/main but in a different instance + # the user has access to + service = QiskitRuntimeService(channel="ibm_quantum", instance="ibm-q/open/main") + service.backend('ibm_torino') # raises the same error + + if the backend is not in the instance but in a different one the user has access to, an error + will be raised. The same error will now be raised if an instance is passed in at initialization + and then a backend not in that instance is retrieved. diff --git a/releasenotes/notes/0.17/faulity-qubits-name-error-f03b90f21cd2d9bf.yaml b/releasenotes/notes/0.17/faulity-qubits-name-error-f03b90f21cd2d9bf.yaml new file mode 100644 index 000000000..9765774a2 --- /dev/null +++ b/releasenotes/notes/0.17/faulity-qubits-name-error-f03b90f21cd2d9bf.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Fixed an issue where retrieving the `coupling_map` of some backends would result + in a `NameError`. + diff --git a/releasenotes/notes/0.17/job-properties-2b5c2f66c50d7d2d.yaml b/releasenotes/notes/0.17/job-properties-2b5c2f66c50d7d2d.yaml new file mode 100644 index 000000000..cd9bfcad4 --- /dev/null +++ b/releasenotes/notes/0.17/job-properties-2b5c2f66c50d7d2d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added a new method :meth:`~qiskit_ibm_runtime.RuntimeJob.properties` which returns the + backend properties of the job at the time the job was run. + diff --git a/releasenotes/notes/0.17/new-session-details-field-48311b4d3313ad94.yaml b/releasenotes/notes/0.17/new-session-details-field-48311b4d3313ad94.yaml new file mode 100644 index 000000000..ef6b210dc --- /dev/null +++ b/releasenotes/notes/0.17/new-session-details-field-48311b4d3313ad94.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + :meth:`~qiskit_ibm_runtime.Session.details` has a new field, `activated_at`, which + is the timestamp of when the session was changed to active. + diff --git a/releasenotes/notes/0.18/deprecate-service-runtime-1138cb5ec43fc4ff.yaml b/releasenotes/notes/0.18/deprecate-service-runtime-1138cb5ec43fc4ff.yaml new file mode 100644 index 000000000..7e7f786b1 --- /dev/null +++ b/releasenotes/notes/0.18/deprecate-service-runtime-1138cb5ec43fc4ff.yaml @@ -0,0 +1,4 @@ +--- +deprecations: + - | + :meth:`~qiskit_ibm_runtime.QiskitRuntimeService.runtime` has been deprecated. diff --git a/releasenotes/notes/0.18/dynamic-circuits-filter-59f771a9b43c00cd.yaml b/releasenotes/notes/0.18/dynamic-circuits-filter-59f771a9b43c00cd.yaml new file mode 100644 index 000000000..ede5247f3 --- /dev/null +++ b/releasenotes/notes/0.18/dynamic-circuits-filter-59f771a9b43c00cd.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added a new parameter, `dynamic_circuits` to :meth:`~qiskit_ibm_runtime.QiskitRuntimeService.backends` + to allow filtering of backends that support dynamic circuits. diff --git a/releasenotes/notes/0.18/exclude-job-params-default-00133498a5c5c15d.yaml b/releasenotes/notes/0.18/exclude-job-params-default-00133498a5c5c15d.yaml new file mode 100644 index 000000000..6d500c8e1 --- /dev/null +++ b/releasenotes/notes/0.18/exclude-job-params-default-00133498a5c5c15d.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Many methods in :class:`~qiskit_ibm_runtime.RuntimeJob` require retrieving the job data from the API with + ``job_get()``. This API call will now exclude the ``params`` field by default because they are only necessary in + :meth:`qiskit_ibm_runtime.RuntimeJob.inputs`. diff --git a/releasenotes/notes/0.18/max_session_time-0bd8665656bf439c.yaml b/releasenotes/notes/0.18/max_session_time-0bd8665656bf439c.yaml new file mode 100644 index 000000000..f5e0cf933 --- /dev/null +++ b/releasenotes/notes/0.18/max_session_time-0bd8665656bf439c.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Added ``max_time`` parameter to ``IBMBackend.open_session()``. diff --git a/releasenotes/notes/0.18/queueinfo-5e1bb815228425bb.yaml b/releasenotes/notes/0.18/queueinfo-5e1bb815228425bb.yaml new file mode 100644 index 000000000..4ac119346 --- /dev/null +++ b/releasenotes/notes/0.18/queueinfo-5e1bb815228425bb.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added a method ``RuntimeJob.queue_info()`` to get the queue information + from the backend. This feature was transferred from ``qiskit_ibm_provider``. diff --git a/requirements-dev.txt b/requirements-dev.txt index 8387babf6..61ee53c7e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -19,11 +19,8 @@ ddt>=1.2.0,!=1.4.0,!=1.4.3 # Documentation nbsphinx Sphinx>=6 -sphinx-tabs>=1.1.11 sphinx-automodapi sphinx-autodoc-typehints<=1.19.2 -sphinx-design>=0.4.0 -sphinx-intl jupyter-sphinx reno>=2.11.0 -qiskit-sphinx-theme~=1.16.0 +sphinxcontrib-katex==0.9.9 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3697f71fd..9203f9632 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -qiskit>=0.44.1 +qiskit>=0.45.0 requests~=2.27 requests_ntlm>=1.1.0 numpy>=1.13 @@ -7,4 +7,4 @@ python-dateutil>=2.8.0 websocket-client>=1.5.1 typing-extensions>=4.0.0 ibm-platform-services>=0.22.6 -qiskit-ibm-provider>=0.7.2 +qiskit-ibm-provider>=0.8.0 diff --git a/setup.py b/setup.py index 5bd6e41d4..eb68ea705 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ import setuptools REQUIREMENTS = [ - "qiskit>=0.44.1", + "qiskit>=0.45.0", "requests>=2.19", "requests-ntlm>=1.1.0", "numpy>=1.13", @@ -27,8 +27,8 @@ "python-dateutil>=2.8.0", "websocket-client>=1.5.1", "ibm-platform-services>=0.22.6", - "qiskit-ibm-provider>=0.7.2", "pydantic", + "qiskit-ibm-provider>=0.8.0", ] # Handle version. @@ -75,7 +75,7 @@ zip_safe=False, project_urls={ "Bug Tracker": "https://github.com/Qiskit/qiskit-ibm-runtime/issues", - "Documentation": "https://qiskit.org/documentation/", + "Documentation": "https://docs.quantum.ibm.com/", "Source Code": "https://github.com/Qiskit/qiskit-ibm-runtime", }, entry_points={ diff --git a/test/account.py b/test/account.py index 65da49c73..65a5c5453 100644 --- a/test/account.py +++ b/test/account.py @@ -121,29 +121,6 @@ def __exit__(self, *exc): management._DEFAULT_ACCOUNT_CONFIG_JSON_FILE = self.account_config_json_backup -class custom_qiskitrc(ContextDecorator): - """Context manager that uses a temporary qiskitrc.""" - - # pylint: disable=invalid-name - - def __init__(self, contents=b""): - # Create a temporary file with the contents. - self.tmp_file = NamedTemporaryFile() - self.tmp_file.write(contents) - self.tmp_file.flush() - self.default_qiskitrc_file_original = management._QISKITRC_CONFIG_FILE - - def __enter__(self): - # Temporarily modify the default location of the qiskitrc file. - management._QISKITRC_CONFIG_FILE = self.tmp_file.name - return self - - def __exit__(self, *exc): - # Delete the temporary file and restore the default location. - self.tmp_file.close() - management._QISKITRC_CONFIG_FILE = self.default_qiskitrc_file_original - - def get_account_config_contents( name=None, channel="ibm_cloud", @@ -154,7 +131,7 @@ def get_account_config_contents( proxies=None, set_default=None, ): - """Generate qiskitrc content""" + """Generate account config file content""" if instance is None: instance = "some_instance" if channel == "ibm_cloud" else "hub/group/project" token = token or uuid.uuid4().hex diff --git a/test/decorators.py b/test/decorators.py index 007b7944e..dfef9f8e0 100644 --- a/test/decorators.py +++ b/test/decorators.py @@ -69,13 +69,14 @@ def _wrapper(self, *args, **kwargs): def _get_integration_test_config(): - token, url, instance = ( + token, url, instance, channel_strategy = ( os.getenv("QISKIT_IBM_TOKEN"), os.getenv("QISKIT_IBM_URL"), os.getenv("QISKIT_IBM_INSTANCE"), + os.getenv("CHANNEL_STRATEGY"), ) channel: Any = "ibm_quantum" if url.find("quantum-computing.ibm.com") >= 0 else "ibm_cloud" - return channel, token, url, instance + return channel, token, url, instance, channel_strategy def run_integration_test(func): @@ -115,7 +116,7 @@ def _wrapper(self, *args, **kwargs): ["ibm_cloud", "ibm_quantum"] if supported_channel is None else supported_channel ) - channel, token, url, instance = _get_integration_test_config() + channel, token, url, instance, channel_strategy = _get_integration_test_config() if not all([channel, token, url]): raise Exception("Configuration Issue") # pylint: disable=broad-exception-raised @@ -131,6 +132,7 @@ def _wrapper(self, *args, **kwargs): channel=channel, token=token, url=url, + channel_strategy=channel_strategy, ) dependencies = IntegrationTestDependencies( channel=channel, @@ -138,6 +140,7 @@ def _wrapper(self, *args, **kwargs): url=url, instance=instance, service=service, + channel_strategy=channel_strategy, ) kwargs["dependencies"] = dependencies func(self, *args, **kwargs) @@ -156,6 +159,7 @@ class IntegrationTestDependencies: token: str channel: str url: str + channel_strategy: str def integration_test_setup_with_backend( diff --git a/test/ibm_test_case.py b/test/ibm_test_case.py index c724ae113..35d5d9972 100644 --- a/test/ibm_test_case.py +++ b/test/ibm_test_case.py @@ -16,21 +16,20 @@ import logging import inspect import warnings +from unittest import TestCase +from unittest.util import safe_repr from contextlib import suppress from collections import defaultdict from typing import DefaultDict, Dict -from qiskit.test.reference_circuits import ReferenceCircuits -from qiskit.test.base import BaseQiskitTestCase - from qiskit_ibm_runtime import QISKIT_IBM_RUNTIME_LOGGER_NAME from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Options -from .utils import setup_test_logging +from .utils import setup_test_logging, bell from .decorators import IntegrationTestDependencies, integration_test_setup -class IBMTestCase(BaseQiskitTestCase): +class IBMTestCase(TestCase): """Custom TestCase for use with qiskit-ibm-runtime.""" log: logging.Logger @@ -69,6 +68,97 @@ def _set_logging_level(cls, logger: logging.Logger) -> None: logger.addHandler(logging.StreamHandler()) logger.propagate = False + def assert_dict_almost_equal( + self, dict1, dict2, delta=None, msg=None, places=None, default_value=0 + ): + """Assert two dictionaries with numeric values are almost equal. + + Fail if the two dictionaries are unequal as determined by + comparing that the difference between values with the same key are + not greater than delta (default 1e-8), or that difference rounded + to the given number of decimal places is not zero. If a key in one + dictionary is not in the other the default_value keyword argument + will be used for the missing value (default 0). If the two objects + compare equal then they will automatically compare almost equal. + + Args: + dict1 (dict): a dictionary. + dict2 (dict): a dictionary. + delta (number): threshold for comparison (defaults to 1e-8). + msg (str): return a custom message on failure. + places (int): number of decimal places for comparison. + default_value (number): default value for missing keys. + + Raises: + TypeError: if the arguments are not valid (both `delta` and + `places` are specified). + AssertionError: if the dictionaries are not almost equal. + """ + + error_msg = self.dicts_almost_equal(dict1, dict2, delta, places, default_value) + + if error_msg: + msg = self._formatMessage(msg, error_msg) + raise self.failureException(msg) + + def dicts_almost_equal(self, dict1, dict2, delta=None, places=None, default_value=0): + """Test if two dictionaries with numeric values are almost equal. + + Fail if the two dictionaries are unequal as determined by + comparing that the difference between values with the same key are + not greater than delta (default 1e-8), or that difference rounded + to the given number of decimal places is not zero. If a key in one + dictionary is not in the other the default_value keyword argument + will be used for the missing value (default 0). If the two objects + compare equal then they will automatically compare almost equal. + + Args: + dict1 (dict): a dictionary. + dict2 (dict): a dictionary. + delta (number): threshold for comparison (defaults to 1e-8). + places (int): number of decimal places for comparison. + default_value (number): default value for missing keys. + + Raises: + TypeError: if the arguments are not valid (both `delta` and + `places` are specified). + + Returns: + String: Empty string if dictionaries are almost equal. A description + of their difference if they are deemed not almost equal. + """ + + def valid_comparison(value): + """compare value to delta, within places accuracy""" + if places is not None: + return round(value, places) == 0 + else: + return value < delta + + # Check arguments. + if dict1 == dict2: + return "" + if places is not None: + if delta is not None: + raise TypeError("specify delta or places not both") + msg_suffix = " within %s places" % places + else: + delta = delta or 1e-8 + msg_suffix = " within %s delta" % delta + + # Compare all keys in both dicts, populating error_msg. + error_msg = "" + for key in set(dict1.keys()) | set(dict2.keys()): + val1 = dict1.get(key, default_value) + val2 = dict2.get(key, default_value) + if not valid_comparison(abs(val1 - val2)): + error_msg += f"({safe_repr(key)}: {safe_repr(val1)} != {safe_repr(val2)}), " + + if error_msg: + return error_msg[:-2] + msg_suffix + else: + return "" + class IBMIntegrationTestCase(IBMTestCase): """Custom integration test case for use with qiskit-ibm-runtime.""" @@ -163,7 +253,7 @@ def _run_program( if inputs is not None else { "interim_results": interim_results or {}, - "circuits": circuits or ReferenceCircuits.bell(), + "circuits": circuits or bell(), } ) pid = program_id or self.program_ids[service.channel] @@ -184,7 +274,7 @@ def _run_program( if max_execution_time: options.max_execution_time = max_execution_time sampler = Sampler(backend=backend, options=options) - job = sampler.run(circuits or ReferenceCircuits.bell(), callback=callback) + job = sampler.run(circuits or bell(), callback=callback) else: job = service.run( program_id=pid, diff --git a/test/integration/test_account.py b/test/integration/test_account.py index ffe8929e2..9b0b86364 100644 --- a/test/integration/test_account.py +++ b/test/integration/test_account.py @@ -24,6 +24,7 @@ get_resource_controller_api_url, get_iam_api_url, ) +from qiskit_ibm_runtime.exceptions import IBMNotAuthorizedError from ..ibm_test_case import IBMIntegrationTestCase from ..decorators import IntegrationTestDependencies @@ -51,6 +52,28 @@ def _skip_on_ibm_quantum(self): if self.dependencies.channel == "ibm_quantum": self.skipTest("Not supported on ibm_quantum") + def test_channel_strategy(self): + """Test passing in a channel strategy.""" + self._skip_on_ibm_quantum() + # test when channel strategy not supported by instance + with self.assertRaises(IBMNotAuthorizedError): + QiskitRuntimeService( + channel="ibm_cloud", + url=self.dependencies.url, + token=self.dependencies.token, + instance=self.dependencies.instance, + channel_strategy="q-ctrl", + ) + # test passing in default + service = QiskitRuntimeService( + channel="ibm_cloud", + url=self.dependencies.url, + token=self.dependencies.token, + instance=self.dependencies.instance, + channel_strategy="default", + ) + self.assertTrue(service) + def test_resolve_crn_for_valid_service_instance_name(self): """Verify if CRN is transparently resolved based for an existing service instance name.""" self._skip_on_ibm_quantum() diff --git a/test/integration/test_backend.py b/test/integration/test_backend.py index 7c726b492..cc9d4c29d 100644 --- a/test/integration/test_backend.py +++ b/test/integration/test_backend.py @@ -19,7 +19,6 @@ from qiskit.transpiler.target import Target from qiskit import QuantumCircuit from qiskit.providers.exceptions import QiskitBackendNotFoundError -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit_ibm_provider.ibm_qubit_properties import IBMQubitProperties from qiskit_ibm_provider.exceptions import IBMBackendValueError @@ -28,6 +27,7 @@ from ..ibm_test_case import IBMIntegrationTestCase from ..decorators import run_integration_test, production_only, quantum_only +from ..utils import bell class TestIntegrationBackend(IBMIntegrationTestCase): @@ -45,6 +45,26 @@ def test_backends(self, service): f"backend_names={backend_names}", ) + @run_integration_test + @quantum_only + def test_backend_wrong_instance(self, service): + """Test getting a backend with wrong instance.""" + hgps = list(service._hgps.keys()) + if len(hgps) < 2: + raise SkipTest("Skipping test, not enough instances") + + hgp_1 = hgps[0] + hgp_2 = hgps[1] + hgp_1_backends = [backend.name for backend in service.backends(instance=hgp_1)] + hgp_2_backends = [backend.name for backend in service.backends(instance=hgp_2)] + unique_backends_list = list( + set(hgp_2_backends) - set(hgp_1_backends) + ) # get differences between the two lists + if unique_backends_list: + unique_backend = unique_backends_list[0] + with self.assertRaises(QiskitBackendNotFoundError): + service.backend(unique_backend, instance=hgp_1) + @run_integration_test @quantum_only def test_backends_no_config(self, service): @@ -223,7 +243,7 @@ def test_sim_backend_options(self): backend = self.service.backend("ibmq_qasm_simulator") backend.options.shots = 2048 backend.set_options(memory=True) - inputs = backend.run(ReferenceCircuits.bell(), shots=1, foo="foo").inputs + inputs = backend.run(bell(), shots=1, foo="foo").inputs self.assertEqual(inputs["shots"], 1) self.assertTrue(inputs["memory"]) self.assertEqual(inputs["foo"], "foo") @@ -236,7 +256,7 @@ def test_paused_backend_warning(self): paused_status.status_msg = "internal" backend.status = mock.MagicMock(return_value=paused_status) with self.assertWarns(Warning): - backend.run(ReferenceCircuits.bell()) + backend.run(bell()) def test_backend_wrong_instance(self): """Test that an error is raised when retrieving a backend not in the instance.""" diff --git a/test/integration/test_estimator.py b/test/integration/test_estimator.py index 48cca7087..37c54c2e0 100644 --- a/test/integration/test_estimator.py +++ b/test/integration/test_estimator.py @@ -18,13 +18,13 @@ from qiskit.circuit.library import RealAmplitudes from qiskit.primitives import Estimator as TerraEstimator from qiskit.quantum_info import SparsePauliOp -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit.primitives import BaseEstimator, EstimatorResult from qiskit_ibm_runtime import Estimator, Session from ..decorators import run_integration_test from ..ibm_test_case import IBMIntegrationTestCase +from ..utils import bell class TestIntegrationEstimator(IBMIntegrationTestCase): @@ -118,12 +118,14 @@ def _callback(job_id_, result_): ws_result = [] job_ids = set() - bell = ReferenceCircuits.bell() + bell_circuit = bell() obs = SparsePauliOp.from_list([("IZ", 1)]) with Session(service, self.backend) as session: estimator = Estimator(session=session) - job = estimator.run(circuits=[bell] * 60, observables=[obs] * 60, callback=_callback) + job = estimator.run( + circuits=[bell_circuit] * 60, observables=[obs] * 60, callback=_callback + ) result = job.result() self.assertIsInstance(ws_result[-1], dict) ws_result_values = np.asarray(ws_result[-1]["values"]) diff --git a/test/integration/test_ibm_job.py b/test/integration/test_ibm_job.py index 0c60c080a..45e56fcc1 100644 --- a/test/integration/test_ibm_job.py +++ b/test/integration/test_ibm_job.py @@ -21,7 +21,6 @@ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.compiler import transpile from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit_ibm_provider.api.rest.job import Job as RestJob from qiskit_ibm_provider.exceptions import IBMBackendApiError @@ -30,11 +29,7 @@ from qiskit_ibm_runtime.exceptions import RuntimeJobTimeoutError, RuntimeJobNotFound from ..ibm_test_case import IBMIntegrationTestCase -from ..utils import ( - most_busy_backend, - cancel_job_safe, - submit_and_cancel, -) +from ..utils import most_busy_backend, cancel_job_safe, submit_and_cancel, bell class TestIBMJob(IBMIntegrationTestCase): @@ -44,7 +39,7 @@ def setUp(self): """Initial test setup.""" super().setUp() self.sim_backend = self.service.backend("ibmq_qasm_simulator") - self.bell = ReferenceCircuits.bell() + self.bell = bell() self.sim_job = self.sim_backend.run(self.bell) self.last_month = datetime.now() - timedelta(days=30) @@ -269,7 +264,7 @@ def test_wait_for_final_state_timeout(self): if self.dependencies.channel == "ibm_cloud": raise SkipTest("Cloud account does not have real backend.") backend = most_busy_backend(TestIBMJob.service) - job = backend.run(transpile(ReferenceCircuits.bell(), backend=backend)) + job = backend.run(transpile(bell(), backend=backend)) try: self.assertRaises(RuntimeJobTimeoutError, job.wait_for_final_state, timeout=0.1) finally: diff --git a/test/integration/test_ibm_job_attributes.py b/test/integration/test_ibm_job_attributes.py index 66d18b22e..fd9ff1ad5 100644 --- a/test/integration/test_ibm_job_attributes.py +++ b/test/integration/test_ibm_job_attributes.py @@ -15,13 +15,12 @@ import uuid import time from datetime import datetime, timedelta -from unittest import skip +from unittest import skip, SkipTest from dateutil import tz from qiskit.compiler import transpile from qiskit import QuantumCircuit from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit_ibm_provider.exceptions import IBMBackendValueError @@ -32,7 +31,7 @@ integration_test_setup, ) from ..ibm_test_case import IBMTestCase -from ..utils import most_busy_backend +from ..utils import most_busy_backend, cancel_job_safe, bell class TestIBMJobAttributes(IBMTestCase): @@ -52,14 +51,14 @@ def setUpClass(cls, dependencies: IntegrationTestDependencies) -> None: cls.dependencies = dependencies cls.service = dependencies.service cls.sim_backend = dependencies.service.backend("ibmq_qasm_simulator") - cls.bell = transpile(ReferenceCircuits.bell(), cls.sim_backend) + cls.bell = transpile(bell(), cls.sim_backend) cls.sim_job = cls.sim_backend.run(cls.bell) cls.last_week = datetime.now() - timedelta(days=7) def setUp(self): """Initial test setup.""" super().setUp() - self._qc = ReferenceCircuits.bell() + self._qc = bell() def test_job_id(self): """Test getting a job ID.""" @@ -200,13 +199,13 @@ def test_time_per_step(self): step, time_data, start_datetime, end_datetime ), ) - rjob = self.service.job(job.job_id()) self.assertTrue(rjob.time_per_step()) - @skip("queue_info supported in provider but not here") def test_queue_info(self): """Test retrieving queue information.""" + if self.dependencies.channel == "ibm_cloud": + raise SkipTest("Not supported on cloud channel.") # Find the most busy backend. backend = most_busy_backend(self.service) leave_states = list(JOB_FINAL_STATES) + [JobStatus.RUNNING] @@ -230,19 +229,11 @@ def test_queue_info(self): ) msg = "Job {} is queued but has no ".format(job.job_id()) self.assertIsNotNone(queue_info, msg + "queue info.") - for attr, value in queue_info.__dict__.items(): - self.assertIsNotNone(value, msg + attr) - self.assertTrue( - all( - 0 < priority <= 1.0 - for priority in [ - queue_info.hub_priority, - queue_info.group_priority, - queue_info.project_priority, - ] - ), - "Unexpected queue info {} for job {}".format(queue_info, job.job_id()), - ) - self.assertTrue(queue_info.format()) self.assertTrue(repr(queue_info)) + elif job._status is not None: + self.assertIsNone(job.queue_position()) + self.log.warning("Unable to retrieve queue information") + + # Cancel job so it doesn't consume more resources. + cancel_job_safe(job, self.log) diff --git a/test/integration/test_ibm_qasm_simulator.py b/test/integration/test_ibm_qasm_simulator.py index daf96f11b..d69b0cd2b 100644 --- a/test/integration/test_ibm_qasm_simulator.py +++ b/test/integration/test_ibm_qasm_simulator.py @@ -17,7 +17,7 @@ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.compiler import transpile -from qiskit.test.reference_circuits import ReferenceCircuits +from ..utils import bell from ..ibm_test_case import IBMIntegrationTestCase @@ -40,7 +40,7 @@ def test_execute_one_circuit_simulator_online(self): counts = result.get_counts(quantum_circuit) target = {"0": shots / 2, "1": shots / 2} threshold = 0.1 * shots - self.assertDictAlmostEqual(counts, target, threshold) + self.assert_dict_almost_equal(counts, target, threshold) def test_execute_several_circuits_simulator_online(self): """Test execute_several_circuits_simulator_online.""" @@ -65,8 +65,8 @@ def test_execute_several_circuits_simulator_online(self): target1 = {"00": shots / 4, "01": shots / 4, "10": shots / 4, "11": shots / 4} target2 = {"00": shots / 2, "11": shots / 2} threshold = 0.1 * shots - self.assertDictAlmostEqual(counts1, target1, threshold) - self.assertDictAlmostEqual(counts2, target2, threshold) + self.assert_dict_almost_equal(counts1, target1, threshold) + self.assert_dict_almost_equal(counts2, target2, threshold) def test_online_qasm_simulator_two_registers(self): """Test online_qasm_simulator_two_registers.""" @@ -114,7 +114,7 @@ def _new_submit(qobj, *args, **kwargs): try: backend._configuration._data["simulation_method"] = "extended_stabilizer" backend._submit_job = _new_submit - circ = transpile(ReferenceCircuits.bell(), backend=backend) + circ = transpile(bell(), backend=backend) backend.run(circ, header={"test": "circuits"}) finally: backend._configuration._data["simulation_method"] = sim_method @@ -137,7 +137,7 @@ def _new_submit(qobj, *args, **kwargs): try: backend._configuration._data["simulation_method"] = "extended_stabilizer" backend._submit_job = _new_submit - circ = transpile(ReferenceCircuits.bell(), backend=backend) + circ = transpile(bell(), backend=backend) backend.run(circ, method="my_method", header={"test": "circuits"}) finally: backend._configuration._data["simulation_method"] = sim_method diff --git a/test/integration/test_job.py b/test/integration/test_job.py index 697d8aeff..c099b18bb 100644 --- a/test/integration/test_job.py +++ b/test/integration/test_job.py @@ -17,8 +17,6 @@ import unittest from qiskit.providers.jobstatus import JOB_FINAL_STATES, JobStatus -from qiskit.test.decorators import slow_test -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit_ibm_runtime.constants import API_TO_JOB_ERROR_MESSAGE from qiskit_ibm_runtime.exceptions import ( @@ -33,7 +31,7 @@ SerializableClassDecoder, SerializableClass, ) -from ..utils import cancel_job_safe, wait_for_status, get_real_device +from ..utils import cancel_job_safe, wait_for_status, get_real_device, bell class TestIntegrationJob(IBMIntegrationJobTestCase): @@ -47,16 +45,6 @@ def test_run_program(self, service): self.assertEqual(JobStatus.DONE, job.status()) self.assertTrue(job.result()) - @slow_test - @run_integration_test - def test_run_program_real_device(self, service): - """Test running a program.""" - device = get_real_device(service) - job = self._run_program(service, backend=device) - result = job.result() - self.assertEqual(JobStatus.DONE, job.status()) - self.assertEqual("foo", result) - @run_integration_test @production_only def test_run_program_cloud_no_backend(self, service): @@ -100,12 +88,8 @@ def test_run_program_failed(self, service): def test_cancel_job_queued(self, service): """Test canceling a queued job.""" real_device = get_real_device(service) - _ = self._run_program( - service, circuits=[ReferenceCircuits.bell()] * 10, backend=real_device - ) - job = self._run_program( - service, circuits=[ReferenceCircuits.bell()] * 2, backend=real_device - ) + _ = self._run_program(service, circuits=[bell()] * 10, backend=real_device) + job = self._run_program(service, circuits=[bell()] * 2, backend=real_device) wait_for_status(job, JobStatus.QUEUED) if not cancel_job_safe(job, self.log): return @@ -118,7 +102,7 @@ def test_cancel_job_running(self, service): """Test canceling a running job.""" job = self._run_program( service, - circuits=[ReferenceCircuits.bell()] * 10, + circuits=[bell()] * 10, ) rjob = service.job(job.job_id()) if not cancel_job_safe(rjob, self.log): @@ -184,7 +168,7 @@ def test_job_inputs(self, service): interim_results = get_complex_types() inputs = { "interim_results": interim_results, - "circuits": ReferenceCircuits.bell(), + "circuits": bell(), } job = self._run_program(service, inputs=inputs, program_id="circuit-runner") self.assertEqual(inputs, job.inputs) diff --git a/test/integration/test_retrieve_job.py b/test/integration/test_retrieve_job.py index 5997a9e62..222fff7f7 100644 --- a/test/integration/test_retrieve_job.py +++ b/test/integration/test_retrieve_job.py @@ -15,11 +15,10 @@ import uuid from datetime import datetime, timezone from qiskit.providers.jobstatus import JobStatus -from qiskit.test.reference_circuits import ReferenceCircuits from ..ibm_test_case import IBMIntegrationJobTestCase from ..decorators import run_integration_test, production_only, quantum_only -from ..utils import wait_for_status, get_real_device +from ..utils import wait_for_status, get_real_device, bell class TestIntegrationRetrieveJob(IBMIntegrationJobTestCase): @@ -61,7 +60,7 @@ def test_lazy_loading_params(self, service): """Test lazy loading job params.""" job = self._run_program( service, - inputs={"circuits": ReferenceCircuits.bell()}, + inputs={"circuits": bell()}, program_id="circuit-runner", backend="ibmq_qasm_simulator", ) @@ -71,6 +70,18 @@ def test_lazy_loading_params(self, service): self.assertTrue(rjob.inputs) self.assertTrue(rjob._params) + @run_integration_test + @quantum_only + def test_params_not_retrieved(self, service): + """Test excluding params when unnecessary.""" + job = self._run_program(service) + job.wait_for_final_state() + + self.assertTrue(job.creation_date) + self.assertFalse(job._params) + self.assertTrue(job.inputs) + self.assertTrue(job._params) + @run_integration_test def test_retrieve_all_jobs(self, service): """Test retrieving all jobs.""" @@ -101,7 +112,7 @@ def test_retrieve_jobs_limit(self, service): @run_integration_test def test_retrieve_pending_jobs(self, service): """Test retrieving pending jobs (QUEUED, RUNNING).""" - circuits = [ReferenceCircuits.bell()] * 20 + circuits = [bell()] * 20 job = self._run_program(service, circuits=circuits) wait_for_status(job, JobStatus.RUNNING) rjobs = service.jobs(pending=True) diff --git a/test/integration/test_sampler.py b/test/integration/test_sampler.py index 835b17493..27aa9ba2a 100644 --- a/test/integration/test_sampler.py +++ b/test/integration/test_sampler.py @@ -16,7 +16,7 @@ from qiskit.circuit import QuantumCircuit, Gate from qiskit.circuit.library import RealAmplitudes -from qiskit.test.reference_circuits import ReferenceCircuits + from qiskit.primitives import BaseSampler, SamplerResult from qiskit.result import QuasiDistribution @@ -25,6 +25,7 @@ from ..decorators import run_integration_test from ..ibm_test_case import IBMIntegrationTestCase +from ..utils import bell class TestIntegrationIBMSampler(IBMIntegrationTestCase): @@ -32,7 +33,7 @@ class TestIntegrationIBMSampler(IBMIntegrationTestCase): def setUp(self) -> None: super().setUp() - self.bell = ReferenceCircuits.bell() + self.bell = bell() self.backend = "ibmq_qasm_simulator" @run_integration_test diff --git a/test/integration/test_session.py b/test/integration/test_session.py index b8c086a59..15e4a92f5 100644 --- a/test/integration/test_session.py +++ b/test/integration/test_session.py @@ -16,12 +16,13 @@ from qiskit.circuit.library import RealAmplitudes from qiskit.quantum_info import SparsePauliOp -from qiskit.test.reference_circuits import ReferenceCircuits + from qiskit.primitives import EstimatorResult, SamplerResult from qiskit.result import Result from qiskit_ibm_runtime import Estimator, Session, Sampler, Options +from ..utils import bell from ..decorators import run_integration_test, quantum_only from ..ibm_test_case import IBMIntegrationTestCase @@ -51,7 +52,7 @@ def test_estimator_sampler(self, service): self.assertEqual(result.metadata[0]["shots"], 100) sampler = Sampler(session=session, options=options) - result = sampler.run(circuits=ReferenceCircuits.bell(), shots=200).result() + result = sampler.run(circuits=bell(), shots=200).result() self.assertIsInstance(result, SamplerResult) self.assertEqual(len(result.quasi_dists), 1) self.assertEqual(len(result.metadata), 1) @@ -67,7 +68,7 @@ def test_estimator_sampler(self, service): self.assertEqual(len(result.metadata), 1) self.assertEqual(result.metadata[0]["shots"], 300) - result = sampler.run(circuits=ReferenceCircuits.bell(), shots=400).result() + result = sampler.run(circuits=bell(), shots=400).result() self.assertIsInstance(result, SamplerResult) self.assertEqual(len(result.quasi_dists), 1) self.assertEqual(len(result.metadata), 1) @@ -84,7 +85,7 @@ def test_using_correct_instance(self, service): backend = service.backend("ibmq_qasm_simulator", instance=instance) with Session(service, backend=backend) as session: sampler = Sampler(session=session) - job = sampler.run(ReferenceCircuits.bell(), shots=400) + job = sampler.run(bell(), shots=400) self.assertEqual(instance, backend._instance) self.assertEqual(instance, job.backend()._instance) @@ -94,11 +95,11 @@ def test_session_from_id(self, service): backend = service.backend("ibmq_qasm_simulator") with Session(service, backend=backend) as session: sampler = Sampler(session=session) - job = sampler.run(ReferenceCircuits.bell(), shots=400) + job = sampler.run(bell(), shots=400) session_id = job.session_id new_session = Session.from_id(backend=backend, session_id=session_id) sampler = Sampler(session=new_session) - job = sampler.run(ReferenceCircuits.bell(), shots=400) + job = sampler.run(bell(), shots=400) self.assertEqual(session_id, job.session_id) @@ -111,9 +112,9 @@ def test_session_id(self): backend.open_session() self.assertEqual(backend.session.session_id, None) self.assertTrue(backend.session.active) - job1 = backend.run(ReferenceCircuits.bell()) + job1 = backend.run(bell()) self.assertEqual(job1._session_id, job1.job_id()) - job2 = backend.run(ReferenceCircuits.bell()) + job2 = backend.run(bell()) self.assertFalse(job2._session_id == job2.job_id()) def test_backend_run_with_session(self): @@ -121,7 +122,7 @@ def test_backend_run_with_session(self): shots = 1000 backend = self.service.backend("ibmq_qasm_simulator") backend.open_session() - result = backend.run(circuits=ReferenceCircuits.bell(), shots=shots).result() + result = backend.run(circuits=bell(), shots=shots).result() backend.cancel_session() self.assertIsInstance(result, Result) self.assertEqual(result.results[0].shots, shots) @@ -134,16 +135,16 @@ def test_backend_and_primitive_in_session(self): backend = self.service.get_backend("ibmq_qasm_simulator") with Session(backend=backend) as session: sampler = Sampler(session=session) - job1 = sampler.run(circuits=ReferenceCircuits.bell()) + job1 = sampler.run(circuits=bell()) with warnings.catch_warnings(record=True): - job2 = backend.run(circuits=ReferenceCircuits.bell()) + job2 = backend.run(circuits=bell()) self.assertEqual(job1.session_id, job1.job_id()) self.assertIsNone(job2.session_id) with backend.open_session() as session: with warnings.catch_warnings(record=True): sampler = Sampler(backend=backend) - job1 = backend.run(ReferenceCircuits.bell()) - job2 = sampler.run(circuits=ReferenceCircuits.bell()) + job1 = backend.run(bell()) + job2 = sampler.run(circuits=bell()) session_id = session.session_id self.assertEqual(session_id, job1.job_id()) self.assertIsNone(job2.session_id) @@ -167,16 +168,16 @@ def test_session_close(self): def test_run_after_cancel(self): """Test running after session is cancelled.""" backend = self.service.backend("ibmq_qasm_simulator") - job1 = backend.run(circuits=ReferenceCircuits.bell()) + job1 = backend.run(circuits=bell()) self.assertIsNone(backend.session) self.assertIsNone(job1._session_id) backend.open_session() - job2 = backend.run(ReferenceCircuits.bell()) + job2 = backend.run(bell()) self.assertIsNotNone(job2._session_id) backend.cancel_session() - job3 = backend.run(circuits=ReferenceCircuits.bell()) + job3 = backend.run(circuits=bell()) self.assertIsNone(backend.session) self.assertIsNone(job3._session_id) @@ -185,19 +186,19 @@ def test_session_as_context_manager(self): backend = self.service.backend("ibmq_qasm_simulator") with backend.open_session() as session: - job1 = backend.run(ReferenceCircuits.bell()) + job1 = backend.run(bell()) session_id = session.session_id self.assertEqual(session_id, job1.job_id()) - job2 = backend.run(ReferenceCircuits.bell()) + job2 = backend.run(bell()) self.assertFalse(session_id == job2.job_id()) def test_run_after_cancel_as_context_manager(self): """Test run after cancel in context manager""" backend = self.service.backend("ibmq_qasm_simulator") with backend.open_session() as session: - _ = backend.run(ReferenceCircuits.bell()) + _ = backend.run(bell()) self.assertEqual(backend.session, session) backend.cancel_session() - job = backend.run(circuits=ReferenceCircuits.bell()) + job = backend.run(circuits=bell()) self.assertIsNone(backend.session) self.assertIsNone(job._session_id) diff --git a/test/qctrl/test_qctrl.py b/test/qctrl/test_qctrl.py new file mode 100644 index 000000000..0eeebbdfb --- /dev/null +++ b/test/qctrl/test_qctrl.py @@ -0,0 +1,393 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for job functions using real runtime service.""" + +import time + +from qiskit import QuantumCircuit +from qiskit.quantum_info import Statevector, hellinger_fidelity +from qiskit.providers.jobstatus import JobStatus +from qiskit.quantum_info import SparsePauliOp + +from qiskit_ibm_runtime import Sampler, Session, Options, Estimator, QiskitRuntimeService +from qiskit_ibm_runtime.exceptions import IBMNotAuthorizedError + +from ..ibm_test_case import IBMIntegrationTestCase +from ..decorators import run_integration_test +from ..utils import cancel_job_safe, bell + +FIDELITY_THRESHOLD = 0.9 +DIFFERENCE_THRESHOLD = 0.1 + + +class TestQCTRL(IBMIntegrationTestCase): + """Integration tests for QCTRL integration.""" + + def setUp(self) -> None: + super().setUp() + self.bell = bell() + self.backend = "alt_canberra" + + def test_channel_strategy_parameter(self): + """Test passing in channel strategy parameter for a q-ctrl instance.""" + service = QiskitRuntimeService( + channel="ibm_cloud", + url=self.dependencies.url, + token=self.dependencies.token, + instance=self.dependencies.instance, + channel_strategy="q-ctrl", + ) + self.assertTrue(service) + + def test_invalid_channel_strategy_parameter(self): + """Test passing in invalid channel strategy parameter for a q-ctrl instance.""" + with self.assertRaises(IBMNotAuthorizedError): + QiskitRuntimeService( + channel="ibm_cloud", + url=self.dependencies.url, + token=self.dependencies.token, + instance=self.dependencies.instance, + channel_strategy=None, + ) + + @run_integration_test + def test_cancel_qctrl_job(self, service): + """Test canceling qctrl job.""" + with Session(service, self.backend) as session: + options = Options(resilience_level=1) + sampler = Sampler(session=session, options=options) + + job = sampler.run([self.bell] * 10) + + rjob = service.job(job.job_id()) + if not cancel_job_safe(rjob, self.log): + return + self.assertEqual(rjob.status(), JobStatus.CANCELLED) + + @run_integration_test + def test_sampler_qctrl_bell(self, service): + """Test qctrl bell state""" + # Set shots for experiment + shots = 1000 + + # Create Bell test circuit + bell_circuit = QuantumCircuit(2) + bell_circuit.h(0) + bell_circuit.cx(0, 1) + + # Add measurements for the sampler + bell_circuit_sampler = bell_circuit.copy() + bell_circuit_sampler.measure_active() + + # Execute circuit in a session with sampler + with Session(service, backend=self.backend): + options = Options(resilience_level=1) + sampler = Sampler(options=options) + + result = sampler.run(bell_circuit_sampler, shots=shots).result() + results_dict = { + "{0:02b}".format(key): value for key, value in result.quasi_dists[0].items() + } # convert keys to bitstrings + + ideal_result = { + key: val / shots for key, val in Statevector(bell_circuit).probabilities_dict().items() + } + fidelity = hellinger_fidelity(results_dict, ideal_result) + + self.assertGreater(fidelity, FIDELITY_THRESHOLD) + + @run_integration_test + def test_sampler_qctrl_ghz(self, service): + """Test qctrl small GHZ""" + shots = 1000 + num_qubits = 5 + ghz_circuit = QuantumCircuit(num_qubits) + ghz_circuit.h(0) + for i in range(num_qubits - 1): + ghz_circuit.cx(i, i + 1) + + # Add measurements for the sampler + ghz_circuit_sampler = ghz_circuit.copy() + ghz_circuit_sampler.measure_active() + + # Execute circuit in a session with sampler + with Session(service, backend=self.backend): + options = Options(resilience_level=1) + sampler = Sampler(options=options) + + result = sampler.run(ghz_circuit_sampler, shots=shots).result() + results_dict = { + f"{{0:0{num_qubits}b}}".format(key): value + for key, value in result.quasi_dists[0].items() + } # convert keys to bitstrings + + ideal_result = { + key: val / shots for key, val in Statevector(ghz_circuit).probabilities_dict().items() + } + fidelity = hellinger_fidelity(results_dict, ideal_result) + self.assertGreater(fidelity, FIDELITY_THRESHOLD) + + @run_integration_test + def test_sampler_qctrl_superposition(self, service): + """Test qctrl small superposition""" + + shots = 1000 + num_qubits = 5 + superposition_circuit = QuantumCircuit(num_qubits) + superposition_circuit.h(range(num_qubits)) + + # Add measurements for the sampler + superposition_circuit_sampler = superposition_circuit.copy() + superposition_circuit_sampler.measure_active() + + # Execute circuit in a session with sampler + with Session(service, backend=self.backend): + options = Options(resilience_level=1) + sampler = Sampler(options=options) + + result = sampler.run(superposition_circuit_sampler, shots=shots).result() + results_dict = { + f"{{0:0{num_qubits}b}}".format(key): value + for key, value in result.quasi_dists[0].items() + } # convert keys to bitstrings + + ideal_result = { + key: val / shots + for key, val in Statevector(superposition_circuit).probabilities_dict().items() + } + fidelity = hellinger_fidelity(results_dict, ideal_result) + self.assertGreater(fidelity, FIDELITY_THRESHOLD) + + @run_integration_test + def test_sampler_qctrl_computational_states(self, service): + """Test qctrl computational states""" + shots = 1000 + num_qubits = 3 + computational_states_circuits = [] + for idx in range(2**num_qubits): + circuit = QuantumCircuit(num_qubits) + bitstring = f"{{0:0{num_qubits}b}}".format(idx) + for bit_pos, bit in enumerate( + bitstring[::-1] + ): # convert to little-endian (qiskit convention) + if bit == "1": + circuit.x(bit_pos) + computational_states_circuits.append(circuit) + + # Add measurements for the sampler + computational_states_sampler_circuits = [] + for circuit in computational_states_circuits: + circuit_sampler = circuit.copy() + circuit_sampler.measure_all() + computational_states_sampler_circuits.append(circuit_sampler) + + # Execute circuit in a session with sampler + with Session(service, backend=self.backend): + options = Options(resilience_level=1) + sampler = Sampler(options=options) + + result = sampler.run(computational_states_sampler_circuits, shots=shots).result() + results_dict_list = [ + {f"{{0:0{num_qubits}b}}".format(key): value for key, value in quasis.items()} + for quasis in result.quasi_dists + ] # convert keys to bitstrings + + ideal_results_list = [ + {key: val / shots for key, val in Statevector(circuit).probabilities_dict().items()} + for circuit in computational_states_circuits + ] + fidelities = [ + hellinger_fidelity(results_dict, ideal_result) + for results_dict, ideal_result in zip(results_dict_list, ideal_results_list) + ] + + for fidelity in fidelities: + self.assertGreater(fidelity, FIDELITY_THRESHOLD) + + @run_integration_test + def test_estimator_qctrl_bell(self, service): + """Test estimator qctrl bell state""" + # Set shots for experiment + shots = 1000 + + # Create Bell test circuit + bell_circuit = QuantumCircuit(2) + bell_circuit.h(0) + bell_circuit.cx(0, 1) + + # Measure some observables in the estimator + observables = [SparsePauliOp("ZZ"), SparsePauliOp("IZ"), SparsePauliOp("ZI")] + + # Execute circuit in a session with estimator + with Session(service, backend=self.backend): + estimator = Estimator() + + result = estimator.run( + [bell_circuit] * len(observables), observables=observables, shots=shots + ).result() + + ideal_result = [ + Statevector(bell_circuit).expectation_value(observable).real + for observable in observables + ] + absolute_difference = [ + abs(obs_theory - obs_exp) for obs_theory, obs_exp in zip(ideal_result, result.values) + ] + # absolute_difference_dict = { + # obs.paulis[0].to_label(): diff for obs, diff in zip(observables, absolute_difference) + # } + + for diff in absolute_difference: + self.assertLess(diff, DIFFERENCE_THRESHOLD) + + @run_integration_test + def test_estimator_qctrl_ghz(self, service): + """Test estimator qctrl GHZ state""" + shots = 1000 + num_qubits = 5 + ghz_circuit = QuantumCircuit(num_qubits) + ghz_circuit.h(0) + for i in range(num_qubits - 1): + ghz_circuit.cx(i, i + 1) + + # Measure some observables in the estimator + observables = [ + SparsePauliOp("Z" * num_qubits), + SparsePauliOp("I" * (num_qubits - 1) + "Z"), + SparsePauliOp("Z" + "I" * (num_qubits - 1)), + ] + + # Execute circuit in a session with estimator + with Session(service, backend=self.backend): + estimator = Estimator() + + result = estimator.run( + [ghz_circuit] * len(observables), observables=observables, shots=shots + ).result() + + ideal_result = [ + Statevector(ghz_circuit).expectation_value(observable).real + for observable in observables + ] + absolute_difference = [ + abs(obs_theory - obs_exp) for obs_theory, obs_exp in zip(ideal_result, result.values) + ] + absolute_difference_dict = { + obs.paulis[0].to_label(): diff for obs, diff in zip(observables, absolute_difference) + } + + print( + "absolute difference between theory and experiment expectation values: ", + absolute_difference_dict, + ) + for diff in absolute_difference: + self.assertLess(diff, DIFFERENCE_THRESHOLD) + + @run_integration_test + def test_estimator_qctrl_superposition(self, service): + """Test estimator qctrl small superposition""" + shots = 1000 + num_qubits = 4 + superposition_circuit = QuantumCircuit(num_qubits) + superposition_circuit.h(range(num_qubits)) + + # Measure some observables in the estimator + obs_labels = [["I"] * num_qubits for _ in range(num_qubits)] + for idx, obs in enumerate(obs_labels): + obs[idx] = "Z" + obs_labels = ["".join(obs) for obs in obs_labels] + observables = [SparsePauliOp(obs) for obs in obs_labels] + + # Execute circuit in a session with estimator + with Session(service, backend=self.backend): + estimator = Estimator() + + result = estimator.run( + [superposition_circuit] * len(observables), observables=observables, shots=shots + ).result() + + ideal_result = [ + Statevector(superposition_circuit).expectation_value(observable).real + for observable in observables + ] + absolute_difference = [ + abs(obs_theory - obs_exp) for obs_theory, obs_exp in zip(ideal_result, result.values) + ] + # absolute_difference_dict = { + # obs.paulis[0].to_label(): diff for obs, diff in zip(observables, absolute_difference) + # } + + for diff in absolute_difference: + self.assertLess(diff, DIFFERENCE_THRESHOLD) + + @run_integration_test + def test_estimator_qctrl_computational(self, service): + """Test estimator qctrl computational states""" + shots = 1000 + num_qubits = 3 + computational_states_circuits = [] + for idx in range(2**num_qubits): + circuit = QuantumCircuit(num_qubits) + bitstring = f"{{0:0{num_qubits}b}}".format(idx) + for bit_pos, bit in enumerate( + bitstring[::-1] + ): # convert to little-endian (qiskit convention) + if bit == "1": + circuit.x(bit_pos) + computational_states_circuits.append(circuit) + + # Measure some observables in the estimator + obs_labels = [["I"] * num_qubits for _ in range(num_qubits)] + for idx, obs in enumerate(obs_labels): + obs[idx] = "Z" + obs_labels = ["".join(obs) for obs in obs_labels] + observables = [SparsePauliOp(obs) for obs in obs_labels] + + computational_states_circuits_estimator, observables_estimator = [], [] + for circuit in computational_states_circuits: + computational_states_circuits_estimator += [circuit] * len(observables) + observables_estimator += observables + + # Execute circuit in a session with estimator + with Session(service, self.backend): + estimator = Estimator() + result = estimator.run( + computational_states_circuits_estimator, + observables=observables_estimator, + shots=shots, + ).result() + + ideal_result = [ + Statevector(circuit).expectation_value(observable).real + for circuit, observable in zip( + computational_states_circuits_estimator, observables_estimator + ) + ] + absolute_difference = [ + abs(obs_theory - obs_exp) for obs_theory, obs_exp in zip(ideal_result, result.values) + ] + + absolute_difference_dict = {} + for idx in range(2**num_qubits): + circuit = QuantumCircuit(num_qubits) + bitstring = f"{{0:0{num_qubits}b}}".format(idx) + + absolute_difference_dict[bitstring] = { + obs.paulis[0].to_label(): diff + for obs, diff in zip( + observables_estimator[idx * len(observables) : (idx + 1) * len(observables)], + absolute_difference[idx * len(observables) : (idx + 1) * len(observables)], + ) + } + for diff in absolute_difference: + self.assertLess(diff, DIFFERENCE_THRESHOLD) diff --git a/test/unit/mock/fake_runtime_client.py b/test/unit/mock/fake_runtime_client.py index fb27ffefc..ba514edb0 100644 --- a/test/unit/mock/fake_runtime_client.py +++ b/test/unit/mock/fake_runtime_client.py @@ -344,7 +344,7 @@ def program_run( self._jobs[job_id] = job return {"id": job_id, "backend": backend_name} - def job_get(self, job_id: str, exclude_params: bool = None) -> Any: + def job_get(self, job_id: str, exclude_params: bool = True) -> Any: """Get the specific job.""" return self._get_job(job_id, exclude_params).to_dict() diff --git a/test/unit/test_account.py b/test/unit/test_account.py index 90605a88f..5c0494784 100644 --- a/test/unit/test_account.py +++ b/test/unit/test_account.py @@ -18,7 +18,6 @@ import uuid from typing import Any from unittest import skipIf -import warnings from qiskit_ibm_provider.proxies import ProxyConfiguration from qiskit_ibm_runtime.accounts import ( @@ -38,7 +37,6 @@ from ..account import ( get_account_config_contents, temporary_account_config_file, - custom_qiskitrc, no_envs, custom_envs, ) @@ -845,27 +843,6 @@ def test_enable_account_by_name_input_instance(self): self.assertTrue(service._account) self.assertEqual(service._account.instance, instance) - @no_envs(["QISKIT_IBM_TOKEN"]) - def test_enable_account_by_qiskitrc(self): - """Test initializing account by a qiskitrc file.""" - token = "token-x" - proxies = {"urls": {"https": "localhost:8080"}} - str_contents = f""" - [ibmq] - token = {token} - url = https://auth.quantum-computing.ibm.com/api - verify = True - default_provider = ibm-q/open/main - proxies = {proxies} - """ - with custom_qiskitrc(contents=str.encode(str_contents)): - with temporary_account_config_file(contents={}): - with warnings.catch_warnings(record=True) as warn: - service = FakeRuntimeService() - self.assertIn("Use of the ~/.qiskit/qiskitrc.json file is deprecated", str(warn[0].message)) - self.assertTrue(service._account) - self.assertEqual(service._account.token, token) - def test_enable_account_by_channel_input_instance(self): """Test initializing account by channel and input instance.""" instance = uuid.uuid4().hex diff --git a/test/unit/test_data_serialization.py b/test/unit/test_data_serialization.py index 2b3a63e42..ad5eeb42d 100644 --- a/test/unit/test_data_serialization.py +++ b/test/unit/test_data_serialization.py @@ -24,7 +24,7 @@ from ddt import data, ddt from qiskit.circuit import Parameter, QuantumCircuit -from qiskit.test.reference_circuits import ReferenceCircuits + from qiskit.circuit.library import EfficientSU2, CXGate, PhaseGate, U2Gate from qiskit.providers.fake_provider import FakeNairobi import qiskit.quantum_info as qi @@ -45,7 +45,7 @@ SerializableClassDecoder, get_complex_types, ) -from ..utils import mock_wait_for_final_state +from ..utils import mock_wait_for_final_state, bell @ddt @@ -87,9 +87,9 @@ def test_coder(self): def test_coder_qc(self): """Test runtime encoder and decoder for circuits.""" - bell = ReferenceCircuits.bell() + bell_circuit = bell() unbound = EfficientSU2(num_qubits=4, reps=1, entanglement="linear") - subtests = (bell, unbound, [bell, unbound]) + subtests = (bell_circuit, unbound, [bell_circuit, unbound]) for circ in subtests: with self.subTest(circ=circ): encoded = json.dumps(circ, cls=RuntimeEncoder) diff --git a/test/unit/test_ibm_primitives.py b/test/unit/test_ibm_primitives.py index 89a4d9855..cc7054107 100644 --- a/test/unit/test_ibm_primitives.py +++ b/test/unit/test_ibm_primitives.py @@ -21,7 +21,7 @@ from qiskit import transpile from qiskit.circuit import QuantumCircuit -from qiskit.test.reference_circuits import ReferenceCircuits + from qiskit.quantum_info import SparsePauliOp from qiskit.providers.fake_provider import FakeManila from qiskit_aer.noise import NoiseModel @@ -41,6 +41,7 @@ flat_dict_partially_equal, dict_keys_equal, create_faulty_backend, + bell, ) @@ -56,7 +57,7 @@ class TestPrimitives(IBMTestCase): @classmethod def setUpClass(cls): - cls.qx = ReferenceCircuits.bell() + cls.qx = bell() cls.obs = SparsePauliOp.from_list([("IZ", 1)]) return super().setUpClass() diff --git a/test/unit/test_ibm_primitives_v2.py b/test/unit/test_ibm_primitives_v2.py index a92983059..ea9f45a61 100644 --- a/test/unit/test_ibm_primitives_v2.py +++ b/test/unit/test_ibm_primitives_v2.py @@ -23,7 +23,6 @@ from qiskit import transpile from qiskit.circuit import QuantumCircuit from qiskit.circuit.library import RealAmplitudes -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit.quantum_info import SparsePauliOp from qiskit.providers.fake_provider import FakeManila @@ -49,6 +48,7 @@ MockSession, get_primitive_inputs, get_mocked_backend, + bell, ) @@ -59,7 +59,7 @@ class TestPrimitivesV2(IBMTestCase): @classmethod def setUpClass(cls): - cls.circ = ReferenceCircuits.bell() + cls.circ = bell() cls.obs = SparsePauliOp.from_list([("IZ", 1)]) return super().setUpClass() diff --git a/test/unit/test_runtime_ws.py b/test/unit/test_runtime_ws.py index 4b7173a9f..27c3230c4 100644 --- a/test/unit/test_runtime_ws.py +++ b/test/unit/test_runtime_ws.py @@ -16,7 +16,6 @@ from unittest.mock import MagicMock from qiskit.providers.fake_provider import FakeQasmSimulator -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit.quantum_info import SparsePauliOp from qiskit_ibm_runtime import ( RuntimeJob, @@ -30,6 +29,7 @@ from qiskit_ibm_runtime.exceptions import RuntimeInvalidStateError from qiskit_ibm_runtime.ibm_backend import IBMBackend +from ..utils import bell from .mock.fake_runtime_client import BaseFakeRuntimeClient from .mock.ws_handler import ( websocket_handler, @@ -93,7 +93,7 @@ def _patched_run(callback, *args, **kwargs): # pylint: disable=unused-argument service.run = _patched_run service._channel_strategy = None - circ = ReferenceCircuits.bell() + circ = bell() obs = SparsePauliOp.from_list([("IZ", 1)]) primitives = [Sampler, Estimator] sub_tests = [ diff --git a/test/unit/test_sampler.py b/test/unit/test_sampler.py index c48315509..f2e694a71 100644 --- a/test/unit/test_sampler.py +++ b/test/unit/test_sampler.py @@ -20,11 +20,11 @@ from qiskit import QuantumCircuit from qiskit.circuit.library import RealAmplitudes -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit_ibm_runtime import Sampler, Session, SamplerV2, SamplerOptions from qiskit_ibm_runtime.qiskit.primitives import SamplerPub from ..ibm_test_case import IBMTestCase +from ..utils import bell from .mock.fake_runtime_service import FakeRuntimeService from ..utils import MockSession, dict_paritally_equal @@ -43,7 +43,7 @@ def test_unsupported_values_for_sampler_options(self): service=FakeRuntimeService(channel="ibm_quantum", token="abc"), backend="common_backend", ) as session: - circuit = ReferenceCircuits.bell() + circuit = bell() for bad_opt in options_bad: inst = Sampler(session=session) with self.assertRaises(ValueError) as exc: diff --git a/test/unit/test_session.py b/test/unit/test_session.py index ee554909f..a8bd57c3d 100644 --- a/test/unit/test_session.py +++ b/test/unit/test_session.py @@ -17,6 +17,7 @@ from concurrent.futures import ThreadPoolExecutor, wait from unittest.mock import MagicMock, Mock, patch +from qiskit_ibm_runtime.fake_provider import FakeManila from qiskit_ibm_runtime import Session from qiskit_ibm_runtime.ibm_backend import IBMBackend @@ -65,6 +66,12 @@ def test_using_ibm_backend_service(self): def test_max_time(self): """Test max time.""" + model_backend = FakeManila() + backend = IBMBackend( + configuration=model_backend.configuration(), + service=MagicMock(), + api_client=None, + ) max_times = [ (42, 42), ("1h", 1 * 60 * 60), @@ -75,6 +82,10 @@ def test_max_time(self): with self.subTest(max_time=max_t): session = Session(service=MagicMock(), backend="ibm_gotham", max_time=max_t) self.assertEqual(session._max_time, expected) + for max_t, expected in max_times: + with self.subTest(max_time=max_t): + backend.open_session(max_time=max_t) + self.assertEqual(backend.session._max_time, expected) def test_run_after_close(self): """Test running after session is closed.""" diff --git a/test/unit/transpiler/__init__.py b/test/unit/transpiler/__init__.py new file mode 100644 index 000000000..139b265e1 --- /dev/null +++ b/test/unit/transpiler/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/test/unit/transpiler/passes/__init__.py b/test/unit/transpiler/passes/__init__.py new file mode 100644 index 000000000..139b265e1 --- /dev/null +++ b/test/unit/transpiler/passes/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/test/unit/transpiler/passes/basis/__init__.py b/test/unit/transpiler/passes/basis/__init__.py new file mode 100644 index 000000000..139b265e1 --- /dev/null +++ b/test/unit/transpiler/passes/basis/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/test/unit/transpiler/passes/basis/test_convert_id_to_delay.py b/test/unit/transpiler/passes/basis/test_convert_id_to_delay.py new file mode 100644 index 000000000..617b86f1e --- /dev/null +++ b/test/unit/transpiler/passes/basis/test_convert_id_to_delay.py @@ -0,0 +1,99 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test the conversion of Id gate operations to a delay.""" + +from qiskit.circuit import QuantumCircuit +from qiskit.transpiler.passmanager import PassManager + +from qiskit_ibm_runtime.transpiler.passes.basis.convert_id_to_delay import ( + ConvertIdToDelay, +) + +from qiskit_ibm_runtime.transpiler.passes.scheduling.utils import ( + DynamicCircuitInstructionDurations, +) + +from .....ibm_test_case import IBMTestCase + +# pylint: disable=invalid-name + + +class TestConvertIdToDelay(IBMTestCase): + """Tests the ConvertIdToDelay pass""" + + def setUp(self): + """Setup.""" + super().setUp() + + self.durations = DynamicCircuitInstructionDurations([("sx", None, 160), ("x", None, 200)]) + + def test_id_gate(self): + """Test if Id gate is converted a delay.""" + qc = QuantumCircuit(1, 0) + qc.id(0) + + pm = PassManager([ConvertIdToDelay(self.durations)]) + transformed = pm.run(qc) + + expected = QuantumCircuit(1, 0) + expected.delay(160, 0) + + self.assertEqual(expected, transformed) + + def test_id_gate_unit(self): + """Test if Id gate is converted a delay with correct units.""" + qc = QuantumCircuit(1, 0) + qc.id(0) + + pm = PassManager([ConvertIdToDelay(self.durations, "x")]) + transformed = pm.run(qc) + + expected = QuantumCircuit(1, 0) + expected.delay(200, 0) + + self.assertEqual(expected, transformed) + + def test_c_if_id_gate(self): + """Test if c_if Id gate is converted a c_if delay.""" + qc = QuantumCircuit(1, 1) + + with qc.if_test((0, 1)): # pylint: disable=not-context-manager + qc.id(0) + + pm = PassManager([ConvertIdToDelay(self.durations)]) + transformed = pm.run(qc) + + expected = QuantumCircuit(1, 1) + with expected.if_test((0, 1)): # pylint: disable=not-context-manager + expected.delay(160, 0) + + self.assertEqual(expected, transformed) + + def test_if_test_id_gate(self): + """Test if if_test Id gate is converted a if_test delay.""" + qc = QuantumCircuit(1, 1) + with qc.if_test((0, 1)) as else_: # pylint: disable=not-context-manager + qc.id(0) + with else_: # pylint: disable=not-context-manager + qc.id(0) + + pm = PassManager([ConvertIdToDelay(self.durations)]) + transformed = pm.run(qc) + + expected = QuantumCircuit(1, 1) + with expected.if_test((0, 1)) as else_: # pylint: disable=not-context-manager + expected.delay(160, 0) + with else_: + expected.delay(160, 0) + + self.assertEqual(expected, transformed) diff --git a/test/unit/transpiler/passes/scheduling/__init__.py b/test/unit/transpiler/passes/scheduling/__init__.py new file mode 100644 index 000000000..139b265e1 --- /dev/null +++ b/test/unit/transpiler/passes/scheduling/__init__.py @@ -0,0 +1,11 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2024. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. diff --git a/test/unit/transpiler/passes/scheduling/control_flow_test_case.py b/test/unit/transpiler/passes/scheduling/control_flow_test_case.py new file mode 100644 index 000000000..b64822883 --- /dev/null +++ b/test/unit/transpiler/passes/scheduling/control_flow_test_case.py @@ -0,0 +1,36 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022, 2023. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Enhanced test case for control flow circuits.""" + +from typing import Any, Optional + +from qiskit import QuantumCircuit +from qiskit.test._canonical import canonicalize_control_flow + +from .....ibm_test_case import IBMTestCase + + +class ControlFlowTestCase(IBMTestCase): + """Test case that enforces control flow canonicalization of quantum circuits.""" + + def assertEqual( # pylint: disable=arguments-differ + self, first: Any, second: Any, msg: Optional[str] = None + ) -> None: + """Modify assertEqual to canonicalize the quantum circuit.""" + if isinstance(first, QuantumCircuit): + first = canonicalize_control_flow(first) + + if isinstance(second, QuantumCircuit): + second = canonicalize_control_flow(second) + + super().assertEqual(first, second, msg=msg) # pylint: disable=no-value-for-parameter diff --git a/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py new file mode 100644 index 000000000..145420478 --- /dev/null +++ b/test/unit/transpiler/passes/scheduling/test_dynamical_decoupling.py @@ -0,0 +1,1072 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test dynamical decoupling insertion pass.""" + +import numpy as np +from numpy import pi + +from ddt import ddt, data +from qiskit import pulse +from qiskit.circuit import QuantumCircuit, Delay +from qiskit.circuit.library import XGate, YGate, RXGate, UGate +from qiskit.quantum_info import Operator +from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.exceptions import TranspilerError +from qiskit.transpiler.coupling import CouplingMap +from qiskit.converters import circuit_to_dag + +from qiskit_ibm_runtime.transpiler.passes.scheduling.dynamical_decoupling import ( + PadDynamicalDecoupling, +) +from qiskit_ibm_runtime.transpiler.passes.scheduling.scheduler import ( + ASAPScheduleAnalysis, +) +from qiskit_ibm_runtime.transpiler.passes.scheduling.utils import ( + DynamicCircuitInstructionDurations, +) + +from .control_flow_test_case import ControlFlowTestCase + +# pylint: disable=invalid-name,not-context-manager + + +@ddt +class TestPadDynamicalDecoupling(ControlFlowTestCase): + """Tests PadDynamicalDecoupling pass.""" + + def setUp(self): + """Circuits to test dynamical decoupling on.""" + super().setUp() + + self.ghz4 = QuantumCircuit(4) + self.ghz4.h(0) + self.ghz4.cx(0, 1) + self.ghz4.cx(1, 2) + self.ghz4.cx(2, 3) + + self.midmeas = QuantumCircuit(3, 1) + self.midmeas.cx(0, 1) + self.midmeas.cx(1, 2) + self.midmeas.u(pi, 0, pi, 0) + self.midmeas.measure(2, 0) + self.midmeas.cx(1, 2) + self.midmeas.cx(0, 1) + + self.durations = DynamicCircuitInstructionDurations( + [ + ("h", 0, 50), + ("cx", [0, 1], 700), + ("cx", [1, 2], 200), + ("cx", [2, 3], 300), + ("x", None, 50), + ("y", None, 50), + ("u", None, 100), + ("rx", None, 100), + ("measure", None, 840), + ("reset", None, 1340), + ] + ) + + self.coupling_map = CouplingMap([[0, 1], [1, 2], [2, 3]]) + + def test_insert_dd_ghz(self): + """Test DD gates are inserted in correct spots.""" + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[1.0], + schedule_idle_qubits=True, + ), + ] + ) + + ghz4_dd = pm.run(self.ghz4) + + expected = self.ghz4.copy() + expected = expected.compose(Delay(50), [1], front=True) + expected = expected.compose(Delay(750), [2], front=True) + expected = expected.compose(Delay(950), [3], front=True) + + expected = expected.compose(Delay(100), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(200), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(100), [0]) + + expected = expected.compose(Delay(50), [1]) + expected = expected.compose(XGate(), [1]) + expected = expected.compose(Delay(100), [1]) + expected = expected.compose(XGate(), [1]) + expected = expected.compose(Delay(50), [1]) + + self.assertEqual(ghz4_dd, expected) + + def test_insert_dd_ghz_one_qubit(self): + """Test DD gates are inserted on only one qubit.""" + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + qubits=[0], + pulse_alignment=1, + schedule_idle_qubits=True, + ), + ] + ) + + ghz4_dd = pm.run(self.ghz4.measure_all(inplace=False)) + + expected = self.ghz4.copy() + expected = expected.compose(Delay(50), [1], front=True) + expected = expected.compose(Delay(750), [2], front=True) + expected = expected.compose(Delay(950), [3], front=True) + + expected = expected.compose(Delay(100), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(200), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(100), [0]) + + expected = expected.compose(Delay(300), [1]) + + expected.measure_all() + + self.assertEqual(ghz4_dd, expected) + + def test_insert_dd_ghz_everywhere(self): + """Test DD gates even on initial idle spots.""" + dd_sequence = [YGate(), YGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + skip_reset_qubits=False, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, + ), + ] + ) + + ghz4_dd = pm.run(self.ghz4) + + expected = self.ghz4.copy() + expected = expected.compose(Delay(50), [1], front=True) + + expected = expected.compose(Delay(100), [0]) + expected = expected.compose(YGate(), [0]) + expected = expected.compose(Delay(200), [0]) + expected = expected.compose(YGate(), [0]) + expected = expected.compose(Delay(100), [0]) + + expected = expected.compose(Delay(50), [1]) + expected = expected.compose(YGate(), [1]) + expected = expected.compose(Delay(100), [1]) + expected = expected.compose(YGate(), [1]) + expected = expected.compose(Delay(50), [1]) + + expected = expected.compose(Delay(750), [2], front=True) + expected = expected.compose(Delay(950), [3], front=True) + + self.assertEqual(ghz4_dd, expected) + + def test_insert_dd_ghz_xy4(self): + """Test XY4 sequence of DD gates.""" + dd_sequence = [XGate(), YGate(), XGate(), YGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[1.0], + schedule_idle_qubits=True, + ), + ] + ) + + ghz4_dd = pm.run(self.ghz4) + + expected = self.ghz4.copy() + expected = expected.compose(Delay(50), [1], front=True) + expected = expected.compose(Delay(750), [2], front=True) + expected = expected.compose(Delay(950), [3], front=True) + + expected = expected.compose(Delay(37), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(75), [0]) + expected = expected.compose(YGate(), [0]) + expected = expected.compose(Delay(76), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(75), [0]) + expected = expected.compose(YGate(), [0]) + expected = expected.compose(Delay(37), [0]) + + expected = expected.compose(Delay(12), [1]) + expected = expected.compose(XGate(), [1]) + expected = expected.compose(Delay(25), [1]) + expected = expected.compose(YGate(), [1]) + expected = expected.compose(Delay(26), [1]) + expected = expected.compose(XGate(), [1]) + expected = expected.compose(Delay(25), [1]) + expected = expected.compose(YGate(), [1]) + expected = expected.compose(Delay(12), [1]) + + self.assertEqual(ghz4_dd, expected) + + def test_insert_midmeas_hahn(self): + """Test a single X gate as Hahn echo can absorb in the upstream circuit.""" + dd_sequence = [RXGate(pi / 4)] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + schedule_idle_qubits=True, + ), + ] + ) + + midmeas_dd = pm.run(self.midmeas) + + combined_u = UGate(3 * pi / 4, -pi / 2, pi / 2) + + expected = QuantumCircuit(3, 1) + expected.cx(0, 1) + expected.compose(combined_u, [0], inplace=True) + expected.delay(600, 0) + expected.rx(pi / 4, 0) + expected.delay(600, 0) + expected.delay(700, 2) + expected.cx(1, 2) + expected.delay(1000, 1) + expected.measure(2, 0) + expected.cx(1, 2) + expected.cx(0, 1) + expected.delay(700, 2) + + self.assertEqual(midmeas_dd, expected) + # check the absorption into U was done correctly + self.assertTrue( + Operator(XGate()).equiv( + Operator(UGate(3 * pi / 4, -pi / 2, pi / 2)) & Operator(RXGate(pi / 4)) + ) + ) + + def test_insert_ghz_uhrig(self): + """Test custom spacing (following Uhrig DD [1]). + [1] Uhrig, G. "Keeping a quantum bit alive by optimized π-pulse sequences." + Physical Review Letters 98.10 (2007): 100504.""" + n = 8 + dd_sequence = [XGate()] * n + + # uhrig specifies the location of the k'th pulse + def uhrig(k): + return np.sin(np.pi * (k + 1) / (2 * n + 2)) ** 2 + + # convert that to spacing between pulses (whatever finite duration pulses have) + spacing = [] + for k in range(n): + spacing.append(uhrig(k) - sum(spacing)) + spacing.append(1 - sum(spacing)) + + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + qubits=[0], + spacings=spacing, + sequence_min_length_ratios=[0.0], + pulse_alignment=1, + schedule_idle_qubits=True, + ), + ] + ) + + ghz4_dd = pm.run(self.ghz4) + + expected = self.ghz4.copy() + expected = expected.compose(Delay(50), [1], front=True) + expected = expected.compose(Delay(750), [2], front=True) + expected = expected.compose(Delay(950), [3], front=True) + + expected = expected.compose(Delay(3), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(8), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(13), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(16), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(20), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(16), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(13), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(8), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(3), [0]) + + expected = expected.compose(Delay(300), [1]) + + self.assertEqual(ghz4_dd, expected) + + def test_asymmetric_xy4_in_t2(self): + """Test insertion of XY4 sequence with unbalanced spacing.""" + dd_sequence = [XGate(), YGate()] * 2 + spacing = [0] + [1 / 4] * 4 + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + spacings=spacing, + schedule_idle_qubits=True, + ), + ] + ) + + t2 = QuantumCircuit(1) + t2.h(0) + t2.delay(2000, 0) + t2.h(0) + + expected = QuantumCircuit(1) + expected.h(0) + expected.x(0) + expected.delay(450, 0) + expected.y(0) + expected.delay(450, 0) + expected.x(0) + expected.delay(450, 0) + expected.y(0) + expected.delay(450, 0) + expected.h(0) + expected.global_phase = pi + + t2_dd = pm.run(t2) + + self.assertEqual(t2_dd, expected) + # check global phase is correct + self.assertEqual(Operator(t2), Operator(expected)) + + def test_dd_after_reset(self): + """Test skip_reset_qubits option works.""" + dd_sequence = [XGate(), XGate()] + spacing = [0.1, 0.9] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + spacings=spacing, + skip_reset_qubits=True, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, + ), + ] + ) + + t2 = QuantumCircuit(1) + t2.reset(0) + t2.delay(1000) + t2.h(0) + t2.delay(2000, 0) + t2.h(0) + + expected = QuantumCircuit(1) + expected.reset(0) + expected.barrier() + expected.delay(90) + expected.x(0) + expected.delay(810) + expected.x(0) + expected.h(0) + expected.delay(190, 0) + expected.x(0) + expected.delay(1710, 0) + expected.x(0) + expected.h(0) + + t2_dd = pm.run(t2) + + self.assertEqual(t2_dd, expected) + + def test_insert_dd_bad_sequence(self): + """Test DD raises when non-identity sequence is inserted.""" + dd_sequence = [XGate(), YGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling(self.durations, dd_sequence, schedule_idle_qubits=True), + ] + ) + + with self.assertRaises(TranspilerError): + pm.run(self.ghz4) + + @data(0.5, 1.5) + def test_dd_with_calibrations_with_parameters(self, param_value): + """Check that calibrations in a circuit with parameters work fine.""" + + circ = QuantumCircuit(2) + circ.x(0) + circ.cx(0, 1) + circ.rx(param_value, 1) + + rx_duration = int(param_value * 1000) + + with pulse.build() as rx: + pulse.play( + pulse.Gaussian(rx_duration, 0.1, rx_duration // 4), + pulse.DriveChannel(1), + ) + + circ.add_calibration("rx", (1,), rx, params=[param_value]) + + durations = DynamicCircuitInstructionDurations([("x", None, 100), ("cx", None, 300)]) + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(durations), + PadDynamicalDecoupling(durations, dd_sequence, schedule_idle_qubits=True), + ] + ) + dd_circuit = pm.run(circ) + + for instruction in dd_circuit.data: + op = instruction.operation + if isinstance(op, RXGate): + self.assertEqual(op.duration, rx_duration) + + def test_insert_dd_ghz_xy4_with_alignment(self): + """Test DD with pulse alignment constraints.""" + dd_sequence = [XGate(), YGate(), XGate(), YGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=10, + extra_slack_distribution="edges", + sequence_min_length_ratios=[1.0], + schedule_idle_qubits=True, + ), + ] + ) + + ghz4_dd = pm.run(self.ghz4) + + expected = self.ghz4.copy() + expected = expected.compose(Delay(50), [1], front=True) + expected = expected.compose(Delay(750), [2], front=True) + expected = expected.compose(Delay(950), [3], front=True) + + expected = expected.compose(Delay(40), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(70), [0]) + expected = expected.compose(YGate(), [0]) + expected = expected.compose(Delay(70), [0]) + expected = expected.compose(XGate(), [0]) + expected = expected.compose(Delay(70), [0]) + expected = expected.compose(YGate(), [0]) + expected = expected.compose(Delay(50), [0]) + + expected = expected.compose(Delay(20), [1]) + expected = expected.compose(XGate(), [1]) + expected = expected.compose(Delay(20), [1]) + expected = expected.compose(YGate(), [1]) + expected = expected.compose(Delay(20), [1]) + expected = expected.compose(XGate(), [1]) + expected = expected.compose(Delay(20), [1]) + expected = expected.compose(YGate(), [1]) + expected = expected.compose(Delay(20), [1]) + + self.assertEqual(ghz4_dd, expected) + + def test_dd_can_sequentially_called(self): + """Test if sequentially called DD pass can output the same circuit. + This test verifies: + - if global phase is properly propagated from the previous padding node. + - if node_start_time property is properly updated for new dag circuit. + """ + dd_sequence = [XGate(), YGate(), XGate(), YGate()] + + pm1 = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, dd_sequence, qubits=[0], schedule_idle_qubits=True + ), + PadDynamicalDecoupling( + self.durations, dd_sequence, qubits=[1], schedule_idle_qubits=True + ), + ] + ) + circ1 = pm1.run(self.ghz4) + + pm2 = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + qubits=[0, 1], + schedule_idle_qubits=True, + ), + ] + ) + circ2 = pm2.run(self.ghz4) + + self.assertEqual(circ1, circ2) + + def test_back_to_back_if_test(self): + """Test DD with if_test circuit back to back.""" + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, + ), + ] + ) + + qc = QuantumCircuit(3, 1) + qc.delay(800, 1) + with qc.if_test((0, True)): + qc.x(1) + with qc.if_test((0, True)): + qc.x(2) + qc.delay(1000, 2) + qc.x(1) + + qc_dd = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(800, 0) + expected.delay(800, 1) + expected.delay(800, 2) + expected.barrier() + with expected.if_test((0, True)): + expected.delay(50, 0) + expected.x(1) + expected.delay(50, 2) + with expected.if_test((0, True)): + expected.delay(50, 0) + expected.delay(50, 1) + expected.x(2) + expected.barrier() + expected.delay(1000, 0) + expected.x(1) + expected.delay(212, 1) + expected.x(1) + expected.delay(426, 1) + expected.x(1) + expected.delay(212, 1) + expected.delay(225, 2) + expected.x(2) + expected.delay(450, 2) + expected.x(2) + expected.delay(225, 2) + + self.assertEqual(expected, qc_dd) + + def test_dd_if_test(self): + """Test DD with if_test circuit.""" + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, + ), + ] + ) + + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(2) + qc.delay(1000, 1) + with qc.if_test((0, True)): + qc.x(1) + qc.delay(8000, 1) + with qc.if_test((0, True)): + qc.x(2) + qc.delay(1000, 2) + qc.x(0) + qc.x(2) + + qc_dd = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(1000, 1) + expected.x(2) + expected.measure(0, 0) + expected.delay(212, 2) + expected.x(2) + expected.delay(426, 2) + expected.x(2) + expected.delay(212, 2) + expected.barrier() + with expected.if_test((0, True)): + expected.delay(50, 0) + expected.x(1) + expected.delay(50, 2) + with expected.if_test((0, True)): + expected.delay(50, 0) + expected.delay(50, 1) + expected.x(2) + expected.barrier() + expected.x(0) + expected.delay(1962, 0) + expected.x(0) + expected.delay(3926, 0) + expected.x(0) + expected.delay(1962, 0) + expected.delay(1975, 1) + expected.x(1) + expected.delay(3950, 1) + expected.x(1) + expected.delay(1975, 1) + expected.delay(225, 2) + expected.x(2) + expected.delay(450, 2) + expected.x(2) + expected.delay(225, 2) + expected.x(2) + expected.delay(1712, 2) + expected.x(2) + expected.delay(3426, 2) + expected.x(2) + expected.delay(1712, 2) + + self.assertEqual(expected, qc_dd) + + def test_reproducible(self): + """Test DD calls are reproducible.""" + + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(2) + qc.delay(1000, 1) + with qc.if_test((0, True)): + qc.x(1) + qc.delay(800, 1) + with qc.if_test((0, True)): + qc.x(2) + qc.delay(1000, 2) + qc.x(0) + qc.x(2) + + dd_sequence = [XGate(), XGate()] + pm0 = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling(self.durations, dd_sequence, schedule_idle_qubits=True), + ] + ) + + pm1 = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling(self.durations, dd_sequence, schedule_idle_qubits=True), + ] + ) + qc_dd0 = pm0.run(qc) + qc_dd1 = pm1.run(qc) + + self.assertEqual(qc_dd0, qc_dd1) + + def test_nested_block_dd(self): + """Test DD applied within a block.""" + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + schedule_idle_qubits=True, + ), + ] + ) + + qc = QuantumCircuit(3, 1) + qc.x(1) + qc.x(2) + qc.barrier() + with qc.if_test((0, True)): + qc.delay(1000, 1) + + qc_dd = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(50, 0) + expected.x(1) + expected.x(2) + expected.barrier() + with expected.if_test((0, True)): + expected.delay(225, 1) + expected.x(1) + expected.delay(450, 1) + expected.x(1) + expected.delay(225, 1) + expected.delay(225, 2) + expected.x(2) + expected.delay(450, 2) + expected.x(2) + expected.delay(225, 2) + expected.delay(1000, 0) + + self.assertEqual(expected, qc_dd) + + def test_multiple_dd_sequences(self): + """Test multiple DD sequence can be submitted""" + + qc = QuantumCircuit(2, 0) + qc.x(0) # First delay so qubits are touched + qc.x(1) + qc.delay(500, 0) + qc.barrier() + qc.delay(2000, 1) + + dd_sequence = [ + [XGate(), XGate(), XGate(), XGate(), XGate(), XGate(), XGate(), XGate()], + [XGate(), XGate()], + ] + + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[1.5, 0.0], + schedule_idle_qubits=True, + ), + ] + ) + + qc_dd = pm.run(qc) + + expected = QuantumCircuit(2, 0) + expected.x(0) + expected.delay(100, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(100, 0) + expected.x(1) + expected.delay(100, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(100, 1) + expected.barrier() + expected.delay(100, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(200, 0) + expected.x(0) + expected.delay(100, 0) + expected.delay(100, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(200, 1) + expected.x(1) + expected.delay(100, 1) + + self.assertEqual(qc_dd, expected) + + def test_multiple_dd_sequence_cycles(self): + """Test a single DD sequence can be inserted for multiple cycles in a single delay.""" + + qc = QuantumCircuit(1, 0) + qc.x(0) # First delay so qubit is touched + qc.delay(2000, 0) + + dd_sequence = [ + [XGate(), XGate()], + ] # cycle has length of 100 cycles + + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + extra_slack_distribution="edges", + pulse_alignment=1, + sequence_min_length_ratios=[10.0], + insert_multiple_cycles=True, + schedule_idle_qubits=True, + ), + ] + ) + + qc_dd = pm.run(qc) + + expected = QuantumCircuit(1, 0) + expected.x(0) + expected.delay(225, 0) + expected.x(0) + expected.delay(450, 0) + expected.x(0) + expected.delay(225, 0) + expected.delay(225, 0) + expected.x(0) + expected.delay(450, 0) + expected.x(0) + expected.delay(225, 0) + self.assertEqual(qc_dd, expected) + + def test_staggered_dd(self): + """Test that timing on DD can be staggered if coupled with each other""" + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + coupling_map=self.coupling_map, + alt_spacings=[0.1, 0.8, 0.1], + schedule_idle_qubits=True, + ), + ] + ) + + qc_barriers = QuantumCircuit(4, 1) + qc_barriers.x(0) + qc_barriers.x(1) + qc_barriers.x(2) + qc_barriers.x(3) + qc_barriers.barrier() + qc_barriers.measure(0, 0) + qc_barriers.delay(14, 0) + qc_barriers.x(1) + qc_barriers.x(2) + qc_barriers.x(3) + qc_barriers.barrier() + + qc_dd = pm.run(qc_barriers) + + expected = QuantumCircuit(4, 1) + expected.x(0) + expected.x(1) + expected.x(2) + expected.x(3) + expected.barrier() + expected.x(1) + expected.delay(208, 1) + expected.x(1) + expected.delay(448, 1) + expected.x(1) + expected.delay(208, 1) + expected.x(2) + expected.delay(80, 2) # q1-q2 are coupled, staggered delays + expected.x(2) + expected.delay(704, 2) + expected.x(2) + expected.delay(80, 2) # q2-q3 are uncoupled, same delays + expected.x(3) + expected.delay(208, 3) + expected.x(3) + expected.delay(448, 3) + expected.x(3) + expected.delay(208, 3) + expected.measure(0, 0) + expected.delay(14, 0) + expected.barrier() + + self.assertEqual(qc_dd, expected) + + def test_insert_dd_bad_spacings(self): + """Test DD raises when spacings don't add up to 1.""" + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + spacings=[0.1, 0.9, 0.1], + coupling_map=self.coupling_map, + ), + ] + ) + + with self.assertRaises(TranspilerError): + pm.run(self.ghz4) + + def test_insert_dd_bad_alt_spacings(self): + """Test DD raises when alt_spacings don't add up to 1.""" + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + alt_spacings=[0.1, 0.9, 0.1], + coupling_map=self.coupling_map, + ), + ] + ) + + with self.assertRaises(TranspilerError): + pm.run(self.ghz4) + + def test_unsupported_coupling_map(self): + """Test DD raises if coupling map is not supported.""" + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + coupling_map=CouplingMap([[0, 1], [0, 2], [1, 2], [2, 3]]), + ), + ] + ) + + with self.assertRaises(TranspilerError): + pm.run(self.ghz4) + + def test_disjoint_coupling_map(self): + """Test staggered DD with disjoint coupling map.""" + qc = QuantumCircuit(5) + for q in range(5): + qc.x(q) + qc.barrier() + for q in range(5): + qc.delay(1600, q) + qc.barrier() + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + coupling_map=CouplingMap([[0, 1], [1, 2], [3, 4]]), + schedule_idle_qubits=True, + ), + ] + ) + dd_qc = pm.run(qc) + + # ensure that delays for nearest neighbors are staggered + dag = circuit_to_dag(dd_qc) + delays = dag.op_nodes(Delay) + delay_dict = {q_ind: [] for q_ind in range(5)} + for delay in delays: + delay_dict[dag.find_bit(delay.qargs[0]).index] += [delay.op.duration] + self.assertNotEqual(delay_dict[0], delay_dict[1]) + self.assertNotEqual(delay_dict[1], delay_dict[2]) + self.assertNotEqual(delay_dict[3], delay_dict[4]) + self.assertEqual(delay_dict[0], delay_dict[2]) + + def test_no_unused_qubits(self): + """Test DD with if_test circuit that unused qubits are untouched and not scheduled. + + This ensures that programs don't have unnecessary information for unused qubits. + Which might hurt performance in later executon stages. + """ + + dd_sequence = [XGate(), XGate()] + pm = PassManager( + [ + ASAPScheduleAnalysis(self.durations), + PadDynamicalDecoupling( + self.durations, + dd_sequence, + pulse_alignment=1, + sequence_min_length_ratios=[0.0], + ), + ] + ) + + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1) + with qc.if_test((0, True)): + qc.x(1) + qc.measure(0, 0) + with qc.if_test((0, True)): + qc.x(0) + qc.x(1) + qc_dd = pm.run(qc) + dont_use = qc_dd.qubits[-1] + for op in qc_dd.data: + self.assertNotIn(dont_use, op.qubits) diff --git a/test/unit/transpiler/passes/scheduling/test_scheduler.py b/test/unit/transpiler/passes/scheduling/test_scheduler.py new file mode 100644 index 000000000..e9ed82e1f --- /dev/null +++ b/test/unit/transpiler/passes/scheduling/test_scheduler.py @@ -0,0 +1,1860 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Test the dynamic circuits scheduling analysis""" + +from unittest.mock import patch + +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile +from qiskit.providers.fake_provider import FakeJakarta +from qiskit.pulse import Schedule, Play, Constant, DriveChannel +from qiskit.transpiler.passes import ConvertConditionsToIfOps +from qiskit.transpiler.passmanager import PassManager +from qiskit.transpiler.exceptions import TranspilerError + +from qiskit_ibm_runtime.transpiler.passes.scheduling.pad_delay import PadDelay +from qiskit_ibm_runtime.transpiler.passes.scheduling.scheduler import ( + ALAPScheduleAnalysis, + ASAPScheduleAnalysis, +) +from qiskit_ibm_runtime.transpiler.passes.scheduling.utils import ( + DynamicCircuitInstructionDurations, +) + +from .control_flow_test_case import ControlFlowTestCase + +# pylint: disable=invalid-name,not-context-manager + + +class TestASAPSchedulingAndPaddingPass(ControlFlowTestCase): + """Tests the ASAP Scheduling passes""" + + def test_if_test_gate_after_measure(self): + """Test if schedules circuits with c_if after measure with a common clbit. + See: https://github.com/Qiskit/qiskit-terra/issues/7654""" + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + with qc.if_test((0, 0)) as else_: + qc.x(1) + with else_: + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(1000, 1) + expected.measure(0, 0) + with expected.if_test((0, 0)) as else_: + expected.delay(200, 0) + expected.x(1) + with else_: + expected.x(0) + expected.delay(200, 1) + + self.assertEqual(expected, scheduled) + + def test_c_if_raises(self): + """Verify that old format c_if raises an error.""" + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + qc.x(1).c_if(0, True) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + with self.assertRaises(TranspilerError): + pm.run(qc) + + def test_c_if_conversion(self): + """Verify that old format c_if may be converted and scheduled.""" + qc = QuantumCircuit(1, 1) + qc.x(0).c_if(0, True) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager( + [ + ConvertConditionsToIfOps(), + ASAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ) + scheduled = pm.run(qc) + + expected = QuantumCircuit(1, 1) + with expected.if_test((0, 1)): + expected.x(0) + + self.assertEqual(expected, scheduled) + + def test_measure_after_measure(self): + """Test if schedules circuits with measure after measure with a common clbit. + + Note: There is no delay to write into the same clbit with IBM backends.""" + qc = QuantumCircuit(2, 1) + qc.x(0) + qc.measure(0, 0) + qc.measure(1, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(0) + expected.delay(200, 1) + expected.measure(0, 0) + expected.measure(1, 0) + self.assertEqual(expected, scheduled) + + def test_measure_block_not_end(self): + """Tests that measures trigger do not trigger the end of a scheduling block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.measure(0, 0) + qc.measure(1, 0) + qc.x(2) + qc.measure(1, 0) + qc.measure(2, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.delay(200, 1) + expected.x(2) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.measure(1, 0) + expected.delay(1000, 0) + expected.measure(1, 0) + expected.measure(2, 0) + + self.assertEqual(expected, scheduled) + + def test_reset_block_end(self): + """Tests that measures trigger do trigger the end of a scheduling block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.reset(0) + qc.measure(1, 0) + qc.x(2) + qc.measure(1, 0) + qc.measure(2, 0) + + durations = DynamicCircuitInstructionDurations( + [("x", None, 200), ("measure", None, 840), ("reset", None, 840)] + ) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.delay(200, 1) + expected.x(2) + expected.delay(1000, 2) + expected.reset(0) + expected.measure(1, 0) + expected.barrier() + expected.delay(1000, 0) + expected.measure(1, 0) + expected.measure(2, 0) + + self.assertEqual(expected, scheduled) + + def test_c_if_on_different_qubits(self): + """Test if schedules circuits with `c_if`s on different qubits.""" + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(1) + qc.x(2) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.x(1) + expected.x(2) + + self.assertEqual(expected, scheduled) + + def test_shorter_measure_after_measure(self): + """Test if schedules circuits with shorter measure after measure + with a common clbit. + + Note: For dynamic circuits support we currently group measurements + to start at the same time which in turn trigger the end of a block.""" + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.measure(1, 0) + + durations = DynamicCircuitInstructionDurations( + [("measure", [0], 840), ("measure", [1], 540)] + ) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.measure(0, 0) + expected.measure(1, 0) + expected.delay(300, 1) + expected.delay(1000, 2) + + self.assertEqual(expected, scheduled) + + def test_measure_after_c_if(self): + """Test if schedules circuits with c_if after measure with a common clbit.""" + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(1) + qc.measure(2, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.x(1) + expected.delay(200, 2) + + expected.barrier() + expected.delay(1000, 0) + expected.measure(2, 0) + expected.delay(1000, 1) + + self.assertEqual(expected, scheduled) + + def test_parallel_gate_different_length(self): + """Test circuit having two parallel instruction with different length.""" + qc = QuantumCircuit(2, 2) + qc.x(0) + qc.x(1) + qc.measure(0, 0) + qc.measure(1, 1) + + durations = DynamicCircuitInstructionDurations( + [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] + ) + + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 2) + expected.x(0) + expected.x(1) + expected.delay(200, 0) + expected.measure(0, 0) # immediately start after X gate + expected.measure(1, 1) + + self.assertEqual(scheduled, expected) + + def test_parallel_gate_different_length_with_barrier(self): + """Test circuit having two parallel instruction with different length with barrier.""" + qc = QuantumCircuit(2, 2) + qc.x(0) + qc.x(1) + qc.barrier() + qc.measure(0, 0) + qc.measure(1, 1) + + durations = DynamicCircuitInstructionDurations( + [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] + ) + + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 2) + expected.x(0) + expected.delay(200, 0) + expected.x(1) + expected.barrier() + expected.measure(0, 0) + expected.measure(1, 1) + + self.assertEqual(scheduled, expected) + + def test_active_reset_circuit(self): + """Test practical example of reset circuit. + + Because of the stimulus pulse overlap with the previous XGate on the q register, + measure instruction is always triggered after XGate regardless of write latency. + Thus only conditional latency matters in the scheduling.""" + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 100), ("measure", None, 840)]) + + scheduled = PassManager( + [ + ASAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ).run(qc) + + expected = QuantumCircuit(1, 1) + expected.measure(0, 0) + with expected.if_test((0, 1)): + expected.x(0) + expected.measure(0, 0) + with expected.if_test((0, 1)): + expected.x(0) + expected.measure(0, 0) + with expected.if_test((0, 1)): + expected.x(0) + expected.barrier() + + self.assertEqual(expected, scheduled) + + def test_dag_introduces_extra_dependency_between_conditionals(self): + """Test dependency between conditional operations in the scheduling. + + In the below example circuit, the conditional x on q1 could start at time 0, + however it must be scheduled after the conditional x on q0 in scheduling. + That is because circuit model used in the transpiler passes (DAGCircuit) + interprets instructions acting on common clbits must be run in the order + given by the original circuit (QuantumCircuit).""" + qc = QuantumCircuit(2, 1) + qc.delay(100, 0) + with qc.if_test((0, 1)): + qc.x(0) + with qc.if_test((0, 1)): + qc.x(1) + + durations = DynamicCircuitInstructionDurations([("x", None, 160)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(100, 0) + expected.delay(100, 1) + expected.barrier() + with expected.if_test((0, 1)): + expected.x(0) + expected.delay(160, 1) + with expected.if_test((0, 1)): + expected.delay(160, 0) + expected.x(1) + + self.assertEqual(expected, scheduled) + + def test_scheduling_with_calibration(self): + """Test if calibrated instruction can update node duration.""" + qc = QuantumCircuit(2) + qc.x(0) + qc.cx(0, 1) + qc.x(1) + qc.cx(0, 1) + + xsched = Schedule(Play(Constant(300, 0.1), DriveChannel(0))) + qc.add_calibration("x", (0,), xsched) + + durations = DynamicCircuitInstructionDurations([("x", None, 160), ("cx", None, 600)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2) + expected.x(0) + expected.delay(300, 1) + expected.cx(0, 1) + expected.x(1) + expected.delay(160, 0) + expected.cx(0, 1) + expected.add_calibration("x", (0,), xsched) + + self.assertEqual(expected, scheduled) + + def test_padding_not_working_without_scheduling(self): + """Test padding fails when un-scheduled DAG is input.""" + qc = QuantumCircuit(1, 1) + qc.delay(100, 0) + qc.x(0) + qc.measure(0, 0) + + with self.assertRaises(TranspilerError): + PassManager(PadDelay()).run(qc) + + def test_no_pad_very_end_of_circuit(self): + """Test padding option that inserts no delay at the very end of circuit. + + This circuit will be unchanged after scheduling/padding.""" + qc = QuantumCircuit(2, 1) + qc.delay(100, 0) + qc.x(1) + qc.measure(0, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 160), ("measure", None, 840)]) + + scheduled = PassManager( + [ + ASAPScheduleAnalysis(durations), + PadDelay(fill_very_end=False, schedule_idle_qubits=True), + ] + ).run(qc) + + expected = qc.copy() + + self.assertEqual(expected, scheduled) + + def test_reset_terminates_block(self): + """Test if reset operations terminate the block scheduled. + + Note: For dynamic circuits support we currently group resets + to start at the same time which in turn trigger the end of a block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.reset(0) + qc.measure(1, 0) + qc.x(0) + + durations = DynamicCircuitInstructionDurations( + [ + ("x", None, 200), + ( + "reset", + [0], + 840, + ), # ignored as only the duration of the measurement is used for scheduling + ( + "reset", + [1], + 740, + ), # ignored as only the duration of the measurement is used for scheduling + ("measure", [0], 440), + ("measure", [1], 540), + ] + ) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.delay(200, 1) + expected.delay(900, 2) + expected.reset(0) + expected.delay(100, 0) + expected.measure(1, 0) + expected.barrier() + expected.x(0) + expected.delay(200, 1) + expected.delay(200, 2) + + self.assertEqual(expected, scheduled) + + def test_reset_merged_with_measure(self): + """Test if reset operations terminate the block scheduled. + + Note: For dynamic circuits support we currently group resets to start + at the same time which in turn trigger the end of a block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.reset(0) + qc.measure(1, 0) + + durations = DynamicCircuitInstructionDurations( + [ + ("x", None, 200), + ( + "reset", + [0], + 840, + ), # ignored as only the duration of the measurement is used for scheduling + ( + "reset", + [1], + 740, + ), # ignored as only the duration of the measurement is used for scheduling + ("measure", [0], 440), + ("measure", [1], 540), + ] + ) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.delay(200, 1) + expected.delay(900, 2) + expected.reset(0) + expected.measure(1, 0) + expected.delay(100, 0) + + self.assertEqual(expected, scheduled) + + def test_scheduling_is_idempotent(self): + """Test that padding can be applied back to back without changing the circuit.""" + qc = QuantumCircuit(3, 2) + qc.x(2) + qc.cx(0, 1) + qc.barrier() + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + + durations = DynamicCircuitInstructionDurations( + [("x", None, 100), ("measure", None, 840), ("cx", None, 500)] + ) + + scheduled0 = PassManager( + [ + ASAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ).run(qc) + + scheduled1 = PassManager( + [ + ASAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ).run(scheduled0) + + self.assertEqual(scheduled0, scheduled1) + + def test_gate_on_measured_qubit(self): + """Test that a gate on a previously measured qubit triggers the end of the block""" + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + qc.x(0) + qc.x(1) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(1) + expected.delay(1000, 1) + expected.measure(0, 0) + expected.x(0) + + self.assertEqual(expected, scheduled) + + def test_grouped_measurements_prior_control_flow(self): + """Test that measurements are grouped prior to control-flow""" + qc = QuantumCircuit(3, 3) + qc.measure(0, 0) + qc.measure(1, 1) + with qc.if_test((0, 1)): + qc.x(2) + with qc.if_test((1, 1)): + qc.x(2) + qc.measure(2, 2) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 3) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.measure(1, 1) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.delay(200, 1) + expected.x(2) + with expected.if_test((1, 1)): + expected.delay(200, 0) + expected.delay(200, 1) + expected.x(2) + expected.barrier() + expected.delay(1000, 0) + expected.delay(1000, 1) + expected.measure(2, 2) + + self.assertEqual(expected, scheduled) + + def test_back_to_back_c_if(self): + """Test back to back c_if scheduling""" + + qc = QuantumCircuit(3, 1) + qc.delay(800, 1) + with qc.if_test((0, 1)): + qc.x(1) + with qc.if_test((0, 1)): + qc.x(2) + + qc.delay(1000, 2) + qc.x(1) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(800, 0) + expected.delay(800, 1) + expected.delay(800, 2) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.x(1) + expected.delay(200, 2) + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.delay(200, 1) + expected.x(2) + expected.barrier() + expected.delay(1000, 0) + expected.x(1) + expected.delay(800, 1) + expected.delay(1000, 2) + + self.assertEqual(expected, scheduled) + + def test_nested_control_scheduling(self): + """Test scheduling of nested control-flow""" + + qc = QuantumCircuit(4, 3) + qc.x(0) + with qc.if_test((0, 1)): + qc.x(1) + qc.measure(0, 1) + with qc.if_test((1, 0)): + qc.x(0) + qc.measure(2, 2) + qc.x(3) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(4, 3) + expected.x(0) + expected.delay(200, 1) + expected.delay(200, 2) + expected.delay(200, 3) + expected.barrier() + with expected.if_test((0, 1)): + expected.measure(0, 1) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.delay(1000, 3) + expected.barrier() + with expected.if_test((1, 0)): + expected.x(0) + expected.delay(800, 0) + expected.delay(1000, 1) + expected.measure(2, 2) + expected.delay(1000, 3) + expected.barrier() + expected.delay(200, 0) + expected.x(1) + expected.delay(200, 2) + expected.delay(200, 3) + expected.barrier() + expected.delay(200, 0) + expected.delay(200, 1) + expected.delay(200, 2) + expected.x(3) + + self.assertEqual(expected, scheduled) + + def test_while_loop(self): + """Test scheduling while loop""" + + qc = QuantumCircuit(2, 1) + qc.x(0) + with qc.while_loop((0, 1)): + qc.x(1) + qc.measure(0, 0) + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(0) + expected.delay(200, 1) + with expected.while_loop((0, 1)): + expected.x(1) + expected.delay(800, 1) + expected.measure(0, 0) + expected.x(0) + expected.delay(200, 1) + + self.assertEqual(expected, scheduled) + + def test_for_loop(self): + """Test scheduling for loop""" + + qc = QuantumCircuit(2, 1) + qc.x(0) + with qc.for_loop(range(2)): + qc.x(1) + qc.measure(0, 0) + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(0) + expected.delay(200, 1) + with expected.for_loop(range(2)): + expected.x(1) + expected.delay(800, 1) + expected.measure(0, 0) + expected.x(0) + expected.delay(200, 1) + + self.assertEqual(expected, scheduled) + + def test_registers(self): + """Verify scheduling works with registers.""" + qr = QuantumRegister(1, name="q") + cr = ClassicalRegister(1) + qc = QuantumCircuit(qr, cr) + with qc.if_test((cr[0], True)): + qc.x(qr[0]) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager( + [ + ConvertConditionsToIfOps(), + ASAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ) + scheduled = pm.run(qc) + + expected = QuantumCircuit(qr, cr) + with expected.if_test((cr[0], True)): + expected.x(qr[0]) + + self.assertEqual(expected, scheduled) + + def test_c_if_plugin_conversion_with_transpile(self): + """Verify that old format c_if may be converted and scheduled + after transpilation with the plugin.""" + # Patch the test backend with the plugin + with patch.object( + FakeJakarta, + "get_translation_stage_plugin", + return_value="ibm_dynamic_circuits", + create=True, + ): + backend = FakeJakarta() + # Temporary workaround for mock backends. For real backends this is not required. + backend.configuration().basis_gates.append("if_else") + + durations = DynamicCircuitInstructionDurations.from_backend(backend) + pm = PassManager([ASAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + + qr0 = QuantumRegister(1, name="q") + cr = ClassicalRegister(1, name="c") + qc = QuantumCircuit(qr0, cr) + qc.x(qr0[0]).c_if(cr[0], True) + + qc_transpiled = transpile(qc, backend, initial_layout=[0]) + + scheduled = pm.run(qc_transpiled) + + qr1 = QuantumRegister(7, name="q") + cr = ClassicalRegister(1, name="c") + expected = QuantumCircuit(qr1, cr) + with expected.if_test((cr[0], True)): + expected.x(qr1[0]) + expected.delay(160, qr1[1]) + expected.delay(160, qr1[2]) + expected.delay(160, qr1[3]) + expected.delay(160, qr1[4]) + expected.delay(160, qr1[5]) + expected.delay(160, qr1[6]) + + self.assertEqual(expected, scheduled) + + +class TestALAPSchedulingAndPaddingPass(ControlFlowTestCase): + """Tests the ALAP Scheduling passes""" + + def test_alap(self): + """Test standard ALAP scheduling""" + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1) + + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.measure(0, 0) + expected.delay(800, 1) + expected.x(1) + expected.delay(1000, 2) + + self.assertEqual(expected, scheduled) + + def test_if_test_gate_after_measure(self): + """Test if schedules circuits with c_if after measure with a common clbit. + See: https://github.com/Qiskit/qiskit-terra/issues/7654""" + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + with qc.if_test((0, 0)) as else_: + qc.x(1) + with else_: + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(1000, 1) + expected.measure(0, 0) + with expected.if_test((0, 0)) as else_: + expected.delay(200, 0) + expected.x(1) + with else_: + expected.x(0) + expected.delay(200, 1) + + self.assertEqual(expected, scheduled) + + def test_classically_controlled_gate_after_measure(self): + """Test if schedules circuits with c_if after measure with a common clbit. + See: https://github.com/Qiskit/qiskit-terra/issues/7654""" + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + with qc.if_test((0, True)): + qc.x(1) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(1000, 1) + expected.measure(0, 0) + expected.barrier() + with expected.if_test((0, True)): + expected.delay(200, 0) + expected.x(1) + + self.assertEqual(expected, scheduled) + + def test_measure_after_measure(self): + """Test if schedules circuits with measure after measure with a common clbit. + + Note: There is no delay to write into the same clbit with IBM backends.""" + qc = QuantumCircuit(2, 1) + qc.x(0) + qc.measure(0, 0) + qc.measure(1, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(0) + expected.delay(200, 1) + expected.measure(0, 0) + expected.measure(1, 0) + + self.assertEqual(expected, scheduled) + + def test_measure_block_not_end(self): + """Tests that measures trigger do not trigger the end of a scheduling block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.measure(0, 0) + qc.measure(1, 0) + qc.x(2) + qc.measure(1, 0) + qc.measure(2, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.delay(200, 1) + expected.delay(1000, 2) + expected.x(2) + expected.measure(0, 0) + expected.delay(1000, 0) + expected.measure(1, 0) + expected.measure(1, 0) + expected.measure(2, 0) + + self.assertEqual(expected, scheduled) + + def test_reset_block_end(self): + """Tests that measures trigger do trigger the end of a scheduling block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.reset(0) + qc.measure(1, 0) + qc.x(2) + qc.measure(1, 0) + qc.measure(2, 0) + qc.measure(0, 0) + + durations = DynamicCircuitInstructionDurations( + [("x", None, 200), ("measure", None, 840), ("reset", None, 840)] + ) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.reset(0) + expected.delay(200, 1) + expected.delay(1000, 2) + expected.x(2) + expected.measure(1, 0) + expected.barrier() + expected.measure(1, 0) + expected.measure(2, 0) + expected.measure(0, 0) + + self.assertEqual(expected, scheduled) + + def test_c_if_on_different_qubits(self): + """Test if schedules circuits with `c_if`s on different qubits.""" + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(1) + qc.x(2) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.x(1) + expected.x(2) + + self.assertEqual(expected, scheduled) + + def test_shorter_measure_after_measure(self): + """Test if schedules circuits with shorter measure after measure + with a common clbit. + + Note: For dynamic circuits support we currently group measurements + to start at the same time which in turn trigger the end of a block.""" + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.measure(1, 0) + + durations = DynamicCircuitInstructionDurations( + [("measure", [0], 840), ("measure", [1], 540)] + ) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.measure(1, 0) + expected.delay(300, 1) + + self.assertEqual(expected, scheduled) + + def test_measure_after_c_if(self): + """Test if schedules circuits with c_if after measure with a common clbit.""" + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(1) + qc.measure(2, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.x(1) + expected.delay(200, 2) + expected.barrier() + expected.delay(1000, 0) + expected.measure(2, 0) + expected.delay(1000, 1) + + self.assertEqual(expected, scheduled) + + def test_parallel_gate_different_length(self): + """Test circuit having two parallel instruction with different length.""" + qc = QuantumCircuit(2, 2) + qc.x(0) + qc.x(1) + qc.measure(0, 0) + qc.measure(1, 1) + + durations = DynamicCircuitInstructionDurations( + [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] + ) + + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 2) + expected.delay(200, 0) + expected.x(0) + expected.x(1) + expected.measure(0, 0) # immediately start after X gate + expected.measure(1, 1) + + self.assertEqual(scheduled, expected) + + def test_parallel_gate_different_length_with_barrier(self): + """Test circuit having two parallel instruction with different length with barrier.""" + qc = QuantumCircuit(2, 2) + qc.x(0) + qc.x(1) + qc.barrier() + qc.measure(0, 0) + qc.measure(1, 1) + + durations = DynamicCircuitInstructionDurations( + [("x", [0], 200), ("x", [1], 400), ("measure", None, 840)] + ) + + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 2) + expected.delay(200, 0) + expected.x(0) + expected.x(1) + expected.barrier() + expected.measure(0, 0) + expected.measure(1, 1) + + self.assertEqual(scheduled, expected) + + def test_active_reset_circuit(self): + """Test practical example of reset circuit. + + Because of the stimulus pulse overlap with the previous XGate on the q register, + measure instruction is always triggered after XGate regardless of write latency. + Thus only conditional latency matters in the scheduling.""" + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 100), ("measure", None, 840)]) + + scheduled = PassManager( + [ + ALAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ).run(qc) + + expected = QuantumCircuit(1, 1) + expected.measure(0, 0) + with expected.if_test((0, 1)): + expected.x(0) + expected.measure(0, 0) + with expected.if_test((0, 1)): + expected.x(0) + expected.measure(0, 0) + with expected.if_test((0, 1)): + expected.x(0) + expected.barrier() + + self.assertEqual(expected, scheduled) + + def test_dag_introduces_extra_dependency_between_conditionals(self): + """Test dependency between conditional operations in the scheduling. + + In the below example circuit, the conditional x on q1 could start at time 0, + however it must be scheduled after the conditional x on q0 in scheduling. + That is because circuit model used in the transpiler passes (DAGCircuit) + interprets instructions acting on common clbits must be run in the order + given by the original circuit (QuantumCircuit).""" + qc = QuantumCircuit(2, 1) + qc.delay(100, 0) + with qc.if_test((0, 1)): + qc.x(0) + with qc.if_test((0, 1)): + qc.x(1) + + durations = DynamicCircuitInstructionDurations([("x", None, 160)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(100, 0) + expected.delay(100, 1) + expected.barrier() + with expected.if_test((0, 1)): + expected.x(0) + expected.delay(160, 1) + with expected.if_test((0, 1)): + expected.delay(160, 0) + expected.x(1) + + self.assertEqual(expected, scheduled) + + def test_scheduling_with_calibration(self): + """Test if calibrated instruction can update node duration.""" + qc = QuantumCircuit(2) + qc.x(0) + qc.cx(0, 1) + qc.x(1) + qc.cx(0, 1) + + xsched = Schedule(Play(Constant(300, 0.1), DriveChannel(0))) + qc.add_calibration("x", (0,), xsched) + + durations = DynamicCircuitInstructionDurations([("x", None, 160), ("cx", None, 600)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2) + expected.x(0) + expected.delay(300, 1) + expected.cx(0, 1) + expected.x(1) + expected.delay(160, 0) + expected.cx(0, 1) + expected.add_calibration("x", (0,), xsched) + + self.assertEqual(expected, scheduled) + + def test_padding_not_working_without_scheduling(self): + """Test padding fails when un-scheduled DAG is input.""" + qc = QuantumCircuit(1, 1) + qc.delay(100, 0) + qc.x(0) + qc.measure(0, 0) + + with self.assertRaises(TranspilerError): + PassManager(PadDelay()).run(qc) + + def test_no_pad_very_end_of_circuit(self): + """Test padding option that inserts no delay at the very end of circuit. + + This circuit will be unchanged after scheduling/padding.""" + qc = QuantumCircuit(2, 1) + qc.delay(100, 0) + qc.x(1) + qc.measure(0, 0) + + durations = DynamicCircuitInstructionDurations([("x", None, 160), ("measure", None, 840)]) + + scheduled = PassManager( + [ + ALAPScheduleAnalysis(durations), + PadDelay(fill_very_end=False, schedule_idle_qubits=True), + ] + ).run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(100, 0) + expected.measure(0, 0) + expected.delay(940, 1) + expected.x(1) + + self.assertEqual(expected, scheduled) + + def test_reset_terminates_block(self): + """Test if reset operations terminate the block scheduled. + + Note: For dynamic circuits support we currently group resets + to start at the same time which in turn trigger the end of a block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.reset(0) + qc.measure(1, 0) + qc.x(0) + + durations = DynamicCircuitInstructionDurations( + [ + ("x", None, 200), + ( + "reset", + [0], + 840, + ), # ignored as only the duration of the measurement is used for scheduling + ( + "reset", + [1], + 740, + ), # ignored as only the duration of the measurement is used for scheduling + ("measure", [0], 440), + ("measure", [1], 540), + ] + ) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.delay(200, 1) + expected.delay(900, 2) + expected.reset(0) + expected.delay(100, 0) + expected.measure(1, 0) + expected.barrier() + expected.x(0) + expected.delay(200, 1) + expected.delay(200, 2) + + self.assertEqual(expected, scheduled) + + def test_reset_merged_with_measure(self): + """Test if reset operations terminate the block scheduled. + + Note: For dynamic circuits support we currently group resets to start + at the same time which in turn trigger the end of a block.""" + qc = QuantumCircuit(3, 1) + qc.x(0) + qc.reset(0) + qc.measure(1, 0) + + durations = DynamicCircuitInstructionDurations( + [ + ("x", None, 200), + ( + "reset", + [0], + 840, + ), # ignored as only the duration of the measurement is used for scheduling + ( + "reset", + [1], + 740, + ), # ignored as only the duration of the measurement is used for scheduling + ("measure", [0], 440), + ("measure", [1], 540), + ] + ) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.x(0) + expected.delay(200, 1) + expected.delay(900, 2) + expected.reset(0) + expected.delay(100, 0) + expected.measure(1, 0) + + self.assertEqual(expected, scheduled) + + def test_already_scheduled(self): + """Test no changes to pre-scheduled""" + qc = QuantumCircuit(3, 2) + qc.cx(0, 1) + qc.delay(400, 2) + qc.x(2) + qc.barrier() + qc.measure(0, 0) + qc.delay(1000, 1) + qc.delay(1000, 2) + qc.barrier() + with qc.if_test((0, 1)): + qc.x(0) + qc.delay(100, 1) + qc.delay(100, 2) + qc.barrier() + qc.measure(0, 0) + qc.delay(1000, 1) + qc.delay(1000, 2) + qc.barrier() + + durations = DynamicCircuitInstructionDurations( + [("x", None, 100), ("measure", None, 840), ("cx", None, 500)] + ) + + scheduled = PassManager( + [ + ALAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ).run(qc) + + self.assertEqual(qc, scheduled) + + def test_scheduling_is_idempotent(self): + """Test that padding can be applied back to back without changing the circuit.""" + qc = QuantumCircuit(3, 2) + qc.x(2) + qc.cx(0, 1) + qc.barrier() + qc.measure(0, 0) + with qc.if_test((0, 1)): + qc.x(0) + qc.measure(0, 0) + + durations = DynamicCircuitInstructionDurations( + [("x", None, 100), ("measure", None, 840), ("cx", None, 500)] + ) + + scheduled0 = PassManager( + [ + ALAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ).run(qc) + + scheduled1 = PassManager( + [ + ALAPScheduleAnalysis(durations), + PadDelay(schedule_idle_qubits=True), + ] + ).run(scheduled0) + + self.assertEqual(scheduled0, scheduled1) + + def test_gate_on_measured_qubit(self): + """Test that a gate on a previously measured qubit triggers the end of the block""" + qc = QuantumCircuit(2, 1) + qc.measure(0, 0) + qc.x(0) + qc.x(1) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.delay(1000, 1) + expected.x(1) + expected.measure(0, 0) + expected.x(0) + + self.assertEqual(expected, scheduled) + + def test_grouped_measurements_prior_control_flow(self): + """Test that measurements are grouped prior to control-flow""" + qc = QuantumCircuit(3, 3) + qc.measure(0, 0) + qc.measure(1, 1) + with qc.if_test((0, 1)): + qc.x(2) + with qc.if_test((1, 1)): + qc.x(2) + qc.measure(2, 2) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 3) + expected.delay(1000, 2) + expected.measure(0, 0) + expected.measure(1, 1) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.delay(200, 1) + expected.x(2) + with expected.if_test((1, 1)): + expected.delay(200, 0) + expected.delay(200, 1) + expected.x(2) + expected.barrier() + expected.delay(1000, 0) + expected.delay(1000, 1) + expected.measure(2, 2) + + self.assertEqual(expected, scheduled) + + def test_fast_path_eligible_scheduling(self): + """Test scheduling of the fast-path eligible blocks. + Verify that no barrier is inserted between measurements and fast-path conditionals. + """ + qc = QuantumCircuit(4, 3) + qc.x(0) + qc.delay(1500, 1) + qc.delay(1500, 2) + qc.x(3) + qc.barrier(1, 2, 3) + qc.measure(0, 0) + qc.measure(1, 1) + with qc.if_test((0, 1)): + qc.x(0) + with qc.if_test((1, 1)): + qc.x(1) + qc.x(0) + qc.x(0) + qc.x(1) + qc.x(2) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(4, 3) + expected.delay(1300, 0) + expected.x(0) + expected.delay(1500, 1) + expected.delay(1500, 2) + expected.delay(1300, 3) + expected.x(3) + expected.barrier(1, 2, 3) + expected.measure(0, 0) + expected.measure(1, 1) + expected.delay(1000, 2) + expected.delay(1000, 3) + with expected.if_test((0, 1)): + expected.x(0) + with expected.if_test((1, 1)): + expected.x(1) + expected.barrier() + expected.x(0) + expected.x(0) + expected.delay(200, 1) + expected.x(1) + expected.delay(200, 2) + expected.x(2) + expected.delay(400, 3) + + self.assertEqual(expected, scheduled) + + def test_back_to_back_c_if(self): + """Test back to back c_if scheduling""" + + qc = QuantumCircuit(3, 1) + qc.delay(800, 1) + with qc.if_test((0, 1)): + qc.x(1) + with qc.if_test((0, 1)): + qc.x(2) + + qc.delay(1000, 2) + qc.x(1) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 1) + expected.delay(800, 0) + expected.delay(800, 1) + expected.delay(800, 2) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.x(1) + expected.delay(200, 2) + with expected.if_test((0, 1)): + expected.delay(200, 0) + expected.delay(200, 1) + expected.x(2) + expected.barrier() + expected.delay(1000, 0) + expected.delay(800, 1) + expected.x(1) + expected.delay(1000, 2) + self.assertEqual(expected, scheduled) + + def test_issue_458_extra_idle_bug_0(self): + """Regression test for https://github.com/Qiskit/qiskit-ibm-provider/issues/458 + + This demonstrates that delays on idle qubits are pushed to the last schedulable + region. This may happen if Terra's default scheduler is used and then the + dynamic circuit scheduler is applied. + """ + + qc = QuantumCircuit(4, 3) + + qc.cx(0, 1) + qc.delay(700, 0) + qc.delay(700, 2) + qc.cx(1, 2) + qc.delay(3560, 3) + qc.barrier([0, 1, 2]) + + qc.delay(1160, 0) + qc.delay(1000, 2) + qc.measure(1, 0) + qc.delay(160, 1) + with qc.if_test((0, 1)): + qc.x(2) + qc.barrier([0, 1, 2]) + qc.measure(0, 1) + qc.delay(1000, 1) + qc.measure(2, 2) + + durations = DynamicCircuitInstructionDurations( + [("x", None, 160), ("cx", None, 700), ("measure", None, 840)] + ) + + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(4, 3) + expected.cx(0, 1) + expected.delay(700, 0) + expected.delay(700, 2) + expected.cx(1, 2) + expected.barrier([0, 1, 2]) + expected.delay(2560, 3) + + expected.delay(1160, 0) + expected.delay(1160, 2) + expected.measure(1, 0) + expected.delay(160, 1) + expected.barrier() + with expected.if_test((0, 1)): + expected.delay(160, 0) + expected.delay(160, 1) + expected.x(2) + expected.delay(160, 3) + expected.barrier() + expected.delay(2560, 0) + expected.delay(2560, 1) + expected.delay(2560, 2) + expected.delay(3560, 3) + expected.barrier([0, 1, 2]) + expected.delay(1000, 1) + expected.measure(0, 1) + expected.measure(2, 2) + + self.assertEqual(scheduled, expected) + + def test_issue_458_extra_idle_bug_1(self): + """Regression test for https://github.com/Qiskit/qiskit-ibm-provider/issues/458 + + This demonstrates that a bug with a double-delay insertion has been resolved. + """ + + qc = QuantumCircuit(3, 3) + + qc.rz(0, 2) + qc.barrier() + qc.measure(1, 0) + + durations = DynamicCircuitInstructionDurations( + [("rz", None, 0), ("cx", None, 700), ("measure", None, 840)] + ) + + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(3, 3) + + expected.rz(0, 2) + expected.barrier() + expected.delay(1000, 0) + expected.measure(1, 0) + expected.delay(1000, 2) + + self.assertEqual(scheduled, expected) + + def test_nested_control_scheduling(self): + """Test scheduling of nested control-flow""" + + qc = QuantumCircuit(4, 3) + qc.x(0) + with qc.if_test((0, 1)): + qc.x(1) + qc.measure(0, 1) + with qc.if_test((1, 0)): + qc.x(0) + qc.measure(2, 2) + qc.x(3) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(4, 3) + expected.x(0) + expected.delay(200, 1) + expected.delay(200, 2) + expected.delay(200, 3) + expected.barrier() + with expected.if_test((0, 1)): + expected.measure(0, 1) + expected.delay(1000, 1) + expected.delay(1000, 2) + expected.delay(1000, 3) + expected.barrier() + with expected.if_test((1, 0)): + expected.delay(800, 0) + expected.x(0) + expected.delay(1000, 1) + expected.measure(2, 2) + expected.delay(1000, 3) + expected.barrier() + expected.delay(200, 0) + expected.x(1) + expected.delay(200, 2) + expected.delay(200, 3) + expected.barrier() + expected.delay(200, 0) + expected.delay(200, 1) + expected.delay(200, 2) + expected.x(3) + + self.assertEqual(expected, scheduled) + + def test_while_loop(self): + """Test scheduling while loop""" + + qc = QuantumCircuit(2, 1) + qc.x(0) + with qc.while_loop((0, 1)): + qc.x(1) + qc.measure(0, 0) + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(0) + expected.delay(200, 1) + with expected.while_loop((0, 1)): + expected.delay(800, 1) + expected.x(1) + expected.measure(0, 0) + expected.x(0) + expected.delay(200, 1) + + self.assertEqual(expected, scheduled) + + def test_for_loop(self): + """Test scheduling for loop""" + + qc = QuantumCircuit(2, 1) + qc.x(0) + with qc.for_loop(range(2)): + qc.x(1) + qc.measure(0, 0) + qc.x(0) + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + scheduled = pm.run(qc) + + expected = QuantumCircuit(2, 1) + expected.x(0) + expected.delay(200, 1) + with expected.for_loop(range(2)): + expected.delay(800, 1) + expected.x(1) + expected.measure(0, 0) + expected.x(0) + expected.delay(200, 1) + + self.assertEqual(expected, scheduled) + + def test_transpile_mock_backend(self): + """Test scheduling works with transpilation.""" + backend = FakeJakarta() + # Temporary workaround for mock backends. For real backends this is not required. + backend.configuration().basis_gates.append("if_else") + backend.configuration().basis_gates.append("while_loop") + + durations = DynamicCircuitInstructionDurations.from_backend(backend) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + + qr = QuantumRegister(3) + cr = ClassicalRegister(2) + + qc = QuantumCircuit(qr, cr) + with qc.while_loop((cr[0], 1)): + qc.x(qr[2]) + with qc.if_test((cr[0], 1)): + qc.x(qr[1]) + qc.x(qr[0]) + + qc_transpiled = transpile(qc, backend, initial_layout=[0, 1, 2]) + + scheduled = pm.run(qc_transpiled) + + qr = QuantumRegister(7, name="q") + expected = QuantumCircuit(qr, cr) + with expected.while_loop((cr[0], 1)): + with expected.if_test((cr[0], 1)): + expected.delay(160, qr[0]) + expected.x(qr[1]) + expected.delay(160, qr[2]) + expected.delay(160, qr[3]) + expected.delay(160, qr[4]) + expected.delay(160, qr[5]) + expected.delay(160, qr[6]) + expected.barrier() + expected.x(qr[0]) + expected.x(qr[2]) + expected.delay(160, qr[1]) + expected.delay(160, qr[3]) + expected.delay(160, qr[4]) + expected.delay(160, qr[5]) + expected.delay(160, qr[6]) + + self.assertEqual(expected, scheduled) + + def test_transpile_both_paths(self): + """Test scheduling works with both fast- and standard path after transpiling.""" + backend = FakeJakarta() + # Temporary workaround for mock backends. For real backends this is not required. + backend.configuration().basis_gates.append("if_else") + + durations = DynamicCircuitInstructionDurations.from_backend(backend) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + + qr = QuantumRegister(3) + cr = ClassicalRegister(2) + + qc = QuantumCircuit(qr, cr) + qc.measure(qr[0], cr[0]) + with qc.if_test((cr[0], 1)): + qc.x(qr[0]) + with qc.if_test((cr[0], 1)): + qc.x(qr[1]) + + qc_transpiled = transpile(qc, backend, initial_layout=[0, 1, 2]) + + scheduled = pm.run(qc_transpiled) + + qr = QuantumRegister(7, name="q") + expected = QuantumCircuit(qr, cr) + expected.delay(24080, qr[1]) + expected.delay(24080, qr[2]) + expected.delay(24080, qr[3]) + expected.delay(24080, qr[4]) + expected.delay(24080, qr[5]) + expected.delay(24080, qr[6]) + expected.measure(qr[0], cr[0]) + with expected.if_test((cr[0], 1)): + expected.x(qr[0]) + with expected.if_test((cr[0], 1)): + expected.delay(160, qr[0]) + expected.x(qr[1]) + expected.delay(160, qr[2]) + expected.delay(160, qr[3]) + expected.delay(160, qr[4]) + expected.delay(160, qr[5]) + expected.delay(160, qr[6]) + self.assertEqual(expected, scheduled) + + def test_c_if_plugin_conversion_with_transpile(self): + """Verify that old format c_if may be converted and scheduled after + transpilation with the plugin.""" + # Patch the test backend with the plugin + with patch.object( + FakeJakarta, + "get_translation_stage_plugin", + return_value="ibm_dynamic_circuits", + create=True, + ): + backend = FakeJakarta() + # Temporary workaround for mock backends. For real backends this is not required. + backend.configuration().basis_gates.append("if_else") + + durations = DynamicCircuitInstructionDurations.from_backend(backend) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay(schedule_idle_qubits=True)]) + + qr0 = QuantumRegister(1, name="q") + cr = ClassicalRegister(1, name="c") + qc = QuantumCircuit(qr0, cr) + qc.x(qr0[0]).c_if(cr[0], True) + + qc_transpiled = transpile(qc, backend, initial_layout=[0]) + + scheduled = pm.run(qc_transpiled) + + qr1 = QuantumRegister(7, name="q") + cr = ClassicalRegister(1, name="c") + expected = QuantumCircuit(qr1, cr) + with expected.if_test((cr[0], True)): + expected.x(qr1[0]) + expected.delay(160, qr1[1]) + expected.delay(160, qr1[2]) + expected.delay(160, qr1[3]) + expected.delay(160, qr1[4]) + expected.delay(160, qr1[5]) + expected.delay(160, qr1[6]) + + self.assertEqual(expected, scheduled) + + def test_no_unused_qubits(self): + """Test DD with if_test circuit that unused qubits are untouched and not scheduled. + + This ensures that programs don't have unnecessary information for unused qubits. + Which might hurt performance in later executon stages. + """ + + durations = DynamicCircuitInstructionDurations([("x", None, 200), ("measure", None, 840)]) + pm = PassManager([ALAPScheduleAnalysis(durations), PadDelay()]) + + qc = QuantumCircuit(3, 1) + qc.measure(0, 0) + qc.x(1) + with qc.if_test((0, True)): + qc.x(1) + qc.measure(0, 0) + with qc.if_test((0, True)): + qc.x(0) + qc.x(1) + + scheduled = pm.run(qc) + + dont_use = scheduled.qubits[-1] + for op in scheduled.data: + self.assertNotIn(dont_use, op.qubits) diff --git a/test/unit/transpiler/passes/scheduling/test_utils.py b/test/unit/transpiler/passes/scheduling/test_utils.py new file mode 100644 index 000000000..50cd79ff7 --- /dev/null +++ b/test/unit/transpiler/passes/scheduling/test_utils.py @@ -0,0 +1,65 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2022. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Tests for Qiskit scheduling utilities.""" + +from qiskit_ibm_runtime.transpiler.passes.scheduling.utils import ( + DynamicCircuitInstructionDurations, +) +from .....ibm_test_case import IBMTestCase + + +class TestDynamicCircuitInstructionDurations(IBMTestCase): + """Tests the DynamicCircuitInstructionDurations patching""" + + def test_patch_measure(self): + """Test if schedules circuits with c_if after measure with a common clbit. + See: https://github.com/Qiskit/qiskit-terra/issues/7654""" + + durations = DynamicCircuitInstructionDurations( + [ + ("x", None, 200), + ("sx", (0,), 200), + ("measure", None, 1000), + ("measure", (0, 1), 1200), + ("reset", None, 800), + ] + ) + + self.assertEqual(durations.get("x", (0,)), 200) + self.assertEqual(durations.get("measure", (0,)), 1160) + self.assertEqual(durations.get("measure", (0, 1)), 1360) + self.assertEqual(durations.get("reset", (0,)), 1160) + + short_odd_durations = DynamicCircuitInstructionDurations( + [ + ("sx", (0,), 112), + ("measure", None, 1000), + ("reset", None, 800), + ] + ) + + self.assertEqual(short_odd_durations.get("measure", (0,)), 1224) + self.assertEqual(short_odd_durations.get("reset", (0,)), 1224) + + def test_patch_disable(self): + """Test if schedules circuits with c_if after measure with a common clbit. + See: https://github.com/Qiskit/qiskit-terra/issues/7654""" + + durations = DynamicCircuitInstructionDurations( + [("x", None, 200), ("measure", None, 1000), ("measure", (0, 1), 1200)], + enable_patching=False, + ) + + self.assertEqual(durations.get("x", (0,)), 200) + self.assertEqual(durations.get("measure", (0,)), 1000) + self.assertEqual(durations.get("measure", (0, 1)), 1200) diff --git a/test/utils.py b/test/utils.py index 41d85d90c..5473ed5af 100644 --- a/test/utils.py +++ b/test/utils.py @@ -22,10 +22,9 @@ from ddt import data, unpack -from qiskit.circuit import QuantumCircuit from qiskit.compiler import transpile -from qiskit.test.reference_circuits import ReferenceCircuits from qiskit.test.utils import generate_cases +from qiskit.circuit import QuantumCircuit, QuantumRegister, ClassicalRegister from qiskit.providers.jobstatus import JOB_FINAL_STATES, JobStatus from qiskit.providers.exceptions import QiskitBackendNotFoundError from qiskit.providers.models import BackendStatus, BackendProperties @@ -300,7 +299,7 @@ def submit_and_cancel(backend: IBMBackend, logger: logging.Logger) -> RuntimeJob Returns: Cancelled job. """ - circuit = transpile(ReferenceCircuits.bell(), backend=backend) + circuit = transpile(bell(), backend=backend) job = backend.run(circuit) cancel_job_safe(job, logger=logger) return job @@ -320,6 +319,18 @@ def deco(func): return deco +def bell(): + """Return a Bell circuit.""" + quantum_register = QuantumRegister(2, name="qr") + classical_register = ClassicalRegister(2, name="cr") + quantum_circuit = QuantumCircuit(quantum_register, classical_register, name="bell") + quantum_circuit.h(quantum_register[0]) + quantum_circuit.cx(quantum_register[0], quantum_register[1]) + quantum_circuit.measure(quantum_register, classical_register) + + return quantum_circuit + + def get_primitive_inputs(primitive, num_sets=1): """Return primitive specific inputs.""" circ = QuantumCircuit(2, 2) diff --git a/tools/deploy_documentation.sh b/tools/deploy_documentation.sh deleted file mode 100755 index 469d75dd8..000000000 --- a/tools/deploy_documentation.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# Script for pushing the documentation to the qiskit.org/ecosystem. -set -e - -curl https://downloads.rclone.org/rclone-current-linux-amd64.deb -o rclone.deb -sudo apt-get install -y ./rclone.deb - -RCLONE_CONFIG_PATH=$(rclone config file | tail -1) - -# Build the documentation. -tox -edocs - -echo "show current dir: " -pwd - -# Push to qiskit.org/ecosystem -openssl aes-256-cbc -K $encrypted_rclone_key -iv $encrypted_rclone_iv -in tools/rclone.conf.enc -out $RCLONE_CONFIG_PATH -d -echo "Pushing built docs to qiskit.org/ecosystem" -rclone sync --progress --exclude locale/** ./docs/_build/html IBMCOS:qiskit-org-web-resources/ecosystem/ibm-runtime diff --git a/tools/deploy_translatable_strings.sh b/tools/deploy_translatable_strings.sh deleted file mode 100755 index 1ec9fc496..000000000 --- a/tools/deploy_translatable_strings.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/bin/bash - -# This code is part of Qiskit. -# -# (C) Copyright IBM 2018, 2023. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -# Script for pushing the translatable strings to qiskit-translations repo. - -# Non-travis variables used by this script. -SOURCE_REPOSITORY="git@github.com:Qiskit/qiskit-ibm-runtime.git" -SOURCE_DIR=`pwd` -SOURCE_LANG='en' - -TARGET_REPOSITORY="git@github.com:qiskit-community/qiskit-translations.git" -TARGET_BRANCH_PO="main" - -DOC_DIR_PO="docs/locale/" - -echo "show current dir: " -pwd - -pushd docs - -# Extract document's translatable messages into pot files -# https://sphinx-intl.readthedocs.io/en/master/quickstart.html -echo "Extract document's translatable messages into pot files and generate po files" -tox -egettext -- -D language=$SOURCE_LANG - -echo "Setup ssh keys" -pwd -set -e -# Add qiskit-translations push key to ssh-agent -openssl enc -aes-256-cbc -d -in ../tools/github_poBranch_update_key.enc -out github_poBranch_deploy_key -K $encrypted_deploy_po_branch_key -iv $encrypted_deploy_po_branch_iv -chmod 600 github_poBranch_deploy_key -eval $(ssh-agent -s) -ssh-add github_poBranch_deploy_key - -# Clone to the working repository for .po and pot files -popd -pwd -echo "git clone for working repo" -git clone --depth 1 $TARGET_REPOSITORY temp --single-branch --branch $TARGET_BRANCH_PO -pushd temp - -git config user.name "Qiskit (IBM Runtime) Autodeploy" -git config user.email "qiskit@qiskit.org" - -echo "git rm -rf for the translation po files" -git rm -rf --ignore-unmatch ibm-runtime/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/*.po \ - qiskit-ibm-runtime/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/api \ - qiskit-ibm-runtime/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/apidocs \ - qiskit-ibm-runtime/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/stubs \ - qiskit-ibm-runtime/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/release_notes.po \ - qiskit-ibm-runtime/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/theme \ - qiskit-ibm-runtime/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/_* - -# Remove api/ and apidoc/ to avoid confusion while translating -rm -rf $SOURCE_DIR/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/api/ \ - $SOURCE_DIR/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/apidocs \ - $SOURCE_DIR/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/stubs \ - $SOURCE_DIR/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/release_notes.po \ - $SOURCE_DIR/$DOC_DIR_PO/$SOURCE_LANG/LC_MESSAGES/theme/ - -# Copy the new rendered files and add them to the commit. -echo "copy directory" -cp -r $SOURCE_DIR/$DOC_DIR_PO/ qiskit-ibm-runtime/docs -cp $SOURCE_DIR/setup.py qiskit-ibm-runtime/. -cp $SOURCE_DIR/requirements-dev.txt qiskit-ibm-runtime/. -cp $SOURCE_DIR/requirements.txt qiskit-ibm-runtime/. -cp $SOURCE_DIR/qiskit_ibm_runtime/VERSION.txt qiskit-ibm-runtime/qiskit_ibm_runtime/. - -# git checkout translationDocs -echo "add to po files to target dir" -git add qiskit-ibm-runtime/ - -# Commit and push the changes. -git commit -m "[Qiskit IBM Runtime] Automated documentation update to add .po files" -m "skip ci" -m "Commit: $GITHUB_SHA" -m "Github Actions Run: https://github.com/Qiskit/qiskit/runs/$GITHUB_RUN_NUMBER" -echo "git push" -git push --quiet origin $TARGET_BRANCH_PO -echo "********** End of pushing po to working repo! *************" -popd diff --git a/tools/github_poBranch_update_key.enc b/tools/github_poBranch_update_key.enc deleted file mode 100644 index cbffab9d4..000000000 Binary files a/tools/github_poBranch_update_key.enc and /dev/null differ diff --git a/tools/rclone.conf.enc b/tools/rclone.conf.enc deleted file mode 100644 index 985bd728a..000000000 Binary files a/tools/rclone.conf.enc and /dev/null differ diff --git a/tox.ini b/tox.ini index 7a30a0d6a..f2662be4b 100644 --- a/tox.ini +++ b/tox.ini @@ -45,15 +45,6 @@ deps = allowlist_externals = rm commands = rm -rf {toxinidir}/docs/stubs/ {toxinidir}/docs/_build -[testenv:gettext] -envdir = .tox/docs -basepython = python3 -deps = - -r requirements-dev.txt -commands = - sphinx-build -W -b gettext docs/ docs/_build/gettext {posargs} - sphinx-intl -c docs/conf.py update -p docs/_build/gettext -l en -d docs/locale - [doc8] max-line-length=100 ignore-path=docs/_build