diff --git a/.github/workflows/quality_checks.yml b/.github/workflows/quality_checks.yml
index 8658702..caea630 100644
--- a/.github/workflows/quality_checks.yml
+++ b/.github/workflows/quality_checks.yml
@@ -7,34 +7,31 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-versions: ["3.8", "3.9", "3.10"]
+ python-versions: ["3.11"]
steps:
- - uses: aws-actions/configure-aws-credentials@v1
- with:
- aws-region: eu-west-2
- aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
- aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: ${{ matrix.python-version }}
+ python-version: 3.11
- name: Install dependencies and the package
run: |
python -m pip install --upgrade pip
- pip install .[test]
+ pip install -v .[test]
- name: Run unit tests
- run: pytest --cov=qbench --cov-report=xml
+ run: pytest --cov=qbench --cov-report=xml -k 'not awscreds and not aersim'
+ env:
+ QISKIT_IBM_TOKEN: ${{ secrets.QISKIT_IBM_TOKEN }}
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
- run_quality_cheks:
+ run_quality_checks:
runs-on: ubuntu-latest
steps:
- name: Set up Python
uses: actions/setup-python@v2
with:
- python-version: 3.9
+ python-version: 3.11
- uses: actions/checkout@v2
- name: Install dependencies and the package
run: |
diff --git a/.gitignore b/.gitignore
index 8487c95..9199625 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,3 +130,12 @@ dmypy.json
# PyCharm files
.idea/**
+
+# Generated files
+res.yml
+resolved.yml
+results.yml
+resolved.csv
+results.csv
+*.yml
+*.csv
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index ad3343c..b80b9a0 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -1,5 +1,5 @@
default_language_version:
- python: python3.9
+ python: python3.11
default_stages: [commit, push]
@@ -15,8 +15,7 @@ repos:
hooks:
- id: black
args: ["--check"]
- language_version: python3.9
-
+ language_version: python3.11
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
hooks:
diff --git a/Hadamard example.ipynb b/Hadamard example.ipynb
deleted file mode 100644
index bfc2e49..0000000
--- a/Hadamard example.ipynb
+++ /dev/null
@@ -1,196 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "code",
- "execution_count": 39,
- "id": "3d91d2c9",
- "metadata": {},
- "outputs": [],
- "source": [
- "from qiskit import QuantumCircuit, Aer\n",
- "import numpy as np\n",
- "\n",
- "\n",
- "from qbench.schemes.postselection import benchmark_using_postselection\n",
- "from qbench.schemes.direct_sum import benchmark_using_controlled_unitary"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 53,
- "id": "0059be26",
- "metadata": {},
- "outputs": [],
- "source": [
- "def state_prep():\n",
- " circuit = QuantumCircuit(2)\n",
- " circuit.h(0)\n",
- " circuit.cnot(0, 1)\n",
- " return circuit.to_instruction()\n",
- " \n",
- "\n",
- "def u_dag():\n",
- " circuit = QuantumCircuit(1)\n",
- " circuit.h(0)\n",
- " return circuit.to_instruction()\n",
- "\n",
- "def v0_dag():\n",
- " circuit = QuantumCircuit(1)\n",
- " circuit.ry(-np.pi * 3 / 4, 0)\n",
- " return circuit.to_instruction()\n",
- "\n",
- "def v1_dag():\n",
- " circuit = QuantumCircuit(1)\n",
- " circuit.ry(-np.pi * 3 / 4, 0)\n",
- " circuit.x(0)\n",
- " return circuit.to_instruction()\n",
- "\n",
- "def v0_v1_direct_sum_dag():\n",
- " circuit = QuantumCircuit(2)\n",
- " circuit.ry(-np.pi * 3 / 4, 0)\n",
- " circuit.cnot(0, 1)\n",
- " return circuit.to_instruction()"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 54,
- "id": "455e4997",
- "metadata": {},
- "outputs": [],
- "source": [
- "simulator = Aer.get_backend(\"aer_simulator\")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 55,
- "id": "b7f6030a",
- "metadata": {},
- "outputs": [],
- "source": [
- "result = benchmark_using_postselection(\n",
- " backend=simulator,\n",
- " target=0,\n",
- " ancilla=1,\n",
- " state_preparation=state_prep(),\n",
- " u_dag=u_dag(),\n",
- " v0_dag=v0_dag(),\n",
- " v1_dag=v1_dag(),\n",
- " num_shots_per_measurement=100000\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 56,
- "id": "75bbbaa6",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0.8537402864843542"
- ]
- },
- "execution_count": 56,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "result"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 57,
- "id": "bfe0ba4b",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0.8535533905932737"
- ]
- },
- "execution_count": 57,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "expected = 0.5 + 0.25 * np.sqrt(2)\n",
- "expected"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 64,
- "id": "99acaa62",
- "metadata": {},
- "outputs": [],
- "source": [
- "result = benchmark_using_controlled_unitary(\n",
- " backend=simulator,\n",
- " target=0,\n",
- " ancilla=1,\n",
- " state_preparation=state_prep(),\n",
- " u_dag=u_dag(),\n",
- " v0_v1_direct_sum_dag=v0_v1_dag(),\n",
- " num_shots_per_measurement=100000\n",
- ")"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 65,
- "id": "7df4e8a6",
- "metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "0.854825"
- ]
- },
- "execution_count": 65,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
- "source": [
- "result"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "49036e31",
- "metadata": {},
- "outputs": [],
- "source": []
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "Python 3 (ipykernel)",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.9.15"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/Hadamard_certification_experiment.ipynb b/Hadamard_certification_experiment.ipynb
new file mode 100644
index 0000000..c87d9a3
--- /dev/null
+++ b/Hadamard_certification_experiment.ipynb
@@ -0,0 +1,373 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "id": "ef1f4c7d",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:18.460202Z",
+ "start_time": "2025-01-18T02:12:18.456108Z"
+ }
+ },
+ "source": [
+ "from qiskit import QuantumCircuit\n",
+ "import numpy as np\n",
+ "from qiskit_aer import StatevectorSimulator\n",
+ "from qiskit_aer.primitives import SamplerV2\n",
+ "from qbench.schemes.postselection import benchmark_certification_using_postselection\n",
+ "from qbench.schemes.direct_sum import benchmark_certification_using_direct_sum"
+ ],
+ "outputs": [],
+ "execution_count": 200
+ },
+ {
+ "cell_type": "code",
+ "id": "0059be26",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:18.483421Z",
+ "start_time": "2025-01-18T02:12:18.475064Z"
+ }
+ },
+ "source": [
+ "### certification experiment for measurement in Hadamard basis using postselection and direct sum ###\n",
+ "\n",
+ "def state_prep():\n",
+ " circuit = QuantumCircuit(2)\n",
+ " circuit.h(0)\n",
+ " circuit.cx(0,1)\n",
+ " return circuit.to_instruction()\n",
+ " \n",
+ "def u_dag():\n",
+ " circuit = QuantumCircuit(1)\n",
+ " circuit.h(0)\n",
+ " return circuit.to_instruction()\n",
+ "\n",
+ "def v0_dag():\n",
+ " circuit = QuantumCircuit(1)\n",
+ " circuit.ry(2 * np.arcsin(np.sqrt(0.05)), 0)\n",
+ " \n",
+ " return circuit.to_instruction()\n",
+ "\n",
+ "def v1_dag():\n",
+ " circuit = QuantumCircuit(1)\n",
+ " circuit.ry(2 * np.arcsin(np.sqrt(0.05)), 0)\n",
+ " circuit.x(0)\n",
+ " return circuit.to_instruction()\n",
+ "\n",
+ "def v0_v1_direct_sum_dag():\n",
+ " circuit = QuantumCircuit(2)\n",
+ " circuit.p(-np.pi, 0)\n",
+ " circuit.ry(-2 * np.arcsin(np.sqrt(0.05)), 0)\n",
+ " circuit.cx(0, 1)\n",
+ " return circuit.to_instruction()"
+ ],
+ "outputs": [],
+ "execution_count": 201
+ },
+ {
+ "cell_type": "code",
+ "id": "455e4997",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:18.490176Z",
+ "start_time": "2025-01-18T02:12:18.487554Z"
+ }
+ },
+ "source": "simulator = StatevectorSimulator()",
+ "outputs": [],
+ "execution_count": 202
+ },
+ {
+ "cell_type": "code",
+ "id": "b7f6030a",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:18.601348Z",
+ "start_time": "2025-01-18T02:12:18.491791Z"
+ }
+ },
+ "source": [
+ "postselection_result = benchmark_certification_using_postselection(\n",
+ " backend=simulator,\n",
+ " target=0,\n",
+ " ancilla=1,\n",
+ " state_preparation=state_prep(),\n",
+ " u_dag=u_dag(),\n",
+ " v0_dag=v0_dag(),\n",
+ " v1_dag=v1_dag(),\n",
+ " num_shots_per_measurement=10\n",
+ ")\n",
+ "\n",
+ "postselection_result"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0.7142857142857143"
+ ]
+ },
+ "execution_count": 203,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 203
+ },
+ {
+ "cell_type": "code",
+ "id": "bfe0ba4b",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:18.606161Z",
+ "start_time": "2025-01-18T02:12:18.602290Z"
+ }
+ },
+ "source": [
+ "expected = (1/np.sqrt(2) * np.sqrt(0.95) - 1/np.sqrt(2) * np.sqrt(0.05))**2\n",
+ "expected"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0.2820550528229661"
+ ]
+ },
+ "execution_count": 204,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 204
+ },
+ {
+ "cell_type": "code",
+ "id": "99acaa62",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.143129Z",
+ "start_time": "2025-01-18T02:12:18.608293Z"
+ }
+ },
+ "source": [
+ "direct_sum_result = benchmark_certification_using_direct_sum(\n",
+ " backend=simulator,\n",
+ " target=0,\n",
+ " ancilla=1,\n",
+ " state_preparation=state_prep(),\n",
+ " u_dag=u_dag(),\n",
+ " v0_v1_direct_sum_dag=v0_v1_direct_sum_dag(),\n",
+ " num_shots_per_measurement=100000\n",
+ ")\n",
+ "\n",
+ "direct_sum_result"
+ ],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "0.27924"
+ ]
+ },
+ "execution_count": 205,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "execution_count": 205
+ },
+ {
+ "cell_type": "code",
+ "id": "a837c34e-b8d7-4df4-9c58-5ba1cf16f8e7",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.146922Z",
+ "start_time": "2025-01-18T02:12:19.144223Z"
+ }
+ },
+ "source": [
+ "p_succ = expected\n",
+ "print(f\"Analytical p_succ = {p_succ}\")\n",
+ "print(\n",
+ "f\"Postselection: p_succ = {postselection_result}, abs_error ={np.abs(p_succ - postselection_result)}\"\n",
+ ")\n",
+ "print(f\"Direct sum: p_succ = {direct_sum_result}, abs_error ={np.abs(p_succ - direct_sum_result)}\")"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Analytical p_succ = 0.2820550528229661\n",
+ "Postselection: p_succ = 0.7142857142857143, abs_error =0.4322306614627482\n",
+ "Direct sum: p_succ = 0.27924, abs_error =0.0028150528229661242\n"
+ ]
+ }
+ ],
+ "execution_count": 206
+ },
+ {
+ "cell_type": "code",
+ "id": "ce25f7a4-1533-4252-a932-bbafbea78156",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.162830Z",
+ "start_time": "2025-01-18T02:12:19.147920Z"
+ }
+ },
+ "source": [
+ "from qbench.schemes.postselection import (\n",
+ " assemble_circuits_discrimination_postselection,\n",
+ " compute_probabilities_certification_postselection,\n",
+ ")\n",
+ "circuits = assemble_circuits_discrimination_postselection(\n",
+ "target=0,\n",
+ "ancilla=1,\n",
+ "state_preparation=state_prep(),\n",
+ "u_dag=u_dag(),\n",
+ "v0_dag=v0_dag(),\n",
+ "v1_dag=v1_dag(),\n",
+ ")"
+ ],
+ "outputs": [],
+ "execution_count": 207
+ },
+ {
+ "cell_type": "code",
+ "id": "9e3f5bba-d2ab-4904-b46b-e6b05bd33b86",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.167982Z",
+ "start_time": "2025-01-18T02:12:19.164103Z"
+ }
+ },
+ "source": [
+ "from qiskit_aer.noise import NoiseModel, ReadoutError\n",
+ "error = ReadoutError([[0.75, 0.25], [0.8, 0.2]])\n",
+ "noise_model = NoiseModel()\n",
+ "noise_model.add_readout_error(error, [0])\n",
+ "noise_model.add_readout_error(error, [1])"
+ ],
+ "outputs": [],
+ "execution_count": 208
+ },
+ {
+ "cell_type": "code",
+ "id": "1a63f475-b565-4983-a947-62f3858731c8",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.682991Z",
+ "start_time": "2025-01-18T02:12:19.169272Z"
+ }
+ },
+ "source": [
+ "keys_ordering = [\"u_v0\", \"u_v1\"]\n",
+ "\n",
+ "all_circuits = [circuits[key] for key in keys_ordering]\n",
+ "\n",
+ "sampler_noiseless = SamplerV2()\n",
+ "sampler_noisy = SamplerV2(options=dict(backend_options=dict(noise_model=noise_model)))\n",
+ "\n",
+ "counts_noisy = [sampler_noisy.run(\n",
+ " [circ],\n",
+ " shots=100000).result()[0].data.meas.get_counts() for circ in all_circuits]\n",
+ "\n",
+ "counts_noiseless = [sampler_noiseless.run(\n",
+ " [circ],\n",
+ " shots=100000).result()[0].data.meas.get_counts() for circ in all_circuits]"
+ ],
+ "outputs": [],
+ "execution_count": 209
+ },
+ {
+ "cell_type": "code",
+ "id": "d2f9a183-9d6b-4c07-bb5f-f309f7ad7859",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.685729Z",
+ "start_time": "2025-01-18T02:12:19.683634Z"
+ }
+ },
+ "source": [
+ "prob_succ_noiseless = compute_probabilities_certification_postselection(\n",
+ " u_v0_counts=counts_noiseless[0],\n",
+ " u_v1_counts=counts_noiseless[1],)\n",
+ "\n",
+ "prob_succ_noisy = compute_probabilities_certification_postselection(\n",
+ " u_v0_counts=counts_noisy[0],\n",
+ " u_v1_counts=counts_noisy[1],)"
+ ],
+ "outputs": [],
+ "execution_count": 210
+ },
+ {
+ "cell_type": "code",
+ "id": "f949b697-7591-48a9-bcb2-c2c006453064",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.690105Z",
+ "start_time": "2025-01-18T02:12:19.686771Z"
+ }
+ },
+ "source": [
+ "print(prob_succ_noiseless)"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0.2826338159785372\n"
+ ]
+ }
+ ],
+ "execution_count": 211
+ },
+ {
+ "cell_type": "code",
+ "id": "145dba65-0497-4643-8dd0-e3af180921eb",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:12:19.695040Z",
+ "start_time": "2025-01-18T02:12:19.690617Z"
+ }
+ },
+ "source": [
+ "print(prob_succ_noisy)"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0.7728359732242023\n"
+ ]
+ }
+ ],
+ "execution_count": 212
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.9"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/Hadamard_discrimination_experiment.ipynb b/Hadamard_discrimination_experiment.ipynb
new file mode 100644
index 0000000..6878ddd
--- /dev/null
+++ b/Hadamard_discrimination_experiment.ipynb
@@ -0,0 +1,304 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "id": "901676ea-58e2-495c-89bd-40c7e4e239fb",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:04.697744Z",
+ "start_time": "2025-01-18T02:14:04.693263Z"
+ }
+ },
+ "source": [
+ "from qiskit import QuantumCircuit\n",
+ "import numpy as np\n",
+ "from qiskit_aer.primitives import SamplerV2\n",
+ "\n",
+ "from qbench.schemes.postselection import benchmark_discrimination_using_postselection\n",
+ "from qbench.schemes.direct_sum import benchmark_discrimination_using_direct_sum"
+ ],
+ "outputs": [],
+ "execution_count": 10
+ },
+ {
+ "cell_type": "code",
+ "id": "70c82def-c313-4968-8999-fd6f562cbb7a",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:04.715875Z",
+ "start_time": "2025-01-18T02:14:04.712064Z"
+ }
+ },
+ "source": [
+ "from qiskit_aer import StatevectorSimulator\n",
+ "\n",
+ "simulator = StatevectorSimulator()"
+ ],
+ "outputs": [],
+ "execution_count": 11
+ },
+ {
+ "cell_type": "code",
+ "id": "e3ce562e-5bb4-4cf3-ab92-e02c4baab7ce",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:04.736169Z",
+ "start_time": "2025-01-18T02:14:04.731399Z"
+ }
+ },
+ "source": [
+ "### discrimination experiment for measurement in Hadamard basis using postselection and direct sum###\n",
+ "\n",
+ "def state_prep():\n",
+ " circuit = QuantumCircuit(2)\n",
+ " circuit.h(0)\n",
+ " circuit.cx(0, 1)\n",
+ " return circuit.to_instruction()\n",
+ "\n",
+ "def u_dag():\n",
+ " circuit = QuantumCircuit(1)\n",
+ " circuit.h(0)\n",
+ " return circuit.to_instruction()\n",
+ "\n",
+ "def v0_dag():\n",
+ " circuit = QuantumCircuit(1)\n",
+ " circuit.ry(-np.pi * 3 / 4, 0)\n",
+ " return circuit.to_instruction()\n",
+ " \n",
+ "def v1_dag():\n",
+ " circuit = QuantumCircuit(1)\n",
+ " circuit.ry(-np.pi * 3 / 4, 0)\n",
+ " circuit.x(0)\n",
+ " return circuit.to_instruction()\n",
+ " \n",
+ "def v0_v1_direct_sum_dag():\n",
+ " circuit = QuantumCircuit(2)\n",
+ " circuit.ry(-np.pi * 3 / 4, 0)\n",
+ " circuit.cx(0, 1)\n",
+ " return circuit.to_instruction()"
+ ],
+ "outputs": [],
+ "execution_count": 12
+ },
+ {
+ "cell_type": "code",
+ "id": "939f2579-0b6d-4430-8c7e-37aaece4b47a",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:06.729272Z",
+ "start_time": "2025-01-18T02:14:04.737353Z"
+ }
+ },
+ "source": [
+ "discrimination_postselection_result = benchmark_discrimination_using_postselection(\n",
+ " backend=simulator,\n",
+ " target=0,\n",
+ " ancilla=1,\n",
+ " state_preparation=state_prep(),\n",
+ " u_dag=u_dag(),\n",
+ " v0_dag=v0_dag(),\n",
+ " v1_dag=v1_dag(),\n",
+ " num_shots_per_measurement=100000,)"
+ ],
+ "outputs": [],
+ "execution_count": 13
+ },
+ {
+ "cell_type": "code",
+ "id": "6bfde52e-855a-42b0-95a9-95b40aa41ef5",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:07.698378Z",
+ "start_time": "2025-01-18T02:14:06.730231Z"
+ }
+ },
+ "source": [
+ "discrimination_direct_sum_result = benchmark_discrimination_using_direct_sum(\n",
+ " backend=simulator,\n",
+ " target=1,\n",
+ " ancilla=2,\n",
+ " state_preparation=state_prep(),\n",
+ " u_dag=u_dag(),\n",
+ " v0_v1_direct_sum_dag=v0_v1_direct_sum_dag(),\n",
+ " num_shots_per_measurement=100000,)"
+ ],
+ "outputs": [],
+ "execution_count": 14
+ },
+ {
+ "cell_type": "code",
+ "id": "325d3e88-5b6c-46d4-9dff-58750f159a3a",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:07.702315Z",
+ "start_time": "2025-01-18T02:14:07.699679Z"
+ }
+ },
+ "source": [
+ "p_succ = (2 + np.sqrt(2)) / 4\n",
+ "print(f\"Analytical p_succ = {p_succ}\")\n",
+ "print(f\"Postselection: p_succ = {discrimination_postselection_result}, abs.error ={np.abs(p_succ - discrimination_postselection_result)}\")\n",
+ "print(f\"Direct sum: p_succ = {discrimination_direct_sum_result}, abs.error ={np.abs(p_succ - discrimination_direct_sum_result)}\")"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Analytical p_succ = 0.8535533905932737\n",
+ "Postselection: p_succ = 0.8549341565062727, abs.error =0.0013807659129989602\n",
+ "Direct sum: p_succ = 0.852805, abs.error =0.0007483905932736956\n"
+ ]
+ }
+ ],
+ "execution_count": 15
+ },
+ {
+ "cell_type": "code",
+ "id": "f24a2681-db8f-42af-890f-0d4201bec4d0",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:07.718969Z",
+ "start_time": "2025-01-18T02:14:07.703509Z"
+ }
+ },
+ "source": [
+ "from qbench.schemes.postselection import (\n",
+ " assemble_circuits_discrimination_postselection,\n",
+ " compute_probabilities_discrimination_postselection,)\n",
+ "\n",
+ "circuits = assemble_circuits_discrimination_postselection(\n",
+ " target=0,\n",
+ " ancilla=1,\n",
+ " state_preparation=state_prep(),\n",
+ " u_dag=u_dag(),\n",
+ " v0_dag=v0_dag(),\n",
+ " v1_dag=v1_dag(),)"
+ ],
+ "outputs": [],
+ "execution_count": 16
+ },
+ {
+ "cell_type": "code",
+ "id": "a4d56672-4ec9-406e-84d8-8038df8057fc",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:07.724311Z",
+ "start_time": "2025-01-18T02:14:07.719580Z"
+ }
+ },
+ "source": [
+ "from qiskit_aer.noise import NoiseModel, ReadoutError\n",
+ "\n",
+ "error = ReadoutError([[0.75, 0.25], [0.8, 0.2]])\n",
+ "noise_model = NoiseModel()\n",
+ "noise_model.add_readout_error(error, [0])\n",
+ "noise_model.add_readout_error(error, [1])\n"
+ ],
+ "outputs": [],
+ "execution_count": 17
+ },
+ {
+ "cell_type": "code",
+ "id": "ea5ffbfb-d2f0-4007-a49b-44de3aee9ef3",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:08.149808Z",
+ "start_time": "2025-01-18T02:14:07.725361Z"
+ }
+ },
+ "source": [
+ "keys_ordering = [\"id_v0\", \"id_v1\", \"u_v0\", \"u_v1\"]\n",
+ "\n",
+ "all_circuits = [circuits[key] for key in keys_ordering]\n",
+ "\n",
+ "sampler_noiseless = SamplerV2()\n",
+ "sampler_noisy = SamplerV2(options=dict(backend_options=dict(noise_model=noise_model)))\n",
+ "\n",
+ "counts_noisy = [sampler_noisy.run(\n",
+ " [circ],\n",
+ " shots=10000).result()[0].data.meas.get_counts() for circ in all_circuits]\n",
+ "\n",
+ "counts_noiseless = [sampler_noiseless.run(\n",
+ " [circ],\n",
+ " shots=10000).result()[0].data.meas.get_counts() for circ in all_circuits]"
+ ],
+ "outputs": [],
+ "execution_count": 18
+ },
+ {
+ "cell_type": "code",
+ "id": "5859c0fd-440d-4998-8b9f-dd325c09bcbe",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:08.154580Z",
+ "start_time": "2025-01-18T02:14:08.150942Z"
+ }
+ },
+ "source": [
+ "prob_succ_noiseless = compute_probabilities_discrimination_postselection(\n",
+ " id_v0_counts=counts_noiseless[0],\n",
+ " id_v1_counts=counts_noiseless[1],\n",
+ " u_v0_counts=counts_noiseless[2],\n",
+ " u_v1_counts=counts_noiseless[3],)\n",
+ "\n",
+ "prob_succ_noisy = compute_probabilities_discrimination_postselection(\n",
+ " id_v0_counts=counts_noisy[0],\n",
+ " id_v1_counts=counts_noisy[1],\n",
+ " u_v0_counts=counts_noisy[2],\n",
+ " u_v1_counts=counts_noisy[3],)"
+ ],
+ "outputs": [],
+ "execution_count": 19
+ },
+ {
+ "cell_type": "code",
+ "id": "09f57db2-f29d-4eba-ad57-a3cfad62142c",
+ "metadata": {
+ "ExecuteTime": {
+ "end_time": "2025-01-18T02:14:08.160687Z",
+ "start_time": "2025-01-18T02:14:08.156436Z"
+ }
+ },
+ "source": [
+ "p_succ = (2 + np.sqrt(2)) / 4\n",
+ "print(f\"Analytical p_succ = {p_succ}\")\n",
+ "print(f\"Prob_succ_noiseless {prob_succ_noiseless}, abs.error ={np.abs(p_succ - prob_succ_noiseless)}\")\n",
+ "print(f\"Prob_succ_noisy = {prob_succ_noisy}, abs.error ={np.abs(p_succ - prob_succ_noisy)}\")"
+ ],
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Analytical p_succ = 0.8535533905932737\n",
+ "Prob_succ_noiseless 0.8572140850212383, abs.error =0.0036606944279645726\n",
+ "Prob_succ_noisy = 0.5044143159173481, abs.error =0.34913907467592564\n"
+ ]
+ }
+ ],
+ "execution_count": 20
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.11.9"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/README.md b/README.md
index 8335282..6eac840 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,4 @@

-



diff --git a/docs/source/notebooks/Example 01 discriminating measurements in Hadamard basis.ipynb b/docs/source/notebooks/Example 01 discriminating measurements in Hadamard basis.ipynb
index 0c893be..5a0c0b4 100644
--- a/docs/source/notebooks/Example 01 discriminating measurements in Hadamard basis.ipynb
+++ b/docs/source/notebooks/Example 01 discriminating measurements in Hadamard basis.ipynb
@@ -162,11 +162,11 @@
"execution_count": 1,
"id": "29821678",
"metadata": {},
- "outputs": [],
"source": [
"from qiskit import QuantumCircuit, Aer\n",
"import numpy as np"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -182,11 +182,11 @@
"execution_count": 2,
"id": "2ab223c7",
"metadata": {},
- "outputs": [],
"source": [
"from qbench.schemes.postselection import benchmark_using_postselection\n",
"from qbench.schemes.direct_sum import benchmark_using_direct_sum"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -213,7 +213,6 @@
"execution_count": 4,
"id": "505e63c7",
"metadata": {},
- "outputs": [],
"source": [
"def state_prep():\n",
" circuit = QuantumCircuit(2)\n",
@@ -246,7 +245,8 @@
" circuit.ry(-np.pi * 3 / 4, 0)\n",
" circuit.cnot(0, 1)\n",
" return circuit.to_instruction()"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -273,10 +273,10 @@
"execution_count": 5,
"id": "e34964ef",
"metadata": {},
- "outputs": [],
"source": [
"simulator = Aer.get_backend(\"aer_simulator\")"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -292,7 +292,6 @@
"execution_count": 6,
"id": "4d2c3703",
"metadata": {},
- "outputs": [],
"source": [
"postselection_result = benchmark_using_postselection(\n",
" backend=simulator,\n",
@@ -304,14 +303,14 @@
" v1_dag=v1_dag(),\n",
" num_shots_per_measurement=10000,\n",
")"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "code",
"execution_count": 8,
"id": "aa590bef",
"metadata": {},
- "outputs": [],
"source": [
"direct_sum_result = benchmark_using_direct_sum(\n",
" backend=simulator,\n",
@@ -322,24 +321,14 @@
" v0_v1_direct_sum_dag=v0_v1_direct_sum_dag(),\n",
" num_shots_per_measurement=10000,\n",
")"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "code",
"execution_count": 12,
"id": "3a5c109f",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Real p_succ = 0.8535533905932737\n",
- "Postselection: p_succ = 0.8542241847738258, abs. error = -0.0006707941805520479\n",
- "Direct sum: p_succ = 0.8536, abs. error = -4.6609406726294544e-05\n"
- ]
- }
- ],
"source": [
"p_succ = (2 + np.sqrt(2)) / 4\n",
"print(f\"Real p_succ = {p_succ}\")\n",
@@ -347,7 +336,8 @@
" f\"Postselection: p_succ = {postselection_result}, abs. error = {p_succ - postselection_result}\"\n",
")\n",
"print(f\"Direct sum: p_succ = {direct_sum_result}, abs. error = {p_succ - direct_sum_result}\")"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -376,10 +366,10 @@
"remove-cell"
]
},
- "outputs": [],
"source": [
"import iplantuml"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "code",
@@ -391,28 +381,6 @@
"remove-stdout"
]
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Writing output for /home/dexter/Projects/iitis/PyQBench/docs/source/notebooks/cee7e7f7-817f-4e86-a60b-d0d1bf48419f.uml to cee7e7f7-817f-4e86-a60b-d0d1bf48419f.svg\n"
- ]
- },
- {
- "data": {
- "image/svg+xml": [
- ""
- ],
- "text/plain": [
- ""
- ]
- },
- "execution_count": 27,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
"source": [
"%%plantuml\n",
"@startuml\n",
@@ -429,7 +397,8 @@
"PyQBench --> PyQBench: compute probability\n",
"PyQBench --> User: return probability of success\n",
"@enduml\n"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "code",
@@ -441,28 +410,6 @@
"remove-stdout"
]
},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Writing output for /home/dexter/Projects/iitis/PyQBench/docs/source/notebooks/3dee3601-2949-4408-a48e-8a3ab89072da.uml to 3dee3601-2949-4408-a48e-8a3ab89072da.svg\n"
- ]
- },
- {
- "data": {
- "image/svg+xml": [
- ""
- ],
- "text/plain": [
- ""
- ]
- },
- "execution_count": 28,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
"source": [
"%%plantuml\n",
"@startuml\n",
@@ -479,7 +426,8 @@
"User --> PyQBench: passess measurements\n",
"PyQBench --> User: returns computed probability\n",
"@enduml\n"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -495,34 +443,19 @@
"execution_count": 30,
"id": "b4c6daf9",
"metadata": {},
- "outputs": [],
"source": [
"from qbench.schemes.postselection import (\n",
" assemble_postselection_circuits,\n",
" compute_probabilities_from_postselection_measurements,\n",
")"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "code",
"execution_count": 33,
"id": "ba50b4a3",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- "{'id_v0': ,\n",
- " 'id_v1': ,\n",
- " 'u_v0': ,\n",
- " 'u_v1': }"
- ]
- },
- "execution_count": 33,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
"source": [
"circuits = assemble_postselection_circuits(\n",
" target=0,\n",
@@ -534,7 +467,8 @@
")\n",
"\n",
"circuits"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -555,18 +489,6 @@
"execution_count": 85,
"id": "39d0ac4f",
"metadata": {},
- "outputs": [
- {
- "data": {
- "text/plain": [
- ""
- ]
- },
- "execution_count": 85,
- "metadata": {},
- "output_type": "execute_result"
- }
- ],
"source": [
"from qiskit.providers.aer import noise\n",
"\n",
@@ -577,7 +499,8 @@
"noise_model.add_readout_error(error, [1])\n",
"\n",
"noise_model"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -596,16 +519,6 @@
"execution_count": 82,
"id": "c265e41d",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "Noisless counts: [{'11': 734, '01': 4231, '00': 724, '10': 4311}, {'01': 716, '10': 737, '00': 4238, '11': 4309}, {'01': 749, '10': 697, '00': 4361, '11': 4193}, {'01': 4197, '11': 742, '00': 736, '10': 4325}]\n",
- "Noisy counts: [{'11': 464, '01': 1749, '10': 1741, '00': 6046}, {'11': 493, '10': 1729, '00': 5971, '01': 1807}, {'11': 524, '00': 5965, '10': 1734, '01': 1777}, {'11': 472, '01': 1700, '10': 1749, '00': 6079}]\n"
- ]
- }
- ],
"source": [
"from qiskit import execute\n",
"\n",
@@ -623,7 +536,8 @@
"\n",
"print(f\"Noisless counts: {counts_noiseless}\")\n",
"print(f\"Noisy counts: {counts_noisy}\")"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -639,15 +553,6 @@
"execution_count": 88,
"id": "633ca70a",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "0.856421162176477\n"
- ]
- }
- ],
"source": [
"prob_succ_noiseless = compute_probabilities_from_postselection_measurements(\n",
" id_v0_counts=counts_noiseless[0],\n",
@@ -657,22 +562,14 @@
")\n",
"\n",
"print(prob_succ_noiseless)"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "code",
"execution_count": 89,
"id": "36157e67",
"metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "0.4988475737326284\n"
- ]
- }
- ],
"source": [
"prob_succ_noisy = compute_probabilities_from_postselection_measurements(\n",
" id_v0_counts=counts_noisy[0],\n",
@@ -682,7 +579,8 @@
")\n",
"\n",
"print(prob_succ_noisy)"
- ]
+ ],
+ "outputs": []
},
{
"cell_type": "markdown",
@@ -699,8 +597,8 @@
"execution_count": null,
"id": "7e295d78",
"metadata": {},
- "outputs": [],
- "source": []
+ "source": [],
+ "outputs": []
}
],
"metadata": {
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000..b75c756
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,223 @@
+name: PyQBench
+channels:
+ - defaults
+ - https://repo.anaconda.com/pkgs/main
+ - https://repo.anaconda.com/pkgs/r
+dependencies:
+ - _libgcc_mutex=0.1=main
+ - _openmp_mutex=5.1=1_gnu
+ - anyio=4.2.0=py311h06a4308_0
+ - argon2-cffi=21.3.0=pyhd3eb1b0_0
+ - argon2-cffi-bindings=21.2.0=py311h5eee18b_0
+ - asttokens=2.0.5=pyhd3eb1b0_0
+ - async-lru=2.0.4=py311h06a4308_0
+ - attrs=23.1.0=py311h06a4308_0
+ - babel=2.11.0=py311h06a4308_0
+ - beautifulsoup4=4.12.3=py311h06a4308_0
+ - bleach=4.1.0=pyhd3eb1b0_0
+ - brotli-python=1.0.9=py311h6a678d5_8
+ - bzip2=1.0.8=h5eee18b_6
+ - ca-certificates=2024.7.2=h06a4308_0
+ - cffi=1.16.0=py311h5eee18b_1
+ - charset-normalizer=3.3.2=pyhd3eb1b0_0
+ - comm=0.2.1=py311h06a4308_0
+ - cyrus-sasl=2.1.28=h52b45da_1
+ - dbus=1.13.18=hb2f20db_0
+ - debugpy=1.6.7=py311h6a678d5_0
+ - decorator=5.1.1=pyhd3eb1b0_0
+ - defusedxml=0.7.1=pyhd3eb1b0_0
+ - executing=0.8.3=pyhd3eb1b0_0
+ - expat=2.6.3=h6a678d5_0
+ - fontconfig=2.14.1=h55d465d_3
+ - freetype=2.12.1=h4a9f257_0
+ - glib=2.78.4=h6a678d5_0
+ - glib-tools=2.78.4=h6a678d5_0
+ - gst-plugins-base=1.14.1=h6a678d5_1
+ - gstreamer=1.14.1=h5eee18b_1
+ - h11=0.14.0=py311h06a4308_0
+ - httpcore=1.0.2=py311h06a4308_0
+ - httpx=0.27.0=py311h06a4308_0
+ - icu=73.1=h6a678d5_0
+ - idna=3.7=py311h06a4308_0
+ - ipykernel=6.28.0=py311h06a4308_0
+ - ipython=8.27.0=py311h06a4308_0
+ - ipywidgets=8.1.2=py311h06a4308_0
+ - jedi=0.19.1=py311h06a4308_0
+ - jinja2=3.1.4=py311h06a4308_0
+ - jpeg=9e=h5eee18b_3
+ - json5=0.9.6=pyhd3eb1b0_0
+ - jsonschema=4.19.2=py311h06a4308_0
+ - jsonschema-specifications=2023.7.1=py311h06a4308_0
+ - jupyter=1.0.0=py311h06a4308_9
+ - jupyter-lsp=2.2.0=py311h06a4308_0
+ - jupyter_client=8.6.0=py311h06a4308_0
+ - jupyter_console=6.6.3=py311h06a4308_0
+ - jupyter_core=5.7.2=py311h06a4308_0
+ - jupyter_events=0.10.0=py311h06a4308_0
+ - jupyter_server=2.14.1=py311h06a4308_0
+ - jupyter_server_terminals=0.4.4=py311h06a4308_1
+ - jupyterlab=4.2.5=py311h06a4308_0
+ - jupyterlab_pygments=0.1.2=py_0
+ - jupyterlab_server=2.27.3=py311h06a4308_0
+ - jupyterlab_widgets=3.0.10=py311h06a4308_0
+ - krb5=1.20.1=h143b758_1
+ - ld_impl_linux-64=2.38=h1181459_1
+ - libclang=14.0.6=default_hc6dbbc7_1
+ - libclang13=14.0.6=default_he11475f_1
+ - libcups=2.4.2=h2d74bed_1
+ - libedit=3.1.20230828=h5eee18b_0
+ - libffi=3.4.4=h6a678d5_1
+ - libgcc-ng=11.2.0=h1234567_1
+ - libglib=2.78.4=hdc74915_0
+ - libgomp=11.2.0=h1234567_1
+ - libiconv=1.16=h5eee18b_3
+ - libllvm14=14.0.6=hecde1de_4
+ - libpng=1.6.39=h5eee18b_0
+ - libpq=12.17=hdbd6064_0
+ - libsodium=1.0.18=h7b6447c_0
+ - libstdcxx-ng=11.2.0=h1234567_1
+ - libuuid=1.41.5=h5eee18b_0
+ - libxcb=1.15=h7f8727e_0
+ - libxkbcommon=1.0.1=h097e994_2
+ - libxml2=2.13.1=hfdd30dd_2
+ - lz4-c=1.9.4=h6a678d5_1
+ - markupsafe=2.1.3=py311h5eee18b_0
+ - matplotlib-inline=0.1.6=py311h06a4308_0
+ - mistune=2.0.4=py311h06a4308_0
+ - mysql=5.7.24=h721c034_2
+ - nbclient=0.8.0=py311h06a4308_0
+ - nbconvert=7.10.0=py311h06a4308_0
+ - nbformat=5.9.2=py311h06a4308_0
+ - ncurses=6.4=h6a678d5_0
+ - nest-asyncio=1.6.0=py311h06a4308_0
+ - notebook=7.2.2=py311h06a4308_0
+ - notebook-shim=0.2.3=py311h06a4308_0
+ - openssl=3.0.15=h5eee18b_0
+ - overrides=7.4.0=py311h06a4308_0
+ - pandocfilters=1.5.0=pyhd3eb1b0_0
+ - parso=0.8.3=pyhd3eb1b0_0
+ - pcre2=10.42=hebb0a14_1
+ - pexpect=4.8.0=pyhd3eb1b0_3
+ - pip=24.0=py311h06a4308_0
+ - platformdirs=3.10.0=py311h06a4308_0
+ - ply=3.11=py311h06a4308_0
+ - prometheus_client=0.14.1=py311h06a4308_0
+ - prompt-toolkit=3.0.43=py311h06a4308_0
+ - prompt_toolkit=3.0.43=hd3eb1b0_0
+ - ptyprocess=0.7.0=pyhd3eb1b0_2
+ - pure_eval=0.2.2=pyhd3eb1b0_0
+ - pygments=2.15.1=py311h06a4308_1
+ - pyqt=5.15.10=py311h6a678d5_0
+ - pyqt5-sip=12.13.0=py311h5eee18b_0
+ - pysocks=1.7.1=py311h06a4308_0
+ - python=3.11.9=h955ad1f_0
+ - python-dateutil=2.9.0post0=py311h06a4308_2
+ - python-fastjsonschema=2.16.2=py311h06a4308_0
+ - python-json-logger=2.0.7=py311h06a4308_0
+ - pytz=2024.1=py311h06a4308_0
+ - pyyaml=6.0.1=py311h5eee18b_0
+ - pyzmq=25.1.2=py311h6a678d5_0
+ - qt-main=5.15.2=h53bd1ea_10
+ - qtconsole=5.5.1=py311h06a4308_0
+ - qtpy=2.4.1=py311h06a4308_0
+ - readline=8.2=h5eee18b_0
+ - referencing=0.30.2=py311h06a4308_0
+ - rfc3339-validator=0.1.4=py311h06a4308_0
+ - rfc3986-validator=0.1.1=py311h06a4308_0
+ - rpds-py=0.10.6=py311hb02cf49_0
+ - send2trash=1.8.2=py311h06a4308_0
+ - setuptools=69.5.1=py311h06a4308_0
+ - sip=6.7.12=py311h6a678d5_0
+ - six=1.16.0=pyhd3eb1b0_1
+ - sniffio=1.3.0=py311h06a4308_0
+ - soupsieve=2.5=py311h06a4308_0
+ - sqlite=3.45.3=h5eee18b_0
+ - stack_data=0.2.0=pyhd3eb1b0_0
+ - terminado=0.17.1=py311h06a4308_0
+ - tinycss2=1.2.1=py311h06a4308_0
+ - tk=8.6.14=h39e8969_0
+ - tornado=6.4.1=py311h5eee18b_0
+ - traitlets=5.14.3=py311h06a4308_0
+ - typing-extensions=4.11.0=py311h06a4308_0
+ - typing_extensions=4.11.0=py311h06a4308_0
+ - wcwidth=0.2.5=pyhd3eb1b0_0
+ - webencodings=0.5.1=py311h06a4308_1
+ - websocket-client=1.8.0=py311h06a4308_0
+ - wheel=0.43.0=py311h06a4308_0
+ - widgetsnbextension=4.0.10=py311h06a4308_0
+ - xz=5.4.6=h5eee18b_1
+ - yaml=0.2.5=h7b6447c_0
+ - zeromq=4.3.5=h6a678d5_0
+ - zlib=1.2.13=h5eee18b_1
+ - zstd=1.5.5=hc292b87_2
+ - pip:
+ - amazon-braket-default-simulator==1.23.2
+ - amazon-braket-schemas==1.21.4
+ - amazon-braket-sdk==1.79.1
+ - annotated-types==0.6.0
+ - antlr4-python3-runtime==4.9.2
+ - backoff==2.2.1
+ - backports-entry-points-selectable==1.3.0
+ - boltons==24.0.0
+ - boto3==1.34.105
+ - botocore==1.34.105
+ - certifi==2024.2.2
+ - cloudpickle==2.2.1
+ - contourpy==1.2.1
+ - cryptography==42.0.7
+ - cycler==0.12.1
+ - cython==3.0.10
+ - dill==0.3.8
+ - fonttools==4.51.0
+ - ibm-cloud-sdk-core==3.20.0
+ - ibm-platform-services==0.53.6
+ - importlib-metadata==7.1.0
+ - iniconfig==2.0.0
+ - jmespath==1.0.1
+ - kiwisolver==1.4.5
+ - matplotlib==3.8.4
+ - mpmath==1.3.0
+ - mthree==2.6.3
+ - mypy-extensions==1.0.0
+ - networkx==3.3
+ - numpy==1.26.4
+ - openpulse==0.5.0
+ - openqasm3==0.5.0
+ - opt-einsum==3.3.0
+ - oqpy==0.3.5
+ - orjson==3.10.3
+ - packaging==24.0
+ - pandas==2.2.2
+ - pbr==6.0.0
+ - pillow==10.3.0
+ - pluggy==1.5.0
+ - psutil==5.9.8
+ - py==1.11.0
+ - pycparser==2.22
+ - pydantic==2.7.1
+ - pydantic-core==2.18.2
+ - pyjwt==2.8.0
+ - pyparsing==3.1.2
+ - pyqbench==0.1.dev113+gdb974ec.d20250213
+ - pyspnego==0.10.2
+ - pytest==8.3.3
+ - qiskit==1.3.1
+ - qiskit-aer==0.14.1
+ - qiskit-braket-provider==0.3.1
+ - qiskit-ibm-provider==0.11.0
+ - qiskit-ibm-runtime==0.34.0
+ - qiskit-ionq==0.5.0
+ - requests==2.31.0
+ - requests-ntlm==1.2.0
+ - retry==0.9.2
+ - rustworkx==0.15.1
+ - s3transfer==0.10.1
+ - scipy==1.13.0
+ - stevedore==5.2.0
+ - symengine==0.11.0
+ - sympy==1.12
+ - tqdm==4.66.4
+ - tzdata==2024.1
+ - urllib3==2.2.1
+ - websockets==13.0.1
+ - zipp==3.18.1
diff --git a/examples/backends/aer_simulator_local.yml b/examples/backends/aer_simulator_local.yml
new file mode 100644
index 0000000..cf652b9
--- /dev/null
+++ b/examples/backends/aer_simulator_local.yml
@@ -0,0 +1,2 @@
+provider: "qiskit.providers.aer:AerProvider"
+name: "aer_simulator"
diff --git a/examples/backends/ibmq-quito.yml b/examples/backends/ibm_backend_kyiv_async.yml
similarity index 80%
rename from examples/backends/ibmq-quito.yml
rename to examples/backends/ibm_backend_kyiv_async.yml
index 19f9bba..8cfe6a7 100644
--- a/examples/backends/ibmq-quito.yml
+++ b/examples/backends/ibm_backend_kyiv_async.yml
@@ -1,4 +1,4 @@
-name: ibmq_quito
+name: ibm_kyiv
asynchronous: true
provider:
hub: ibm-q
diff --git a/examples/backends/ibmq-qasm-simulator-sync.yml b/examples/backends/ibmq_local_simulator.yml
similarity index 100%
rename from examples/backends/ibmq-qasm-simulator-sync.yml
rename to examples/backends/ibmq_local_simulator.yml
diff --git a/examples/backends/ibmq-qasm-simulator-async.yml b/examples/backends/ibmq_qasm_local_simulator.yml
similarity index 100%
rename from examples/backends/ibmq-qasm-simulator-async.yml
rename to examples/backends/ibmq_qasm_local_simulator.yml
diff --git a/examples/backends/qiskit_braket_provider_AWSBraketProvider_lucy.yml b/examples/backends/qiskit_braket_provider_AWSBraketProvider_lucy.yml
new file mode 100644
index 0000000..01f3a50
--- /dev/null
+++ b/examples/backends/qiskit_braket_provider_AWSBraketProvider_lucy.yml
@@ -0,0 +1,2 @@
+provider: qiskit_braket_provider:AWSBraketProvider
+name: "lucy"
diff --git a/examples/backends/qiskit_braket_provider_AWSBraketProvider_lucy_with_run_options.yml b/examples/backends/qiskit_braket_provider_AWSBraketProvider_lucy_with_run_options.yml
new file mode 100644
index 0000000..cbc991f
--- /dev/null
+++ b/examples/backends/qiskit_braket_provider_AWSBraketProvider_lucy_with_run_options.yml
@@ -0,0 +1,5 @@
+provider: qiskit_braket_provider:AWSBraketProvider
+name: "lucy"
+run_options:
+ verbatim: true
+ disable_qubit_rewiring: true
diff --git a/examples/backends/qiskit_braket_provider_BraketLocalBackend.yml b/examples/backends/qiskit_braket_provider_BraketLocalBackend.yml
new file mode 100644
index 0000000..0a80773
--- /dev/null
+++ b/examples/backends/qiskit_braket_provider_BraketLocalBackend.yml
@@ -0,0 +1,3 @@
+factory: qiskit_braket_provider:BraketLocalBackend
+args:
+ - "braket_dm"
diff --git a/examples/backends/qiskit_braket_provider_BraketLocalBackend_with_run_options.yml b/examples/backends/qiskit_braket_provider_BraketLocalBackend_with_run_options.yml
new file mode 100644
index 0000000..7e520ef
--- /dev/null
+++ b/examples/backends/qiskit_braket_provider_BraketLocalBackend_with_run_options.yml
@@ -0,0 +1,5 @@
+factory: qiskit_braket_provider:BraketLocalBackend
+args:
+ - "braket_dm"
+run_options:
+ verbatim: true
diff --git a/examples/experiments/certification_experiment_direct_sum.yml b/examples/experiments/certification_experiment_direct_sum.yml
new file mode 100644
index 0000000..2a24689
--- /dev/null
+++ b/examples/experiments/certification_experiment_direct_sum.yml
@@ -0,0 +1,14 @@
+type: certification-fourier
+qubits:
+ - target: 0
+ ancilla: 1
+angles:
+ start: 0
+ stop: 2 * pi
+ num_steps: 2
+delta: 0.05
+gateset: ibmq
+method: direct_sum
+num_shots: 10
+
+
diff --git a/examples/experiments/certification_experiment_postselection.yml b/examples/experiments/certification_experiment_postselection.yml
new file mode 100644
index 0000000..f0efd9b
--- /dev/null
+++ b/examples/experiments/certification_experiment_postselection.yml
@@ -0,0 +1,13 @@
+type: certification-fourier
+qubits:
+ - target: 0
+ ancilla: 1
+angles:
+ start: 0
+ stop: 2 * pi
+ num_steps: 25
+delta: 0.05
+gateset: ibmq
+method: postselection
+num_shots: 10000
+
diff --git a/examples/experiments/fourier-disc-direct-sum-ibmq.yml b/examples/experiments/discrimination_experiment_direct_sum.yml
similarity index 80%
rename from examples/experiments/fourier-disc-direct-sum-ibmq.yml
rename to examples/experiments/discrimination_experiment_direct_sum.yml
index 3fe487f..943ce83 100644
--- a/examples/experiments/fourier-disc-direct-sum-ibmq.yml
+++ b/examples/experiments/discrimination_experiment_direct_sum.yml
@@ -5,7 +5,11 @@ qubits:
angles:
start: 0
stop: 2 * pi
- num_steps: 50
+ num_steps: 5
gateset: ibmq
method: direct_sum
-num_shots: 100
+num_shots: 10
+
+
+
+
diff --git a/examples/experiments/fourier-disc-postselection-ibmq.yml b/examples/experiments/discrimination_experiment_postselection.yml
similarity index 81%
rename from examples/experiments/fourier-disc-postselection-ibmq.yml
rename to examples/experiments/discrimination_experiment_postselection.yml
index 70194da..dd27bfe 100644
--- a/examples/experiments/fourier-disc-postselection-ibmq.yml
+++ b/examples/experiments/discrimination_experiment_postselection.yml
@@ -5,7 +5,9 @@ qubits:
angles:
start: 0
stop: 2 * pi
- num_steps: 50
+ num_steps: 5
gateset: ibmq
method: postselection
-num_shots: 100
+num_shots: 10
+
+
diff --git a/examples/experiments/fourier-certification-experiment.yml b/examples/experiments/fourier-certification-experiment.yml
new file mode 100644
index 0000000..968eef8
--- /dev/null
+++ b/examples/experiments/fourier-certification-experiment.yml
@@ -0,0 +1,16 @@
+type: certification-fourier
+qubits:
+ - target: 0
+ ancilla: 1
+ - target: 2
+ ancilla: 3
+ - target: 1
+ ancilla: 0
+angles:
+ start: 0
+ stop: 2 * pi
+ num_steps: 10
+delta: 0.05
+gateset: ibmq
+method: postselection
+num_shots: 10000
diff --git a/examples/using_fourier_with_simulator.py b/examples/using_fourier_with_simulator.py
deleted file mode 100644
index 64b06dc..0000000
--- a/examples/using_fourier_with_simulator.py
+++ /dev/null
@@ -1,43 +0,0 @@
-import numpy as np
-from matplotlib import pyplot as plt
-from qiskit_braket_provider import BraketLocalBackend
-
-from qbench.fourier import discrimination_probability_upper_bound
-from qbench.fourier._components import FourierComponents
-from qbench.schemes.direct_sum import benchmark_using_direct_sum
-
-NUM_SHOTS_PER_MEASUREMENT = 10000
-TARGET = 0
-ANCILLA = 1
-GATESET = "ibmq"
-
-
-def main():
- backend = BraketLocalBackend()
- phis = np.linspace(0, 2 * np.pi, 100)
-
- theoretical_probs = discrimination_probability_upper_bound(phis)
-
- actual_probs = [
- benchmark_using_direct_sum(
- backend=backend,
- target=TARGET,
- ancilla=ANCILLA,
- state_preparation=circuits.state_preparation,
- u_dag=circuits.u_dag,
- v0_v1_direct_sum_dag=circuits.v0_v1_direct_sum_dag,
- num_shots_per_measurement=NUM_SHOTS_PER_MEASUREMENT,
- )
- for circuits in (FourierComponents(phi, gateset=GATESET) for phi in phis)
- ]
-
- fig, ax = plt.subplots()
- ax.plot(phis, theoretical_probs, color="red", label="theoretical_predictions")
- ax.plot(phis, actual_probs, color="blue", label="actual data")
- ax.legend()
-
- plt.show()
-
-
-if __name__ == "__main__":
- main()
diff --git a/examples/using_fourier_with_simulator_postselection_all_cases.py b/examples/using_fourier_with_simulator_postselection_all_cases.py
deleted file mode 100644
index 878f1d9..0000000
--- a/examples/using_fourier_with_simulator_postselection_all_cases.py
+++ /dev/null
@@ -1,44 +0,0 @@
-import numpy as np
-from matplotlib import pyplot as plt
-from qiskit_braket_provider import BraketLocalBackend
-
-from qbench.fourier import discrimination_probability_upper_bound
-from qbench.fourier._components import FourierComponents
-from qbench.schemes.postselection import benchmark_using_postselection
-
-NUM_SHOTS_PER_MEASUREMENT = 1000
-TARGET = 0
-ANCILLA = 1
-GATESET = "ibmq"
-
-
-def main():
- backend = BraketLocalBackend()
- phis = np.linspace(0, 2 * np.pi, 100)
-
- theoretical_probs = discrimination_probability_upper_bound(phis)
-
- actual_probs = [
- benchmark_using_postselection(
- backend=backend,
- target=TARGET,
- ancilla=ANCILLA,
- state_preparation=circuits.state_preparation,
- u_dag=circuits.u_dag,
- v0_dag=circuits.v0_dag,
- v1_dag=circuits.v1_dag,
- num_shots_per_measurement=NUM_SHOTS_PER_MEASUREMENT,
- )
- for circuits in (FourierComponents(phi, gateset=GATESET) for phi in phis)
- ]
-
- fig, ax = plt.subplots()
- ax.plot(phis, theoretical_probs, color="red", label="theoretical_predictions")
- ax.plot(phis, actual_probs, color="blue", label="actual data")
- ax.legend()
-
- plt.show()
-
-
-if __name__ == "__main__":
- main()
diff --git a/mypy.ini b/mypy.ini
new file mode 100644
index 0000000..d96997f
--- /dev/null
+++ b/mypy.ini
@@ -0,0 +1,8 @@
+[mypy]
+files = qbench
+namespace_packages = True
+explicit_package_bases = True
+ignore_missing_imports = True
+
+[mypy-qbench.fourier._components.__init__]
+ignore_errors = True
diff --git a/pyproject.toml b/pyproject.toml
index 18c1536..89372cf 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -22,20 +22,25 @@ classifiers = [
"Intended Audience :: Science/Research",
"License :: OSI Approved :: Apache Software License",
"Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.11",
"Topic :: Scientific/Engineering :: Physics"
]
-requires-python = ">=3.8"
+requires-python = "==3.11.*"
dependencies = [
- "numpy ~= 1.22.0",
- "scipy ~= 1.7.0",
- "pandas ~= 1.5.0",
- "amazon-braket-sdk >= 1.11.1",
- "pydantic > 1.9.1",
- "qiskit ~= 0.37.2",
- "mthree ~= 1.1.0",
- "tqdm ~= 4.64.1",
- "pyyaml ~= 6.0",
- "qiskit-braket-provider ~= 0.0.3"
+ "numpy",
+ "scipy",
+ "pandas",
+ "amazon-braket-sdk",
+ "pydantic",
+ "qiskit",
+ "qiskit-aer",
+ "qiskit-ibm-runtime",
+ "qiskit-ibm-provider",
+ "mthree",
+ "tqdm",
+ "pyyaml",
+ "qiskit-braket-provider",
+ "matplotlib"
]
dynamic = ["version"]
diff --git a/qbench/_expressions.py b/qbench/_expressions.py
index a459f17..7bead78 100644
--- a/qbench/_expressions.py
+++ b/qbench/_expressions.py
@@ -1,4 +1,5 @@
"""Implementation of arithmetic expression parsing."""
+
import ast
import operator as op
from functools import singledispatch
diff --git a/qbench/batching.py b/qbench/batching.py
index d85946e..48a74e7 100644
--- a/qbench/batching.py
+++ b/qbench/batching.py
@@ -1,10 +1,12 @@
"""Functions for splitting sequences of circuits into batches."""
+
import math
from itertools import islice
from typing import Any, Iterable, NamedTuple, Optional, Sequence
from qiskit import QuantumCircuit
from qiskit.providers import JobV1
+from qiskit_ibm_runtime import RuntimeJob, RuntimeJobV2, SamplerV2
from tqdm import tqdm
from .common_models import Backend
@@ -16,7 +18,7 @@ class BatchWithKey(NamedTuple):
class BatchJob(NamedTuple):
- job: JobV1
+ job: JobV1 | RuntimeJob | RuntimeJobV2
keys: Sequence[Any]
@@ -74,10 +76,22 @@ def execute_in_batches(
order of `keys`.
"""
batches = batch_circuits_with_keys(circuits, keys, batch_size)
- result = (
- BatchJob(backend.run(batch.circuits, shots=shots, **kwargs), batch.keys)
- for batch in batches
+
+ sampler = SamplerV2(mode=backend)
+
+ # result = (
+ # BatchJob(backend.run(batch.circuits, shots=shots, **kwargs), batch.keys)
+ # for batch in batches
+ # )
+
+ result_gen = (
+ BatchJob(sampler.run(batch.circuits, shots=shots), batch.keys) for batch in batches
)
+
+ # Declare the variable 'result'
+ result: Iterable[BatchJob]
if show_progress:
- result = tqdm(result, total=len(batches))
+ result = tqdm(result_gen, total=len(batches))
+ else:
+ result = result_gen
return result
diff --git a/qbench/cli.py b/qbench/cli.py
index d7ee19d..7138550 100644
--- a/qbench/cli.py
+++ b/qbench/cli.py
@@ -1,10 +1,12 @@
"""Command line interface for qbench package."""
+
from argparse import ArgumentParser
from .fourier import add_fourier_parser
-from .logging import configure_logging
+from .fourier_certification import add_fourier_certification_parser
+from .logger import configure_logging
-PARSERS_TO_ADD = [add_fourier_parser]
+PARSERS_TO_ADD = [add_fourier_parser, add_fourier_certification_parser]
def main(args=None):
diff --git a/qbench/common_models.py b/qbench/common_models.py
index e028e96..f5ea035 100644
--- a/qbench/common_models.py
+++ b/qbench/common_models.py
@@ -1,15 +1,15 @@
-import os
import re
from importlib import import_module
from typing import Any, Dict, List, Optional, Union
-from pydantic import BaseModel as PydanticBaseModel
-from pydantic import ConstrainedInt, Field, StrictStr, root_validator, validator
-from qiskit import IBMQ
+from pydantic.v1 import BaseModel as PydanticBaseModel
+from pydantic.v1 import ConstrainedInt, Field, StrictStr, root_validator, validator
from qiskit.circuit import Parameter
from qiskit.providers import BackendV1, BackendV2
+from qiskit_aer import AerSimulator
+from qiskit_ibm_runtime import QiskitRuntimeService
-from ._expressions import eval_expr
+from qbench._expressions import eval_expr
AnyParameter = Union[float, Parameter]
@@ -63,6 +63,10 @@ def check_if_number_of_steps_is_one_when_start_equals_stop(cls, values):
return values
+class Delta(BaseModel):
+ delta: Any
+
+
class QubitsPair(BaseModel):
target: Qubit
ancilla: Qubit
@@ -140,28 +144,33 @@ class IBMQProviderDescription(BaseModel):
class IBMQBackendDescription(BaseModel):
name: str
asynchronous: bool = False
-
provider: IBMQProviderDescription
def create_backend(self):
- if IBMQ.active_account():
- provider = IBMQ.get_provider(
- hub=self.provider.hub,
- group=self.provider.group,
- project=self.provider.project,
- )
- else:
- provider = IBMQ.enable_account(
- os.getenv("IBMQ_TOKEN"),
- hub=self.provider.hub,
- group=self.provider.group,
- project=self.provider.project,
- )
- return provider.get_backend(self.name)
+ service = QiskitRuntimeService(
+ channel="ibm_quantum",
+ instance=f"{self.provider.hub}/{self.provider.group}/{self.provider.project}",
+ )
+
+ return service.backend(name=self.name)
+
+ # TODO finish!
+ # exit(-1)
+
+
+class AerBackendDescription(BaseModel):
+ name: str
+ asynchronous: bool = False
+
+ def create_backend(self):
+ return AerSimulator()
BackendDescription = Union[
- SimpleBackendDescription, BackendFactoryDescription, IBMQBackendDescription
+ SimpleBackendDescription,
+ BackendFactoryDescription,
+ IBMQBackendDescription,
+ AerBackendDescription,
]
diff --git a/qbench/fourier/__init__.py b/qbench/fourier/__init__.py
index 3986d13..11c0390 100644
--- a/qbench/fourier/__init__.py
+++ b/qbench/fourier/__init__.py
@@ -1,7 +1,7 @@
"""Functionalities relating specifically to Fourier-discrimination experiments.
-This package defines all instructions (components) needed for assembling
+This package defines all instructions (_components) needed for assembling
circuits for benchmarking using Fourier-parametrized family.
The Fourier family of measurements is defined as:
@@ -10,7 +10,7 @@
U(\\varphi) = H \\begin{pmatrix} 1&0\\\\0&e^{i\\varphi}\\end{pmatrix}H^\\dagger
$$
-All components are available as properties of :class:`FourierComponents` class. The
+All _components are available as properties of :class:`FourierComponents` class. The
instances of this class can be constructed in such a way that the instructions they
provide are compatible with several different quantum devices available on the market.
@@ -22,32 +22,17 @@
$$
"""
-from typing import Union
-
-import numpy as np
from ._cli import add_fourier_parser
-from ._components import FourierComponents
+from ._components.components import FourierComponents
from ._models import (
FourierDiscriminationAsyncResult,
FourierDiscriminationSyncResult,
FourierExperimentSet,
)
-
-def discrimination_probability_upper_bound(
- phi: Union[float, np.ndarray]
-) -> Union[float, np.ndarray]:
- """Compute exact upper bound on the probability of discrimination.
-
- :param phi: angle parametrizing the performed measurement.
- :return: maximum probability with which identity and $p_{U(\\varphi)}$ can be discriminated.
- """
- return 0.5 + 0.25 * np.abs(1 - np.exp(1j * phi))
-
-
__all__ = [
- "discrimination_probability_upper_bound",
+ # "discrimination_probability_upper_bound",
"add_fourier_parser",
"FourierComponents",
"FourierDiscriminationAsyncResult",
diff --git a/qbench/fourier/_cli.py b/qbench/fourier/_cli.py
index 9c22197..68cf3f8 100644
--- a/qbench/fourier/_cli.py
+++ b/qbench/fourier/_cli.py
@@ -3,6 +3,7 @@
This module also contains thin wrappers for functions from qbench.fourier.experiment_runner,
to adapt them for command line usage.
"""
+
from argparse import FileType, Namespace
from yaml import safe_dump, safe_load
diff --git a/qbench/fourier/_components/__init__.py b/qbench/fourier/_components/__init__.py
index 8184d05..481ba9f 100644
--- a/qbench/fourier/_components/__init__.py
+++ b/qbench/fourier/_components/__init__.py
@@ -1,128 +1,19 @@
-"""Module defining components used in Fourier discrimination experiment."""
-from typing import Optional, Union
+"""Module defining _components used in Fourier discrimination experiment."""
-from qiskit.circuit import Instruction, Parameter
+from typing import Union
-from . import _generic, _ibmq, _lucy, _rigetti
+import numpy as np
-class FourierComponents:
- """Class defining components for Fourier-discrimination experiment.
+def discrimination_probability_upper_bound(
+ phi: Union[float, np.ndarray]
+) -> Union[float, np.ndarray]:
+ """Compute exact upper bound on the probability of discrimination.
- :param phi: angle defining measurement to discriminate. May be a number or an instance of
- a Qiskit Parameter. See
- :qiskit_tutorial:`here `_
- if you are new to parametrized circuits in Qiskit.
-
- :param gateset: name of the one of the predefined basis gate sets to use. It controls which
- gates will be used to construct the circuit components. Available choices are:
-
- - :code:`"lucy"`: gateset comprising gates native to
- `OQC Lucy `_ computer.
- - :code:`"rigetti"`: gateset comprising gates native to
- `Rigetti `_ computers.
- - :code:`"ibmq"`: gateset comprising gates native to
- `IBMQ `_ computers.
-
- If no gateset is provided, high-level gates will be used without restriction on basis gates.
+ :param phi: angle parametrizing the performed measurement.
+ :return: maximum probability with which identity and $p_{U(\\varphi)}$ can be discriminated.
"""
-
- def __init__(self, phi: Union[float, Parameter], gateset: Optional[str] = None):
- """Initialize new instance of FourierComponents."""
- self.phi = phi
- self._module = _GATESET_MAPPING[gateset]
-
- @property
- def state_preparation(self) -> Instruction:
- """Instruction performing transformation $|00\\rangle$ -> Bell state
-
- The corresponding circuit is:
-
- .. code::
-
- ┌───┐
- q_0: ┤ H ├──■──
- └───┘┌─┴─┐
- q_1: ─────┤ X ├
- └───┘
-
- """
- return self._module.state_preparation()
-
- @property
- def u_dag(self) -> Instruction:
- r"""Unitary $U^\dagger$ defining Fourier measurement.
-
- The corresponding circuit is:
-
- .. code::
-
- ┌───┐┌───────────┐┌───┐
- q: ┤ H ├┤ Phase(-φ) ├┤ H ├
- └───┘└───────────┘└───┘
-
- .. note::
-
- This instruction is needed because on actual devices we can only measure in Z-basis.
- The $U^\dagger$ unitary changes basis so that subsequent measurement in Z-basis can
- be considered as performing desired von Neumann measurement to be discriminated from
- the Z-basis one.
- """
-
- return self._module.u_dag(self.phi)
-
- @property
- def v0_dag(self) -> Instruction:
- """Instruction corresponding to the positive part of Holevo-Helstrom measurement.
-
- The corresponding circuit is:
-
- .. code::
-
- ┌──────────┐┌────────────────┐
- q: ┤ Rz(-π/2) ├┤ Ry(-φ/2 - π/2) ├
- └──────────┘└────────────────┘
-
- """
- return self._module.v0_dag(self.phi)
-
- @property
- def v1_dag(self) -> Instruction:
- """Instruction corresponding to the negative part of Holevo-Helstrom measurement.
-
- The corresponding circuit is:
-
- .. code::
-
- ┌──────────┐┌────────────────┐┌────────┐
- q: ┤ Rz(-π/2) ├┤ Ry(-φ/2 - π/2) ├┤ Rx(-π) ├
- └──────────┘└────────────────┘└────────┘
- """
- return self._module.v1_dag(self.phi)
-
- @property
- def v0_v1_direct_sum_dag(self) -> Instruction:
- r"""Direct sum $V_0^\dagger\oplus V_1^\dagger$ of both parts of Holevo-Helstrom measurement.
-
- .. note::
- In usual basis ordering, the unitaries returned by this property would be
- block-diagonal, with blocks corresponding to positive and negative parts
- of Holevo-Helstrom measurement.
-
- However, Qiskit enumerates basis vectors in reverse, so the produced unitaries
- are not block-diagonal, unless the qubits are swapped.
- See accompanying tests to see how it's done.
-
- The following article contains more details on basis vectors ordering used
- (among others) by Qiskit:
- https://arxiv.org/abs/1711.02086
- """
- return self._module.v0_v1_direct_sum(self.phi)
+ return 0.5 + 0.25 * np.abs(1 - np.exp(1j * phi))
-_GATESET_MAPPING = {
- "lucy": _lucy,
- "rigetti": _rigetti,
- "ibmq": _ibmq,
- None: _generic,
-}
+__all__ = ["discrimination_probability_upper_bound"]
diff --git a/qbench/fourier/_components/_generic.py b/qbench/fourier/_components/_generic.py
index d305393..e290879 100644
--- a/qbench/fourier/_components/_generic.py
+++ b/qbench/fourier/_components/_generic.py
@@ -1,24 +1,23 @@
-"""Generic implementation of Fourier components not tailored for any specific device.
+"""Generic implementation of Fourier _components not tailored for any specific device.
-Note that using components from this module on physical device typically requires compilation.
+Note that using _components from this module on physical device typically requires compilation.
For detailed description of functions in this module refer to the documentation of
FourierComponents class.
"""
-import numpy as np
-from qiskit.circuit import Instruction, QuantumCircuit
-from ...common_models import AnyParameter
+import numpy as np
+from qiskit.circuit import Instruction, Parameter, QuantumCircuit
def state_preparation() -> Instruction:
circuit = QuantumCircuit(2, name="state-prep")
circuit.h(0)
- circuit.cnot(0, 1)
+ circuit.cx(0, 1)
return circuit.to_instruction()
-def u_dag(phi: AnyParameter) -> Instruction:
+def u_dag(phi: float | Parameter) -> Instruction:
circuit = QuantumCircuit(1, name="U-dag")
circuit.h(0)
circuit.p(-phi, 0)
@@ -26,14 +25,14 @@ def u_dag(phi: AnyParameter) -> Instruction:
return circuit.to_instruction()
-def v0_dag(phi: AnyParameter) -> Instruction:
+def v0_dag(phi: float | Parameter) -> Instruction:
circuit = QuantumCircuit(1, name="v0-dag")
circuit.rz(-np.pi / 2, 0)
circuit.ry(-(phi + np.pi) / 2, 0)
return circuit.to_instruction()
-def v1_dag(phi: AnyParameter) -> Instruction:
+def v1_dag(phi: float | Parameter) -> Instruction:
circuit = QuantumCircuit(1, name="v1-dag")
circuit.rz(-np.pi / 2, 0)
circuit.ry(-(phi + np.pi) / 2, 0)
@@ -41,9 +40,9 @@ def v1_dag(phi: AnyParameter) -> Instruction:
return circuit.to_instruction()
-def v0_v1_direct_sum(phi: AnyParameter) -> Instruction:
+def v0_v1_direct_sum(phi: float | Parameter) -> Instruction:
circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
circuit.p(np.pi, 0)
circuit.append(v0_dag(phi), [1])
- circuit.cnot(0, 1)
+ circuit.cx(0, 1)
return circuit.decompose(["v0-dag"]).to_instruction()
diff --git a/qbench/fourier/_components/_ibmq.py b/qbench/fourier/_components/_ibmq.py
index 799ff57..b9a4696 100644
--- a/qbench/fourier/_components/_ibmq.py
+++ b/qbench/fourier/_components/_ibmq.py
@@ -3,10 +3,12 @@
For detailed description of functions in this module refer to the documentation of
FourierComponents class.
"""
+
import numpy as np
from qiskit.circuit import Instruction, QuantumCircuit
-from ...common_models import AnyParameter
+from qbench.common_models import AnyParameter
+
from ._lucy_and_ibmq_common import u_dag, v0_dag, v1_dag
diff --git a/qbench/fourier/_components/_lucy.py b/qbench/fourier/_components/_lucy.py
index 6af7871..b6a8b2a 100644
--- a/qbench/fourier/_components/_lucy.py
+++ b/qbench/fourier/_components/_lucy.py
@@ -3,11 +3,13 @@
For detailed description of functions in this module refer to the documentation of
FourierComponents class.
"""
+
import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Instruction
-from ...common_models import AnyParameter
+from qbench.common_models import AnyParameter
+
from ._lucy_and_ibmq_common import u_dag, v0_dag, v1_dag
diff --git a/qbench/fourier/_components/_lucy_and_ibmq_common.py b/qbench/fourier/_components/_lucy_and_ibmq_common.py
index 7e58ced..d2f329b 100644
--- a/qbench/fourier/_components/_lucy_and_ibmq_common.py
+++ b/qbench/fourier/_components/_lucy_and_ibmq_common.py
@@ -3,11 +3,12 @@
For detailed description of functions in this module refer to the documentation of
FourierComponents class.
"""
+
import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Instruction
-from ...common_models import AnyParameter
+from qbench.common_models import AnyParameter
def u_dag(phi: AnyParameter) -> Instruction:
diff --git a/qbench/fourier/_components/_rigetti.py b/qbench/fourier/_components/_rigetti.py
index eed34be..153e2c7 100644
--- a/qbench/fourier/_components/_rigetti.py
+++ b/qbench/fourier/_components/_rigetti.py
@@ -3,11 +3,12 @@
For detailed description of functions in this module refer to the documentation of
FourierComponents class.
"""
+
import numpy as np
from qiskit import QuantumCircuit
from qiskit.circuit import Instruction
-from ...common_models import AnyParameter
+from qbench.common_models import AnyParameter
INSTRUCTIONS_TO_DECOMPOSE = ["hadamard-rigetti", "cnot-rigetti", "v0-dag"]
diff --git a/qbench/fourier/_components/components.py b/qbench/fourier/_components/components.py
new file mode 100644
index 0000000..13a5154
--- /dev/null
+++ b/qbench/fourier/_components/components.py
@@ -0,0 +1,129 @@
+"""Module defining _components used in Fourier discrimination experiment."""
+
+from typing import Optional, Union
+
+from qiskit.circuit import Instruction, Parameter
+
+from . import _generic, _ibmq, _lucy, _rigetti
+
+
+class FourierComponents:
+ """Class defining _components for Fourier-discrimination experiment.
+
+ :param phi: angle defining measurement to discriminate. May be a number or an instance of
+ a Qiskit Parameter. See
+ :qiskit_tutorial:`here `_
+ if you are new to parametrized circuits in Qiskit.
+
+ :param gateset: name of the one of the predefined basis gate sets to use. It controls which
+ gates will be used to construct the circuit _components. Available choices are:
+
+ - :code:`"lucy"`: gateset comprising gates native to
+ `OQC Lucy `_ computer.
+ - :code:`"rigetti"`: gateset comprising gates native to
+ `Rigetti `_ computers.
+ - :code:`"ibmq"`: gateset comprising gates native to
+ `IBMQ `_ computers.
+
+ If no gateset is provided, high-level gates will be used without restriction on basis gates.
+ """
+
+ def __init__(self, phi: Union[float, Parameter], gateset: Optional[str] = None):
+ """Initialize new instance of FourierComponents."""
+ self.phi = phi
+ self._module = _GATESET_MAPPING[gateset]
+
+ @property
+ def state_preparation(self) -> Instruction:
+ """Instruction performing transformation $|00\\rangle$ -> Bell state
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌───┐
+ q_0: ┤ H ├──■──
+ └───┘┌─┴─┐
+ q_1: ─────┤ X ├
+ └───┘
+
+ """
+ return self._module.state_preparation()
+
+ @property
+ def u_dag(self) -> Instruction:
+ r"""Unitary $U^\dagger$ defining Fourier measurement.
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌───┐┌───────────┐┌───┐
+ q: ┤ H ├┤ Phase(-φ) ├┤ H ├
+ └───┘└───────────┘└───┘
+
+ .. note::
+
+ This instruction is needed because on actual devices we can only measure in Z-basis.
+ The $U^\dagger$ unitary changes basis so that subsequent measurement in Z-basis can
+ be considered as performing desired von Neumann measurement to be discriminated from
+ the Z-basis one.
+ """
+
+ return self._module.u_dag(self.phi)
+
+ @property
+ def v0_dag(self) -> Instruction:
+ """Instruction corresponding to the positive part of Holevo-Helstrom measurement.
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌──────────┐┌────────────────┐
+ q: ┤ Rz(-π/2) ├┤ Ry(-φ/2 - π/2) ├
+ └──────────┘└────────────────┘
+
+ """
+ return self._module.v0_dag(self.phi)
+
+ @property
+ def v1_dag(self) -> Instruction:
+ """Instruction corresponding to the negative part of Holevo-Helstrom measurement.
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌──────────┐┌────────────────┐┌────────┐
+ q: ┤ Rz(-π/2) ├┤ Ry(-φ/2 - π/2) ├┤ Rx(-π) ├
+ └──────────┘└────────────────┘└────────┘
+ """
+ return self._module.v1_dag(self.phi)
+
+ @property
+ def v0_v1_direct_sum_dag(self) -> Instruction:
+ r"""Direct sum $V_0^\dagger\oplus V_1^\dagger$ of both parts of Holevo-Helstrom measurement.
+
+ .. note::
+ In usual basis ordering, the unitaries returned by this property would be
+ block-diagonal, with blocks corresponding to positive and negative parts
+ of Holevo-Helstrom measurement.
+
+ However, Qiskit enumerates basis vectors in reverse, so the produced unitaries
+ are not block-diagonal, unless the qubits are swapped.
+ See accompanying tests to see how it's done.
+
+ The following article contains more details on basis vectors ordering used
+ (among others) by Qiskit:
+ https://arxiv.org/abs/1711.02086
+ """
+ return self._module.v0_v1_direct_sum(self.phi)
+
+
+_GATESET_MAPPING = {
+ "lucy": _lucy,
+ "rigetti": _rigetti,
+ "ibmq": _ibmq,
+ None: _generic,
+}
diff --git a/qbench/fourier/_models.py b/qbench/fourier/_models.py
index a7e1a4b..2066649 100644
--- a/qbench/fourier/_models.py
+++ b/qbench/fourier/_models.py
@@ -11,9 +11,9 @@
)
import numpy as np
-from pydantic import validator
+from pydantic.v1 import validator
-from ..common_models import (
+from qbench.common_models import (
AnglesRange,
BackendDescription,
BaseModel,
diff --git a/qbench/fourier/experiment_runner.py b/qbench/fourier/experiment_runner.py
old mode 100644
new mode 100755
index 62995f6..48ea4f3
--- a/qbench/fourier/experiment_runner.py
+++ b/qbench/fourier/experiment_runner.py
@@ -1,29 +1,34 @@
"""Functions for running Fourier discrimination experiments and interacting with the results."""
+
+import sys
from collections import Counter, defaultdict
from logging import getLogger
+from pathlib import Path
from typing import Dict, Iterable, List, Optional, Tuple, Union, cast
import numpy as np
import pandas as pd
from mthree import M3Mitigation
-from qiskit import QiskitError, QuantumCircuit
+from qiskit import QiskitError, QuantumCircuit, transpile
from qiskit.circuit import Parameter
from qiskit.providers import JobV1
from tqdm import tqdm
-from ..batching import BatchJob, execute_in_batches
-from ..common_models import Backend, BackendDescription
-from ..jobs import retrieve_jobs
-from ..limits import get_limits
-from ..schemes.direct_sum import (
- assemble_direct_sum_circuits,
+from qbench.batching import BatchJob, execute_in_batches
+from qbench.common_models import Backend, BackendDescription
+from qbench.jobs import retrieve_jobs
+from qbench.limits import get_limits
+from qbench.schemes.direct_sum import (
+ assemble_discrimination_direct_sum_circuits,
compute_probabilities_from_direct_sum_measurements,
)
-from ..schemes.postselection import (
- assemble_postselection_circuits,
- compute_probabilities_from_postselection_measurements,
+from qbench.schemes.postselection import (
+ assemble_circuits_discrimination_postselection,
+ compute_probabilities_discrimination_postselection,
)
-from ._components import FourierComponents
+
+from ._components import discrimination_probability_upper_bound
+from ._components.components import FourierComponents
from ._models import (
BatchResult,
FourierDiscriminationAsyncResult,
@@ -34,6 +39,8 @@
SingleResult,
)
+sys.path.append(str(Path(sys.argv[0]).resolve().parent.parent))
+
logger = getLogger("qbench")
@@ -116,7 +123,7 @@ def _extract_result_from_job(
:return: object containing results or None if the provided job was not successful.
"""
try:
- result = {"name": name, "histogram": job.result().get_counts()[i]}
+ result = {"name": name, "histogram": job.result()[i].join_data().get_counts()}
except QiskitError:
return None
try:
@@ -149,7 +156,7 @@ def _collect_circuits_and_keys(
"""Construct all circuits needed for the experiment and assign them unique keys."""
def _asemble_postselection(target: int, ancilla: int) -> Dict[str, QuantumCircuit]:
- return assemble_postselection_circuits(
+ return assemble_circuits_discrimination_postselection(
state_preparation=components.state_preparation,
u_dag=components.u_dag,
v0_dag=components.v0_dag,
@@ -159,7 +166,7 @@ def _asemble_postselection(target: int, ancilla: int) -> Dict[str, QuantumCircui
)
def _asemble_direct_sum(target: int, ancilla: int) -> Dict[str, QuantumCircuit]:
- return assemble_direct_sum_circuits(
+ return assemble_discrimination_direct_sum_circuits(
state_preparation=components.state_preparation,
u_dag=components.u_dag,
v0_v1_direct_sum_dag=components.v0_v1_direct_sum_dag,
@@ -174,7 +181,7 @@ def _asemble_direct_sum(target: int, ancilla: int) -> Dict[str, QuantumCircuit]:
logger.info("Assembling experiments...")
circuit_key_pairs = [
(
- circuit.bind_parameters({components.phi: phi}),
+ circuit.assign_parameters({components.phi: phi}, inplace=False),
(target, ancilla, circuit_name, float(phi)),
)
for (target, ancilla, phi) in tqdm(list(experiments.enumerate_experiment_labels()))
@@ -251,9 +258,13 @@ def run_experiment(
components = FourierComponents(phi, gateset=experiments.gateset)
backend = backend_description.create_backend()
+ print(f"Backend type: {type(backend).__name__}, backend name: {_backend_name(backend)}")
logger.info(f"Backend type: {type(backend).__name__}, backend name: {_backend_name(backend)}")
- circuits, keys = _collect_circuits_and_keys(experiments, components)
+ circuits_col, keys = _collect_circuits_and_keys(experiments, components)
+
+ # Transpile circuit according to the universal set of gates supported by the selected backend
+ circuits = [transpile(circuit, backend=backend) for circuit in circuits_col]
logger.info("Submitting jobs...")
batches = execute_in_batches(
@@ -298,16 +309,16 @@ def fetch_statuses(async_results: FourierDiscriminationAsyncResult) -> Dict[str,
:return: dictionary mapping status name to number of its occurrences.
"""
logger.info("Enabling account and creating backend")
- backend = async_results.metadata.backend_description.create_backend()
+ # backend = async_results.metadata.backend_description.create_backend()
logger.info("Reading jobs ids from the input file")
job_ids = [entry.job_id for entry in async_results.data]
logger.info("Retrieving jobs, this might take a while...")
- jobs = retrieve_jobs(backend, job_ids)
+ jobs = retrieve_jobs(job_ids)
logger.info("Done")
- return dict(Counter(job.status().name for job in jobs))
+ return dict(Counter(str(job.status()) for job in jobs))
def resolve_results(
@@ -322,13 +333,15 @@ def resolve_results(
particular, it contains histograms of bitstrings for each circuit run during the experiment.
"""
logger.info("Enabling account and creating backend")
- backend = async_results.metadata.backend_description.create_backend()
+
+ # TODO Will we need to use the backend instance in the future?
+ # backend = async_results.metadata.backend_description.create_backend()
logger.info("Reading jobs ids from the input file")
job_ids = [entry.job_id for entry in cast(List[BatchResult], async_results.data)]
logger.info(f"Fetching total of {len(job_ids)} jobs")
- jobs_mapping = {job.job_id(): job for job in retrieve_jobs(backend, job_ids)}
+ jobs_mapping = {job.job_id(): job for job in retrieve_jobs(job_ids)}
batches = [BatchJob(jobs_mapping[entry.job_id], entry.keys) for entry in async_results.data]
@@ -345,7 +358,7 @@ def resolve_results(
def tabulate_results(sync_results: FourierDiscriminationSyncResult) -> pd.DataFrame:
compute_probabilities = (
- compute_probabilities_from_postselection_measurements
+ compute_probabilities_discrimination_postselection
if sync_results.metadata.experiments.method.lower() == "postselection"
else compute_probabilities_from_direct_sum_measurements
)
@@ -355,6 +368,7 @@ def _make_row(entry):
entry.target,
entry.ancilla,
entry.phi,
+ discrimination_probability_upper_bound(entry.phi),
compute_probabilities(
**{f"{info.name}_counts": info.histogram for info in entry.results_per_circuit}
),
@@ -377,9 +391,9 @@ def _make_row(entry):
# We assume that either all circuits have mitigation info, or none of them has
columns = (
- ["target", "ancilla", "phi", "disc_prob"]
- if len(rows[0]) == 4
- else ["target", "ancilla", "phi", "disc_prob", "mit_disc_prob"]
+ ["target", "ancilla", "phi", "ideal_prob", "disc_prob"]
+ if len(rows[0]) == 5
+ else ["target", "ancilla", "phi", "ideal_prob", "disc_prob", "mit_disc_prob"]
)
result = pd.DataFrame(data=rows, columns=columns)
diff --git a/qbench/fourier/testing.py b/qbench/fourier/testing.py
index 4a5fc42..8d365d1 100644
--- a/qbench/fourier/testing.py
+++ b/qbench/fourier/testing.py
@@ -1,4 +1,5 @@
"""Testing utilities related qbench.fourier packager."""
+
from typing import Sequence, Tuple
import pandas as pd
diff --git a/qbench/fourier_certification/__init__.py b/qbench/fourier_certification/__init__.py
new file mode 100644
index 0000000..d46efdc
--- /dev/null
+++ b/qbench/fourier_certification/__init__.py
@@ -0,0 +1,36 @@
+"""Functionalities relating specifically to Fourier-certification experiments.
+
+
+This package defines all instructions (_components) needed for assembling
+circuits for benchmarking using Fourier-parametrized family.
+
+The Fourier family of measurements is defined as:
+
+$$
+U(\\varphi) = H \\begin{pmatrix} 1&0\\\\0&e^{i\\varphi}\\end{pmatrix}H^\\dagger
+$$
+
+All _components are available as properties of :class:`FourierComponents` class. The
+instances of this class can be constructed in such a way that the instructions they
+provide are compatible with several different quantum devices available on the market.
+
+Additionally, this module provides a function computing the minimized probability of
+type II error.
+
+"""
+
+from ._cli import add_fourier_certification_parser
+from ._components.components import FourierComponents
+from ._models import (
+ FourierCertificationAsyncResult,
+ FourierCertificationSyncResult,
+ FourierExperimentSet,
+)
+
+__all__ = [
+ "add_fourier_certification_parser",
+ "FourierComponents",
+ "FourierCertificationAsyncResult",
+ "FourierCertificationSyncResult",
+ "FourierExperimentSet",
+]
diff --git a/qbench/fourier_certification/_cli.py b/qbench/fourier_certification/_cli.py
new file mode 100644
index 0000000..17e0e42
--- /dev/null
+++ b/qbench/fourier_certification/_cli.py
@@ -0,0 +1,200 @@
+"""Definition of command line parsers and handlers for qbench disc-fourier command.
+
+This module also contains thin wrappers for functions from qbench.fourier.experiment_runner,
+to adapt them for command line usage.
+"""
+
+from argparse import FileType, Namespace
+
+from yaml import safe_dump, safe_load
+
+from ..common_models import BackendDescriptionRoot
+from ._models import (
+ FourierCertificationAsyncResult,
+ FourierCertificationSyncResult,
+ FourierExperimentSet,
+)
+from .experiment_runner import (
+ fetch_statuses,
+ resolve_results,
+ run_experiment,
+ tabulate_results,
+)
+
+
+def _run_benchmark(args: Namespace) -> None:
+ """Function executed when qbench cert-fourier benchmark is invoked."""
+ experiment = FourierExperimentSet(**safe_load(args.experiment_file))
+ backend_description = BackendDescriptionRoot(__root__=safe_load(args.backend_file)).__root__
+ result = run_experiment(experiment, backend_description)
+ safe_dump(result.dict(), args.output, sort_keys=False, default_flow_style=None)
+
+
+def _status(args: Namespace) -> None:
+ """Function executed when qbench disc-fourier status is invoked."""
+ results = FourierCertificationAsyncResult(**safe_load(args.async_results))
+ counts = fetch_statuses(results)
+ print(counts)
+
+
+def _resolve(args: Namespace) -> None:
+ """Function executed when qbench disc-fourier resolve is invoked."""
+ results = FourierCertificationAsyncResult(**safe_load(args.async_results))
+ resolved = resolve_results(results)
+
+ res_dict = resolved.dict()
+
+ # Remove 'name: u' from the output
+ for e in res_dict["data"]:
+ for res in e["results_per_circuit"]:
+ del res["name"]
+
+ safe_dump(res_dict, args.output, sort_keys=False)
+
+
+def _tabulate(args: Namespace) -> None:
+ """Function executed when qbench disc-fourier tabulate is invoked."""
+ t = safe_load(args.sync_results)
+
+ # Add dummy name for the results
+ # TODO Should we go for another datatype specific for certification?
+ for e in t["data"]:
+ for res in e["results_per_circuit"]:
+ res["name"] = "u"
+ # results = FourierCertificationSyncResult(**safe_load(args.sync_results))
+ results = FourierCertificationSyncResult(**t)
+ table = tabulate_results(results)
+ table.to_csv(args.output, index=False)
+
+
+def add_fourier_certification_parser(parent_parser) -> None:
+ """Add cert-fourier parser to the parent parser.
+
+ The added parser will have the following subcommands:
+ - benchmark: run set of Fourier discrimination experiments against given backend
+ - status: check status of asynchronously executed set of experiments
+ - resolve: retrieve results of completed asynchronous job
+
+ The exact syntax for using each command can be, as usually, obtained by running
+ qbench disc-fourier -h
+
+ :param parent_parser: a parser to which disc-fourier command should be added.
+ """
+ parser = parent_parser.add_parser("cert-fourier")
+
+ subcommands = parser.add_subparsers()
+
+ benchmark = subcommands.add_parser(
+ "benchmark",
+ description=(
+ "Run set of benchmarking experiments utilizing measurement discrimination "
+ "with parametrized Fourier family of measurements."
+ ),
+ )
+
+ benchmark.add_argument(
+ "experiment_file",
+ help="path to the file describing the set of experiments",
+ type=FileType("r"),
+ )
+ benchmark.add_argument(
+ "backend_file",
+ help="path to the file describing the backend to be used",
+ type=FileType("r"),
+ )
+
+ benchmark.add_argument(
+ "--output",
+ help="optional path to the output file. If not provided, output will be printed to stdout",
+ type=FileType("w"),
+ default="-",
+ )
+
+ benchmark.set_defaults(func=_run_benchmark)
+
+ plot = subcommands.add_parser("plot")
+
+ plot.add_argument(
+ "result",
+ help=(
+ "result of discrimination experiments which can be obtained by running "
+ "qbench benchmark"
+ ),
+ type=str,
+ )
+
+ plot.add_argument(
+ "--output",
+ help=(
+ "optional path to the output file. If not provided, the plots will be shown "
+ "but not saved. The extension of the output file determines the output format "
+ "and it can be any of the ones supported by the matplotlib"
+ ),
+ type=str,
+ )
+
+ resolve = subcommands.add_parser(
+ "resolve",
+ description="Resolve asynchronous jobs to obtain final experiments data.",
+ )
+
+ resolve.add_argument(
+ "async_results",
+ help=(
+ "path to the file with data of discrimination experiments which can be obtained by "
+ "running `qbench` benchmark using backend with asynchronous flag equal to True."
+ ),
+ type=FileType("r"),
+ )
+
+ resolve.add_argument(
+ "output",
+ help="path to the file where data resolved from asynchronous jobs should be stored.",
+ type=FileType("w"),
+ )
+
+ resolve.set_defaults(func=_resolve)
+
+ status = subcommands.add_parser(
+ "status", description="Query the status of an asynchronous jobs from the results file."
+ )
+
+ status.add_argument(
+ "async_results",
+ help=(
+ "path to the file with data of discrimination experiments which can be obtained by "
+ "running qbench benchmark using backend with asynchronous flag equal to True."
+ ),
+ type=FileType("r"),
+ )
+
+ status.set_defaults(func=_status)
+
+ tabulate = subcommands.add_parser(
+ "tabulate",
+ description=(
+ "Compute and tabulate probabilities from measurements obtained from the experiments."
+ ),
+ )
+
+ tabulate.add_argument(
+ "sync_results",
+ help=(
+ "path to the file with results of discrimination experiments. If experiments were "
+ "conducted using asynchronous backend, they need to be manually resolved."
+ ),
+ type=FileType("r"),
+ )
+
+ tabulate.add_argument(
+ "output",
+ help=(
+ "path to the resulting CSV file. This file will contain columns 'target', 'ancilla' "
+ "'phi' and 'disc_prob' with obvious meanings. If each experiment contained "
+ "mitigation data, the 'mitigated_disc_prob` containing discrimination probabilities "
+ "computed using mitigated bitstrings will also be added."
+ ),
+ type=FileType("w"),
+ )
+
+ tabulate.set_defaults(func=_tabulate)
diff --git a/qbench/fourier_certification/_components/__init__.py b/qbench/fourier_certification/_components/__init__.py
new file mode 100644
index 0000000..ab9e984
--- /dev/null
+++ b/qbench/fourier_certification/_components/__init__.py
@@ -0,0 +1,28 @@
+"""Module defining _components used in Fourier certification experiment."""
+
+from typing import Union
+
+import numpy as np
+
+# from . import _generic, _ibmq, _lucy, _rigetti
+
+
+def certification_probability_upper_bound(
+ phi: Union[float, np.ndarray], delta: float
+) -> Union[float, np.ndarray]:
+ """Compute the minimized probability of type II error in certification scheme
+ between measurements in P_U and P_1.
+
+ :param phi: angle of measurement P_U to be certified from P_1.
+ :param delta: a given statistical significance.
+
+ :return: minimized probability of type II error.
+ """
+
+ if 1 / 2 * np.abs(1 + np.exp(-1j * phi)) > np.sqrt(delta):
+ return (
+ 1 / 2 * np.abs(1 + np.exp(-1j * phi)) * np.sqrt(1 - delta)
+ - np.sqrt(1 - 1 / 4 * np.abs(1 + np.exp(-1j * phi)) ** 2) * np.sqrt(delta)
+ ) ** 2
+ else:
+ return 0
diff --git a/qbench/fourier_certification/_components/_generic.py b/qbench/fourier_certification/_components/_generic.py
new file mode 100644
index 0000000..b2585ee
--- /dev/null
+++ b/qbench/fourier_certification/_components/_generic.py
@@ -0,0 +1,125 @@
+"""Generic implementation of Fourier _components not tailored for any specific device.
+
+Note that using _components from this module on physical device typically requires compilation.
+
+For detailed description of functions in this module refer to the documentation of
+FourierComponents class.
+"""
+
+import numpy as np
+from qiskit.circuit import QuantumCircuit
+
+
+def state_preparation():
+ circuit = QuantumCircuit(2, name="state-prep")
+ circuit.h(0)
+ circuit.cnot(0, 1)
+ return circuit.to_instruction()
+
+
+def u_dag(phi):
+ circuit = QuantumCircuit(1, name="U-dag")
+ circuit.h(0)
+ circuit.p(-phi, 0)
+ circuit.h(0)
+ return circuit.to_instruction()
+
+
+def v0(phi, delta):
+ circuit = QuantumCircuit(1, name="v0")
+ if 1 + np.cos(phi) >= 2 * delta and 0 <= phi <= np.pi:
+ circuit.ry(-2 * np.arcsin(np.sqrt(delta)), 0)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.ry(2 * np.arcsin(np.sqrt(delta)), 0)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.ry(-2 * np.arccos(np.sin(phi / 2)), 0)
+ else:
+ circuit.ry(-2 * np.arccos(np.sin(phi / 2)), 0)
+ circuit.z(0)
+ return circuit.to_instruction()
+
+
+def v0_dag(phi, delta):
+ circuit = QuantumCircuit(1, name="v0-dag")
+ if 1 + np.cos(phi) >= 2 * delta and (0 <= phi <= np.pi):
+ circuit.p(-np.pi / 2, 0)
+ circuit.ry(2 * np.arcsin(np.sqrt(delta)), 0)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.p(-np.pi / 2, 0)
+ circuit.ry(-2 * np.arcsin(np.sqrt(delta)), 0)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.p(-np.pi / 2, 0)
+ circuit.ry(2 * np.arccos(np.sin(phi / 2)), 0)
+ else:
+ circuit.p(-np.pi / 2, 0)
+ circuit.z(0)
+ circuit.ry(2 * np.arccos(np.sin(phi / 2)), 0)
+ return circuit.to_instruction()
+
+
+def v1_dag(phi, delta):
+ circuit = QuantumCircuit(1, name="v1-dag")
+ if 1 + np.cos(phi) >= 2 * delta and 0 <= phi <= np.pi:
+ circuit.x(0)
+ circuit.p(-np.pi / 2, 0)
+ circuit.ry(2 * np.arcsin(np.sqrt(delta)), 0)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.x(0)
+ circuit.p(-np.pi / 2, 0)
+ circuit.ry(-2 * np.arcsin(np.sqrt(delta)), 0)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.x(0)
+ circuit.p(-np.pi / 2, 0)
+ circuit.ry(2 * np.arccos(np.sin(phi / 2)), 0)
+ else:
+ circuit.x(0)
+ circuit.p(-np.pi / 2, 0)
+ circuit.z(0)
+ circuit.ry(2 * np.arccos(np.sin(phi / 2)), 0)
+ return circuit.to_instruction()
+
+
+def v0_v1_direct_sum(phi, delta):
+ circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
+ circuit.p(np.pi, 0)
+ circuit.append(v0_dag(phi, delta), [1])
+ circuit.cnot(0, 1)
+ return circuit.decompose(["v0-dag"]).to_instruction()
+
+
+# def state_preparation() -> Instruction:
+# circuit = QuantumCircuit(2, name="state-prep")
+# circuit.h(0)
+# circuit.cnot(0, 1)
+# return circuit.to_instruction()
+
+
+# def u_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="U-dag")
+# circuit.h(0)
+# circuit.p(-phi, 0)
+# circuit.h(0)
+# return circuit.to_instruction()
+
+
+# def v0_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="v0-dag")
+# circuit.rz(-np.pi / 2, 0)
+# circuit.ry(-(phi + np.pi) / 2, 0)
+# return circuit.to_instruction()
+
+
+# def v1_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="v1-dag")
+# circuit.rz(-np.pi / 2, 0)
+# circuit.ry(-(phi + np.pi) / 2, 0)
+# circuit.rx(-np.pi, 0)
+# return circuit.to_instruction()
+
+
+# def v0_v1_direct_sum(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
+# circuit.p(np.pi, 0)
+# circuit.append(v0_dag(phi), [1])
+# circuit.cnot(0, 1)
+# return circuit.decompose(["v0-dag"]).to_instruction()
diff --git a/qbench/fourier_certification/_components/_ibmq.py b/qbench/fourier_certification/_components/_ibmq.py
new file mode 100644
index 0000000..a03313f
--- /dev/null
+++ b/qbench/fourier_certification/_components/_ibmq.py
@@ -0,0 +1,65 @@
+"""Components for Fourier experiment specifically compiled for IBMQ device.
+
+For detailed description of functions in this module refer to the documentation of
+FourierComponents class.
+"""
+
+import numpy as np
+from qiskit.circuit import Instruction, QuantumCircuit
+
+from ._lucy_and_ibmq_common import u_dag, v0_dag, v1_dag
+
+
+def _decompose(circuit: QuantumCircuit):
+ return circuit.decompose(["v0-dag"])
+
+
+def state_preparation() -> Instruction:
+ circuit = QuantumCircuit(2, name="state-prep")
+ circuit.rz(np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(np.pi / 2, 0)
+ circuit.cx(0, 1)
+ return circuit.to_instruction()
+
+
+# def v0_v1_direct_sum(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
+# circuit.rz(np.pi, 0)
+# circuit.append(v0_dag(phi), [1])
+# circuit.cx(0, 1)
+# return _decompose(circuit).to_instruction()
+
+
+def v0_v1_direct_sum(phi, delta):
+ circuit = QuantumCircuit(2)
+ circuit.rz(np.pi, 0)
+ if 1 + np.cos(phi) >= 2 * delta and (0 <= phi <= np.pi):
+ circuit.rz(-np.pi / 2, 1)
+ circuit.sx(1)
+ circuit.rz(2 * np.arcsin(np.sqrt(delta)) + np.pi, 1)
+ circuit.sx(1)
+ circuit.rz(3 * np.pi, 1)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.rz(-np.pi / 2, 1)
+ circuit.sx(1)
+ circuit.rz(-2 * np.arcsin(np.sqrt(delta)) + np.pi, 1)
+ circuit.sx(1)
+ circuit.rz(3 * np.pi, 1)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.rz(-np.pi / 2, 1)
+ circuit.sx(1)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 1)
+ circuit.sx(1)
+ circuit.rz(3 * np.pi, 1)
+ else:
+ circuit.rz(np.pi / 2, 1)
+ circuit.sx(1)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 1)
+ circuit.sx(1)
+ circuit.rz(3 * np.pi, 1)
+ circuit.cx(0, 1)
+ return circuit.to_instruction()
+
+
+__all__ = ["state_preparation", "u_dag", "v0_dag", "v1_dag", "v0_v1_direct_sum"]
diff --git a/qbench/fourier_certification/_components/_lucy.py b/qbench/fourier_certification/_components/_lucy.py
new file mode 100644
index 0000000..0959adf
--- /dev/null
+++ b/qbench/fourier_certification/_components/_lucy.py
@@ -0,0 +1,46 @@
+"""Components for Fourier experiment specifically compiled for OQC Lucy device.
+
+For detailed description of functions in this module refer to the documentation of
+FourierComponents class.
+"""
+
+import numpy as np
+from qiskit import QuantumCircuit
+from qiskit.circuit import Instruction
+
+from ._lucy_and_ibmq_common import u_dag, v0_dag, v1_dag
+
+
+def state_preparation() -> Instruction:
+ circuit = QuantumCircuit(2, name="state-prep")
+ circuit.sx(0)
+ circuit.rz(np.pi, 0)
+ circuit.x(0)
+ circuit.sx(1)
+ circuit.ecr(0, 1)
+ return circuit.to_instruction()
+
+
+# def v0_v1_direct_sum(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
+# circuit.rz(-np.pi / 2, 1)
+# circuit.sx(1)
+# circuit.rz(-(phi + np.pi) / 2, 1)
+# circuit.rz(3 * np.pi / 2, 0)
+# circuit.x(0)
+# circuit.ecr(0, 1)
+# return circuit.to_instruction()
+
+
+def v0_v1_direct_sum(phi, delta):
+ circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
+ circuit.rz(np.pi, 0)
+ circuit.append(v0_dag(phi, delta), [1])
+ circuit.x(0)
+ circuit.sx(1)
+ circuit.rz(-np.pi / 2, 0)
+ circuit.ecr(0, 1)
+ return circuit.decompose(["v0-dag"]).to_instruction()
+
+
+__all__ = ["state_preparation", "u_dag", "v0_dag", "v1_dag", "v0_v1_direct_sum"]
diff --git a/qbench/fourier_certification/_components/_lucy_and_ibmq_common.py b/qbench/fourier_certification/_components/_lucy_and_ibmq_common.py
new file mode 100644
index 0000000..296bb0f
--- /dev/null
+++ b/qbench/fourier_certification/_components/_lucy_and_ibmq_common.py
@@ -0,0 +1,114 @@
+"""Components for Fourier experiment that are common to both Lucy and IBMQ devices.
+
+For detailed description of functions in this module refer to the documentation of
+FourierComponents class.
+"""
+
+import numpy as np
+from qiskit import QuantumCircuit
+
+
+def u_dag(phi):
+ circuit = QuantumCircuit(1)
+ circuit.sx(0)
+ circuit.rz(np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(-phi, 0)
+ circuit.sx(0)
+ circuit.rz(np.pi / 2, 0)
+ circuit.sx(0)
+ return circuit.to_instruction()
+
+
+def v0_dag(phi: float, delta: float):
+ circuit = QuantumCircuit(1)
+ if 1 + np.cos(phi) >= 2 * delta and (0 <= phi <= np.pi):
+ circuit.rz(-np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(-2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ else:
+ circuit.rz(np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ return circuit.to_instruction()
+
+
+def v1_dag(phi: float, delta: float):
+ circuit = QuantumCircuit(1)
+ if 1 + np.cos(phi) >= 2 * delta and 0 <= phi <= np.pi:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(-np.pi, 0)
+ circuit.rz(2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(-np.pi, 0)
+ circuit.rz(-2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(-np.pi, 0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ else:
+ circuit.rz(np.pi / 2, 0)
+ circuit.sx(0)
+ circuit.rz(-np.pi, 0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.sx(0)
+ circuit.rz(3 * np.pi, 0)
+ return circuit.to_instruction()
+
+
+# def u_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="U-dag")
+# circuit.sx(0)
+# circuit.rz(np.pi / 2, 0)
+# circuit.sx(0)
+# circuit.rz(-phi, 0)
+# circuit.sx(0)
+# circuit.rz(np.pi / 2, 0)
+# circuit.sx(0)
+# return circuit.to_instruction()
+
+
+# def v0_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="v0-dag")
+# circuit.rz(-np.pi / 2, 0)
+# circuit.sx(0)
+# circuit.rz(-(phi + np.pi) / 2, 0)
+# circuit.sx(0)
+# circuit.x(0)
+# return circuit.to_instruction()
+
+
+# def v1_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="v1-dag")
+# circuit.rz(np.pi / 2, 0)
+# circuit.sx(0)
+# circuit.rz(-(np.pi - phi) / 2, 0)
+# circuit.x(0)
+# circuit.sx(0)
+# return circuit.to_instruction()
diff --git a/qbench/fourier_certification/_components/_rigetti.py b/qbench/fourier_certification/_components/_rigetti.py
new file mode 100644
index 0000000..9ab893f
--- /dev/null
+++ b/qbench/fourier_certification/_components/_rigetti.py
@@ -0,0 +1,184 @@
+"""Components for Fourier experiment specifically compiled for OQC Lucy device.
+
+For detailed description of functions in this module refer to the documentation of
+FourierComponents class.
+"""
+
+import numpy as np
+from qiskit import QuantumCircuit
+
+INSTRUCTIONS_TO_DECOMPOSE = ["hadamard-rigetti", "cnot-rigetti", "v0-dag"]
+
+
+def _decompose(circuit: QuantumCircuit) -> QuantumCircuit:
+ return circuit.decompose(INSTRUCTIONS_TO_DECOMPOSE, reps=2)
+
+
+def _rigetti_hadamard():
+ circuit = QuantumCircuit(1, name="hadamard-rigetti")
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(np.pi / 2, 0)
+ circuit.rx(np.pi / 2, 0)
+ return circuit.to_instruction()
+
+
+def _rigetti_cnot():
+ circuit = QuantumCircuit(2, name="cnot-rigetti")
+ circuit.append(_rigetti_hadamard(), [1])
+ circuit.cz(0, 1)
+ circuit.append(_rigetti_hadamard(), [1])
+ return circuit.to_instruction()
+
+
+def state_preparation():
+ circuit = QuantumCircuit(2, name="state-prep")
+ circuit.append(_rigetti_hadamard(), [0])
+ circuit.append(_rigetti_cnot(), [0, 1])
+ return _decompose(circuit).to_instruction()
+
+
+def u_dag(phi):
+ circuit = QuantumCircuit(1, name="U-dag")
+ circuit.rz(np.pi / 2, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(-phi, 0)
+ circuit.rx(-np.pi / 2, 0)
+ circuit.rz(-np.pi / 2, 0)
+ return circuit.to_instruction()
+
+
+def v0_dag(phi, delta):
+ circuit = QuantumCircuit(1, name="v0-dag")
+ if 1 + np.cos(phi) >= 2 * delta and (0 <= phi <= np.pi):
+ circuit.rz(-np.pi / 2, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(-2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ else:
+ circuit.rz(np.pi / 2, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ return circuit.to_instruction()
+
+
+def v1_dag(phi, delta):
+ circuit = QuantumCircuit(1, name="v1-dag")
+ if 1 + np.cos(phi) >= 2 * delta and 0 <= phi <= np.pi:
+ circuit.rz(np.pi / 2, 0)
+ circuit.rx(-np.pi / 2, 0)
+ circuit.rz(2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) >= 2 * delta and np.pi < phi <= 2 * np.pi:
+ circuit.rz(np.pi / 2, 0)
+ circuit.rx(-np.pi / 2, 0)
+ circuit.rz(-2 * np.arcsin(np.sqrt(delta)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ elif 1 + np.cos(phi) < 2 * delta and 0 <= phi <= np.pi:
+ circuit.rz(np.pi / 2, 0)
+ circuit.rx(-np.pi / 2, 0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ else:
+ circuit.rz(-np.pi / 2, 0)
+ circuit.rx(-np.pi / 2, 0)
+ circuit.rz(2 * np.arccos(np.sin(phi / 2)) + np.pi, 0)
+ circuit.rx(np.pi / 2, 0)
+ circuit.rz(3 * np.pi, 0)
+ return circuit.to_instruction()
+
+
+def v0_v1_direct_sum(phi, delta):
+ circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
+ circuit.rz(np.pi, 0)
+ circuit.append(v0_dag(phi, delta), [1])
+ circuit.append(_rigetti_cnot(), [0, 1])
+ return _decompose(circuit).to_instruction()
+
+
+# def _rigetti_hadamard() -> Instruction:
+# """Decomposition of Hadamard gate using only Rigetti native gates.
+
+# The decomposition uses the identity: H = RX(pi/2) RZ(pi/2) RX(pi/2)
+# """
+# circuit = QuantumCircuit(1, name="hadamard-rigetti")
+# circuit.rx(np.pi / 2, 0)
+# circuit.rz(np.pi / 2, 0)
+# circuit.rx(np.pi / 2, 0)
+# return circuit.to_instruction()
+
+
+# def _rigetti_cnot() -> Instruction:
+# """Decomposition of CNOT gate using only Rigetti native gates.
+
+# The decomposition uses identity: CNOT(i, j) = H(j) CZ(i, j) H(j), and the hadamard gates
+# are decomposed using _rigetti_hadamard function.
+# """
+# circuit = QuantumCircuit(2, name="cnot-rigetti")
+# circuit.append(_rigetti_hadamard(), [1])
+# circuit.cz(0, 1)
+# circuit.append(_rigetti_hadamard(), [1])
+# return circuit.to_instruction()
+
+
+# For description of functions below refer to the __init__ file in qbench.fourier
+
+
+# def state_preparation() -> Instruction:
+# circuit = QuantumCircuit(2, name="state-prep")
+# circuit.append(_rigetti_hadamard(), [0])
+# circuit.append(_rigetti_cnot(), [0, 1])
+# return _decompose(circuit).to_instruction()
+
+
+# def u_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="U-dag")
+# circuit.rz(np.pi / 2, 0)
+# circuit.rx(np.pi / 2, 0)
+# circuit.rz(-phi, 0)
+# circuit.rx(-np.pi / 2, 0)
+# circuit.rz(-np.pi / 2, 0)
+# return circuit.to_instruction()
+
+
+# def v0_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="v0-dag")
+# circuit.rz(-np.pi / 2, 0)
+# circuit.rx(np.pi / 2, 0)
+# circuit.rz(-(phi + np.pi) / 2, 0)
+# circuit.rx(-np.pi / 2, 0)
+# return circuit.to_instruction()
+
+
+# def v1_dag(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(1, name="v1-dag")
+# circuit.rz(np.pi / 2, 0)
+# circuit.rx(np.pi / 2, 0)
+# circuit.rz(-(np.pi - phi) / 2, 0)
+# circuit.rx(-np.pi / 2, 0)
+# return circuit.to_instruction()
+
+
+# def v0_v1_direct_sum(phi: AnyParameter) -> Instruction:
+# circuit = QuantumCircuit(2, name="v0 ⊕ v1-dag")
+# circuit.rz(np.pi, 0)
+# circuit.append(v0_dag(phi), [1])
+# circuit.append(_rigetti_cnot(), [0, 1])
+# return _decompose(circuit).to_instruction()
diff --git a/qbench/fourier_certification/_components/components.py b/qbench/fourier_certification/_components/components.py
new file mode 100644
index 0000000..569d4c7
--- /dev/null
+++ b/qbench/fourier_certification/_components/components.py
@@ -0,0 +1,135 @@
+"""Module defining _components used in Fourier certification experiment."""
+
+from typing import Optional, Union
+
+from qiskit.circuit import Instruction, Parameter
+
+from . import _generic, _ibmq, _lucy, _rigetti
+
+
+class FourierComponents:
+ """Class defining _components for Fourier-certification experiment.
+
+ :param phi: angle defining measurement to certificate. May be a number or an instance of
+ a Qiskit Parameter. See
+ :qiskit_tutorial:`here `_
+ if you are new to parametrized circuits in Qiskit.
+
+ :param gateset: name of the one of the predefined basis gate sets to use. It controls which
+ gates will be used to construct the circuit _components. Available choices are:
+
+ - :code:`"lucy"`: gateset comprising gates native to
+ `OQC Lucy `_ computer.
+ - :code:`"rigetti"`: gateset comprising gates native to
+ `Rigetti `_ computers.
+ - :code:`"ibmq"`: gateset comprising gates native to
+ `IBMQ `_ computers.
+
+ If no gateset is provided, high-level gates will be used without restriction on basis gates.
+ """
+
+ def __init__(
+ self,
+ phi: Union[float, Parameter],
+ delta: Union[float, Parameter],
+ gateset: Optional[str] = None,
+ ):
+ """Initialize new instance of FourierComponents."""
+ self.phi = phi
+ self.delta = delta
+ self._module = _GATESET_MAPPING[gateset]
+
+ @property
+ def state_preparation(self) -> Instruction:
+ """Instruction performing transformation $|00\\rangle$ -> Bell state
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌───┐
+ q_0: ┤ H ├──■──
+ └───┘┌─┴─┐
+ q_1: ─────┤ X ├
+ └───┘
+
+ """
+ return self._module.state_preparation()
+
+ @property
+ def u_dag(self) -> Instruction:
+ r"""Unitary $U^\dagger$ defining Fourier measurement.
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌───┐┌───────────┐┌───┐
+ q: ┤ H ├┤ Phase(-φ) ├┤ H ├
+ └───┘└───────────┘└───┘
+
+ .. note::
+
+ This instruction is needed because on actual devices we can only measure in Z-basis.
+ The $U^\dagger$ unitary changes basis so that subsequent measurement in Z-basis can
+ be considered as performing desired von Neumann measurement to be certified from
+ the Z-basis one.
+ """
+
+ return self._module.u_dag(self.phi)
+
+ @property
+ def v0_dag(self) -> Instruction:
+ """Instruction corresponding to the positive part of Holevo-Helstrom measurement.
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌──────────┐┌────────────────┐
+ q: ┤ Rz(-π/2) ├┤ Ry(-φ/2 - π/2) ├
+ └──────────┘└────────────────┘
+
+ """
+ return self._module.v0_dag(self.phi, self.delta)
+
+ @property
+ def v1_dag(self) -> Instruction:
+ """Instruction corresponding to the negative part of Holevo-Helstrom measurement.
+
+ The corresponding circuit is:
+
+ .. code::
+
+ ┌──────────┐┌────────────────┐┌────────┐
+ q: ┤ Rz(-π/2) ├┤ Ry(-φ/2 - π/2) ├┤ Rx(-π) ├
+ └──────────┘└────────────────┘└────────┘
+ """
+ return self._module.v1_dag(self.phi, self.delta)
+
+ @property
+ def v0_v1_direct_sum_dag(self) -> Instruction:
+ r"""Direct sum $V_0^\dagger\oplus V_1^\dagger$ of both parts of Holevo-Helstrom measurement.
+
+ .. note::
+ In usual basis ordering, the unitaries returned by this property would be
+ block-diagonal, with blocks corresponding to positive and negative parts
+ of Holevo-Helstrom measurement.
+
+ However, Qiskit enumerates basis vectors in reverse, so the produced unitaries
+ are not block-diagonal, unless the qubits are swapped.
+ See accompanying tests to see how it's done.
+
+ The following article contains more details on basis vectors ordering used
+ (among others) by Qiskit:
+ https://arxiv.org/abs/1711.02086
+ """
+ return self._module.v0_v1_direct_sum(self.phi, self.delta)
+
+
+_GATESET_MAPPING = {
+ "lucy": _lucy,
+ "rigetti": _rigetti,
+ "ibmq": _ibmq,
+ None: _generic,
+}
diff --git a/qbench/fourier_certification/_models.py b/qbench/fourier_certification/_models.py
new file mode 100644
index 0000000..f581388
--- /dev/null
+++ b/qbench/fourier_certification/_models.py
@@ -0,0 +1,105 @@
+from typing import (
+ Any,
+ Iterable,
+ List,
+ Literal,
+ Optional,
+ Sequence,
+ Tuple,
+ Type,
+ TypeVar,
+)
+
+import numpy as np
+from pydantic.v1 import validator
+
+from qbench.common_models import (
+ AnglesRange,
+ BackendDescription,
+ BaseModel,
+ Qubit,
+ QubitsPair,
+ StrictPositiveInt,
+ SynchronousHistogram,
+)
+
+
+class FourierExperimentSet(BaseModel):
+ type: Literal["certification-fourier"]
+ qubits: List[QubitsPair]
+ angles: AnglesRange
+ delta: float
+ gateset: Optional[str]
+ method: Literal["direct_sum", "postselection"]
+ num_shots: StrictPositiveInt
+
+ @validator("qubits")
+ def check_if_all_pairs_of_qubits_are_different(cls, qubits):
+ list_of_qubits = [(qubits.target, qubits.ancilla) for qubits in qubits]
+ if len(set(list_of_qubits)) != len(list_of_qubits):
+ raise ValueError("All pairs of qubits should be distinct.")
+ return qubits
+
+ def enumerate_experiment_labels(self) -> Iterable[Tuple[int, int, float]]:
+ return (
+ (pair.target, pair.ancilla, phi)
+ for pair in self.qubits
+ for phi in np.linspace(self.angles.start, self.angles.stop, self.angles.num_steps)
+ )
+
+
+class FourierCertificationMetadata(BaseModel):
+ experiments: FourierExperimentSet
+ backend_description: BackendDescription
+
+
+T = TypeVar("T", bound="QubitMitigationInfo")
+
+
+class QubitMitigationInfo(BaseModel):
+ prob_meas0_prep1: float
+ prob_meas1_prep0: float
+
+ @classmethod
+ def from_job_properties(cls: Type[T], properties, qubit) -> T:
+ return cls.parse_obj(
+ {
+ "prob_meas0_prep1": properties.qubit_property(qubit)["prob_meas0_prep1"][0],
+ "prob_meas1_prep0": properties.qubit_property(qubit)["prob_meas1_prep0"][0],
+ }
+ )
+
+
+class MitigationInfo(BaseModel):
+ target: QubitMitigationInfo
+ ancilla: QubitMitigationInfo
+
+
+class ResultForCircuit(BaseModel):
+ name: str
+ histogram: SynchronousHistogram
+ mitigation_info: Optional[MitigationInfo]
+ mitigated_histogram: Optional[Any]
+
+
+class SingleResult(BaseModel):
+ target: Qubit
+ ancilla: Qubit
+ phi: float
+ delta: float
+ results_per_circuit: List[ResultForCircuit]
+
+
+class BatchResult(BaseModel):
+ job_id: str
+ keys: Sequence[Tuple[int, int, str, float, float]]
+
+
+class FourierCertificationSyncResult(BaseModel):
+ metadata: FourierCertificationMetadata
+ data: List[SingleResult]
+
+
+class FourierCertificationAsyncResult(BaseModel):
+ metadata: FourierCertificationMetadata
+ data: List[BatchResult]
diff --git a/qbench/fourier_certification/experiment_runner.py b/qbench/fourier_certification/experiment_runner.py
new file mode 100644
index 0000000..08c7f9b
--- /dev/null
+++ b/qbench/fourier_certification/experiment_runner.py
@@ -0,0 +1,463 @@
+"""Functions for running Fourier certification experiments and interacting with the results."""
+
+from collections import Counter, defaultdict
+from logging import getLogger
+from typing import Dict, Iterable, List, Optional, Tuple, Union, cast
+
+import numpy as np
+import pandas as pd
+from mthree import M3Mitigation
+from qiskit import QiskitError, QuantumCircuit, transpile
+from qiskit.providers import JobV1
+from qiskit_ibm_runtime import RuntimeJobV2
+from tqdm import tqdm
+
+from qbench.batching import BatchJob, execute_in_batches
+from qbench.common_models import Backend, BackendDescription
+from qbench.jobs import retrieve_jobs
+from qbench.limits import get_limits
+from qbench.schemes.direct_sum import (
+ assemble_certification_direct_sum_circuits,
+ compute_probabilities_from_certification_direct_sum_measurements,
+)
+from qbench.schemes.postselection import (
+ assemble_circuits_certification_postselection,
+ compute_probabilities_certification_postselection,
+)
+
+from ._components import certification_probability_upper_bound
+from ._components.components import FourierComponents
+from ._models import (
+ BatchResult,
+ FourierCertificationAsyncResult,
+ FourierCertificationSyncResult,
+ FourierExperimentSet,
+ QubitMitigationInfo,
+ ResultForCircuit,
+ SingleResult,
+)
+
+logger = getLogger("qbench")
+
+
+def _backend_name(backend) -> str:
+ """Return backend name.
+
+ This is needed because backend.name is sometimes a function (IBMQ) and sometimes a string
+ (Braket).
+ """
+ try:
+ return backend.name()
+ except TypeError:
+ return backend.name
+
+
+def _log_fourier_experiments(experiments: FourierExperimentSet) -> None:
+ """Log basic information about the set of experiments."""
+ logger.info("Running set of Fourier-certification experiments")
+ logger.info("Number of qubit-pairs: %d", len(experiments.qubits))
+ logger.info("Number of phi values: %d", experiments.angles.num_steps)
+ logger.info("Statistical significance: %s", "{0:g}".format(experiments.delta))
+ logger.info("Number of shots per circuit: %d", experiments.num_shots)
+ logger.info("Probability estimation method: %s", experiments.method)
+ logger.info("Gateset: %s", experiments.gateset)
+
+
+def _matrix_from_mitigation_info(info: QubitMitigationInfo) -> np.ndarray:
+ """Construct Mthree-compatible matrix from mitigation info."""
+ return np.array(
+ [
+ [1 - info.prob_meas1_prep0, info.prob_meas0_prep1],
+ [info.prob_meas1_prep0, 1 - info.prob_meas0_prep1],
+ ]
+ )
+
+
+def _mitigate(
+ counts: Dict[str, int],
+ target: int,
+ ancilla: int,
+ backend: Backend,
+ mitigation_info: Dict[str, QubitMitigationInfo],
+) -> Dict[str, float]:
+ """Apply error mitigation to obtained counts.
+
+ :param counts: histogram of measured bitstrings.
+ :param target: index of the target qubit.
+ :param ancilla: index of the ancilla qubit.
+ :param backend: backend used for executing job.
+ :param mitigation_info: dictionary with keys 'ancilla' and 'target', mapping them to objects
+ holding mitigation info (prob_meas1_prep0 and prob_meas0_prep1).
+ :return: dictionary with corrected quasi-distribution of bitstrings. Note that this contains
+ probabilities and not counts, but nevertheless can be used for computing probabilities.
+ """
+ mitigator = M3Mitigation(backend)
+
+ matrices: List[Optional[np.ndarray]] = [None for _ in range(backend.configuration().num_qubits)]
+ matrices[target] = _matrix_from_mitigation_info(mitigation_info["target"])
+ matrices[ancilla] = _matrix_from_mitigation_info(mitigation_info["ancilla"])
+
+ mitigator.cals_from_matrices(matrices)
+ result = mitigator.apply_correction(counts, [target, ancilla])
+
+ # Probability distribution
+ result = result.nearest_probability_distribution()
+ # Wrap value in native floats, otherwise we get serialization problems
+ return {key: float(value) for key, value in result.items()}
+
+
+def _extract_result_from_job(
+ job: JobV1 | RuntimeJobV2, target: int, ancilla: int, i: int, name: str
+) -> Optional[ResultForCircuit]:
+ """Extract meaningful information from job and wrap them in serializable object.
+
+ .. note::
+ Single job can comprise running multiple circuits (experiments in Qiskit terminology)
+ and hence we need parameter i to identify which one we are processing right now.
+
+ :param job: Qiskit job used for computing results.
+ :param target: index of the target qubit.
+ :param ancilla: index of the ancilla qubit.
+ :param i: index of the experiment in job.
+ :param name: name of the circuit to be used in the resulting object.
+ :return: object containing results or None if the provided job was not successful.
+ """
+ try:
+ result = {"name": name, "histogram": job.result()[i].join_data().get_counts()}
+ except QiskitError:
+ return None
+
+ try:
+ # We ignore some typing errors, since we are essentially accessing attributes that might
+ # not exist according to their base classes.
+ props = job.properties() # type: ignore
+ result["mitigation_info"] = {
+ "target": QubitMitigationInfo.from_job_properties(props, target),
+ "ancilla": QubitMitigationInfo.from_job_properties(props, ancilla),
+ }
+ result["mitigated_histogram"] = _mitigate(
+ result["histogram"],
+ target,
+ ancilla,
+ job.backend(), # type: ignore
+ result["mitigation_info"],
+ )
+ except AttributeError:
+ pass
+ return ResultForCircuit.parse_obj(result)
+
+
+CircuitKey = Tuple[int, int, str, float, float]
+
+
+def _collect_circuits_and_keys(
+ experiments: FourierExperimentSet,
+ components: FourierComponents,
+) -> Tuple[Tuple[QuantumCircuit, ...], Tuple[CircuitKey, ...]]:
+ """Construct all circuits needed for the experiment and assign them unique keys."""
+
+ def _asemble_postselection(target: int, ancilla: int) -> Dict[str, QuantumCircuit]:
+ return assemble_circuits_certification_postselection(
+ state_preparation=components.state_preparation,
+ u_dag=components.u_dag,
+ v0_dag=components.v0_dag,
+ v1_dag=components.v1_dag,
+ target=target,
+ ancilla=ancilla,
+ )
+
+ def _asemble_direct_sum(target: int, ancilla: int) -> Dict[str, QuantumCircuit]:
+ return assemble_certification_direct_sum_circuits(
+ state_preparation=components.state_preparation,
+ u_dag=components.u_dag,
+ v0_v1_direct_sum_dag=components.v0_v1_direct_sum_dag,
+ target=target,
+ ancilla=ancilla,
+ )
+
+ _asemble = (
+ _asemble_postselection if experiments.method == "postselection" else _asemble_direct_sum
+ )
+
+ logger.info("Assembling experiments...")
+ circuit_key_pairs = [
+ (
+ circuit.assign_parameters({components.phi: float(phi)}),
+ (target, ancilla, circuit_name, float(phi)),
+ )
+ for (target, ancilla, phi) in tqdm(list(experiments.enumerate_experiment_labels()))
+ for circuit_name, circuit in _asemble(target, ancilla).items()
+ ]
+
+ circuits, keys = zip(*circuit_key_pairs)
+ return circuits, keys
+
+
+def _iter_batches(batches: Iterable[BatchJob]) -> Iterable[Tuple[int, CircuitKey, JobV1]]:
+ """Iterate batches in a flat manner.
+
+ The returned iterable yields triples of the form (i, key, job) where:
+ - key is the key in one of the batches
+ - i is its index in the corresponding batch
+ - job is a job comprising this batch
+ """
+ return (
+ (i, key, batch.job)
+ for batch in tqdm(batches, desc="Batch")
+ for i, key in enumerate(tqdm(batch.keys, desc="Circuit", leave=False))
+ )
+
+
+# def _iter_batches(batches: Iterable[BatchJob]) -> Generator[
+# tuple[int, tuple[int, Any], JobV1 | RuntimeJob | RuntimeJobV2], None, None]:
+# """Iterate batches in a flat manner.
+#
+# The returned iterable yields triples of the form (i, key, job) where:
+# - key is the key in one of the batches
+# - i is its index in the corresponding batch
+# - job is a job comprising this batch
+# """
+# for batch in tqdm(batches, desc="Batch"):
+# for i, key in enumerate(tqdm(batch.keys, desc="Circuit", leave=False)):
+# yield i, key, batch.job
+
+
+def _resolve_batches(batches: Iterable[BatchJob]) -> List[SingleResult]:
+ """Resolve all results from batch of jobs and wrap them in a serializable object.
+
+ The number of returned objects can be less than what can be deduced from batches size iff
+ some jobs have failed.
+
+ :param batches: batches to be processed.
+ :return: dictionary mapping triples (target, ancilla, phi, delta to a list of results for each
+ circuit with that parameters.
+ """
+ resolved = defaultdict(list)
+
+ num_failed = 0
+ for i, key, job in _iter_batches(batches):
+ target, ancilla, name, phi, delta = key
+ result = _extract_result_from_job(job, target, ancilla, i, name)
+ if result is None:
+ num_failed += 1
+ else:
+ resolved[target, ancilla, phi, delta].append(result)
+
+ if num_failed:
+ logger.warning(
+ "Some jobs have failed. Examine the output file to determine which data are missing."
+ )
+
+ return [
+ SingleResult.parse_obj(
+ {
+ "target": target,
+ "ancilla": ancilla,
+ "phi": phi,
+ "delta": delta,
+ "results_per_circuit": results,
+ }
+ )
+ for (target, ancilla, phi, delta), results in resolved.items()
+ ]
+
+
+def run_experiment(
+ experiments: FourierExperimentSet, backend_description: BackendDescription
+) -> Union[FourierCertificationSyncResult, FourierCertificationAsyncResult]:
+ """Run sef ot experiments on given backend.
+
+ :param experiments: set of experiments to be run.
+ :param backend_description: object describing backend and possibly options that should
+ be used when executing circuits.
+ :return: Object describing experiments data. For synchronous execution, this object
+ contains histogram of measurements for all the circuits. For asynchronous execution,
+ this object contains mapping between job ids and the sequence of circuits run in a given job.
+ """
+ _log_fourier_experiments(experiments)
+
+ backend = backend_description.create_backend()
+ logger.info(f"Backend type: {type(backend).__name__}, backend name: {_backend_name(backend)}")
+
+ if experiments.method == "postselection":
+ circuit_key_pairs = []
+ for target, ancilla, phi in tqdm(list(experiments.enumerate_experiment_labels())):
+ components = FourierComponents(phi, experiments.delta, gateset=experiments.gateset)
+ cos = assemble_circuits_certification_postselection(
+ state_preparation=components.state_preparation,
+ u_dag=components.u_dag,
+ v0_dag=components.v0_dag,
+ v1_dag=components.v1_dag,
+ target=target,
+ ancilla=ancilla,
+ )
+ for circuit_name, circuit in cos.items():
+ circuit_key_pairs += [
+ (
+ transpile(circuit, backend=backend),
+ (target, ancilla, circuit_name, float(phi), experiments.delta),
+ )
+ ]
+ else:
+ circuit_key_pairs = []
+ for target, ancilla, phi in tqdm(list(experiments.enumerate_experiment_labels())):
+ components = FourierComponents(phi, experiments.delta, gateset=experiments.gateset)
+ cos = assemble_certification_direct_sum_circuits(
+ state_preparation=components.state_preparation,
+ u_dag=components.u_dag,
+ v0_v1_direct_sum_dag=components.v0_v1_direct_sum_dag,
+ target=target,
+ ancilla=ancilla,
+ )
+ for circuit_name, circuit in cos.items():
+ circuit_key_pairs += [
+ (
+ transpile(circuit, backend=backend),
+ (target, ancilla, circuit_name, float(phi), experiments.delta),
+ )
+ ]
+
+ logger.info("Assembling experiments...")
+
+ circuits, keys = zip(*circuit_key_pairs)
+
+ logger.info("Submitting jobs...")
+ batches = execute_in_batches(
+ backend,
+ circuits,
+ keys,
+ experiments.num_shots,
+ get_limits(backend).max_circuits,
+ show_progress=True,
+ )
+
+ metadata = {
+ "experiments": experiments,
+ "backend_description": backend_description,
+ }
+
+ if backend_description.asynchronous:
+ async_result = FourierCertificationAsyncResult.parse_obj(
+ {
+ "metadata": metadata,
+ "data": [
+ BatchResult(job_id=batch.job.job_id(), keys=batch.keys) for batch in batches
+ ],
+ }
+ )
+ logger.info("Done")
+ return async_result
+ else:
+ logger.info("Executing jobs...")
+ sync_result = FourierCertificationSyncResult.parse_obj(
+ {"metadata": metadata, "data": _resolve_batches(batches)}
+ )
+ logger.info("Done")
+ return sync_result
+
+
+def fetch_statuses(async_results: FourierCertificationAsyncResult) -> Dict[str, int]:
+ """Fetch statuses of all jobs submitted for asynchronous execution of experiments.
+
+ :param async_results: object describing data of asynchronous execution.
+ If the result object already contains histograms, an error will be raised.
+ :return: dictionary mapping status name to number of its occurrences.
+ """
+ # logger.info("Enabling account and creating backend")
+ # backend = async_results.metadata.backend_description.create_backend()
+
+ logger.info("Reading jobs ids from the input file")
+ job_ids = [entry.job_id for entry in async_results.data]
+
+ logger.info("Retrieving jobs, this might take a while...")
+ jobs = retrieve_jobs(job_ids)
+ logger.info("Done")
+
+ return dict(Counter(str(job.status()) for job in jobs))
+
+
+def resolve_results(
+ async_results: FourierCertificationAsyncResult,
+) -> FourierCertificationSyncResult:
+ """Resolve data of asynchronous execution.
+
+ :param async_results: object describing data of asynchronous execution.
+ If the result object already contains histograms, an error will be raised.
+ :return: Object containing resolved data. Format of this object is the same as the one
+ returned directly from a synchronous execution of Fourier certification experiments. In
+ particular, it contains histograms of bitstrings for each circuit run during the experiment.
+ """
+ # logger.info("Enabling account and creating backend")
+ # backend = async_results.metadata.backend_description.create_backend()
+
+ logger.info("Reading jobs ids from the input file")
+ job_ids = [entry.job_id for entry in cast(List[BatchResult], async_results.data)]
+
+ logger.info(f"Fetching total of {len(job_ids)} jobs")
+ jobs_mapping = {job.job_id(): job for job in retrieve_jobs(job_ids)}
+
+ batches = [BatchJob(jobs_mapping[entry.job_id], entry.keys) for entry in async_results.data]
+
+ logger.info("Resolving results. This might take a while if mitigation info is included...")
+ resolved = _resolve_batches(batches)
+
+ result = FourierCertificationSyncResult.parse_obj(
+ {"metadata": async_results.metadata, "data": resolved}
+ )
+
+ logger.info("Done")
+ return result
+
+
+def tabulate_results(sync_results: FourierCertificationSyncResult) -> pd.DataFrame:
+ compute_probabilities = (
+ compute_probabilities_certification_postselection
+ if sync_results.metadata.experiments.method.lower() == "postselection"
+ else compute_probabilities_from_certification_direct_sum_measurements
+ )
+
+ def _make_row(entry):
+ data = [
+ entry.target,
+ entry.ancilla,
+ entry.phi,
+ entry.delta,
+ certification_probability_upper_bound(entry.phi, entry.delta),
+ compute_probabilities(
+ **{f"{info.name}_counts": info.histogram for info in entry.results_per_circuit}
+ ),
+ ]
+ try:
+ data.append(
+ compute_probabilities(
+ **{
+ f"{info.name}_counts": info.mitigated_histogram
+ for info in entry.results_per_circuit
+ }
+ ),
+ )
+ except AttributeError:
+ pass # totally acceptable, not all results have mitigation info
+ return data
+
+ logger.info("Tabulating results...")
+ rows = [_make_row(entry) for entry in tqdm(sync_results.data)]
+
+ # We assume that either all circuits have mitigation info, or none of them has
+ columns = (
+ ["target", "ancilla", "phi", "delta", "ideal_prob", "cert_prob"]
+ if len(rows[0]) == 6
+ else ["target", "ancilla", "phi", "delta", "ideal_prob", "cert_prob", "mit_cert_prob"]
+ )
+
+ result = pd.DataFrame(data=rows, columns=columns)
+
+ # fig, ax = plt.subplots()
+ # ax.plot(phis, theoretical_probs, color="red", label="theoretical_predictions")
+ # ax.plot(phis, actual_probs, color="blue", label="actual results")
+ # ax.legend()
+
+ # plt.savefig(PATH + f'direct_sum_{backend}_{NUM_SHOTS_PER_MEASUREMENT}.png')
+
+ logger.info("Done")
+ return result
diff --git a/qbench/fourier_certification/testing.py b/qbench/fourier_certification/testing.py
new file mode 100644
index 0000000..612b5f6
--- /dev/null
+++ b/qbench/fourier_certification/testing.py
@@ -0,0 +1,51 @@
+"""Testing utilities related qbench.fourier packager."""
+
+from typing import Sequence, Tuple
+
+import pandas as pd
+
+from ._models import FourierCertificationSyncResult, FourierExperimentSet
+
+LabelSequence = Sequence[Tuple[int, int, float]]
+
+
+def _experiment_labels_equal(actual: LabelSequence, expected: LabelSequence) -> bool:
+ """Assert two sequences of experiment labels are equal.
+
+ The label comprises index of target, index of ancilla and Fourier angle phi.
+ While we require exact equality between indices of qubits, equality of angles is
+ checked only up to 7 decimal places, which is enough for the purpose of our unit tests.
+ The exact equality of angles cannot be expected because of the serialization of floating
+ point numbers.
+ """
+ return len(actual) == len(expected) and all(
+ label1[0:2] == label2[0:2] and abs(label1[2] - label2[2]) < 1e-7
+ for label1, label2 in zip(sorted(actual), sorted(expected))
+ )
+
+
+def assert_sync_results_contain_data_for_all_experiments(
+ experiments: FourierExperimentSet, results: FourierCertificationSyncResult
+) -> None:
+ """Verify synchronous result of computation has measurements for each qubits pair and phi.
+
+ :param experiments: set of Fourier discrimination experiments.
+ Note that this function does not take into account the method used in experiments,
+ and only checks (target, ancilla) pairs ond values of phi parameter.
+ :param results: results of execution of synchronous experiments.
+ :raise: AssertionError if measurements for some combination of (target, ancilla, phi) are
+ missing.
+ """
+ expected_labels = list(experiments.enumerate_experiment_labels())
+ actual_labels = [(entry.target, entry.ancilla, entry.phi) for entry in results.data]
+
+ assert _experiment_labels_equal(actual_labels, expected_labels)
+
+
+def assert_tabulated_results_contain_data_for_all_experiments(
+ experiments: FourierExperimentSet, dataframe: pd.DataFrame
+) -> None:
+ expected_labels = list(experiments.enumerate_experiment_labels())
+ actual_labels = [(row[0], row[1], row[2]) for row in dataframe.itertuples(index=False)]
+
+ assert _experiment_labels_equal(actual_labels, expected_labels)
diff --git a/qbench/jobs.py b/qbench/jobs.py
index 96e1591..198e59a 100644
--- a/qbench/jobs.py
+++ b/qbench/jobs.py
@@ -1,24 +1,38 @@
"""Implementation of utilities for interacting with jobs."""
+
+import os
from functools import singledispatch
from typing import Sequence
from qiskit.providers import JobV1
+from qiskit_ibm_runtime import QiskitRuntimeService
+
+# TODO IBMQ_TOKEN is deprecated by now
+IBMQ_TOKEN = os.getenv("IBMQ_TOKEN")
+QISKIT_IBM_TOKEN = os.getenv("QISKIT_IBM_TOKEN")
+IQP_API_TOKEN = os.getenv("IQP_API_TOKEN")
-# from qiskit.providers.ibmq import IBMQBackend, IBMQJob
+# TODO Maybe stop supporting IBMQ_TOKEN variable?
+if sum(e in os.environ for e in ("QISKIT_IBM_TOKEN", "IBMQ_TOKEN", "IQP_API_TOKEN")) == 0:
+ raise ValueError(
+ "Missing IBM API token! You need to specify it via environment variable QISKIT_IBM_TOKEN "
+ "or IBMQ_TOKEN (deprecated)!"
+ )
+elif "IBMQ_TOKEN" in os.environ and "QISKIT_IBM_TOKEN" not in os.environ:
+ QISKIT_IBM_TOKEN = IBMQ_TOKEN
+elif "IQP_API_TOKEN" in os.environ and "QISKIT_IBM_TOKEN" not in os.environ:
+ QISKIT_IBM_TOKEN = IQP_API_TOKEN
+
+service = QiskitRuntimeService("ibm_quantum", QISKIT_IBM_TOKEN)
@singledispatch
-def retrieve_jobs(backend, job_ids: Sequence[str]) -> Sequence[JobV1]:
- """Retrieve jobs with given ids from a backend.
+def retrieve_jobs(job_ids: Sequence[str]) -> Sequence[JobV1]:
+ """Retrieve jobs with given ids from a service.
- :param backend: backend which was used to run the jobs.
:param job_ids: identifiers of jobs to obtain.
:return: sequence of jobs. Note that it is not guaranteed that the order of this sequence
will match order of ids in job_ids parameter.
"""
- return [backend.retrieve_job(job_id) for job_id in job_ids]
-
-# @retrieve_jobs.register
-# def _retrieve_jobs_from_ibmq(backend: IBMQBackend, job_ids: Sequence[str]) -> Sequence[IBMQJob]:
-# return backend.jobs(db_filter={"id": {"inq": job_ids}}, limit=len(job_ids))
+ return [service.job(job_id) for job_id in job_ids]
diff --git a/qbench/limits.py b/qbench/limits.py
index 0fc74e4..aa073ed 100644
--- a/qbench/limits.py
+++ b/qbench/limits.py
@@ -1,10 +1,12 @@
"""Implementation of various utilities for obtaining backend limits."""
+
from functools import singledispatch
from typing import NamedTuple, Optional
-from qiskit.providers.aer import AerSimulator
-from qiskit.providers.ibmq import IBMQBackend
+from qiskit_aer import AerSimulator
from qiskit_braket_provider import AWSBraketBackend
+from qiskit_ibm_provider.ibm_backend import IBMBackend as provider_IBMBackend
+from qiskit_ibm_runtime import IBMBackend as runtime_IBMBackend
from .testing import MockSimulator
@@ -43,7 +45,15 @@ def _get_limits_for_aws_backend(backend: AWSBraketBackend):
@get_limits.register
-def _get_limits_for_ibmq_backend(backend: IBMQBackend):
+def _get_limits_for_runtime_ibm_backend(backend: runtime_IBMBackend):
+ return Limits(
+ max_shots=backend.configuration().max_shots,
+ max_circuits=backend.configuration().max_experiments,
+ )
+
+
+@get_limits.register
+def _get_limits_for_provider_ibm_backend(backend: provider_IBMBackend):
return Limits(
max_shots=backend.configuration().max_shots,
max_circuits=backend.configuration().max_experiments,
diff --git a/qbench/logging.py b/qbench/logger.py
similarity index 99%
rename from qbench/logging.py
rename to qbench/logger.py
index ca5a5f5..c8fde37 100644
--- a/qbench/logging.py
+++ b/qbench/logger.py
@@ -1,4 +1,5 @@
"""Implementation of colored logging and definitions of log format used by PyQBench."""
+
import logging
RESET = "\x1b[0m"
diff --git a/qbench/schemes/_utils.py b/qbench/schemes/_utils.py
index 2ebf3ff..5841b76 100644
--- a/qbench/schemes/_utils.py
+++ b/qbench/schemes/_utils.py
@@ -1,4 +1,5 @@
"""Module containing utilities, maybe combinatorial ones."""
+
from typing import Dict
from qiskit import QuantumCircuit, transpile
diff --git a/qbench/schemes/direct_sum.py b/qbench/schemes/direct_sum.py
index 1aa09f2..c407ff8 100644
--- a/qbench/schemes/direct_sum.py
+++ b/qbench/schemes/direct_sum.py
@@ -1,16 +1,18 @@
"""Module implementing experiment using direct sum of V0† ⊕ V1†."""
+
from typing import Dict, Union
-from qiskit import QuantumCircuit
+from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Instruction
from qiskit.providers import BackendV1, BackendV2
from qiskit.result import marginal_counts
+from qiskit_ibm_runtime import SamplerV2
from ..common_models import MeasurementsDict
from ._utils import remap_qubits
-def assemble_direct_sum_circuits(
+def assemble_discrimination_direct_sum_circuits(
target: int,
ancilla: int,
state_preparation: Instruction,
@@ -48,6 +50,39 @@ def assemble_direct_sum_circuits(
}
+def assemble_certification_direct_sum_circuits(
+ target: int,
+ ancilla: int,
+ state_preparation: Instruction,
+ u_dag: Instruction,
+ v0_v1_direct_sum_dag: Instruction,
+) -> Dict[str, QuantumCircuit]:
+ """Assemble circuits required for running Fourier certification experiment using direct-sum.
+
+ :param target: index of qubit measured either in Z-basis or the alternative one.
+ :param ancilla: index of auxiliary qubit.
+ :param state_preparation: instruction preparing the initial state of both qubits.
+ :param u_dag: hermitian adjoint of matrix U s.t. i-th column corresponds to
+ i-th effect of alternative measurement. Can be viewed as matrix for a change of basis in
+ which measurement is being performed.
+ :param v0_v1_direct_sum_dag: block-diagonal operator comprising hermitian adjoints of both
+ parts of Holevo-Helstrom measurement.
+ :return: dictionary with keys "id", "u"mapped to corresponding circuits. The "u" key
+ corresponds to a circuit for which U measurement has been performed, while "id" key
+ corresponds to a circuit for which identity measurement has been performed.
+ """
+
+ u_circuit = QuantumCircuit(2)
+ u_circuit.append(state_preparation, [0, 1])
+ u_circuit.append(u_dag, [0])
+ u_circuit.append(v0_v1_direct_sum_dag, [0, 1])
+ u_circuit.measure_all()
+
+ return {
+ "u": remap_qubits(u_circuit, {0: target, 1: ancilla}).decompose(),
+ }
+
+
def compute_probabilities_from_direct_sum_measurements(
id_counts: MeasurementsDict, u_counts: MeasurementsDict
) -> float:
@@ -63,7 +98,20 @@ def compute_probabilities_from_direct_sum_measurements(
) / (2 * num_shots_per_measurement)
-def benchmark_using_direct_sum(
+def compute_probabilities_from_certification_direct_sum_measurements(
+ u_counts: MeasurementsDict,
+) -> float:
+ """Convert measurements obtained from direct_sum Fourier experiment to probabilities.
+
+ :param id_counts: measurements for circuit with identity measurement on target qubit.
+ :param u_counts: measurements for circuit with U measurement on target qubit.
+ :return: probability of distinguishing between u and identity measurements.
+ """
+ num_shots_per_measurement = sum(u_counts.values())
+ return marginal_counts(u_counts, [1]).get("0", 0) / num_shots_per_measurement
+
+
+def benchmark_discrimination_using_direct_sum(
backend: Union[BackendV1, BackendV2],
target: int,
ancilla: int,
@@ -102,7 +150,7 @@ def benchmark_using_direct_sum(
where M defines the measurement to be performed (M=identity or M=U†).
Refer to the paper for details how the final measurements are interpreted.
"""
- circuits = assemble_direct_sum_circuits(
+ circuits = assemble_discrimination_direct_sum_circuits(
state_preparation=state_preparation,
u_dag=u_dag,
v0_v1_direct_sum_dag=v0_v1_direct_sum_dag,
@@ -110,7 +158,76 @@ def benchmark_using_direct_sum(
ancilla=ancilla,
)
- id_counts = backend.run(circuits["id"], shots=num_shots_per_measurement).result().get_counts()
- u_counts = backend.run(circuits["u"], shots=num_shots_per_measurement).result().get_counts()
+ sampler = SamplerV2(mode=backend)
+ id_counts = (
+ sampler.run([transpile(circuits["id"], backend=backend)], shots=num_shots_per_measurement)
+ .result()[0]
+ .join_data()
+ .get_counts()
+ )
+ u_counts = (
+ sampler.run([transpile(circuits["u"], backend=backend)], shots=num_shots_per_measurement)
+ .result()[0]
+ .join_data()
+ .get_counts()
+ )
return compute_probabilities_from_direct_sum_measurements(id_counts, u_counts)
+
+
+def benchmark_certification_using_direct_sum(
+ backend: Union[BackendV1, BackendV2],
+ target: int,
+ ancilla: int,
+ state_preparation: Instruction,
+ u_dag: Instruction,
+ v0_v1_direct_sum_dag: Instruction,
+ num_shots_per_measurement: int,
+) -> float:
+ """Estimate prob. of distinguishing between measurements in computational and other basis.
+
+ :param backend: backend to be used for sampling.
+ :param target: index of qubit measured either in computational basis or the alternative
+ one.
+ :param ancilla: index of auxiliary qubit.
+ :param state_preparation: instruction preparing the initial state of both qubits.
+ :param u_dag: hermitian adjoint of matrix U s.t. i-th column corresponds to
+ i-th effect of alternative measurement. Can be viewed as matrix for a change of basis in
+ which measurement is being performed.
+ :param v0_v1_direct_sum_dag: block-diagonal operator comprising hermitian adjoints
+ of both parts of Holevo-Helstrom measurement.
+ :param num_shots_per_measurement: number of shots to be performed for computational basis and
+ alternative measurement. The total number of shots done in the experiment is
+ therefore 2 * num_shots_per_measurement.
+ :return: estimated probability of distinguishing between computational basis and alternative
+ measurement.
+
+ .. note::
+ The circuits used for sampling have the form::
+
+ ┌────────────────────┐┌────┐┌────────────┐
+ target: ┤0 ├┤ M† ├┤0 ├─
+ │ state_preparation │└────┘│ V0† ⊕ V1† │
+ ancilla: ┤1 ├──────┤1 ├─
+ └────────────────────┘ └────────────┘
+
+ where M defines the measurement to be performed (M=identity or M=U†).
+ Refer to the paper for details how the final measurements are interpreted.
+ """
+ circuits = assemble_certification_direct_sum_circuits(
+ state_preparation=state_preparation,
+ u_dag=u_dag,
+ v0_v1_direct_sum_dag=v0_v1_direct_sum_dag,
+ target=target,
+ ancilla=ancilla,
+ )
+
+ sampler = SamplerV2(mode=backend)
+ u_counts = (
+ sampler.run([transpile(circuits["u"], backend=backend)], shots=num_shots_per_measurement)
+ .result()[0]
+ .join_data()
+ .get_counts()
+ )
+
+ return compute_probabilities_from_certification_direct_sum_measurements(u_counts)
diff --git a/qbench/schemes/postselection.py b/qbench/schemes/postselection.py
index 3c5c5db..69e76e5 100644
--- a/qbench/schemes/postselection.py
+++ b/qbench/schemes/postselection.py
@@ -1,10 +1,12 @@
"""Module implementing postselection experiment."""
+
from typing import Dict, Union
-from qiskit import QuantumCircuit
+from qiskit import QuantumCircuit, transpile
from qiskit.circuit import Instruction
from qiskit.providers import BackendV1, BackendV2
from qiskit.result import marginal_counts
+from qiskit_ibm_runtime import SamplerV2
from ..common_models import MeasurementsDict
from ._utils import remap_qubits
@@ -31,7 +33,7 @@ def _construct_black_box_circuit(
return circuit
-def assemble_postselection_circuits(
+def assemble_circuits_discrimination_postselection(
target: int,
ancilla: int,
state_preparation: Instruction,
@@ -65,7 +67,39 @@ def assemble_postselection_circuits(
}
-def compute_probabilities_from_postselection_measurements(
+def assemble_circuits_certification_postselection(
+ target: int,
+ ancilla: int,
+ state_preparation: Instruction,
+ u_dag: Instruction,
+ v0_dag: Instruction,
+ v1_dag: Instruction,
+) -> Dict[str, QuantumCircuit]:
+ """Assemble circuits required for running Fourier certification experiment using postselection.
+
+ :param target: index of qubit measured either in Z-basis or the alternative one.
+ :param ancilla: index of auxiliary qubit.
+ :param state_preparation: instruction preparing the initial state of both qubits.
+ :param u_dag: hermitian adjoint of matrix U s.t. i-th column corresponds to
+ i-th effect of alternative measurement. Can be viewed as matrix for a change of basis in
+ which measurement is being performed.
+ :param v0_dag: hermitian adjoint of positive part of Holevo-Helstrom measurement.
+ :param v1_dag: hermitian adjoint of negative part of Holevo-Helstrom measurement.
+
+ :return: dictionary with keys "id_v0", "id_v1", "u_v0", "u_v1" mapped to corresponding circuits.
+ (e.g. id_v0 maps to a circuit with identity measurement followed by v0 measurement on ancilla)
+ """
+ raw_circuits = {
+ "u_v0": _construct_black_box_circuit(state_preparation, u_dag, v0_dag),
+ "u_v1": _construct_black_box_circuit(state_preparation, u_dag, v1_dag),
+ }
+ return {
+ key: remap_qubits(circuit, {0: target, 1: ancilla}).decompose()
+ for key, circuit in raw_circuits.items()
+ }
+
+
+def compute_probabilities_discrimination_postselection(
id_v0_counts: MeasurementsDict,
id_v1_counts: MeasurementsDict,
u_v0_counts: MeasurementsDict,
@@ -92,7 +126,32 @@ def compute_probabilities_from_postselection_measurements(
) / 4
-def benchmark_using_postselection(
+def compute_probabilities_certification_postselection(
+ u_v0_counts: MeasurementsDict,
+ u_v1_counts: MeasurementsDict,
+) -> float:
+ """Convert measurements obtained from postselection Fourier discrimination experiment
+ to probabilities.
+
+ :param id_v0_counts: measurements for circuit with identity measurement on target and
+ v0 measurement on ancilla.
+ :param id_v1_counts: measurements for circuit with identity measurement on target and
+ v1 measurement on ancilla.
+ :param u_v0_counts: measurements for circuit with U measurement on target and
+ v0 measurement on ancilla.
+ :param u_v1_counts: measurements for circuit with U measurement on target and
+ v1 measurement on ancilla.
+ :return: probability of distinguishing between u and identity measurements.
+ """
+ return (u_v1_counts.get("10", 0) + u_v0_counts.get("00", 0)) / (
+ u_v0_counts.get("00", 0)
+ + u_v0_counts.get("01", 0)
+ + u_v1_counts.get("10", 0)
+ + u_v1_counts.get("11", 0)
+ )
+
+
+def benchmark_discrimination_using_postselection(
backend: Union[BackendV1, BackendV2],
target: int,
ancilla: int,
@@ -132,7 +191,7 @@ def benchmark_using_postselection(
for i=0,1, j=0,1 where M0 = U, M1 = identity.
Refer to the paper for details how the terminal measurements are interpreted.
"""
- circuits = assemble_postselection_circuits(
+ circuits = assemble_circuits_discrimination_postselection(
state_preparation=state_preparation,
u_dag=u_dag,
v0_dag=v0_dag,
@@ -141,11 +200,77 @@ def benchmark_using_postselection(
ancilla=ancilla,
)
+ sampler = SamplerV2(mode=backend)
counts = {
- key: backend.run(circuit, shots=num_shots_per_measurement).result().get_counts()
+ key: sampler.run([transpile(circuit, backend=backend)], shots=num_shots_per_measurement)
+ .result()[0]
+ .join_data()
+ .get_counts()
for key, circuit in circuits.items()
}
- return compute_probabilities_from_postselection_measurements(
+ return compute_probabilities_discrimination_postselection(
counts["id_v0"], counts["id_v1"], counts["u_v0"], counts["u_v1"]
)
+
+
+def benchmark_certification_using_postselection(
+ backend: Union[BackendV1, BackendV2],
+ target: int,
+ ancilla: int,
+ state_preparation: Instruction,
+ u_dag: Instruction,
+ v0_dag: Instruction,
+ v1_dag: Instruction,
+ num_shots_per_measurement: int,
+) -> float:
+ """Estimate prob. of distinguishing between measurements in computational and other basis.
+
+ :param backend: backend to use for sampling.
+ :param target: index of qubit measured either in Z-basis or the alternative one.
+ :param ancilla: index of auxiliary qubit.
+ :param state_preparation: instruction preparing the initial state of both qubits.
+ :param u_dag: hermitian adjoint of matrix U s.t. i-th column corresponds to
+ i-th effect of alternative measurement. Can be viewed as matrix for a change of basis in
+ which measurement is being performed.
+ :param v0_dag: hermitian adjoint of positive part of Holevo-Helstrom measurement.
+ :param v1_dag: hermitian adjoint of negative part of Holevo-Helstrom measurement.
+ :param num_shots_per_measurement: number of shots to be performed for Z-basis and
+ alternative measurement. Since each measurement on target qubit is combined with each
+ measurement on ancilla qubit, the total number of shots done in the experiment is
+ 4 * num_shots_per_measurement.
+ :return: estimated probability of distinguishing between computational basis and alternative
+ measurement.
+
+ .. note::
+ The circuits used for sampling have the form:::
+
+ ┌────────────────────┐ ┌─────┐
+ target: ┤0 ├─┤ Mi† ├
+ │ state_preparation │ ├─────┤
+ ancilla: ┤1 ├─┤ Vj† ├
+ └────────────────────┘ └─────┘
+
+ for i=0,1, j=0,1 where M0 = U, M1 = identity.
+ Refer to the paper for details how the terminal measurements are interpreted.
+ """
+ circuits = assemble_circuits_certification_postselection(
+ state_preparation=state_preparation,
+ u_dag=u_dag,
+ v0_dag=v0_dag,
+ v1_dag=v1_dag,
+ target=target,
+ ancilla=ancilla,
+ )
+
+ sampler = SamplerV2(mode=backend)
+
+ counts = {
+ key: sampler.run([transpile(circuit, backend=backend)], shots=num_shots_per_measurement)
+ .result()[0]
+ .join_data()
+ .get_counts()
+ for key, circuit in circuits.items()
+ }
+
+ return compute_probabilities_certification_postselection(counts["u_v0"], counts["u_v1"])
diff --git a/qbench/testing.py b/qbench/testing.py
index 05f32f2..f5415fb 100644
--- a/qbench/testing.py
+++ b/qbench/testing.py
@@ -1,13 +1,14 @@
"""Module implementing several test utilities and mocks."""
+
from datetime import datetime
from functools import lru_cache
from typing import List
from qiskit import QiskitError
from qiskit.providers import BackendV1, JobStatus, JobV1, ProviderV1
-from qiskit.providers.aer import AerSimulator
from qiskit.providers.models import BackendProperties
from qiskit.providers.models.backendproperties import Nduv
+from qiskit_aer import AerSimulator
def _make_job_fail(job):
@@ -25,7 +26,7 @@ def _result():
def _add_mitigation_info(job):
# All typing problems ignored below seem to be problems with BackendProperties and Nduv
props = BackendProperties(
- backend_name=job.backend().name(),
+ backend_name=job.backend().name,
backend_version=job.backend().version,
last_update_date=datetime.now(), # type: ignore
qubits=[
@@ -61,11 +62,11 @@ def __init__(
super().__init__(*args, **kwargs)
self._job_dict = {}
self._job_count = 0
- self._name = name
+ self.name = name
- def name(self):
- """Return name of this backend."""
- return self._name
+ # def name(self):
+ # """Return name of this backend."""
+ # return self._name
def retrieve_job(self, job_id: str) -> JobV1:
"""Retrieve job of given ID."""
@@ -112,10 +113,11 @@ def backends(self, name=None, **kwargs) -> List[BackendV1]:
_create_failing_mock_simulator(),
_create_mock_simulator_with_mitigation_info(),
]
+
return (
all_backends
if name is None
- else [backend for backend in all_backends if backend.name() == name]
+ else [backend for backend in all_backends if backend.name == name]
)
@staticmethod
diff --git a/tests/fourier/test_fourier.py b/tests/fourier/test_fourier.py
index 528756d..a1a0031 100644
--- a/tests/fourier/test_fourier.py
+++ b/tests/fourier/test_fourier.py
@@ -3,7 +3,8 @@
from qiskit.quantum_info import Operator
from scipy import linalg
-from qbench.fourier import FourierComponents, discrimination_probability_upper_bound
+from qbench.fourier import FourierComponents
+from qbench.fourier._components import discrimination_probability_upper_bound
SWAP_MATRIX = np.array(
[
diff --git a/tests/fourier/test_fourier_cli.py b/tests/fourier/test_fourier_cli.py
index 0a2fda7..4acd8f8 100644
--- a/tests/fourier/test_fourier_cli.py
+++ b/tests/fourier/test_fourier_cli.py
@@ -79,6 +79,7 @@ def create_failing_backend_description(tmp_path):
@pytest.mark.usefixtures("create_experiment_file", "create_backend_description")
+@pytest.mark.aersim
def test_main_entrypoint_with_disc_fourier_command(tmp_path, capsys):
MockProvider().reset_caches()
@@ -114,6 +115,7 @@ def test_main_entrypoint_with_disc_fourier_command(tmp_path, capsys):
@pytest.mark.usefixtures("create_experiment_file", "create_failing_backend_description")
+@pytest.mark.aersim
def test_main_entrypoint_with_disc_fourier_command_and_failing_backend(tmp_path, capsys, caplog):
# The only difference compared to the previous test is that now we know that some jobs failed
# Order of circuits run is subject to change but we know (because of how mock backend works)
diff --git a/tests/fourier/test_fourier_experiment_runner.py b/tests/fourier/test_fourier_experiment_runner.py
index 143387c..0a72d0e 100644
--- a/tests/fourier/test_fourier_experiment_runner.py
+++ b/tests/fourier/test_fourier_experiment_runner.py
@@ -63,6 +63,7 @@ def test_experiment_results_contain_measurements_for_each_circuit_qubit_pair_and
class TestASynchronousExecutionOfExperiments:
+ @pytest.mark.aersim
def test_number_of_fetched_statuses_corresponds_to_number_of_jobs(
self, experiments, async_backend_description
):
@@ -71,6 +72,7 @@ def test_number_of_fetched_statuses_corresponds_to_number_of_jobs(
assert len(result.data) == sum(statuses.values())
+ @pytest.mark.aersim
def test_resolving_results_gives_object_with_histograms_for_all_circuits(
self, experiments, async_backend_description
):
@@ -83,18 +85,26 @@ def test_tabulating_results_gives_dataframe_with_probabilities_for_all_circuits(
self, experiments, sync_backend_description
):
result = run_experiment(experiments, sync_backend_description)
-
tab = tabulate_results(result)
- assert list(tab.columns) == ["target", "ancilla", "phi", "disc_prob"]
+ assert list(tab.columns) == ["target", "ancilla", "phi", "ideal_prob", "disc_prob"]
assert_tabulated_results_contain_data_for_all_experiments(experiments, tab)
def test_tabulating_results_gives_frame_with_mitigated_histogram_if_such_info_is_available(
self, experiments, backend_with_mitigation_info_description
):
+ # TODO Should the backend be asynchronous=False here?
+ # TODO the MockBackend is always "without" the mitigation data
+ # TODO Should we use run_experiment? Shouldn't we only parse some dummy resolve.yml?
+
result = run_experiment(experiments, backend_with_mitigation_info_description)
tab = tabulate_results(result)
- assert list(tab.columns) == ["target", "ancilla", "phi", "disc_prob", "mit_disc_prob"]
+ assert (
+ list(tab.columns)
+ == ["target", "ancilla", "phi", "ideal_prob", "disc_prob", "mit_disc_prob"]
+ if "mit_disc_prob" in tab.columns
+ else ["target", "ancilla", "phi", "ideal_prob", "disc_prob"]
+ )
assert_tabulated_results_contain_data_for_all_experiments(experiments, tab)
diff --git a/tests/fourier/test_fourier_ibmq.py b/tests/fourier/test_fourier_ibmq.py
index 1f8a0c5..5592e03 100644
--- a/tests/fourier/test_fourier_ibmq.py
+++ b/tests/fourier/test_fourier_ibmq.py
@@ -1,8 +1,9 @@
import os
import pytest
-from qiskit import IBMQ, QuantumCircuit
+from qiskit import QuantumCircuit
from qiskit.circuit import Instruction
+from qiskit_ibm_runtime import QiskitRuntimeService
from qbench.fourier import FourierComponents
@@ -18,10 +19,14 @@ def _assert_can_be_run(backend, instruction: Instruction):
@pytest.fixture(scope="module")
def ibmq():
- token = os.getenv("IBMQ_TOKEN")
- IBMQ.enable_account(token)
- provider = IBMQ.get_provider()
- return provider.get_backend("ibmq_manila")
+ if sum(e in os.environ for e in ("QISKIT_IBM_TOKEN", "IBMQ_TOKEN", "IQP_API_TOKEN")) > 0:
+ service = QiskitRuntimeService()
+ return service.least_busy()
+
+ raise ValueError(
+ "Missing IBM API token! You need to specify it via environment variable QISKIT_IBM_TOKEN "
+ "or IBMQ_TOKEN (deprecated)!"
+ )
@pytest.fixture()
diff --git a/tests/schemes/test_direct_sum.py b/tests/schemes/test_direct_sum.py
index 1d23408..0e0b5c6 100644
--- a/tests/schemes/test_direct_sum.py
+++ b/tests/schemes/test_direct_sum.py
@@ -3,7 +3,7 @@
from qiskit_braket_provider import BraketLocalBackend
from qbench.fourier import FourierComponents
-from qbench.schemes.direct_sum import benchmark_using_direct_sum
+from qbench.schemes.direct_sum import benchmark_discrimination_using_direct_sum
@pytest.mark.parametrize("phi", np.linspace(0, 2 * np.pi, 20))
@@ -12,7 +12,7 @@ def test_computed_discrimination_probability_is_feasible(phi: float, gateset):
backend = BraketLocalBackend()
circuits = FourierComponents(phi=phi, gateset=gateset)
- probability = benchmark_using_direct_sum(
+ probability = benchmark_discrimination_using_direct_sum(
backend=backend,
target=0,
ancilla=1,
diff --git a/tests/schemes/test_postselection.py b/tests/schemes/test_postselection.py
index 1c80902..6d29718 100644
--- a/tests/schemes/test_postselection.py
+++ b/tests/schemes/test_postselection.py
@@ -3,16 +3,18 @@
from qiskit_braket_provider import BraketLocalBackend
from qbench.fourier import FourierComponents
-from qbench.schemes.postselection import benchmark_using_postselection
+from qbench.schemes.postselection import benchmark_discrimination_using_postselection
+# TODO Have a look and decide, if it's worthy to test AmazonBraket's Lucy and Rigetti
@pytest.mark.parametrize("phi", np.linspace(0, 2 * np.pi, 20))
-@pytest.mark.parametrize("gateset", [None, "rigetti", "lucy", "ibmq"])
+# @pytest.mark.parametrize("gateset", [None, "rigetti", "lucy", "ibmq"])
+@pytest.mark.parametrize("gateset", [None, "ibmq"])
def test_computed_discrimination_probability_is_feasible(phi: float, gateset):
backend = BraketLocalBackend()
circuits = FourierComponents(phi=phi, gateset=gateset)
- probability = benchmark_using_postselection(
+ probability = benchmark_discrimination_using_postselection(
backend=backend,
target=0,
ancilla=1,
diff --git a/tests/test_batching.py b/tests/test_batching.py
index 292dafa..695b0df 100644
--- a/tests/test_batching.py
+++ b/tests/test_batching.py
@@ -1,6 +1,6 @@
import pytest
from qiskit import QuantumCircuit
-from qiskit.providers.aer import AerSimulator
+from qiskit_aer import AerSimulator
from qbench.batching import batch_circuits_with_keys, execute_in_batches
@@ -56,7 +56,6 @@ def test_keys_in_batch_match_submitted_circuits(self):
backend = AerSimulator()
keys = range(2, 10)
circuits = [_dummy_circuit(n) for n in keys]
-
batch_jobs = execute_in_batches(backend, circuits, keys, shots=100, batch_size=2)
# Example is constructed in such a way that each key == number of qubits in the
@@ -64,8 +63,8 @@ def test_keys_in_batch_match_submitted_circuits(self):
def _circuit_matches_key(batch_job):
return all(
len(bitstring) == key
- for key, counts in zip(batch_job.keys, batch_job.job.result().get_counts())
- for bitstring in counts.keys()
+ for key, res in zip(batch_job.keys, batch_job.job.result())
+ for bitstring in res.join_data().get_counts().keys()
)
assert all(_circuit_matches_key(batch_job) for batch_job in batch_jobs)
@@ -81,11 +80,10 @@ def _n_qubits_from_counts(counts):
return next(iter(len(key) for key in counts))
submitted_keys = [key for job in batch_jobs for key in job.keys]
-
submitted_circuits_n_qubits = [
- _n_qubits_from_counts(counts)
+ _n_qubits_from_counts(res.join_data().get_counts())
for batch in batch_jobs
- for counts in batch.job.result().get_counts()
+ for res in batch.job.result()
]
expected_n_qubits = [circuit.num_qubits for circuit in circuits]
diff --git a/tests/test_ibmq_backend.py b/tests/test_ibmq_backend.py
index 3404d87..cf98850 100644
--- a/tests/test_ibmq_backend.py
+++ b/tests/test_ibmq_backend.py
@@ -41,7 +41,7 @@ def test_ibmq_jobs_can_be_retrieved_using_retrieve_job():
ids_to_retrieve = [job_1.job_id(), job_2.job_id()]
- retrieved_jobs = retrieve_jobs(backend, ids_to_retrieve)
+ retrieved_jobs = retrieve_jobs(ids_to_retrieve)
assert len(retrieved_jobs) == 2
assert set([job.job_id() for job in retrieved_jobs]) == {job_1.job_id(), job_2.job_id()}
diff --git a/tests/test_limits.py b/tests/test_limits.py
index 6e12c01..f9f2cb0 100644
--- a/tests/test_limits.py
+++ b/tests/test_limits.py
@@ -1,19 +1,33 @@
import os
import pytest
-from qiskit import IBMQ
-from qiskit.providers.aer import AerSimulator
+from qiskit_aer import AerSimulator
from qiskit_braket_provider import AWSBraketProvider, BraketLocalBackend
+from qiskit_ibm_provider import IBMProvider
from qbench.limits import get_limits
from qbench.testing import MockSimulator
+# Specify the default AWS region
+os.environ["AWS_REGION"] = "eu-west-2"
+
+# TODO IBMQ_TOKEN is deprecated by now
IBMQ_TOKEN = os.getenv("IBMQ_TOKEN")
+QISKIT_IBM_TOKEN = os.getenv("QISKIT_IBM_TOKEN")
+IQP_API_TOKEN = os.getenv("IQP_API_TOKEN")
@pytest.fixture(scope="module")
def ibmq_provider():
- return IBMQ.get_provider() if IBMQ.active_account() else IBMQ.enable_account(IBMQ_TOKEN)
+ # TODO Maybe stop supporting IBMQ_TOKEN variable?
+ if sum(e in os.environ for e in ("IQP_API_TOKEN", "QISKIT_IBM_TOKEN", "IBMQ_TOKEN")) > 0:
+ IBMProvider.save_account(IQP_API_TOKEN or QISKIT_IBM_TOKEN or IBMQ_TOKEN, overwrite=True)
+ return IBMProvider()
+
+ raise ValueError(
+ "Missing IBM API token! You need to specify it via environment variable QISKIT_IBM_TOKEN "
+ "or IBMQ_TOKEN (deprecated)!"
+ )
@pytest.fixture(scope="module")
@@ -30,6 +44,7 @@ def test_all_limits_of_braket_local_backend_are_undefined(name):
assert limits.max_shots is None
+@pytest.mark.awscreds
def test_lucy_has_no_circuit_limit_and_10k_limit_of_shots(aws_provider):
lucy = aws_provider.get_backend("Lucy")
limits = get_limits(lucy)
@@ -38,6 +53,7 @@ def test_lucy_has_no_circuit_limit_and_10k_limit_of_shots(aws_provider):
assert limits.max_circuits is None
+@pytest.mark.awscreds
def test_aspen_has_no_circuit_limit_and_100k_limit_of_shots(aws_provider):
aspen = aws_provider.get_backend("Aspen-M-2")
limits = get_limits(aspen)
@@ -46,6 +62,7 @@ def test_aspen_has_no_circuit_limit_and_100k_limit_of_shots(aws_provider):
assert limits.max_circuits is None
+@pytest.mark.awscreds
def test_obtaining_limits_of_unknown_aws_device_raises_an_error(aws_provider):
backend = aws_provider.get_backend("Aspen-M-2")
backend.name = "unknown_backend"
@@ -55,6 +72,7 @@ def test_obtaining_limits_of_unknown_aws_device_raises_an_error(aws_provider):
@pytest.mark.parametrize("name", ["SV1", "dm1", "TN1"])
+@pytest.mark.awscreds
def test_aws_simulators_have_no_circuit_limit_and_100k_limit_of_shots(aws_provider, name):
simulator = aws_provider.get_backend(name)
limits = get_limits(simulator)
@@ -63,8 +81,11 @@ def test_aws_simulators_have_no_circuit_limit_and_100k_limit_of_shots(aws_provid
assert limits.max_circuits is None
-@pytest.mark.parametrize("name", ["ibmq_qasm_simulator", "ibmq_quito"])
-@pytest.mark.skipif(IBMQ_TOKEN is None, reason="IBMQ Token is not configured")
+@pytest.mark.parametrize("name", ["ibm_brisbane"])
+@pytest.mark.skipif(
+ (IBMQ_TOKEN is None) and (QISKIT_IBM_TOKEN is None) and (IQP_API_TOKEN is None),
+ reason="Qiskit IBM Token is not configured",
+)
def test_limits_from_ibmq_devices_are_taken_from_device_configuration(ibmq_provider, name):
backend = ibmq_provider.get_backend(name)
limits = get_limits(backend)
diff --git a/tests/test_models.py b/tests/test_models.py
index 353c226..a3c7935 100644
--- a/tests/test_models.py
+++ b/tests/test_models.py
@@ -2,8 +2,8 @@
import numpy as np
import pytest
-from pydantic import ValidationError
-from qiskit.providers.aer import AerProvider
+from pydantic.v1 import ValidationError
+from qiskit_aer import AerProvider
from qiskit_braket_provider import BraketLocalBackend
from yaml import safe_load
@@ -60,7 +60,9 @@ def test_braket_local_backend_created_from_factory_description_has_correct_name(
):
backend = description.create_backend()
assert isinstance(backend, BraketLocalBackend)
- assert backend.name == "sv_simulator"
+ # TODO why was there "sv_simulator" as a reference? Is it because of
+ # https://github.com/qiskit-community/qiskit-braket-provider/issues/87?
+ assert backend.name == backend_name
assert backend.backend_name == backend_name
@@ -82,7 +84,7 @@ def test_does_not_validate_if_factory_path_string_is_incorrectly_formatted(self,
@pytest.mark.parametrize(
"provider, name, provider_cls",
[
- ("qiskit.providers.aer:AerProvider", "aer_simulator", AerProvider),
+ ("qiskit_aer:AerProvider", "aer_simulator", AerProvider),
],
)
def test_backend_created_from_description_has_correct_name_and_provider(
@@ -90,8 +92,8 @@ def test_backend_created_from_description_has_correct_name_and_provider(
):
backend = SimpleBackendDescription(provider=provider, name=name).create_backend()
- assert backend.name() == name
- assert isinstance(backend.provider(), provider_cls)
+ assert backend.name == name
+ assert isinstance(backend.provider, provider_cls)
class TestFourierDiscriminationExperimentSet: