From 46cf5ed5d82e0767bb239c0ed23f5e81e096b4d3 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 31 Jan 2025 12:24:53 -0500 Subject: [PATCH 01/14] first draft --- docs/guides/primitive-input-output.ipynb | 193 ++++++++++++++++------- 1 file changed, 135 insertions(+), 58 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 7bbd3d12fb2..179db852c12 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -45,7 +45,7 @@ "id": "09501afd-ae49-4274-bf4c-512a1187201a", "metadata": {}, "source": [ - "This page gives an overview of the inputs and outputs of the Qiskit Runtime primitives that execute workloads on IBM Quantum™ compute resources. These primitives provide you with the ability to efficiently define vectorized workloads by using a data structure known as a **Primitive Unified Bloc (PUB)**. These PUBs are the fundamental unit of work a QPU needs to execute these workloads. They are used as inputs to the [`run()`](../api/qiskit-ibm-runtime/estimator-v2#run) method for the Sampler and Estimator primitives, which execute the defined workload as a job. Then, after the job has completed, the results are returned in a format that is dependent on both the PUBs used as well as the runtime options specified from the Sampler or Estimator primitives." + "This page gives an overview of the inputs and outputs of the Qiskit Runtime primitives that execute workloads on IBM Quantum™ compute resources. These primitives provide you with the ability to efficiently define vectorized workloads by using a data structure known as a **Primitive Unified Bloc (PUB)**. These PUBs are the fundamental unit of work a QPU needs to execute these workloads. They are used as inputs to the [`run()`](../api/qiskit-ibm-runtime/qiskit_ibm_runtime.EstimatorV2#run) method for the Sampler and Estimator primitives, which execute the defined workload as a job. Then, after the job has completed, the results are returned in a format that is dependent on both the PUBs used as well as the runtime options specified from the Sampler or Estimator primitives." ] }, { @@ -56,7 +56,7 @@ "\n", "## Overview of PUBs\n", "\n", - "When invoking a primitive's [`run()`](../api/qiskit-ibm-runtime/estimator-v2#run) method, the main argument that is required is a `list` of one or more tuples -- one for each circuit being executed by the primitive. Each of these tuples is considered a PUB, and the required elements of each tuple in the list depends on the the primitive used. The data provided to these tuples can also be arranged in a variety of shapes to provide flexibility in a workload through broadcasting -- the rules of which are described in a [following section](#broadcasting-rules).\n", + "When invoking a primitive's [`run()`](../api/qiskit-ibm-runtime/qiskit_ibm_runtime.EstimatorV2#run) method, the main argument that is required is a `list` of one or more tuples -- one for each circuit being executed by the primitive. Each of these tuples is considered a PUB, and the required elements of each tuple in the list depends on the the primitive used. The data provided to these tuples can also be arranged in a variety of shapes to provide flexibility in a workload through broadcasting -- the rules of which are described in a [following section](#broadcasting-rules).\n", "\n", "### Estimator PUB\n", "For the Estimator primitive, the format of the PUB should contain at most four values:\n", @@ -90,14 +90,13 @@ "metadata": {}, "outputs": [], "source": [ - "from qiskit_ibm_runtime import QiskitRuntimeService\n", - "from qiskit.circuit import Parameter, QuantumCircuit\n", - "from qiskit_ibm_runtime import (\n", - " EstimatorV2 as Estimator,\n", - " SamplerV2 as Sampler,\n", - ")\n", + "from qiskit.circuit import Parameter, QuantumCircuit, ClassicalRegister, QuantumRegister\n", "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", "from qiskit.quantum_info import SparsePauliOp\n", + "from qiskit.primitives.containers import BitArray\n", + "\n", + "from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator, SamplerV2 as Sampler\n", + "\n", "import numpy as np\n", "\n", "# Instantiate runtime service and get\n", @@ -119,7 +118,6 @@ "transpiled_circuit = pm.run(circuit)\n", "layout = transpiled_circuit.layout\n", "\n", - "\n", "# Now define a sweep over parameter values, the last axis of dimension 2 is\n", "# for the two parameters \"a\" and \"b\"\n", "params = np.vstack(\n", @@ -418,76 +416,155 @@ "source": [ "### Sampler output\n", "\n", + "When a Sampler job is completed successfully, the returned [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult) object contains a list of [`SamplerPubResult`](../api/qiskit/qiskit.primitives.SamplerPubResult)s, one per PUB. The databins of these `SamplerPubResult`s are dict-like objects that contain one or more `BitArray`s, each one storing the samples of the circuits attached to a particular `ClassicalRegister`. \n", "\n", - "The Sampler primitive outputs job results in a similar format, with the exception that each `DataBin` will contain one or more `BitArray` objects which store the samples of the circuit attached to a particular `ClassicalRegister`, typically one bitstring per shot. The attribute label for each bit array object depends on the `ClassicalRegisters` defined in the circuit being executed. The measurement data from these `BitArrays` can then be processed into a dictionary with key-value pairs corresponding to each bitstring measured (for example, '1011001') and the number of times (or counts) it was measured.\n", - "\n", - "For example, a circuit that has measurement instructions added by the [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all) function possesses a classical register with the label *'meas'*. After execution, a count data dictionary can be created by executing:" + "The `get_counts` method returns a dictionary mapping the sampled measurement outcomes to their counts. For example, for a circuit with measurement instructions added by the [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all), the counts can be access as follows:" ] }, { "cell_type": "code", - "execution_count": 4, - "id": "a2316ece-c1ef-49b9-ae07-860decb0747d", + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Add measurement instructions to the example circuit\n", + "# generate a ten-qubit GHZ circuit\n", + "circuit = QuantumCircuit(10)\n", + "circuit.h(0)\n", + "circuit.cx(range(0, 9), range(1, 10))\n", + "\n", + "# append measurements with the `measure_all` method\n", "circuit.measure_all()\n", "\n", - "# Transpile the circuit\n", - "pm = generate_preset_pass_manager(optimization_level=1, backend=backend)\n", + "# transpile the circuit\n", "transpiled_circuit = pm.run(circuit)\n", "\n", - "# Create a PUB for the Sampler primitive using the same parameters defined earlier\n", - "sampler_pub = (transpiled_circuit, params)\n", + "# run the Sampler job and retrieve the results\n", + "sampler = Sampler(mode=backend)\n", + "job = sampler.run([transpiled_circuit])\n", + "result = job.result()\n", + "\n", + "# the databin contains one BitArray\n", + "data = result[0].data\n", + "print(f\"Databin: {data}\")\n", "\n", + "# to access the BitArray, one can use the keyword \"meas\", which is the default\n", + "# name of the classical register when this is added by the `measure_all` method\n", + "array = data.meas\n", + "print(f\"BitArray: {array}\")\n", "\n", - "sampler = Sampler(mode=backend)\n", - "job = sampler.run([sampler_pub])\n", - "result = job.result()" + "# use the `get_counts` method to look up the results\n", + "counts = array.get_counts()\n", + "print(f\"Counts: {counts}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "When a circuit contains more classical registers, the results are stored in different `BitArray`s. The following example modifies the previous snippet by splitting the classical register into two distinct registers:" ] }, { "cell_type": "code", - "execution_count": 5, - "id": "f459cd09-3243-487c-9fc4-a8e7a5589e4e", + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The result of the submitted job had 1 PUB and has a value:\n", - " PrimitiveResult([SamplerPubResult(data=DataBin(meas=BitArray(), shape=(100,)), metadata={'circuit_metadata': {}})], metadata={'execution': {'execution_spans': ExecutionSpans([SliceSpan()])}, 'version': 2})\n", - "\n", - "The associated PubResult of this Sampler job has the following DataBins:\n", - " DataBin(meas=BitArray(), shape=(100,))\n", - "\n", - "It has a key-value pair dict: \n", - "dict_items([('meas', BitArray())])\n", - "\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "And the raw data can be converted to a bitstring-count format: \n", - "{'11': 105249, '01': 101186, '10': 101180, '00': 101985}\n" - ] - } - ], + "outputs": [], "source": [ - "print(\n", - " f\"The result of the submitted job had {len(result)} PUB and has a value:\\n {result}\\n\"\n", - ")\n", - "print(\n", - " f\"The associated PubResult of this Sampler job has the following DataBins:\\n {result[0].data}\\n\"\n", + "# generate a ten-qubit GHZ circuit with two classical registers\n", + "circuit = QuantumCircuit(\n", + " qreg := QuantumRegister(10), \n", + " alpha := ClassicalRegister(1, \"alpha\"),\n", + " beta := ClassicalRegister(9, \"beta\"),\n", ")\n", - "print(f\"It has a key-value pair dict: \\n{result[0].data.items()}\\n\")\n", - "print(\n", - " f\"And the raw data can be converted to a bitstring-count format: \\n{result[0].data.meas.get_counts()}\"\n", - ")" + "circuit.h(0)\n", + "circuit.cx(range(0, 9), range(1, 10))\n", + "\n", + "# append measurements with the `measure_all` method\n", + "circuit.measure([0], alpha)\n", + "circuit.measure(range(1, 10), beta)\n", + "\n", + "# transpile the circuit\n", + "transpiled_circuit = pm.run(circuit)\n", + "\n", + "# run the Sampler job and retrieve the results\n", + "sampler = Sampler(mode=backend)\n", + "job = sampler.run([transpiled_circuit])\n", + "result = job.result()\n", + "\n", + "# the databin contains two BitArrays, one per register, and can be accessed\n", + "# as attributes using the registers' names\n", + "data = result[0].data\n", + "print(f\"BitArray for register 'alpha': {data.alpha}\")\n", + "print(f\"BitArray for register 'beta': {data.beta}\")\n", + "\n", + "print(f\"\\nCounts for register 'alpha': {data.alpha.get_counts()}\")\n", + "print(f\"\\nCounts for register 'beta': {data.beta.get_counts()}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Leveraging `BitArray`s for performant post-processing\n", + "\n", + "Dictionaries of counts allow for a quick and convenient lookup of results. However, `BitArray`s store the sampled bitstrings in `Numpy` arrays, not in dictionaries. Since arrays generally offer better performance compared to dictionaries, it is advisable to perform any further post-processing directly on the `BitArray`s rather than on the counts.\n", + "\n", + "Every `BitArray` owns a two-dimensional array that stores the bitstrings as bytes (more precisely, as `numpy.uint8`s whose bits are the bitstrings that appear in the dictionary of counts). The left-most axis of said array runs over ordered shots, while the right-most axis runs over bytes (whose bits are the bitstrings that appear in the dictionary of counts). The following cell shows how to retrieve the array of bytes for the results obtained in the previous example, as well how to manipulate them as `Numpy` arrays:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"The shape of register `alpha` is {data.alpha.array.shape}.\")\n", + "print(f\"The bytes in register `alpha`, shot by shot:\\n{data.alpha.array}\\n\")\n", + "\n", + "print(f\"The shape of register `beta` is {data.beta.array.shape}.\")\n", + "print(f\"The bytes in register `beta`, shot by shot:\\n{data.beta.array}\\n\")\n", + "\n", + "# post-select the bitstrings of `beta` based on having sampled \"1\" in `alpha`\n", + "mask = data.alpha.array == \"0b1\"\n", + "ps_beta = data.beta[mask[:, 0]]\n", + "print(f\"The shape of `beta` after post-selection is {ps_beta.array.shape}.\")\n", + "print(f\"The bytes in `beta` after post-selection:\\n{ps_beta.array}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "`BitArray` offers a wide range of built-in methods to perform some of the operations commonly performed during post-processing:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# get a slice of `beta` to retrieve the first three bits\n", + "beta_sl_bits = data.beta.slice_bits([0, 1, 2])\n", + "print(f\"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}.\")\n", + "print(f\"The bytes in `beta` after bit-wise slicing:\\n{beta_sl_bits.array}\\n\")\n", + "\n", + "# get a slice of `beta` to retrieve the bytes of the first five shots\n", + "beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])\n", + "print(f\"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}.\")\n", + "print(f\"The bytes in `beta` after shot-wise slicing:\\n{beta_sl_shots.array}\\n\")\n", + "\n", + "# calculate the expectation value of diagonal operators on `beta`\n", + "ops = [\"ZZZZZZZZZ\", \"IIIIIIIIZ\"]\n", + "exp_vals = data.beta.expectation_values(ops)\n", + "for o, e in zip(ops, exp_vals):\n", + " print(f\"Exp. val. for observable `{o}` is: {e}\")\n", + "\n", + "# concatenate the bitstrings in `alpha` and `beta` to \"merge\" the results of the two\n", + "# registers\n", + "merged_results = BitArray.concatenate_bits([data.alpha, data.beta])\n", + "print(f\"\\nThe shape of the merged results is {merged_results.array.shape}.\")\n", + "print(f\"The bytes of the merged results:\\n{merged_results.array}\\n\")" ] }, { From 55dc8ecb1bc63cd06f73bc8dc86ce2202d4784bc Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 31 Jan 2025 16:02:28 -0500 Subject: [PATCH 02/14] typo --- docs/guides/primitive-input-output.ipynb | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 179db852c12..b2d4f29121f 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -371,20 +371,14 @@ } ], "source": [ - "print(\n", - " f\"The result of the submitted job had {len(result)} PUB and has a value:\\n {result}\\n\"\n", - ")\n", - "print(\n", - " f\"The associated PubResult of this job has the following DataBins:\\n {result[0].data}\\n\"\n", - ")\n", + "print(f\"The result of the submitted job had {len(result)} PUB and has a value:\\n {result}\\n\")\n", + "print(f\"The associated PubResult of this job has the following DataBins:\\n {result[0].data}\\n\")\n", "print(f\"And this DataBin has attributes: {result[0].data.keys()}\")\n", "print(\n", - " \"Recall that this shape is due to our array of parameter binding sets having shape (100,), combined with \\n\\\n", + " \"Recall that this shape is due to our array of parameter binding sets having shape (100,), combined with \\n\\\n", " our array of observables having shape (3, 1), where 2 is the number of parameters in the circuit.\\n\"\n", ")\n", - "print(\n", - " f\"The expectation values measured from this PUB are: \\n{result[0].data.evs}\"\n", - ")" + "print(f\"The expectation values measured from this PUB are: \\n{result[0].data.evs}\")" ] }, { @@ -416,9 +410,9 @@ "source": [ "### Sampler output\n", "\n", - "When a Sampler job is completed successfully, the returned [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult) object contains a list of [`SamplerPubResult`](../api/qiskit/qiskit.primitives.SamplerPubResult)s, one per PUB. The databins of these `SamplerPubResult`s are dict-like objects that contain one or more `BitArray`s, each one storing the samples of the circuits attached to a particular `ClassicalRegister`. \n", + "When a Sampler job is completed successfully, the returned [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult) object contains a list of [`SamplerPubResult`](../api/qiskit/qiskit.primitives.SamplerPubResult)s, one per PUB. The databins of these `SamplerPubResult`s are dict-like objects that contain one `BitArray`s per `ClassicalRegister` in the circuit. \n", "\n", - "The `get_counts` method returns a dictionary mapping the sampled measurement outcomes to their counts. For example, for a circuit with measurement instructions added by the [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all), the counts can be access as follows:" + "The `get_counts` method of `BitArray` returns a dictionary mapping the sampled measurement outcomes to their counts. For example, for a circuit with measurement instructions added by the [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all), the counts can be access as follows:" ] }, { @@ -461,7 +455,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "When a circuit contains more classical registers, the results are stored in different `BitArray`s. The following example modifies the previous snippet by splitting the classical register into two distinct registers:" + "When a circuit contains more than one classical registers, the results are stored in different `BitArray`s. The following example modifies the previous snippet by splitting the classical register into two distinct registers:" ] }, { @@ -507,7 +501,7 @@ "source": [ "#### Leveraging `BitArray`s for performant post-processing\n", "\n", - "Dictionaries of counts allow for a quick and convenient lookup of results. However, `BitArray`s store the sampled bitstrings in `Numpy` arrays, not in dictionaries. Since arrays generally offer better performance compared to dictionaries, it is advisable to perform any further post-processing directly on the `BitArray`s rather than on the counts.\n", + "Dictionaries of counts allow for a quick and convenient lookup of results. However, `BitArray`s store the sampled bitstrings in `Numpy` arrays, not in dictionaries. Since arrays generally offer better performance compared to dictionaries, it is advisable to perform any post-processing directly on the `BitArray`s rather than on the counts.\n", "\n", "Every `BitArray` owns a two-dimensional array that stores the bitstrings as bytes (more precisely, as `numpy.uint8`s whose bits are the bitstrings that appear in the dictionary of counts). The left-most axis of said array runs over ordered shots, while the right-most axis runs over bytes (whose bits are the bitstrings that appear in the dictionary of counts). The following cell shows how to retrieve the array of bytes for the results obtained in the previous example, as well how to manipulate them as `Numpy` arrays:" ] From 9a8ddf64d271a22bded28bd2427345412d925162 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 31 Jan 2025 16:09:57 -0500 Subject: [PATCH 03/14] lint --- docs/guides/primitive-input-output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index b2d4f29121f..5a6830d09ab 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -549,7 +549,7 @@ "print(f\"The bytes in `beta` after shot-wise slicing:\\n{beta_sl_shots.array}\\n\")\n", "\n", "# calculate the expectation value of diagonal operators on `beta`\n", - "ops = [\"ZZZZZZZZZ\", \"IIIIIIIIZ\"]\n", + "ops = [SparsePauliOp(\"ZZZZZZZZZ\"), SparsePauliOp(\"IIIIIIIIZ\")]\n", "exp_vals = data.beta.expectation_values(ops)\n", "for o, e in zip(ops, exp_vals):\n", " print(f\"Exp. val. for observable `{o}` is: {e}\")\n", From bdb038512d3d5cd750b751a354d629a98f05dc4d Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Fri, 31 Jan 2025 16:12:43 -0500 Subject: [PATCH 04/14] fix --- docs/guides/primitive-input-output.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 5a6830d09ab..8200b13a221 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -45,7 +45,7 @@ "id": "09501afd-ae49-4274-bf4c-512a1187201a", "metadata": {}, "source": [ - "This page gives an overview of the inputs and outputs of the Qiskit Runtime primitives that execute workloads on IBM Quantum™ compute resources. These primitives provide you with the ability to efficiently define vectorized workloads by using a data structure known as a **Primitive Unified Bloc (PUB)**. These PUBs are the fundamental unit of work a QPU needs to execute these workloads. They are used as inputs to the [`run()`](../api/qiskit-ibm-runtime/qiskit_ibm_runtime.EstimatorV2#run) method for the Sampler and Estimator primitives, which execute the defined workload as a job. Then, after the job has completed, the results are returned in a format that is dependent on both the PUBs used as well as the runtime options specified from the Sampler or Estimator primitives." + "This page gives an overview of the inputs and outputs of the Qiskit Runtime primitives that execute workloads on IBM Quantum™ compute resources. These primitives provide you with the ability to efficiently define vectorized workloads by using a data structure known as a **Primitive Unified Bloc (PUB)**. These PUBs are the fundamental unit of work a QPU needs to execute these workloads. They are used as inputs to the [`run()`](../api/qiskit-ibm-runtime/estimator-v2#run) method for the Sampler and Estimator primitives, which execute the defined workload as a job. Then, after the job has completed, the results are returned in a format that is dependent on both the PUBs used as well as the runtime options specified from the Sampler or Estimator primitives." ] }, { @@ -56,7 +56,7 @@ "\n", "## Overview of PUBs\n", "\n", - "When invoking a primitive's [`run()`](../api/qiskit-ibm-runtime/qiskit_ibm_runtime.EstimatorV2#run) method, the main argument that is required is a `list` of one or more tuples -- one for each circuit being executed by the primitive. Each of these tuples is considered a PUB, and the required elements of each tuple in the list depends on the the primitive used. The data provided to these tuples can also be arranged in a variety of shapes to provide flexibility in a workload through broadcasting -- the rules of which are described in a [following section](#broadcasting-rules).\n", + "When invoking a primitive's [`run()`](../api/qiskit-ibm-runtime/estimator-v2#run) method, the main argument that is required is a `list` of one or more tuples -- one for each circuit being executed by the primitive. Each of these tuples is considered a PUB, and the required elements of each tuple in the list depends on the the primitive used. The data provided to these tuples can also be arranged in a variety of shapes to provide flexibility in a workload through broadcasting -- the rules of which are described in a [following section](#broadcasting-rules).\n", "\n", "### Estimator PUB\n", "For the Estimator primitive, the format of the PUB should contain at most four values:\n", From 6a2c4a3692012d1b685ecf396db48075a948708a Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Mon, 3 Feb 2025 13:44:12 -0500 Subject: [PATCH 05/14] lint --- docs/guides/primitive-input-output.ipynb | 50 ++++++++++++++++++------ 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 8200b13a221..0636a1c4479 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -90,12 +90,21 @@ "metadata": {}, "outputs": [], "source": [ - "from qiskit.circuit import Parameter, QuantumCircuit, ClassicalRegister, QuantumRegister\n", + "from qiskit.circuit import (\n", + " Parameter,\n", + " QuantumCircuit,\n", + " ClassicalRegister,\n", + " QuantumRegister,\n", + ")\n", "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n", "from qiskit.quantum_info import SparsePauliOp\n", "from qiskit.primitives.containers import BitArray\n", "\n", - "from qiskit_ibm_runtime import QiskitRuntimeService, EstimatorV2 as Estimator, SamplerV2 as Sampler\n", + "from qiskit_ibm_runtime import (\n", + " QiskitRuntimeService,\n", + " EstimatorV2 as Estimator,\n", + " SamplerV2 as Sampler,\n", + ")\n", "\n", "import numpy as np\n", "\n", @@ -371,14 +380,20 @@ } ], "source": [ - "print(f\"The result of the submitted job had {len(result)} PUB and has a value:\\n {result}\\n\")\n", - "print(f\"The associated PubResult of this job has the following DataBins:\\n {result[0].data}\\n\")\n", + "print(\n", + " f\"The result of the submitted job had {len(result)} PUB and has a value:\\n {result}\\n\"\n", + ")\n", + "print(\n", + " f\"The associated PubResult of this job has the following DataBins:\\n {result[0].data}\\n\"\n", + ")\n", "print(f\"And this DataBin has attributes: {result[0].data.keys()}\")\n", "print(\n", - " \"Recall that this shape is due to our array of parameter binding sets having shape (100,), combined with \\n\\\n", + " \"Recall that this shape is due to our array of parameter binding sets having shape (100,), combined with \\n\\\n", " our array of observables having shape (3, 1), where 2 is the number of parameters in the circuit.\\n\"\n", ")\n", - "print(f\"The expectation values measured from this PUB are: \\n{result[0].data.evs}\")" + "print(\n", + " f\"The expectation values measured from this PUB are: \\n{result[0].data.evs}\"\n", + ")" ] }, { @@ -410,7 +425,7 @@ "source": [ "### Sampler output\n", "\n", - "When a Sampler job is completed successfully, the returned [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult) object contains a list of [`SamplerPubResult`](../api/qiskit/qiskit.primitives.SamplerPubResult)s, one per PUB. The databins of these `SamplerPubResult`s are dict-like objects that contain one `BitArray`s per `ClassicalRegister` in the circuit. \n", + "When a Sampler job is completed successfully, the returned [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult) object contains a list of [`SamplerPubResult`](../api/qiskit/qiskit.primitives.SamplerPubResult)s, one per PUB. The databins of these `SamplerPubResult`s are dict-like objects that contain one `BitArray`s per `ClassicalRegister` in the circuit.\n", "\n", "The `get_counts` method of `BitArray` returns a dictionary mapping the sampled measurement outcomes to their counts. For example, for a circuit with measurement instructions added by the [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all), the counts can be access as follows:" ] @@ -418,6 +433,7 @@ { "cell_type": "code", "execution_count": null, + "id": "40f4fa9d-9464-4063-b8e9-a1b57c42a579", "metadata": {}, "outputs": [], "source": [ @@ -453,6 +469,7 @@ }, { "cell_type": "markdown", + "id": "61c70ff7-7814-4535-9268-ac163c10121c", "metadata": {}, "source": [ "When a circuit contains more than one classical registers, the results are stored in different `BitArray`s. The following example modifies the previous snippet by splitting the classical register into two distinct registers:" @@ -461,12 +478,13 @@ { "cell_type": "code", "execution_count": null, + "id": "7c21a74d-26b0-4494-8d5f-92bd5c0cbdc2", "metadata": {}, "outputs": [], "source": [ "# generate a ten-qubit GHZ circuit with two classical registers\n", "circuit = QuantumCircuit(\n", - " qreg := QuantumRegister(10), \n", + " qreg := QuantumRegister(10),\n", " alpha := ClassicalRegister(1, \"alpha\"),\n", " beta := ClassicalRegister(9, \"beta\"),\n", ")\n", @@ -497,6 +515,7 @@ }, { "cell_type": "markdown", + "id": "ed24d1ba-e2ab-4d02-b218-c61d9336080e", "metadata": {}, "source": [ "#### Leveraging `BitArray`s for performant post-processing\n", @@ -509,6 +528,7 @@ { "cell_type": "code", "execution_count": null, + "id": "7682a2d3-be68-4822-a59f-7a5e8e55c11a", "metadata": {}, "outputs": [], "source": [ @@ -527,6 +547,7 @@ }, { "cell_type": "markdown", + "id": "998bfcca-835e-42f9-9919-6474058e8970", "metadata": {}, "source": [ "`BitArray` offers a wide range of built-in methods to perform some of the operations commonly performed during post-processing:" @@ -535,18 +556,25 @@ { "cell_type": "code", "execution_count": null, + "id": "6f51c14f-434a-4c7e-8fc7-6acbcde68959", "metadata": {}, "outputs": [], "source": [ "# get a slice of `beta` to retrieve the first three bits\n", "beta_sl_bits = data.beta.slice_bits([0, 1, 2])\n", - "print(f\"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}.\")\n", + "print(\n", + " f\"The shape of `beta` after bit-wise slicing is {beta_sl_bits.array.shape}.\"\n", + ")\n", "print(f\"The bytes in `beta` after bit-wise slicing:\\n{beta_sl_bits.array}\\n\")\n", "\n", "# get a slice of `beta` to retrieve the bytes of the first five shots\n", "beta_sl_shots = data.beta.slice_shots([0, 1, 2, 3, 4])\n", - "print(f\"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}.\")\n", - "print(f\"The bytes in `beta` after shot-wise slicing:\\n{beta_sl_shots.array}\\n\")\n", + "print(\n", + " f\"The shape of `beta` after shot-wise slicing is {beta_sl_shots.array.shape}.\"\n", + ")\n", + "print(\n", + " f\"The bytes in `beta` after shot-wise slicing:\\n{beta_sl_shots.array}\\n\"\n", + ")\n", "\n", "# calculate the expectation value of diagonal operators on `beta`\n", "ops = [SparsePauliOp(\"ZZZZZZZZZ\"), SparsePauliOp(\"IIIIIIIIZ\")]\n", From 834b37d3ed009ef9ed6e050730f79aa4d4014a54 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Mon, 24 Mar 2025 16:15:57 -0400 Subject: [PATCH 06/14] optionally, convert away from the native BitArray format to a dictionary format --- docs/guides/primitive-input-output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index a3b7274564e..968aa46eb0b 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -452,7 +452,7 @@ "array = data.meas\n", "print(f\"BitArray: {array}\")\n", "\n", - "# use the `get_counts` method to look up the results\n", + "# optionally, convert away from the native BitArray format to a dictionary format\n", "counts = array.get_counts()\n", "print(f\"Counts: {counts}\")" ] From de896954f28427db4d8556344f40f492a16e34f8 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Mon, 24 Mar 2025 16:16:25 -0400 Subject: [PATCH 07/14] removed getcounts --- docs/guides/primitive-input-output.ipynb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 968aa46eb0b..dc0d3aafd96 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -497,10 +497,7 @@ "# as attributes using the registers' names\n", "data = result[0].data\n", "print(f\"BitArray for register 'alpha': {data.alpha}\")\n", - "print(f\"BitArray for register 'beta': {data.beta}\")\n", - "\n", - "print(f\"\\nCounts for register 'alpha': {data.alpha.get_counts()}\")\n", - "print(f\"\\nCounts for register 'beta': {data.beta.get_counts()}\")" + "print(f\"BitArray for register 'beta': {data.beta}\")" ] }, { From cd627fdba1130c3479925fb1388baa6be955ac43 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Mon, 24 Mar 2025 16:17:58 -0400 Subject: [PATCH 08/14] rewording --- docs/guides/primitive-input-output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index dc0d3aafd96..e675879be45 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -537,7 +537,7 @@ "id": "998bfcca-835e-42f9-9919-6474058e8970", "metadata": {}, "source": [ - "`BitArray` offers a wide range of built-in methods to perform some of the operations commonly performed during post-processing:" + "The `BitArray` class offers a range of methods to perform some common post-processing operations:" ] }, { From f1c9c7176226b2b32b6281816c91d82f0393d5fe Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Mon, 24 Mar 2025 16:18:34 -0400 Subject: [PATCH 09/14] keyword --> keu --- docs/guides/primitive-input-output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index e675879be45..40be0a13a9c 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -447,7 +447,7 @@ "data = result[0].data\n", "print(f\"Databin: {data}\")\n", "\n", - "# to access the BitArray, one can use the keyword \"meas\", which is the default\n", + "# to access the BitArray, one can use the key \"meas\", which is the default\n", "# name of the classical register when this is added by the `measure_all` method\n", "array = data.meas\n", "print(f\"BitArray: {array}\")\n", From a2645cbd28511b1b33c3a7281a98057100db0b67 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Mon, 24 Mar 2025 16:46:15 -0400 Subject: [PATCH 10/14] more progress --- docs/guides/primitive-input-output.ipynb | 52 ++++++++++++------------ 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 40be0a13a9c..03cc5ad31ff 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -417,7 +417,9 @@ "\n", "When a Sampler job is completed successfully, the returned [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult) object contains a list of [`SamplerPubResult`](../api/qiskit/qiskit.primitives.SamplerPubResult)s, one per PUB. The databins of these `SamplerPubResult`s are dict-like objects that contain one `BitArray`s per `ClassicalRegister` in the circuit.\n", "\n", - "The `get_counts` method of `BitArray` returns a dictionary mapping the sampled measurement outcomes to their counts. For example, for a circuit with measurement instructions added by the [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all), the counts can be access as follows:" + "The `BitArray` class is a container for ordered shot data. In more detail, it stores the information sampled bitstrings as bytes inside a two-dimensional array. The left-most axis of this array runs over ordered shots, while the right-most axis runs over bytes.\n", + "\n", + "As a first example, let us look at the following ten-qubit circuit:" ] }, { @@ -445,15 +447,31 @@ "\n", "# the databin contains one BitArray\n", "data = result[0].data\n", - "print(f\"Databin: {data}\")\n", + "print(f\"Databin: {data}\\n\")\n", "\n", "# to access the BitArray, one can use the key \"meas\", which is the default\n", "# name of the classical register when this is added by the `measure_all` method\n", "array = data.meas\n", - "print(f\"BitArray: {array}\")\n", - "\n", + "print(f\"BitArray: {array}\\n\")\n", + "print(f\"The shape of register `meas` is {data.meas.array.shape}.\\n\")\n", + "print(f\"The bytes in register `alpha`, shot by shot:\\n{data.meas.array}\\n\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As can be seen, ..." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "# optionally, convert away from the native BitArray format to a dictionary format\n", - "counts = array.get_counts()\n", + "counts = data.meas.get_counts()\n", "print(f\"Counts: {counts}\")" ] }, @@ -507,9 +525,7 @@ "source": [ "#### Leveraging `BitArray`s for performant post-processing\n", "\n", - "Dictionaries of counts allow for a quick and convenient lookup of results. However, `BitArray`s store the sampled bitstrings in `Numpy` arrays, not in dictionaries. Since arrays generally offer better performance compared to dictionaries, it is advisable to perform any post-processing directly on the `BitArray`s rather than on the counts.\n", - "\n", - "Every `BitArray` owns a two-dimensional array that stores the bitstrings as bytes (more precisely, as `numpy.uint8`s whose bits are the bitstrings that appear in the dictionary of counts). The left-most axis of said array runs over ordered shots, while the right-most axis runs over bytes (whose bits are the bitstrings that appear in the dictionary of counts). The following cell shows how to retrieve the array of bytes for the results obtained in the previous example, as well how to manipulate them as `Numpy` arrays:" + "Since arrays generally offer better performance compared to dictionaries, it is advisable to perform any post-processing directly on the `BitArray`s rather than on dictionaries of counts. The `BitArray` class offers a range of methods to perform some common post-processing operations:" ] }, { @@ -529,24 +545,8 @@ "mask = data.alpha.array == \"0b1\"\n", "ps_beta = data.beta[mask[:, 0]]\n", "print(f\"The shape of `beta` after post-selection is {ps_beta.array.shape}.\")\n", - "print(f\"The bytes in `beta` after post-selection:\\n{ps_beta.array}\")" - ] - }, - { - "cell_type": "markdown", - "id": "998bfcca-835e-42f9-9919-6474058e8970", - "metadata": {}, - "source": [ - "The `BitArray` class offers a range of methods to perform some common post-processing operations:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6f51c14f-434a-4c7e-8fc7-6acbcde68959", - "metadata": {}, - "outputs": [], - "source": [ + "print(f\"The bytes in `beta` after post-selection:\\n{ps_beta.array}\")\n", + "\n", "# get a slice of `beta` to retrieve the first three bits\n", "beta_sl_bits = data.beta.slice_bits([0, 1, 2])\n", "print(\n", From 0d26c245a23fcd18921f3ce1e9f4b1a139a4f86c Mon Sep 17 00:00:00 2001 From: abbycross Date: Mon, 24 Mar 2025 19:17:53 -0400 Subject: [PATCH 11/14] minor copyediting tweaks --- docs/guides/primitive-input-output.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index a3b7274564e..1cce9c6ec7a 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -417,7 +417,7 @@ "\n", "When a Sampler job is completed successfully, the returned [`PrimitiveResult`](../api/qiskit/qiskit.primitives.PrimitiveResult) object contains a list of [`SamplerPubResult`](../api/qiskit/qiskit.primitives.SamplerPubResult)s, one per PUB. The databins of these `SamplerPubResult`s are dict-like objects that contain one `BitArray`s per `ClassicalRegister` in the circuit.\n", "\n", - "The `get_counts` method of `BitArray` returns a dictionary mapping the sampled measurement outcomes to their counts. For example, for a circuit with measurement instructions added by the [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all), the counts can be access as follows:" + "The `get_counts` method of `BitArray` returns a dictionary mapping the sampled measurement outcomes to their counts. For example, for a circuit with measurement instructions added by [`QuantumCircuit.measure_all()`](../api/qiskit/qiskit.circuit.QuantumCircuit#measure_all), the counts can be accessed as follows:" ] }, { @@ -447,7 +447,7 @@ "data = result[0].data\n", "print(f\"Databin: {data}\")\n", "\n", - "# to access the BitArray, one can use the keyword \"meas\", which is the default\n", + "# to access the BitArray, use the keyword \"meas\", which is the default\n", "# name of the classical register when this is added by the `measure_all` method\n", "array = data.meas\n", "print(f\"BitArray: {array}\")\n", @@ -462,7 +462,7 @@ "id": "61c70ff7-7814-4535-9268-ac163c10121c", "metadata": {}, "source": [ - "When a circuit contains more than one classical registers, the results are stored in different `BitArray`s. The following example modifies the previous snippet by splitting the classical register into two distinct registers:" + "When a circuit contains more than one classical register, the results are stored in different `BitArray`s. The following example modifies the previous snippet by splitting the classical register into two distinct registers:" ] }, { @@ -512,7 +512,7 @@ "\n", "Dictionaries of counts allow for a quick and convenient lookup of results. However, `BitArray`s store the sampled bitstrings in `Numpy` arrays, not in dictionaries. Since arrays generally offer better performance compared to dictionaries, it is advisable to perform any post-processing directly on the `BitArray`s rather than on the counts.\n", "\n", - "Every `BitArray` owns a two-dimensional array that stores the bitstrings as bytes (more precisely, as `numpy.uint8`s whose bits are the bitstrings that appear in the dictionary of counts). The left-most axis of said array runs over ordered shots, while the right-most axis runs over bytes (whose bits are the bitstrings that appear in the dictionary of counts). The following cell shows how to retrieve the array of bytes for the results obtained in the previous example, as well how to manipulate them as `Numpy` arrays:" + "Every `BitArray` owns a two-dimensional array that stores the bitstrings as bytes (more precisely, as `numpy.uint8`s whose bits are the bitstrings that appear in the dictionary of counts). The left-most axis of said array runs over ordered shots, while the right-most axis runs over bytes (whose bits are the bitstrings that appear in the dictionary of counts). The following cell shows how to retrieve the array of bytes for the results obtained in the previous example, as well as how to manipulate them as `Numpy` arrays:" ] }, { From 6a9921264cf83d0686c8260c9293a9fa8e86b862 Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Tue, 25 Mar 2025 10:08:18 -0400 Subject: [PATCH 12/14] fineshed up narrative --- docs/guides/primitive-input-output.ipynb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 03cc5ad31ff..6f4166f85d0 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -461,7 +461,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "As can be seen, ..." + "It can sometimes be convenient to convert away from the bytes format in the `BitArray` to bitstrings. The `get_count` method returns a dictionary mapping bitstrings to the number of times that they occurred." ] }, { From 326cf34a7f472ffda9309721af85a13f91f8c5be Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Tue, 25 Mar 2025 10:22:07 -0400 Subject: [PATCH 13/14] lint --- docs/guides/primitive-input-output.ipynb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index e265cb1beb0..3dfb1139c4c 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -459,6 +459,7 @@ }, { "cell_type": "markdown", + "id": "9739b9d5-26e0-45a0-b301-f206c9f95fd3", "metadata": {}, "source": [ "It can sometimes be convenient to convert away from the bytes format in the `BitArray` to bitstrings. The `get_count` method returns a dictionary mapping bitstrings to the number of times that they occurred." @@ -467,6 +468,7 @@ { "cell_type": "code", "execution_count": null, + "id": "d3654210-34f9-4db4-a08b-28cb01f5d433", "metadata": {}, "outputs": [], "source": [ From 66f9151bc7dbc17bba8fcb43f71c1dd50b63f11b Mon Sep 17 00:00:00 2001 From: SamFerracin Date: Tue, 25 Mar 2025 10:28:37 -0400 Subject: [PATCH 14/14] one can use --> use --- docs/guides/primitive-input-output.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/guides/primitive-input-output.ipynb b/docs/guides/primitive-input-output.ipynb index 3dfb1139c4c..daf15bcdcd6 100644 --- a/docs/guides/primitive-input-output.ipynb +++ b/docs/guides/primitive-input-output.ipynb @@ -449,8 +449,8 @@ "data = result[0].data\n", "print(f\"Databin: {data}\\n\")\n", "\n", - "# to access the BitArray, one can use the key \"meas\", which is the default\n", - "# name of the classical register when this is added by the `measure_all` method\n", + "# to access the BitArray, use the key \"meas\", which is the default name of\n", + "# the classical register when this is added by the `measure_all` method\n", "array = data.meas\n", "print(f\"BitArray: {array}\\n\")\n", "print(f\"The shape of register `meas` is {data.meas.array.shape}.\\n\")\n",