diff --git a/docs/addons/qiskit-addon-obp/_toc.json b/docs/addons/qiskit-addon-obp/_toc.json
new file mode 100644
index 00000000000..5546faff959
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/_toc.json
@@ -0,0 +1,64 @@
+{
+ "parentUrl": "/docs/guides/addons",
+ "parentLabel": "Documentation",
+ "title": "Operator backpropagation (OBP) 0.3.0",
+ "collapsed": true,
+ "children": [
+ {
+ "title": "",
+ "children": [
+ {
+ "title": "Home",
+ "url": "/docs/addons/qiskit-addon-obp"
+ },
+ {
+ "title": "Installation instructions",
+ "url": "/docs/addons/qiskit-addon-obp/install"
+ },
+ {
+ "title": "Guides",
+ "children": [
+ {
+ "title": "Using different Lp-norms for Pauli term truncation",
+ "url": "/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm"
+ },
+ {
+ "title": "Classically simulating circuits with OBP",
+ "url": "/docs/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp"
+ },
+ {
+ "title": "Truncating Pauli terms during backpropagation",
+ "url": "/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms"
+ }
+ ],
+ "url": "/docs/addons/qiskit-addon-obp/how-tos/index"
+ },
+ {
+ "title": "GitHub",
+ "url": "https://github.com/Qiskit/qiskit-addon-obp"
+ }
+ ],
+ "collapsible": false
+ },
+ {
+ "title": "Tutorials",
+ "collapsible": false,
+ "children": [
+ {
+ "title": "Reducing depth of circuits with operator backpropagation",
+ "url": "/docs/addons/qiskit-addon-obp/tutorials/01-getting-started"
+ }
+ ]
+ },
+ {
+ "title": "API reference",
+ "collapsible": false,
+ "children": [
+ {
+ "title": "Operator backpropagation (OBP) API reference",
+ "url": "/docs/api/qiskit-addon-obp"
+ }
+ ]
+ }
+ ]
+}
diff --git a/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm.ipynb b/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm.ipynb
new file mode 100644
index 00000000000..cdc20febc8a
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm.ipynb
@@ -0,0 +1,457 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "frontmatter",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "title: \"Using different Lp-norms for Pauli term truncation\"\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8a7c9df5-d836-4ecb-8aec-8a1f2faa017a",
+ "metadata": {},
+ "source": [
+ "# Using different Lp-norms for Pauli term truncation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5958e635-64c1-4599-82ca-225b83312906",
+ "metadata": {},
+ "source": [
+ "**Note:** If you have not yet read the how-to guide on [truncating Pauli terms](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms) you should do so before reading this guide."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2780c587-cd2a-4398-8faa-de5e0c661a10",
+ "metadata": {},
+ "source": [
+ "You are already familiar with the truncation of low-weight Pauli terms that is built into the [backpropagate](/docs/api/qiskit-addon-obp/qiskit-addon-obp#backpropagate) method based on a specified [TruncationErrorBudget](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget).\n",
+ "In this guide, you will learn about its additional keyword argument `p_norm`, which can be used to change the Lp-norm used to estimate the error incurred by the truncated Pauli terms."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "872aeae7-0312-41db-8ebb-f7b02838b54d",
+ "metadata": {},
+ "source": [
+ "## Constructing an example circuit\n",
+ "\n",
+ "For the purposes of this guide, we will use the same example circuit as in the [Pauli term truncation guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "d6f281d4-706e-41ad-b7b5-bc7ebe106996",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import rustworkx.generators\n",
+ "from qiskit.synthesis import LieTrotter\n",
+ "from qiskit_addon_utils.problem_generators import (\n",
+ " PauliOrderStrategy,\n",
+ " generate_time_evolution_circuit,\n",
+ " generate_xyz_hamiltonian,\n",
+ ")\n",
+ "from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types\n",
+ "\n",
+ "# we generate a linear chain of 10 qubits\n",
+ "num_qubits = 10\n",
+ "linear_chain = rustworkx.generators.path_graph(num_qubits)\n",
+ "\n",
+ "# we use an arbitrary XY model\n",
+ "hamiltonian = generate_xyz_hamiltonian(\n",
+ " linear_chain,\n",
+ " coupling_constants=(0.05, 0.02, 0.0),\n",
+ " ext_magnetic_field=(0.02, 0.08, 0.0),\n",
+ " pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,\n",
+ ")\n",
+ "# we evolve for some time\n",
+ "circuit = generate_time_evolution_circuit(hamiltonian, synthesis=LieTrotter(reps=3), time=2.0)\n",
+ "# slice the circuit by gate type\n",
+ "slices = slice_by_gate_types(circuit)\n",
+ "\n",
+ "# for visualization purposes only, we recombine the slices with barriers between them and draw the resulting circuit\n",
+ "combine_slices(slices, include_barriers=True).draw(\"mpl\", fold=50, scale=0.6)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7a7c7299-0c82-4279-b3dc-4f39e8dddd7e",
+ "metadata": {},
+ "source": [
+ "The observable that we will look at is the total magnetization:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "ea9e7adc-b003-4762-a889-10e8d84a63d5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit.quantum_info import SparsePauliOp\n",
+ "\n",
+ "obs = SparsePauliOp.from_sparse_list(\n",
+ " [(\"Z\", [i], 1.0) for i in range(num_qubits)], num_qubits=num_qubits\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "423150d8-80d2-4b80-9665-033808d30272",
+ "metadata": {},
+ "source": [
+ "For reference, we compute the exact expectation value:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "4dc72426-2eb5-4f8d-8bba-79650da06b54",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "9.318197859862146\n"
+ ]
+ }
+ ],
+ "source": [
+ "from qiskit.primitives import StatevectorEstimator\n",
+ "\n",
+ "estimator = StatevectorEstimator()\n",
+ "job = estimator.run([(circuit, obs)])\n",
+ "res = job.result()\n",
+ "exact_exp = res[0].data.evs\n",
+ "print(exact_exp)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d21490c0-e86c-4fe0-af90-c9625813e0c0",
+ "metadata": {},
+ "source": [
+ "## Using the L1 norm\n",
+ "\n",
+ "By default, and as you have already seen in the [other how-to guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms), `p_norm=1` which means that the error is estimated as:\n",
+ "$$\n",
+ "|\\langle\\psi|\\Delta|\\psi\\rangle| \\leq \\sum_{P\\in\\mathcal{T}} |c_P|\n",
+ "$$\n",
+ "where $\\psi$ is the quantum state, $\\Delta$ is the real difference between the exact and truncated observable (which is unknown), $\\mathcal{T}$ is the set of Pauli terms that were truncated and $c_P$ is the Pauli terms coefficient.\n",
+ "This inequality is rigorous but a very loose upper bound for most scenarios."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "425293ce-49fb-4bd0-8d2f-44a3a43db967",
+ "metadata": {},
+ "source": [
+ "For this simple guide, we will backpropagate 6 slices of our example circuit.\n",
+ "We will use a constant error per slice of `0.001`. This value is to be understood as the budget within the given `p_norm`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "b46531b3-055b-4112-903a-11f2dd52a9ac",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "from qiskit_addon_obp.utils.truncating import setup_budget\n",
+ "\n",
+ "l1_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=1)\n",
+ "print(l1_truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "bc252678-58a0-407e-91fe-b7c545d57ae8",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 6 circuit slices.\n",
+ "New observable contains 116 terms and 10 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "from qiskit_addon_obp import backpropagate\n",
+ "\n",
+ "max_slices = 6\n",
+ "l1_bp_obs, l1_remaining_slices, l1_metadata = backpropagate(\n",
+ " obs, slices[-max_slices:], truncation_error_budget=l1_truncation_error_budget\n",
+ ")\n",
+ "l1_reduced_circuit = combine_slices(slices[:-max_slices] + l1_remaining_slices)\n",
+ "print(f\"Backpropagated {max_slices - len(l1_remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(l1_bp_obs)} terms and {len(l1_bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9f2782f8-1e93-4319-af26-dc467d7ed459",
+ "metadata": {},
+ "source": [
+ "We can now compute the expectation value of the backpropagated observable, as well as the error with respect to the exact reference:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "5be8c457-6569-452d-8502-0fc5e39bf918",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "9.317869899338842 0.00032796052330397174\n"
+ ]
+ }
+ ],
+ "source": [
+ "estimator = StatevectorEstimator()\n",
+ "job = estimator.run([(l1_reduced_circuit, l1_bp_obs)])\n",
+ "res = job.result()\n",
+ "l1_exp = res[0].data.evs\n",
+ "l1_error = exact_exp - l1_exp\n",
+ "print(l1_exp, l1_error)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c0eccc5b-dc71-45b3-ad79-3d3dcb491f2d",
+ "metadata": {},
+ "source": [
+ "Finally, we can plot the error incurred during the backpropagation of each slice, as well as the accumulated error.\n",
+ "The accumulated error is simply the accumulated sum of the slice errors. We can see clearly that the accumulated error is a very loose upper bound to the actual error."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "ff72e77f-e2c0-4cce-8575-c5aa587f78fb",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from matplotlib import pyplot as plt\n",
+ "from qiskit_addon_obp.utils.visualization import (\n",
+ " plot_accumulated_error,\n",
+ " plot_slice_errors,\n",
+ ")\n",
+ "\n",
+ "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
+ "axes[1].plot([6], [l1_error], \"x\", color=\"red\", label=\"actual error\")\n",
+ "plot_slice_errors(l1_metadata, axes[0])\n",
+ "plot_accumulated_error(l1_metadata, axes[1])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10dd1139-c597-43c2-9f58-0a0988de768f",
+ "metadata": {},
+ "source": [
+ "## Using the L2 norm\n",
+ "\n",
+ "One can argue that the L2 norm is a better approximation of the incurred error than the L1 norm.\n",
+ "That is because we can assume the quantum state $|\\psi\\rangle$ to be drawn from a Haar random ensemble in which case the incurred error follows a distribution with a vanishing mean and a variance that can be approximately bound by the L2 norm:\n",
+ "$$\n",
+ "|\\langle\\psi|\\Delta|\\psi\\rangle| \\lesssim \\left( \\sum_{P\\in\\mathcal{T}} |c_P|^2 \\right)^{1/2}\n",
+ "$$\n",
+ "While the bound is not rigorous, it will only be violated in pathological cases."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f302d808-b341-49cb-8bbd-b03f2bfc6dd6",
+ "metadata": {},
+ "source": [
+ "Once again, we will backpropagate 6 slices of our example circuit using a maximum error per slice of `0.001` (this time, interpreted on the L2 norm)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "c7b7e21f-22e4-479d-954c-39400974f8c1",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=2)\n"
+ ]
+ }
+ ],
+ "source": [
+ "l2_truncation_error_budget = setup_budget(max_error_per_slice=0.001, p_norm=2)\n",
+ "print(l2_truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "e92680c5-497e-47e0-ae0f-69465028dd66",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 6 circuit slices.\n",
+ "New observable contains 84 terms and 6 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "max_slices = 6\n",
+ "l2_bp_obs, l2_remaining_slices, l2_metadata = backpropagate(\n",
+ " obs, slices[-max_slices:], truncation_error_budget=l2_truncation_error_budget\n",
+ ")\n",
+ "l2_reduced_circuit = combine_slices(slices[:-max_slices] + l2_remaining_slices)\n",
+ "print(f\"Backpropagated {max_slices - len(l2_remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(l2_bp_obs)} terms and {len(l2_bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a05f992a-3a38-4912-9e1b-bcd40463c35e",
+ "metadata": {},
+ "source": [
+ "Once again, we can compute the expectation value of the backpropagated observable, as well as the error with respect to the exact reference:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "73fe737a-c3b0-4639-83dc-d614521dd77a",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "9.317829770853422 0.00036808900872387085\n"
+ ]
+ }
+ ],
+ "source": [
+ "estimator = StatevectorEstimator()\n",
+ "job = estimator.run([(l2_reduced_circuit, l2_bp_obs)])\n",
+ "res = job.result()\n",
+ "l2_exp = res[0].data.evs\n",
+ "l2_error = exact_exp - l2_exp\n",
+ "print(l2_exp, l2_error)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "81fd099c-f144-4c59-9b76-72fd8b06d8ad",
+ "metadata": {},
+ "source": [
+ "Plotting the incurred errors per slice and the accumulated error yields a similar picture to before."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "fe7f454c-7d79-4c61-b1dd-6a98564fb5f1",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
+ "axes[1].plot([6], [l2_error], \"x\", color=\"red\", label=\"actual error\")\n",
+ "plot_slice_errors(l2_metadata, axes[0])\n",
+ "plot_accumulated_error(l2_metadata, axes[1])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9ef23a93-5c3b-48be-a02a-e666179c8c65",
+ "metadata": {},
+ "source": [
+ "Note, that the accumulated error is once again simply the sum of the single slice errors. This is yet another loose bound due to the Minkowski inequality since we have to compute this bound recursively:\n",
+ "$$\n",
+ "|\\langle\\psi|\\Delta_{i}|\\psi\\rangle| \\leq |\\langle\\psi|\\tilde{\\Delta}_{i-1}|\\psi\\rangle| + \\left( \\sum_{P\\in\\mathcal{T_i}} |c_P|^2 \\right)^{1/2} = |\\langle\\psi|\\tilde{\\Delta}_{i}|\\psi\\rangle|\n",
+ "$$\n",
+ "where the new subscript $i$ indicates the current slice iteration making $\\Delta_i$ the actual error at backpropagation iteration $i$, $\\tilde{\\Delta}_{i-1}$ the approximated truncation error from iteration $i-1$ and $\\mathcal{T}_i$ the set of Pauli terms truncated at iteration $i$."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/addons/qiskit-addon-obp/how-tos/index.mdx b/docs/addons/qiskit-addon-obp/how-tos/index.mdx
new file mode 100644
index 00000000000..b61973645bc
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/how-tos/index.mdx
@@ -0,0 +1,12 @@
+---
+title: "How-To Guides - Qiskit addon: operator backpropagation (OBP) 0.3.0"
+---
+
+# How-To Guides
+
+This page summarizes the available how-to guides.
+
+* [Using different Lp-norms for Pauli term truncation](bound-error-using-p-norm)
+* [Classically simulating circuits with OBP](simulating-circuits-with-obp)
+* [Truncating Pauli terms during backpropagation](truncate-operator-terms)
+
diff --git a/docs/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp.ipynb b/docs/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp.ipynb
new file mode 100644
index 00000000000..b3b8a185e16
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp.ipynb
@@ -0,0 +1,547 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "frontmatter",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "title: \"Classically simulating circuits with OBP\"\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "26d926db-2e2f-41ff-947e-9757a9f426d8",
+ "metadata": {},
+ "source": [
+ "# Classically simulating circuits with OBP"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7642d6db-fe6d-4506-8e49-6c840f166e2b",
+ "metadata": {},
+ "source": [
+ "In this guide, you will learn how to classically simulate `QuantumCircuit` instances to estimate expectation values entirely through the means of OBP.\n",
+ "\n",
+ "Since OBP will take an observable and backpropagate it through a given circuit, the \"simulation\" of a circuit amounts to computing the expectation value of the target observable with respect to this circuit.\n",
+ "As you will see later, the `qiskit-addon-obp` package is even capable of handling simple noise models, allowing you to compute noisy expectation values, too!"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2e996d79-36fd-4a71-bd6b-737fc8e44987",
+ "metadata": {},
+ "source": [
+ "## Constructing an example circuit\n",
+ "\n",
+ "For the purposes of this guide, we will use the same example circuit as in the [Pauli term truncation guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms):"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "b7946767-904b-422e-b076-7f956f3fdb70",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import rustworkx.generators\n",
+ "from qiskit.synthesis import LieTrotter\n",
+ "from qiskit_addon_utils.problem_generators import (\n",
+ " PauliOrderStrategy,\n",
+ " generate_time_evolution_circuit,\n",
+ " generate_xyz_hamiltonian,\n",
+ ")\n",
+ "from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types\n",
+ "\n",
+ "# we generate a linear chain of 10 qubits\n",
+ "num_qubits = 10\n",
+ "linear_chain = rustworkx.generators.path_graph(num_qubits)\n",
+ "\n",
+ "# we use an arbitrary XY model\n",
+ "hamiltonian = generate_xyz_hamiltonian(\n",
+ " linear_chain,\n",
+ " coupling_constants=(0.05, 0.02, 0.0),\n",
+ " ext_magnetic_field=(0.02, 0.08, 0.0),\n",
+ " pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,\n",
+ ")\n",
+ "# we evolve for some time\n",
+ "circuit = generate_time_evolution_circuit(hamiltonian, synthesis=LieTrotter(reps=3), time=2.0)\n",
+ "# slice the circuit by gate type\n",
+ "slices = slice_by_gate_types(circuit)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "63bcf4d0-7abe-4467-bc2b-f95d8d66dee5",
+ "metadata": {},
+ "source": [
+ "However, the above is purely the circuit describing the time evolution under a chosen Hamiltonian.\n",
+ "We also need an initial state to start from, with respect to which we compute the expectation values of our observable.\n",
+ "\n",
+ "Of course, we could choose the all-zero (or vacuum) state as our initial state, but to show how one would insert their own initial state, we choose a different one below.\n",
+ "\n",
+ "One possibility, would be to prepend the initial state to our time-evolution circuit above: `circuit.compose(initial_state, front=True)`.\n",
+ "But since we have already sliced our `circuit`, it is easier to simply insert the initial state as the first slice, which we do below.\n",
+ "\n",
+ "In this way, we can simply replace the first slice with another initial state, if we want to exchange that in the future, without having to recompute our slices."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "ffaa07e2-af75-4424-9e7b-61c81b3bb9ff",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit.circuit import QuantumCircuit\n",
+ "\n",
+ "initial_state = QuantumCircuit(num_qubits)\n",
+ "for i in range(0, num_qubits, 2):\n",
+ " initial_state.x(i)\n",
+ "\n",
+ "slices.insert(0, initial_state)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "8758f393-8691-4116-94ca-1185c6188bb4",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# for visualization purposes only, we recombine the slices with barriers between them and draw the resulting circuit\n",
+ "combine_slices(slices, include_barriers=True).draw(\"mpl\", fold=50, scale=0.6)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bc9d51da-0a4e-46e2-8a5a-3501456aeb81",
+ "metadata": {},
+ "source": [
+ "## Simulating a noiseless expectation value"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1125cb39-4eda-4928-86b2-24e1872ed87e",
+ "metadata": {},
+ "source": [
+ "As our target observable, we choose the `ZZ` observable on the central qubits:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "ee438fc3-8196-43d9-bcdb-956fd9ec4cf4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit.quantum_info import SparsePauliOp\n",
+ "\n",
+ "obs = SparsePauliOp(\"IIIIZZIIII\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d06f10b0-683e-476e-9ac3-5c110364a0c8",
+ "metadata": {},
+ "source": [
+ "At this point, we are already set to classically simulate the expectation value using OBP.\n",
+ "To do so, we simply provide the _all_ the slices to the `backpropagate` method, like so:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "6402f5f5-08e0-4d05-ab2b-0c8863e9f966",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit_addon_obp import backpropagate\n",
+ "\n",
+ "vacuum_state_obs, _, metadata = backpropagate(obs, slices)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7b2d555e-7fab-49f1-82d4-74a770f1be75",
+ "metadata": {},
+ "source": [
+ "We have now backpropagated our target observable `obs` through the _entire_ circuit (**including** the `initial_state` which we placed on `slices[0]`) resulting in a new `SparsePauliOp` whose expectation value we obtain by projecting it on the _vacuum state_ (`|00...00>`).\n",
+ "\n",
+ "This can be achieved in a straight forward manner by summing up the coefficients of all Pauli terms defined in the computational basis:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "63ae0edc-e8f6-467b-896a-ddd451d40a9a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "np.complex128(-0.8285688012239535+4.9487770271457865e-20j)"
+ ]
+ },
+ "execution_count": 6,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vacuum_state_obs.coeffs[~vacuum_state_obs.paulis.x.any(axis=1)].sum()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "107a0d89-323c-469b-8442-5e3f48169cd4",
+ "metadata": {},
+ "source": [
+ "As a sanity check (and to prove that this works) we can compare our result against Qiskit's `Statevector`:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "f3cb999d-688a-4e82-bf12-d958ef0e2ec0",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "np.complex128(-0.8285687255430366+0j)"
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from qiskit.quantum_info import Statevector\n",
+ "\n",
+ "Statevector(combine_slices(slices)).expectation_value(obs)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "02207cde-e89e-4cf6-8b3e-727a750d245c",
+ "metadata": {},
+ "source": [
+ "### Some notes on performance\n",
+ "\n",
+ "The computational efficiency of the `backpropagate` call above will heavily depend on many things, including:\n",
+ "- the structure of the `circuit`\n",
+ "- the method of slicing the circuit\n",
+ "- the target observable\n",
+ "- the truncation parameters\n",
+ "\n",
+ "Since the `backpropagate` method simplifies the observable after every _slice_ has been applied, the number of gates in a slice can dramatically influence the computational burden.\n",
+ "The most aggressive strategy in terms of operator simplification can be achieved by slicing your circuit into slices of individual gates.\n",
+ "\n",
+ "Additionally, you can leverage all of the truncation mechanism built into the `backpropagate` method.\n",
+ "We did not do so above, effectively resulting in an exact expectation value, but you can learn how to in the [Pauli term truncation guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ba37abf-0f78-4053-8995-2ab8340237ab",
+ "metadata": {},
+ "source": [
+ "## Simulating a noisy expectation value"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3a5e753b-c479-42d9-84d0-5ce4860ec091",
+ "metadata": {},
+ "source": [
+ "The `qiskit-addon-obp` package also supports handling of noise models in the form of `PauliLindbladError`s.\n",
+ "This is especially useful when you have characterized the noise model of the 2-qubit layers in your circuit, for example using the [`NoiseLearner`](/api/qiskit-ibm-runtime/noise-learner-noise-learner).\n",
+ "\n",
+ "In this section, you will see how you can use the `LayerError` objects returned by the `NoiseLearner` to compute noisy expectation values using OBP."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2d50dbf1-3abd-4945-bed9-1ed49758e9cc",
+ "metadata": {},
+ "source": [
+ "### Obtaining a noise model\n",
+ "\n",
+ "Normally, you would execute the `NoiseLearner` to obtain a noise model of your specific circuit.\n",
+ "To avoid complexity (and randomness) in this tutorial, we will refrain from doing so, and instead hard-code some noise model for our circuit below.\n",
+ "\n",
+ "However, we make sure that the structure of our data matches that of the [`NoiseLearnerResult`](/api/qiskit-ibm-runtime/noise-learner-result)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "54a7aba1-a7ee-4d0f-b95b-095158511ce0",
+ "metadata": {},
+ "source": [
+ "In its current (non-transpiled) form, our circuit contains 4 unique layers of 2-qubit gates:\n",
+ "- `slices[1]`: which has `Rxx` gates acting on all odd pairs of qubits\n",
+ "- `slices[2]`: which has `Rxx` gates acting on all even pairs of qubits\n",
+ "- `slices[3]`: which has `Ryy` gates acting on all odd pairs of qubits\n",
+ "- `slices[4]`: which has `Ryy` gates acting on all even pairs of qubits\n",
+ "\n",
+ "In the cell below, we manually construct 4 `LayerError` instances for each one of these layers with some randomized error rates."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "e7a3b1f9-b60c-4735-8d6b-5151a7c5b92b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import numpy as np\n",
+ "from qiskit.quantum_info import PauliList\n",
+ "from qiskit_ibm_runtime.utils.noise_learner_result import LayerError, PauliLindbladError\n",
+ "\n",
+ "# fmt: off\n",
+ "pauli_errors_even = ['IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIXX', 'IIIIIIIIXY', 'IIIIIIIIXZ', 'IIIIIIIIYI', 'IIIIIIIIYX', 'IIIIIIIIYY', 'IIIIIIIIYZ', 'IIIIIIIIZI', 'IIIIIIIIZX', 'IIIIIIIIZY', 'IIIIIIIIZZ', 'IIIIIIIXII', 'IIIIIIIXXI', 'IIIIIIIXYI', 'IIIIIIIXZI', 'IIIIIIIYII', 'IIIIIIIYXI', 'IIIIIIIYYI', 'IIIIIIIYZI', 'IIIIIIIZII', 'IIIIIIIZXI', 'IIIIIIIZYI', 'IIIIIIIZZI', 'IIIIIIXIII', 'IIIIIIXXII', 'IIIIIIXYII', 'IIIIIIXZII', 'IIIIIIYIII', 'IIIIIIYXII', 'IIIIIIYYII', 'IIIIIIYZII', 'IIIIIIZIII', 'IIIIIIZXII', 'IIIIIIZYII', 'IIIIIIZZII', 'IIIIIXIIII', 'IIIIIXXIII', 'IIIIIXYIII', 'IIIIIXZIII', 'IIIIIYIIII', 'IIIIIYXIII', 'IIIIIYYIII', 'IIIIIYZIII', 'IIIIIZIIII', 'IIIIIZXIII', 'IIIIIZYIII', 'IIIIIZZIII', 'IIIIXIIIII', 'IIIIXXIIII', 'IIIIXYIIII', 'IIIIXZIIII', 'IIIIYIIIII', 'IIIIYXIIII', 'IIIIYYIIII', 'IIIIYZIIII', 'IIIIZIIIII', 'IIIIZXIIII', 'IIIIZYIIII', 'IIIIZZIIII', 'IIIXIIIIII', 'IIIXXIIIII', 'IIIXYIIIII', 'IIIXZIIIII', 'IIIYIIIIII', 'IIIYXIIIII', 'IIIYYIIIII', 'IIIYZIIIII', 'IIIZIIIIII', 'IIIZXIIIII', 'IIIZYIIIII', 'IIIZZIIIII', 'IIXIIIIIII', 'IIXXIIIIII', 'IIXYIIIIII', 'IIXZIIIIII', 'IIYIIIIIII', 'IIYXIIIIII', 'IIYYIIIIII', 'IIYZIIIIII', 'IIZIIIIIII', 'IIZXIIIIII', 'IIZYIIIIII', 'IIZZIIIIII', 'IXIIIIIIII', 'IXXIIIIIII', 'IXYIIIIIII', 'IXZIIIIIII', 'IYIIIIIIII', 'IYXIIIIIII', 'IYYIIIIIII', 'IYZIIIIIII', 'IZIIIIIIII', 'IZXIIIIIII', 'IZYIIIIIII', 'IZZIIIIIII', 'XIIIIIIIII', 'XXIIIIIIII', 'XYIIIIIIII', 'XZIIIIIIII', 'YIIIIIIIII', 'YXIIIIIIII', 'YYIIIIIIII', 'YZIIIIIIII', 'ZIIIIIIIII', 'ZXIIIIIIII', 'ZYIIIIIIII', 'ZZIIIIIIII']\n",
+ "pauli_errors_odd = ['IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIXXI', 'IIIIIIIXYI', 'IIIIIIIXZI', 'IIIIIIIYII', 'IIIIIIIYXI', 'IIIIIIIYYI', 'IIIIIIIYZI', 'IIIIIIIZII', 'IIIIIIIZXI', 'IIIIIIIZYI', 'IIIIIIIZZI', 'IIIIIIXIII', 'IIIIIIXXII', 'IIIIIIXYII', 'IIIIIIXZII', 'IIIIIIYIII', 'IIIIIIYXII', 'IIIIIIYYII', 'IIIIIIYZII', 'IIIIIIZIII', 'IIIIIIZXII', 'IIIIIIZYII', 'IIIIIIZZII', 'IIIIIXIIII', 'IIIIIXXIII', 'IIIIIXYIII', 'IIIIIXZIII', 'IIIIIYIIII', 'IIIIIYXIII', 'IIIIIYYIII', 'IIIIIYZIII', 'IIIIIZIIII', 'IIIIIZXIII', 'IIIIIZYIII', 'IIIIIZZIII', 'IIIIXIIIII', 'IIIIXXIIII', 'IIIIXYIIII', 'IIIIXZIIII', 'IIIIYIIIII', 'IIIIYXIIII', 'IIIIYYIIII', 'IIIIYZIIII', 'IIIIZIIIII', 'IIIIZXIIII', 'IIIIZYIIII', 'IIIIZZIIII', 'IIIXIIIIII', 'IIIXXIIIII', 'IIIXYIIIII', 'IIIXZIIIII', 'IIIYIIIIII', 'IIIYXIIIII', 'IIIYYIIIII', 'IIIYZIIIII', 'IIIZIIIIII', 'IIIZXIIIII', 'IIIZYIIIII', 'IIIZZIIIII', 'IIXIIIIIII', 'IIXXIIIIII', 'IIXYIIIIII', 'IIXZIIIIII', 'IIYIIIIIII', 'IIYXIIIIII', 'IIYYIIIIII', 'IIYZIIIIII', 'IIZIIIIIII', 'IIZXIIIIII', 'IIZYIIIIII', 'IIZZIIIIII', 'IXIIIIIIII', 'IXXIIIIIII', 'IXYIIIIIII', 'IXZIIIIIII', 'IYIIIIIIII', 'IYXIIIIIII', 'IYYIIIIIII', 'IYZIIIIIII', 'IZIIIIIIII', 'IZXIIIIIII', 'IZYIIIIIII', 'IZZIIIIIII']\n",
+ "# fmt: on\n",
+ "\n",
+ "np.random.seed(42)\n",
+ "\n",
+ "layer_error_odd_xx = LayerError(\n",
+ " circuit=slices[1],\n",
+ " qubits=list(range(num_qubits)),\n",
+ " error=PauliLindbladError(\n",
+ " PauliList(pauli_errors_odd),\n",
+ " 0.0001 + 0.0004 * np.random.rand(len(pauli_errors_odd)),\n",
+ " ),\n",
+ ")\n",
+ "\n",
+ "layer_error_even_xx = LayerError(\n",
+ " circuit=slices[2],\n",
+ " qubits=list(range(num_qubits)),\n",
+ " error=PauliLindbladError(\n",
+ " PauliList(pauli_errors_even),\n",
+ " 0.0001 + 0.0004 * np.random.rand(len(pauli_errors_even)),\n",
+ " ),\n",
+ ")\n",
+ "\n",
+ "layer_error_odd_yy = LayerError(\n",
+ " circuit=slices[3],\n",
+ " qubits=list(range(num_qubits)),\n",
+ " error=PauliLindbladError(\n",
+ " PauliList(pauli_errors_odd),\n",
+ " 0.0001 + 0.0004 * np.random.rand(len(pauli_errors_odd)),\n",
+ " ),\n",
+ ")\n",
+ "\n",
+ "layer_error_even_yy = LayerError(\n",
+ " circuit=slices[4],\n",
+ " qubits=list(range(num_qubits)),\n",
+ " error=PauliLindbladError(\n",
+ " PauliList(pauli_errors_even),\n",
+ " 0.0001 + 0.0004 * np.random.rand(len(pauli_errors_even)),\n",
+ " ),\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a9dff7ad-2c7a-4fc2-b7b5-5aa1a0e9b9db",
+ "metadata": {},
+ "source": [
+ "If you would have used the `NoiseLearner` to identify the unique 2-qubit gate layers of your circuit and characterize their noise, you would obtain a `NoiseLearnerResult` object.\n",
+ "This result would contain a list of `LayerError` objects, just like the ones we have manually constructed above.\n",
+ "\n",
+ "For each unique 2-qubit layer, the `LayerError` contains:\n",
+ "- the `QuantumCircuit` representing that 2-qubit gate layer\n",
+ "- the qubit indices which this circuit is acting upon\n",
+ "- the `PauliLindbladError` which represents the characterized noise model of this layer\n",
+ "\n",
+ "The `PauliLindbladError` will contain two objects:\n",
+ "- the list of Pauli errors that have been characterized\n",
+ "- the error rates corresponding to each one of those Pauli errors\n",
+ "\n",
+ "Normally, the list of Pauli errors will be sparse. More specifically, it will contain the single-qubit Pauli errors on all qubits that have gates acting upon them as well as the two-qubit Pauli errors on all those qubits that are connected."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "9c3a1a9d-6fcb-4345-b9ba-e86e2e362a7e",
+ "metadata": {},
+ "source": [
+ "### Inserting the noisy layers into our circuit\n",
+ "\n",
+ "In the previous section, we have specifically constructed one `LayerError` for each of our known unique 2-qubit gate layers.\n",
+ "This means, we know which `LayerError` matches a specific one of our slices exactly.\n",
+ "\n",
+ "Normally, when using the `LayerError`, you will need to figure out what the unique 2-qubit gate layer is, and where it occurs inside of your circuit.\n",
+ "You will then need to adjust your `circuit` and/or `slices` to insert the `LayerError` accordingly.\n",
+ "How to do this in the general case, is beyond the scope of this how-to guide.\n",
+ "**TODO: link to external documentation, once it exists!**"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dcb5937e-83d8-41ab-a526-d6677201c8f4",
+ "metadata": {},
+ "source": [
+ "Here, our life is simpler because we know which slice a `LayerError` corresponds to.\n",
+ "Therefore, it is now just a matter of inserting new slices to represent the noise.\n",
+ "\n",
+ "Note, that we must wrap each `PauliLindbladError` from `LayerError.error` in a `PauliLindbladErrorInstruction` for it to be a valid `QuantumCircuit` instruction."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "1f5c4655-f6af-439f-8475-0ecf0c673de5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit_addon_obp.utils.noise import PauliLindbladErrorInstruction\n",
+ "\n",
+ "noisy_slices = []\n",
+ "for slice_ in slices:\n",
+ " if slice_ == layer_error_even_xx.circuit:\n",
+ " noisy_slices.append(\n",
+ " QuantumCircuit.from_instructions(\n",
+ " [(PauliLindbladErrorInstruction(layer_error_even_xx.error), slice_.qubits)]\n",
+ " )\n",
+ " )\n",
+ " elif slice_ == layer_error_odd_xx.circuit:\n",
+ " noisy_slices.append(\n",
+ " QuantumCircuit.from_instructions(\n",
+ " [(PauliLindbladErrorInstruction(layer_error_odd_xx.error), slice_.qubits)]\n",
+ " )\n",
+ " )\n",
+ " elif slice_ == layer_error_even_yy.circuit:\n",
+ " noisy_slices.append(\n",
+ " QuantumCircuit.from_instructions(\n",
+ " [(PauliLindbladErrorInstruction(layer_error_even_yy.error), slice_.qubits)]\n",
+ " )\n",
+ " )\n",
+ " elif slice_ == layer_error_odd_yy.circuit:\n",
+ " noisy_slices.append(\n",
+ " QuantumCircuit.from_instructions(\n",
+ " [(PauliLindbladErrorInstruction(layer_error_odd_yy.error), slice_.qubits)]\n",
+ " )\n",
+ " )\n",
+ " noisy_slices.append(slice_)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b3778d94-4d04-4094-990d-a354f2062932",
+ "metadata": {},
+ "source": [
+ "We can check our work and draw the circuit below:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "dadc314d-452f-465c-a382-d54685ce3f2a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 10,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "combine_slices(noisy_slices, include_barriers=True).draw(\"mpl\", fold=100, scale=0.8)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "10b88555-0263-4cdb-a4c1-6895d7e55a5b",
+ "metadata": {},
+ "source": [
+ "### Simulating a noisy expectation value\n",
+ "\n",
+ "At this point, classically simulating the expectation value works exactly the same as before, just"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "0e3ecb85-08c4-4fda-85e2-1000983c8842",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "vacuum_state_noisy_obs, _, metadata = backpropagate(obs, noisy_slices)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "a74e9e88-2f1c-4d40-873d-bf4524e2b7fe",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "np.complex128(-0.7230801696448901+7.082755280463563e-19j)"
+ ]
+ },
+ "execution_count": 12,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "vacuum_state_noisy_obs.coeffs[~vacuum_state_noisy_obs.paulis.x.any(axis=1)].sum()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "7a4d2ff5-7c49-463e-8515-6fc9daa951ad",
+ "metadata": {},
+ "source": [
+ "We point out again, that multiple performance concerns should be considered.\n",
+ "Please go back to the [corresponding section above](#some-notes-on-performance)."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms.ipynb b/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms.ipynb
new file mode 100644
index 00000000000..93107bcb2ba
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms.ipynb
@@ -0,0 +1,923 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "frontmatter",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "title: \"Truncating Pauli terms during backpropagation\"\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "11cf5076-2777-499c-be50-531e305bf011",
+ "metadata": {},
+ "source": [
+ "# Truncating Pauli terms during backpropagation"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c1453472-3506-4008-b432-f8ef9ccbf3ee",
+ "metadata": {},
+ "source": [
+ "This guide explains how to configure the Pauli term truncation mechanism provided by the [qiskit_addon_obp.utils.truncating](/docs/api/qiskit-addon-obp/utils-truncating#module-qiskit_addon_obp.utils.truncating) module.\n",
+ "\n",
+ "Operator backpropagation (OBP) can be used to reduce the depth of quantum circuits at the cost of a more complex observable. In order to get meaningful results from OBP, one usually needs to truncate terms from their observable to prevent it from growing too large. One way to allow for deeper backpropagation into the circuit, while preventing the operator from growing too large, is to truncate terms with small coefficients, rather than adding them to the operator. Truncating terms can result in fewer quantum circuits to execute, but doing so results in some error in the final expectation value calculation proportional to the magnitude of the truncated terms' coefficients.\n",
+ "\n",
+ "The [backpropagate](/docs/api/qiskit-addon-obp/qiskit-addon-obp#backpropagate) method takes an optional [TruncationErrorBudget](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget) which configures the truncation of low-weight Pauli terms for each observable after the successful backpropagation of every slice.\n",
+ "The amount of terms that are truncated depends on various configuration parameters specified by the user.\n",
+ "As of now, only a single truncation strategy is available -- the [truncate_binary_search](/docs/api/qiskit-addon-obp/utils-truncating#truncate_binary_search) method.\n",
+ "Given an observable and some _budget_ it will perform a binary search over the Pauli terms and coefficients within that observable to find the optimal threshold,\n",
+ "such that the sum of the truncated coefficients is maximal but lower than the budget.\n",
+ "\n",
+ "**Note**: By default, the L1 norm is used to evaluate and bound the truncation error; however, the `p_norm` setting enables the specification of the Lp-norm to be used.\n",
+ "For more information on how to use that setting, please refer to [its how-to guide](/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm)."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "aaf5f046-6d5e-4b6f-add7-b60604edef30",
+ "metadata": {},
+ "source": [
+ "In the following examples, we will get to know various ways of building an [TruncationErrorBudget](/docs/api/qiskit-addon-obp/utils-truncating#truncationerrorbudget) via the accompanying [setup_budget](/docs/api/qiskit-addon-obp/utils-truncating#setup_budget) function."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c83c0741-4a9e-4f89-92a9-4756ef133130",
+ "metadata": {},
+ "source": [
+ "## Constructing an example circuit\n",
+ "\n",
+ "For the purposes of this guide, we will use the following circuit slices:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "297fc3b0-d3b4-432d-96cd-7e175b7e6a52",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 1,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import rustworkx.generators\n",
+ "from qiskit.synthesis import LieTrotter\n",
+ "from qiskit_addon_utils.problem_generators import (\n",
+ " PauliOrderStrategy,\n",
+ " generate_time_evolution_circuit,\n",
+ " generate_xyz_hamiltonian,\n",
+ ")\n",
+ "from qiskit_addon_utils.slicing import combine_slices, slice_by_gate_types\n",
+ "\n",
+ "# we generate a linear chain of 10 qubits\n",
+ "linear_chain = rustworkx.generators.path_graph(10)\n",
+ "\n",
+ "# we use an arbitrary XY model\n",
+ "hamiltonian = generate_xyz_hamiltonian(\n",
+ " linear_chain,\n",
+ " coupling_constants=(0.05, 0.02, 0.0),\n",
+ " ext_magnetic_field=(0.02, 0.08, 0.0),\n",
+ " pauli_order_strategy=PauliOrderStrategy.InteractionThenColor,\n",
+ ")\n",
+ "# we evolve for some time\n",
+ "circuit = generate_time_evolution_circuit(hamiltonian, synthesis=LieTrotter(reps=3), time=2.0)\n",
+ "# slice the circuit by gate type\n",
+ "slices = slice_by_gate_types(circuit)\n",
+ "\n",
+ "# for visualization purposes only, we recombine the slices with barriers between them and draw the resulting circuit\n",
+ "combine_slices(slices, include_barriers=True).draw(\"mpl\", fold=50, scale=0.6)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "89ce4fa9-06ba-4009-8594-d1b85f5e719f",
+ "metadata": {},
+ "source": [
+ "We will look at a single simple observable:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "b168d34c-f147-4524-9af9-264f56440a55",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit.quantum_info import SparsePauliOp\n",
+ "\n",
+ "obs = SparsePauliOp(\"IIIIIZIIII\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "723dcf4e-1dab-4c26-a8e4-cd30b1564189",
+ "metadata": {},
+ "source": [
+ "## The simplest case: a fixed truncation budget for each slice"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5e04e5c4-1e6b-4720-9563-becbb9edc4d7",
+ "metadata": {},
+ "source": [
+ "The available budget for truncation of Pauli terms can vary at each step of the backpropagation.\n",
+ "As a first step towards understanding how this works, we look at the simplest case of a fixed truncation budget as specified by the user.\n",
+ "\n",
+ "The most straight-forward way to specify the truncation budget is via the `max_error_per_slice` argument.\n",
+ "In fact, this is what is being done in [this tutorial](/docs/addons/qiskit-addon-obp/tutorials/01-getting-started-ipynb). Setting `max_error_per_slice` to a `float` results in each slice being allotted a budget equaling that value.\n",
+ "In the example below, we set this value to `0.001` which guarantees us an implicit truncation error of at most `0.018`, if the entire 18 slices were to be backpropagated.\n",
+ "\n",
+ "
\n",
+ "Note that any error budget remaining after backpropagating a slice and truncating terms with small coefficients will always be added to the following slice's error budget.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "5f20a9b9-c3c4-43c4-9f13-fdf451958081",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "from qiskit_addon_obp.utils.truncating import setup_budget\n",
+ "\n",
+ "truncation_error_budget = setup_budget(max_error_per_slice=0.001)\n",
+ "print(truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "c3b8b9ef-7d5c-43dd-8cb9-90756d4753b7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 11 circuit slices.\n",
+ "New observable contains 29 terms and 10 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "from qiskit_addon_obp import backpropagate\n",
+ "from qiskit_addon_obp.utils.simplify import OperatorBudget\n",
+ "\n",
+ "op_budget = OperatorBudget(max_qwc_groups=10)\n",
+ "bp_obs, remaining_slices, metadata = backpropagate(\n",
+ " obs, slices, operator_budget=op_budget, truncation_error_budget=truncation_error_budget\n",
+ ")\n",
+ "reduced_circuit = combine_slices(remaining_slices)\n",
+ "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "4a772af6-edc6-4e53-81c7-53f3b72bf58a",
+ "metadata": {},
+ "source": [
+ "Let's use the [OBPMetadata](/docs/api/qiskit-addon-obp/utils-metadata-obp-metadata) instance and the tools provided by the [visualization](/docs/api/qiskit-addon-obp/utils-visualization#module-qiskit_addon_obp.utils.visualization) module to visualize the backpropagation process.\n",
+ "\n",
+ "- **The top/left plot** shows that we have enough budget to begin truncating observable terms after backpropagating the third slice. Starting from the third slice, we know that we truncate at least one term from each slice we backpropagate because we are incurring some truncation error after each slice.\n",
+ "- **The top/right plot** shows that the error budget ramps up to `.003` for the third slice. We see a sharp drop in remaining budget, which means terms were truncated from the observable. This is consistent with what we inferred from the top/left plot.\n",
+ "- **The bottom/left plot** shows that as we truncate terms from our observable, our overall accumulated error is growing monotonically. This graph also confirms that no terms were truncated until after the third slice had been backpropagated.\n",
+ "- **The bottom/right plot** shows that the number of commuting Pauli groups in our observable has grown to the specified limit of `10`. This plot also shows how backpropagating one more layer would cause our observable to outgrow the specified bound as you can see from the cross-over of the black and red lines."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8ff436fa-3b52-4387-a9f4-d8d9df13c480",
+ "metadata": {},
+ "source": [
+ "
\n",
+ "Note that in all of these plots, the x-axis enumerates the backpropagated slices, but since OBP works on the end of the circuit, slice 1 is in fact the very last slice, slice 2 the one before that, and so on.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "68f118df-6718-49b2-ba80-c37ef461f71a",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "from matplotlib import pyplot as plt\n",
+ "from qiskit_addon_obp.utils.visualization import (\n",
+ " plot_accumulated_error,\n",
+ " plot_left_over_error_budget,\n",
+ " plot_num_qwc_groups,\n",
+ " plot_slice_errors,\n",
+ ")\n",
+ "\n",
+ "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
+ "plot_slice_errors(metadata, axes[(0, 0)])\n",
+ "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
+ "plot_accumulated_error(metadata, axes[(1, 0)])\n",
+ "plot_num_qwc_groups(metadata, axes[(1, 1)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "523f5a81-afbc-4eee-9d66-05cc3b4a781a",
+ "metadata": {},
+ "source": [
+ "## Specifying slice budget explicitly"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "80f9337d-ea37-454c-ae0b-b101259568c9",
+ "metadata": {},
+ "source": [
+ "If one has knowledge of how to assign a budget to each slice, such that the backpropagation performance is optimized, they may want to explicitly assign a budget for each slice. For demonstration purposes, let's assign the first 3 slices zero budget and use the same budget budget of `.001` per slice for the remaining slices."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "17ce7232-7e3a-44e4-8081-1b1b27430bad",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001, 0.001], max_error_total=inf, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Zero out the first 3 slices' budgets\n",
+ "max_error_per_slice = [0.0] * 3 + [0.001] * (len(slices) - 3)\n",
+ "\n",
+ "truncation_error_budget = setup_budget(max_error_per_slice=max_error_per_slice)\n",
+ "print(truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "7cd711a7-1aea-4b45-a46a-63d771230969",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 11 circuit slices.\n",
+ "New observable contains 32 terms and 10 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "bp_obs, remaining_slices, metadata = backpropagate(\n",
+ " obs, slices, operator_budget=op_budget, truncation_error_budget=truncation_error_budget\n",
+ ")\n",
+ "reduced_circuit = combine_slices(remaining_slices)\n",
+ "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3e853723-454b-4c69-b9c5-180eb1b2e6c2",
+ "metadata": {},
+ "source": [
+ "Removing the budget from the first 3 layers resulted in no terms being truncated until after the fourth layer, as can be confirmed in 3 of these 4 plots. What may be somewhat surprising is that although the 4th slice had no leftover budget passed to it, a term was still truncated using the allocated `.001` budget. This is obvious from the **top/left** plot but can also be observed in the **top/right** plot, as the left-over budget curve flattens between slice ID's 3 and 4. Another notable detail is that at least one term was truncated from this point on, same as above.\n",
+ "\n",
+ "The key takeaway is that although we incurred less truncation error in this second example due to zeroing out some slices' budgets, we were able to backpropagate the same number of slices, and our observable contains the same number of commuting Pauli groups. We can confirm that the bound on our error is smaller in the second example by inspecting the **bottom/left** plot."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "b7167f80-5d29-4c32-be97-cba733f18b1d",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
+ "plot_slice_errors(metadata, axes[(0, 0)])\n",
+ "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
+ "plot_accumulated_error(metadata, axes[(1, 0)])\n",
+ "plot_num_qwc_groups(metadata, axes[(1, 1)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f03a92ee-e9a0-4707-b23a-ebdc3c9f35a3",
+ "metadata": {},
+ "source": [
+ "## Specifying the budget cyclically"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "62f0ea1f-eaec-408d-99df-25a8647c1eb9",
+ "metadata": {},
+ "source": [
+ "If one has a circuit with some repeatable pattern, such as a Trotter circuit, it may be desired to specify a budget for that repeated subset of slices and have that budget used for all of the following repetitions of those slices.\n",
+ "\n",
+ "More concretely, the example circuit we are using has 6 slices which are repeated 3 times for a total of 18 slices. Here we will arbitrarily assign zero budget to the single-qubit and `RYY` layers and `.003` budget to each of the `RXX` layers. We will observe how specifying the budget as a length-6 sequence causes the budget to be applied to all 18 slices cyclically.\n",
+ "\n",
+ "
\n",
+ "Once more pay attention to the fact that the slices are backpropagated in reverse order (i.e. starting at the end). Therefore, the first entry in our cyclic budget actually gets used for the last slice, the second entry for the slice before that, and so on.\n",
+ "
"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "4f37a576-5a61-4589-a896-8304056d0e19",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=inf, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Specify a length-6 per-slice budget.\n",
+ "# This will be cycled over 3 times to be applied to the 18 slices\n",
+ "max_error_per_slice = [0.0] * 4 + [0.003] * 2\n",
+ "\n",
+ "truncation_error_budget = setup_budget(max_error_per_slice=max_error_per_slice)\n",
+ "print(truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "7f78cfe4-13e4-472c-b6cc-9d1e867cbd9c",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 13 circuit slices.\n",
+ "New observable contains 49 terms and 14 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "op_budget = OperatorBudget(max_qwc_groups=20)\n",
+ "bp_obs, remaining_slices, metadata = backpropagate(\n",
+ " obs, slices, operator_budget=op_budget, truncation_error_budget=truncation_error_budget\n",
+ ")\n",
+ "reduced_circuit = combine_slices(remaining_slices)\n",
+ "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3511206b-2972-41ba-b981-6c20ccb7712b",
+ "metadata": {},
+ "source": [
+ "As seen in the **top/left** and **bottom/left**, no truncation was performed over the first four slices since they were allocated no error budget. Slices 5 and 6 had some of their terms truncated as budget became available.\n",
+ "\n",
+ "As seen in the **top/left**, a relatively large amount of error was incurred after backpropagating slice 7, even though that slice was allocated no budget. This is because only about `.002` of the `.006` total budget allocated to slices 5 and 6 was used, so the remaining was forwarded to slice 7 and mostly expended, as seen in the **top/right**.\n",
+ "\n",
+ "**top/left** and **top/right** show that the small amount of leftover budget is used up between slices 8-10, and new budget becomes available in slice 11, as expected. **top/left**, **top/right**, and **bottom/left** all demonstrate the cyclical behavior of the `max_error_per_slice` argument when its length is less than the number of slices. This cyclical behavior would have continued through all the slices, but the `max_qwc_groups` stopping criteria was reached after backpropagating 13 slices, as seen in **bottom/right**.\n",
+ "\n",
+ "Another interesting thing is that the number of Pauli groups actually drops after the second round of budget becomes available at slice 11. This is because there were groups with small coefficients which could not be truncated until there was more budget after backpropagating slice 11, so they accumulated in the observable for several iterations. This particular case also highlights that `max_qwc_groups` must be _exceeded_ for the algorithm to terminate."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "4c36768e-03e7-4e1b-81b3-59b6e9eab43e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
+ "plot_slice_errors(metadata, axes[(0, 0)])\n",
+ "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
+ "plot_accumulated_error(metadata, axes[(1, 0)])\n",
+ "plot_num_qwc_groups(metadata, axes[(1, 1)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "269536c5-8ba4-48da-8e99-6b8ce3a52bd0",
+ "metadata": {},
+ "source": [
+ "## Capping the total error\n",
+ "\n",
+ "In addition to specifying the per-slice error budget, one can specify the maximum amount of error to incur from truncation. Once that limit is hit, no more truncation will be performed; however, backpropagation will continue until the observable becomes too large and one of the stopping criteria is met.\n",
+ "\n",
+ "Let's re-run the above experiment but set a cap on `max_error_total` such that the error budget is expended after backpropagating slice 7."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "id": "8ef6a100-fb4b-4b66-be6e-a094db2c29d4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.0, 0.0, 0.0, 0.0, 0.003, 0.003], max_error_total=0.006, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Specify a length-6 per-slice budget.\n",
+ "# This will be cycled over 3 times to be applied to the 18 slices\n",
+ "max_error_per_slice = [0.0] * 4 + [0.003] * 2\n",
+ "\n",
+ "truncation_error_budget = setup_budget(\n",
+ " max_error_per_slice=max_error_per_slice, max_error_total=0.006\n",
+ ")\n",
+ "print(truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "id": "c58bccd5-aa40-482f-acaf-4ef50d420053",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 10 circuit slices.\n",
+ "New observable contains 67 terms and 20 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "bp_obs, remaining_slices, metadata = backpropagate(\n",
+ " obs,\n",
+ " slices,\n",
+ " operator_budget=op_budget,\n",
+ " truncation_error_budget=truncation_error_budget,\n",
+ ")\n",
+ "reduced_circuit = combine_slices(remaining_slices)\n",
+ "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "dadb77a5-00d7-427d-921b-4a005f5ab36f",
+ "metadata": {},
+ "source": [
+ "As expected, our truncation error caps out at `.006` (**bottom/left**). It is notable that in this run we were not able to backpropagate slice 11. This is because we did not have enough budget to truncate terms and the number of commuting Pauli groups grew past the limit of `20`, as seen in **bottom/right**."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "69c9069a-9038-4319-8dc6-37a1be973ebf",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
+ "plot_slice_errors(metadata, axes[(0, 0)])\n",
+ "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
+ "plot_accumulated_error(metadata, axes[(1, 0)])\n",
+ "plot_num_qwc_groups(metadata, axes[(1, 1)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2049d223-1b91-4042-ba98-a86f7a59483c",
+ "metadata": {},
+ "source": [
+ "It may be desirable to simply cap the overall budget with `max_error_total` without specifying `max_error_per_slice`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "fac59f4f-3528-49c1-9d4d-9be98d2c7d78",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.018], max_error_total=0.018, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "truncation_error_budget = setup_budget(max_error_total=0.018)\n",
+ "print(truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "20c55e75-f619-4ac9-aecd-29491867ab82",
+ "metadata": {},
+ "source": [
+ "The output of the cell above, might be slightly surprising because the `per_slice_budget` is set to the `max_error_total`. This indicates, that the entire available budget will be consumed **greedily**.\n",
+ "You can think of it this way: the entire budget is available to each slice (because we loop over the `per_slice_budget`). But any budget that has already been consumed, will be deducted from the budget that is available at that given point in the algorithm."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 16,
+ "id": "5d65a057-eb5e-4308-b84d-82bfbea06989",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 9 circuit slices.\n",
+ "New observable contains 25 terms and 9 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "op_budget = OperatorBudget(max_qwc_groups=10)\n",
+ "bp_obs, remaining_slices, metadata = backpropagate(\n",
+ " obs, slices, operator_budget=op_budget, truncation_error_budget=truncation_error_budget\n",
+ ")\n",
+ "reduced_circuit = combine_slices(remaining_slices)\n",
+ "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a77ceb1e-bc6d-462d-aebe-ec478d312233",
+ "metadata": {},
+ "source": [
+ "**top/right** shows how the entire `.018` error budget is made available to the first slice. No truncation is performed until the third slices, so the left-over budget remains constant. The budget monotonically decreases, as the full budget is made available to each backpropagated slice until it is expended.\n",
+ "\n",
+ "It is notable that this experiment yielded two fewer backpropagated slices compared to the first experiment in this notebook, which is almost identical. This demonstrates that for some problems distributing the budget evenly might be optimal. For other problems, allowing slices to greedily expend the full budget might yield better performance."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "51d36d7e-0bd5-4388-bbb8-6831bf1f02e6",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
+ "plot_slice_errors(metadata, axes[(0, 0)])\n",
+ "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
+ "plot_accumulated_error(metadata, axes[(1, 0)])\n",
+ "plot_num_qwc_groups(metadata, axes[(1, 1)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8859d36c-258f-441f-a7cf-3997b9f6e8ff",
+ "metadata": {},
+ "source": [
+ "## Capping the number of backpropagated slices and the total error together\n",
+ "\n",
+ "If one doesn't want to distribute their error budget all the way through the circuit but doesn't want to greedily expend it either, one can specify a the number of slices they intend to backpropagate (`num_slices`), along with a total error budget (`max_error_total`). This will distribute the error budget uniformly (accordingly to `p_norm`) across the input slices.\n",
+ "\n",
+ "Here, we will cap the number of slices we may backpropagate at `12`, and we will keep the same total error budget."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "id": "474cf708-5447-4984-873c-5f62f81a402d",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.0014999999999999998], max_error_total=0.018, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "num_slices = 12\n",
+ "\n",
+ "truncation_error_budget = setup_budget(max_error_total=0.018, num_slices=num_slices, p_norm=1)\n",
+ "print(truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1ac2fb58-db97-4c9e-9a96-fce97b52e7e2",
+ "metadata": {},
+ "source": [
+ "Now we will attempt to backpropagate the `12` slices for which we allocated some budget in the previous step. We do this by just passing in the final `12` slices in our circuit. With `p_norm=1`, each of the `12` slices has an available budget of `0.018 / num_slices = 0.0015`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "id": "6efc92bc-3433-4ea6-a683-684205201f4f",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "bp_obs, remaining_slices, metadata = backpropagate(\n",
+ " obs,\n",
+ " slices[-num_slices:],\n",
+ " operator_budget=op_budget,\n",
+ " truncation_error_budget=truncation_error_budget,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "b15c2a20-19c3-417c-b32d-173c10f13852",
+ "metadata": {},
+ "source": [
+ "Since we passed a subset of our slices (i.e. ``slices[-num_slices:]``) to ``backpropagate``, we must combine the slices remaining after backpropagation with the slices that were never sent for backpropagation (i.e. ``slices[:-num_slices]``).\n",
+ "\n",
+ "Once we have combined all of the remaining slices, we can use [combine_slices](/docs/api/qiskit-addon-utils/slicing#qiskit_addon_utils.slicing.combine_slices) to generate the reduced-depth [QuantumCircuit](/docs/api/qiskit/qiskit.circuit.QuantumCircuit#html). Here we inspect how many slices were backpropagated vs. how large our observable became."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 20,
+ "id": "7c42256f-e7f2-40ed-999e-6a51fd9351c4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 12 circuit slices.\n",
+ "New observable contains 29 terms and 9 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Recombine the slices remaining after backprop with the rest of the original circuit\n",
+ "reduced_circuit = combine_slices(slices[:-num_slices] + remaining_slices)\n",
+ "\n",
+ "print(f\"Backpropagated {num_slices - len(remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"New observable contains {len(bp_obs)} terms and {len(bp_obs.group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ae2d383c-c11c-457e-856f-85602f2df306",
+ "metadata": {},
+ "source": [
+ "The plots show us that we did successfully backpropagate all `12` slices while keeping our observable under `10` commuting Pauli groups. We can also see that using `num_slices` along with `max_error_total` results in a distribution of budgets across the slices and unused budget is again forwarded to the next slice. This is most apparent in the **top/right** plot, as the budget is both expended and replenished throughout backpropagation.\n",
+ "\n",
+ "It is notable that this method of distributing error produced the best results (more backpropagated slices), compared with the first experiment in this notebook and the example just above this one. All of these examples allotted `.018` error budget, but backpropagation performed differently depending on how that budget was distributed."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 21,
+ "id": "b0bf5050-2928-41a1-9c3d-c9d79e918f6f",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
+ "plot_slice_errors(metadata, axes[(0, 0)])\n",
+ "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
+ "plot_accumulated_error(metadata, axes[(1, 0)])\n",
+ "plot_num_qwc_groups(metadata, axes[(1, 1)])"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5c554774-2986-43a0-bdcd-45eaeb81ef5a",
+ "metadata": {},
+ "source": [
+ "## Working with multiple observables\n",
+ "\n",
+ "The `qiskit_addon_obp.backpropagate` method allows one to pass in a sequence of observables. This simplifies the workflow when dealing with multiple target observables.\n",
+ "\n",
+ "We explicitly mention this here to teach you how the truncation strategy handles such a case. For the sake of this example, we add an extra observable to the one which we have been using so far:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 22,
+ "id": "0857ea4c-82c1-4641-a268-1a9ba44a312b",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "obs = [SparsePauliOp(\"IIIIIZIIII\"), SparsePauliOp(\"IIIIIXIIII\")]"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ba3ac81c-7395-4287-907a-742a1751cbd0",
+ "metadata": {},
+ "source": [
+ "For simplicity, we repeat the very first experiment from this tutorial. The only difference is that we now have two observables to backpropagate the circuit into.\n",
+ "\n",
+ "For this particular example, this does not affect the number of slices which could be backpropagated. However, we can see now that the two observables resulted in different numbers of Pauli terms and commuting groups."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 23,
+ "id": "10c50f14-5bbd-427d-ae77-24389498a1e4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "TruncationErrorBudget(per_slice_budget=[0.001], max_error_total=inf, p_norm=1)\n"
+ ]
+ }
+ ],
+ "source": [
+ "truncation_error_budget = setup_budget(max_error_per_slice=0.001)\n",
+ "print(truncation_error_budget)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 24,
+ "id": "862edaec-fc2c-488b-875d-23841ecc80b3",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 11 circuit slices.\n",
+ "The new first observable contains 29 terms and 10 commuting groups.\n",
+ "The new second observable contains 23 terms and 8 commuting groups.\n"
+ ]
+ }
+ ],
+ "source": [
+ "bp_obs, remaining_slices, metadata = backpropagate(\n",
+ " obs, slices, operator_budget=op_budget, truncation_error_budget=truncation_error_budget\n",
+ ")\n",
+ "reduced_circuit = combine_slices(remaining_slices)\n",
+ "print(f\"Backpropagated {len(slices) - len(remaining_slices)} circuit slices.\")\n",
+ "print(\n",
+ " f\"The new first observable contains {len(bp_obs[0])} terms and {len(bp_obs[0].group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")\n",
+ "print(\n",
+ " f\"The new second observable contains {len(bp_obs[1])} terms and {len(bp_obs[1].group_commuting(qubit_wise=True))} commuting groups.\"\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "467c614d-6bac-4826-a42b-39937b9d0c72",
+ "metadata": {},
+ "source": [
+ "The plots below shed some light on how the backpropagation algorithm handles multiple observables.\n",
+ "\n",
+ "First, the plots **top/left**, **top/right** and **bottom/left** teach us that the budget for truncating terms is set individually for each observable. In other words, both observables have the ability to truncate terms assuming an error of `0.001` per backpropagated slice.\n",
+ "Due to the different nature of the observables, this results in different consumption of the budget. In this example, they exhibit a lot of overlap which is not necessarily the case in general.\n",
+ "\n",
+ "**bottom/right** teaches us that `max_qwc_groups` is actually taking _all_ observables combined into account. That means that the terms of all observables are grouped together to arrive at one final number of qubit-wise commuting groups which is compared against `max_qwc_paulis`. The same is done for the `max_paulis` threshold (which we have not discussed in this notebook) which allows you to set a limit on the number of Pauli terms across all observables."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 25,
+ "id": "82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "fig, axes = plt.subplots(2, 2, figsize=(20, 10))\n",
+ "plot_slice_errors(metadata, axes[(0, 0)])\n",
+ "plot_left_over_error_budget(metadata, axes[(0, 1)])\n",
+ "plot_accumulated_error(metadata, axes[(1, 0)])\n",
+ "plot_num_qwc_groups(metadata, axes[(1, 1)])"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/addons/qiskit-addon-obp/index.mdx b/docs/addons/qiskit-addon-obp/index.mdx
new file mode 100644
index 00000000000..29de023afa6
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/index.mdx
@@ -0,0 +1,48 @@
+---
+title: "Qiskit addon: operator backpropagation (OBP) 0.3.0"
+---
+
+# Qiskit addon: operator backpropagation (OBP)
+
+[Qiskit addons](/docs/guides/addons) are a collection of modular tools for building utility-scale workloads powered by Qiskit.
+
+This package contains the Qiskit addon for operator backpropagation (OBP). Experimental errors limit the depth of quantum circuits that can be executed on near-term devices. OBP is a technique to reduce circuit depth by trimming operations from its end at the cost of more operator measurements.
+
+As one backpropagates an operator further through a circuit, the size of the observable will grow exponentially, which results in both a classical and quantum resource overhead. However, for some circuits, the resulting distribution of Pauli observables is more concentrated than the worst-case exponential scaling, meaning that some terms in the Hamiltonian with small coefficients can be truncated to reduce the quantum overhead. The error incurred by doing this can be controlled to find a suitable tradeoff between precision and efficiency.
+
+There are a number of ways in which operator backpropagation can be performed, this package uses a method based on Clifford perturbation theory, which has the benefit that the overhead incurred by backpropagating various gates is determined by the non-Cliffordness of that gate. This leads to an increased efficiency for some families of circuits relative to tensor-network based methods for OBP, which currently have high classical overheads even in cases where the quantum overhead remains tame.
+
+## Documentation
+
+All documentation is available [here](/docs/addons/qiskit-addon-obp/).
+
+## Installation
+
+We encourage installing this package via `pip`, when possible:
+
+```bash
+pip install 'qiskit-addon-obp'
+```
+
+For more installation information refer to the [installation instructions](install) in the documentation.
+
+## Deprecation Policy
+
+We follow [semantic versioning](https://semver.org/) and are guided by the principles in [Qiskit’s deprecation policy](https://github.com/Qiskit/qiskit/blob/main/DEPRECATION.md). We may occasionally make breaking changes in order to improve the user experience. When possible, we will keep old interfaces and mark them as deprecated, as long as they can co-exist with the new ones. Each substantial improvement, breaking change, or deprecation will be documented in the [release notes](/docs/addons/qiskit-addon-obp/release-notes).
+
+## Contributing
+
+The source code is available [on GitHub](https://github.com/Qiskit/qiskit-addon-obp).
+
+The developer guide is located at [CONTRIBUTING.md](https://github.com/Qiskit/qiskit-addon-obp/blob/main/CONTRIBUTING.md) in the root of this project’s repository. By participating, you are expected to uphold Qiskit’s [code of conduct](https://github.com/Qiskit/qiskit/blob/main/CODE_OF_CONDUCT.md).
+
+We use [GitHub issues](https://github.com/Qiskit/qiskit-addon-obp/issues/new/choose) for tracking requests and bugs.
+
+## References
+
+1. 2. Fuller et al. [Improved Quantum Computation using Operator Backpropagation](https://arxiv.org/abs/2502.01897), arXiv:2502.01897 \[quant-ph].
+
+## License
+
+[Apache License 2.0](https://github.com/Qiskit/qiskit-addon-obp/blob/main/LICENSE.txt)
+
diff --git a/docs/addons/qiskit-addon-obp/install.mdx b/docs/addons/qiskit-addon-obp/install.mdx
new file mode 100644
index 00000000000..dd9b9648b0c
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/install.mdx
@@ -0,0 +1,76 @@
+---
+title: "Installation Instructions - Qiskit addon: operator backpropagation (OBP) 0.3.0"
+---
+
+# Installation Instructions
+
+Let’s see how to install the package. The first thing to do is choose how you’re going to run and install the packages. There are two primary ways to do this:
+
+* [Option 1: Install from PyPI](#option-1)
+* [Option 2: Install from Source](#option-2)
+
+## Pre-Installation
+
+First, create a minimal environment with only Python installed in it. We recommend using [Python virtual environments](https://docs.python.org/3.10/tutorial/venv.html).
+
+```sh
+python3 -m venv /path/to/virtual/environment
+```
+
+Activate your new environment.
+
+```sh
+source /path/to/virtual/environment/bin/activate
+```
+
+Note: If you are using Windows, use the following commands in PowerShell:
+
+```pwsh
+python3 -m venv c:\path\to\virtual\environment
+c:\path\to\virtual\environment\Scripts\Activate.ps1
+```
+
+
+
+## Option 1: Install from PyPI
+
+The most straightforward way to install the `qiskit-addon-obp` package is via `PyPI`.
+
+```sh
+pip install 'qiskit-addon-obp'
+```
+
+
+
+## Option 2: Install from Source
+
+Users who wish to develop in the repository or run the notebooks locally may want to install from source.
+
+If so, the first step is to clone the `qiskit-addon-obp` repository.
+
+```sh
+git clone git@github.com:Qiskit/qiskit-addon-obp.git
+```
+
+Next, upgrade pip and enter the repository.
+
+```sh
+pip install --upgrade pip
+cd qiskit-addon-obp
+```
+
+The next step is to install `qiskit-addon-obp` to the virtual environment. If you plan on running the notebooks, install the notebook dependencies in order to run all the visualizations in the notebooks. If you plan on developing in the repository, you may want to install the `dev` dependencies.
+
+Adjust the options below to suit your needs.
+
+```sh
+pip install tox notebook -e '.[notebook-dependencies,dev]'
+```
+
+If you installed the notebook dependencies, you can get started by running the notebooks in the docs.
+
+```python
+cd docs/
+jupyter lab
+```
+
diff --git a/docs/addons/qiskit-addon-obp/tutorials/01-getting-started.ipynb b/docs/addons/qiskit-addon-obp/tutorials/01-getting-started.ipynb
new file mode 100644
index 00000000000..a8a2b5c4969
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/tutorials/01-getting-started.ipynb
@@ -0,0 +1,519 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "frontmatter",
+ "metadata": {},
+ "source": [
+ "---\n",
+ "title: \"Reducing depth of circuits with operator backpropagation\"\n",
+ "---"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5f9e3f0d-7cc1-4ea7-826b-35dd2a87af1e",
+ "metadata": {},
+ "source": [
+ "# Reducing depth of circuits with operator backpropagation\n",
+ "\n",
+ "Operator backpropagation is a technique which involves absorbing operations from the end of a quantum circuit into a Pauli operator, generally reducing the depth of the circuit at the cost of additional terms in the operator. The goal is to backpropagate as much of the circuit as possible without allowing the operator to grow too large.\n",
+ "\n",
+ "One way to allow for deeper backpropagation into the circuit, while preventing the operator from growing too large, is to truncate terms with small coefficients, rather than adding them to the operator. Truncating terms can result in fewer quantum circuits to execute, but doing so results in some error in the final expectation value calculation proportional to the magnitude of the truncated terms' coefficients."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2cbd99bb-edc8-4032-8253-2f7195080aaa",
+ "metadata": {},
+ "source": [
+ "In this tutorial we will implement a [Qiskit pattern](/docs/guides/serverless#qiskit-patterns-with-quantum-serverless) for simulating the quantum dynamics of a Heisenberg spin chain using operator backpropagation:\n",
+ "\n",
+ "- **Step 1: Map to quantum problem**\n",
+ " - Map the time-evolved Hamiltonian onto a quantum circuit\n",
+ "- **Step 2: Optimize the problem**\n",
+ " - Slice the circuit\n",
+ " - Backpropagate slices from the circuit onto a Pauli observable\n",
+ " - Combine the remaining slices into a single circuit\n",
+ " - Transpile the circuit for the backend\n",
+ "- **Step 3: Execute experiments**\n",
+ " - Calculate the expectation value using the reduced circuit and expanded observable with a [StatevectorEstimator](/docs/api/qiskit/qiskit.primitives.StatevectorEstimator) for sake of simplicity in this notebook\n",
+ "- **Step 4: Reconstruct results**\n",
+ " - N.A.\n",
+ "\n",
+ "**Note:** Qiskit loosely describes [layers](/docs/api/qiskit/qiskit.dagcircuit.DAGCircuit) as being depth-1 partitions of the circuit across all qubits. This package makes use of the term **slices** to describe layers of arbitrary depth. The [qiskit_addon_obp.backpropagate](/docs/api/qiskit-addon-obp/qiskit-addon-obp#backpropagate) function is designed to backpropagate entire slices at a time, so the choice of how to slice the quantum circuit can have a major impact on how well backpropagation performs for a given problem. You will lean more about **slices** below."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "706ce970-5dd2-431a-890b-6476f232ae7f",
+ "metadata": {},
+ "source": [
+ "## Step 1: Map to quantum problem\n",
+ "### Map the time-evolution of a quantum Heisenberg model to a quantum experiment.\n",
+ "\n",
+ "The [qiskit_addon_utils](/docs/addons/qiskit-addon-utils/) package provides some reusable functionalities for various purposes.\n",
+ "\n",
+ "Its [qiskit_addon_utils.problem_generators](/docs/api/qiskit-addon-utils/problem-generators#module-qiskit_addon_utils.problem_generators) module provides functions to generate Heisenberg-like Hamiltonians on a given connectivity graph.\n",
+ "This graph can be either a [rustworkx.PyGraph](https://www.rustworkx.org/apiref/rustworkx.PyGraph.html) or a [CouplingMap](/docs/api/qiskit/qiskit.transpiler.CouplingMap) making it easy to use in Qiskit-centric workflows.\n",
+ "\n",
+ "In the following, we first generate a heavy-hex `CouplingMap` out of which we carve a linear chain of 10 qubits. Note, that the indices of this new `reduced_coupling_map` are again zero-based."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "de8ce35e-a2c5-474f-adb9-88c9afb2bd76",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit.transpiler import CouplingMap\n",
+ "\n",
+ "coupling_map = CouplingMap.from_heavy_hex(3, bidirectional=False)\n",
+ "\n",
+ "# Choose a 10-qubit linear chain on this coupling map\n",
+ "reduced_coupling_map = coupling_map.reduce([0, 13, 1, 14, 10, 16, 5, 12, 8, 18])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "c79b8484-7de0-47cb-be7b-147b502ad1e5",
+ "metadata": {
+ "editable": true,
+ "slideshow": {
+ "slide_type": ""
+ },
+ "tags": [
+ "skip-execution"
+ ]
+ },
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from rustworkx.visualization import graphviz_draw\n",
+ "\n",
+ "graphviz_draw(reduced_coupling_map.graph, method=\"circo\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f95ba561-f211-4935-bde6-989c0d364ec8",
+ "metadata": {},
+ "source": [
+ "Next, we generate a Pauli operator modeling a Heisenberg XYZ Hamiltonian.\n",
+ "\n",
+ "$$\\hat{H} = \\sum_{(j,k)\\in E} (J_{x} \\sigma_j^{x} \\sigma_{k}^{x} +\n",
+ " J_{y} \\sigma_j^{y} \\sigma_{k}^{y} + J_{z} \\sigma_j^{z} \\sigma_{k}^{z}) +\n",
+ " \\sum_{j\\in V} (h_{x} \\sigma_j^{x} + h_{y} \\sigma_j^{y} + h_{z} \\sigma_j^{z})$$\n",
+ "\n",
+ "Where $G(V,E)$ is the graph of the coupling map provided."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "056df48a-0f8d-4a42-9c47-784343a5d6d7",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "SparsePauliOp(['IIIIIIIXXI', 'IIIIIIIYYI', 'IIIIIIIZZI', 'IIIIIXXIII', 'IIIIIYYIII', 'IIIIIZZIII', 'IIIXXIIIII', 'IIIYYIIIII', 'IIIZZIIIII', 'IXXIIIIIII', 'IYYIIIIIII', 'IZZIIIIIII', 'IIIIIIIIXX', 'IIIIIIIIYY', 'IIIIIIIIZZ', 'IIIIIIXXII', 'IIIIIIYYII', 'IIIIIIZZII', 'IIIIXXIIII', 'IIIIYYIIII', 'IIIIZZIIII', 'IIXXIIIIII', 'IIYYIIIIII', 'IIZZIIIIII', 'XXIIIIIIII', 'YYIIIIIIII', 'ZZIIIIIIII', 'IIIIIIIIIX', 'IIIIIIIIIY', 'IIIIIIIIIZ', 'IIIIIIIIXI', 'IIIIIIIIYI', 'IIIIIIIIZI', 'IIIIIIIXII', 'IIIIIIIYII', 'IIIIIIIZII', 'IIIIIIXIII', 'IIIIIIYIII', 'IIIIIIZIII', 'IIIIIXIIII', 'IIIIIYIIII', 'IIIIIZIIII', 'IIIIXIIIII', 'IIIIYIIIII', 'IIIIZIIIII', 'IIIXIIIIII', 'IIIYIIIIII', 'IIIZIIIIII', 'IIXIIIIIII', 'IIYIIIIIII', 'IIZIIIIIII', 'IXIIIIIIII', 'IYIIIIIIII', 'IZIIIIIIII', 'XIIIIIIIII', 'YIIIIIIIII', 'ZIIIIIIIII'],\n",
+ " coeffs=[0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,\n",
+ " 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,\n",
+ " 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,\n",
+ " 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j,\n",
+ " 0.78539816+0.j, 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j,\n",
+ " 1.57079633+0.j, 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j,\n",
+ " 0.39269908+0.j, 0.78539816+0.j, 1.57079633+0.j, 1.04719755+0.j,\n",
+ " 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,\n",
+ " 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,\n",
+ " 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,\n",
+ " 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,\n",
+ " 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j,\n",
+ " 1.04719755+0.j, 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j,\n",
+ " 0.52359878+0.j, 0.34906585+0.j, 1.04719755+0.j, 0.52359878+0.j,\n",
+ " 0.34906585+0.j])\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "from qiskit_addon_utils.problem_generators import generate_xyz_hamiltonian\n",
+ "\n",
+ "# Get a qubit operator describing the Heisenberg XYZ model\n",
+ "hamiltonian = generate_xyz_hamiltonian(\n",
+ " reduced_coupling_map,\n",
+ " coupling_constants=(np.pi / 8, np.pi / 4, np.pi / 2),\n",
+ " ext_magnetic_field=(np.pi / 3, np.pi / 6, np.pi / 9),\n",
+ ")\n",
+ "print(hamiltonian)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3a858a3f-fc3d-4e09-9db9-8d064e036045",
+ "metadata": {},
+ "source": [
+ "From the qubit operator, we can generate a quantum circuit which models its time evolution.\n",
+ "Once again, the [qiskit_addon_utils.problem_generators](/docs/api/qiskit-addon-utils/problem-generators#module-qiskit_addon_utils.problem_generators) module comes to the resuce with a handy function do just that:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "1d68f197-ffa4-49de-9fe8-243b1facbd00",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 4,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from qiskit.synthesis import LieTrotter\n",
+ "from qiskit_addon_utils.problem_generators import generate_time_evolution_circuit\n",
+ "\n",
+ "circuit = generate_time_evolution_circuit(\n",
+ " hamiltonian,\n",
+ " time=0.2,\n",
+ " synthesis=LieTrotter(reps=2),\n",
+ ")\n",
+ "circuit.draw(\"mpl\", style=\"iqp\", scale=0.6)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ddc43a7d-6558-4f82-90a6-7ec5370a7fd5",
+ "metadata": {},
+ "source": [
+ "## Step 2: Optimize the problem\n",
+ "### Create circuit slices to backpropagate\n",
+ "\n",
+ "Remember, the ``backpropagate`` function will backpropagate entire circuit slices at a time, so the choice of how to slice can have an impact on how well backpropagation performs for a given problem. Here, we will group gates of the same type into slices using the [slice_by_gate_types](/docs/api/qiskit-addon-utils/slicing#qiskit_addon_utils.slicing.slice_by_gate_types) function.\n",
+ "\n",
+ "For a more detailed discussion on circuit slicing, check out this [how-to guide](/docs/addons/qiskit-addon-utils/how-tos/create-circuit-slices) of the [qiskit-addon-utils](/docs/addons/qiskit-addon-utils/index) package."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "5d7f0d4e-ce4a-4f11-976b-437a74244b08",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Separated the circuit into 18 slices.\n"
+ ]
+ }
+ ],
+ "source": [
+ "from qiskit_addon_utils.slicing import slice_by_gate_types\n",
+ "\n",
+ "slices = slice_by_gate_types(circuit)\n",
+ "print(f\"Separated the circuit into {len(slices)} slices.\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c7e3ef27-8c58-4790-9269-26dacfafa0a1",
+ "metadata": {},
+ "source": [
+ "### Constrain how large the operator may grow during backpropagation\n",
+ "\n",
+ "During backpropagation, the number of terms in the operator will generally approach $4^N$ quickly, where $N$ is the number of qubits. The size of the operator can be bounded by specifying the ``operator_budget`` kwarg of the ``backpropagate`` function, which accepts an [OperatorBudget](/docs/api/qiskit-addon-obp/utils-simplify#operatorbudget) instance.\n",
+ "\n",
+ "Here we specify that backpropagation should stop when the number of qubit-wise commuting Pauli groups in the operator grows past 8."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "1279b567-ba19-4542-9dc1-c57a91118a20",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit_addon_obp.utils.simplify import OperatorBudget\n",
+ "\n",
+ "op_budget = OperatorBudget(max_qwc_groups=8)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6306e6a1-56fb-432d-80aa-5277233c9ed9",
+ "metadata": {},
+ "source": [
+ "### Backpropagate slices from the circuit\n",
+ "\n",
+ "First, we will specify the Pauli-Z observable on qubit 0, and we will backpropagate slices from the time-evolution circuit until the terms in the observable can no longer be combined into 8 or fewer qubit-wise commuting Pauli groups.\n",
+ "\n",
+ "Below you will see that we backpropagated 7 slices but only used 6 of the 8 allotted Pauli groups. This implies that backpropagating one more slice would cause the number of Pauli groups to exceed 8. We can verify that this is the case by inspecting the returned metadata."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "65ec9cb1-a4ed-497b-a616-180e9659956f",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 7 slices.\n",
+ "New observable has 18 terms, which can be combined into 8 groups.\n",
+ "Note that backpropagating one more slice would result in 27 terms across 12 groups.\n",
+ "The remaining circuit after backpropagation looks as follows:\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 7,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from qiskit.quantum_info import SparsePauliOp\n",
+ "from qiskit_addon_obp import backpropagate\n",
+ "from qiskit_addon_utils.slicing import combine_slices\n",
+ "\n",
+ "# Specify a single-qubit observable\n",
+ "observable = SparsePauliOp(\"IIIIIIIIIZ\")\n",
+ "\n",
+ "# Backpropagate slices onto the observable\n",
+ "bp_obs, remaining_slices, metadata = backpropagate(observable, slices, operator_budget=op_budget)\n",
+ "# Recombine the slices remaining after backpropagation\n",
+ "bp_circuit = combine_slices(remaining_slices, include_barriers=True)\n",
+ "\n",
+ "print(f\"Backpropagated {metadata.num_backpropagated_slices} slices.\")\n",
+ "print(\n",
+ " f\"New observable has {len(bp_obs.paulis)} terms, which can be combined into {len(bp_obs.group_commuting(qubit_wise=True))} groups.\"\n",
+ ")\n",
+ "print(\n",
+ " f\"Note that backpropagating one more slice would result in {metadata.backpropagation_history[-1].num_paulis[0]} terms \"\n",
+ " f\"across {metadata.backpropagation_history[-1].num_qwc_groups} groups.\"\n",
+ ")\n",
+ "print(\"The remaining circuit after backpropagation looks as follows:\")\n",
+ "bp_circuit.draw(\"mpl\", scale=0.6)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "d25b9701-f1b1-4230-9dcc-554b85e91cf7",
+ "metadata": {},
+ "source": [
+ "Next, we will specify the same problem with the same constraints on the size of the output observable. However, this time, we allot an error budget to each slice using the [setup_budet](/docs/api/qiskit-addon-obp/utils-truncating#setup_budget) function. Pauli terms with small coefficients will be truncated from each slice until the error budget is filled, and leftover budget will be added to the following slice's budget.\n",
+ "\n",
+ "In order to enable this truncation, we need to setup our error budget like so:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "3ba04824-dbe7-4e11-80b9-ea2320a131d5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit_addon_obp.utils.truncating import setup_budget\n",
+ "\n",
+ "truncation_error_budget = setup_budget(max_error_per_slice=0.005)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c6dda21f-4a28-4c4e-9727-15288973150e",
+ "metadata": {},
+ "source": [
+ "Note that by allocating `5e-3` error per slice for truncation, we are able to remove 3 more slices from the circuit, while remaining within the original budget of 8 commuting Pauli groups in the observable. By default, `backpropagate` uses the L1 norm of the truncated coefficients to bound the total error incurred from truncation. For other options refer to the [how-to guide on specifying the p_norm](/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm).\n",
+ "\n",
+ "In this particular example where we have backpropagated 10 slices, the total truncation error should not exceed ``(5e-3 error/slice) * (10 slices) = 5e-2``.\n",
+ "For further discussion on distributing an error budget across your slices, check out [this how-to guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms)."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "5e8bae1a-ef18-4eb0-9d2a-1ac7bbdced3b",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Backpropagated 10 slices.\n",
+ "New observable has 19 terms, which can be combined into 8 groups.\n",
+ "After truncation, the error in our observable is bounded by 4.933e-02\n",
+ "Note that backpropagating one more slice would result in 27 terms across 13 groups.\n",
+ "The remaining circuit after backpropagation looks as follows:\n"
+ ]
+ },
+ {
+ "data": {
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 9,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "# Run the same experiment but truncate observable terms with small coefficients\n",
+ "bp_obs_trunc, remaining_slices_trunc, metadata = backpropagate(\n",
+ " observable, slices, operator_budget=op_budget, truncation_error_budget=truncation_error_budget\n",
+ ")\n",
+ "\n",
+ "# Recombine the slices remaining after backpropagation\n",
+ "bp_circuit_trunc = combine_slices(remaining_slices_trunc, include_barriers=True)\n",
+ "\n",
+ "print(f\"Backpropagated {metadata.num_backpropagated_slices} slices.\")\n",
+ "print(\n",
+ " f\"New observable has {len(bp_obs_trunc.paulis)} terms, which can be combined into {len(bp_obs_trunc.group_commuting(qubit_wise=True))} groups.\\n\"\n",
+ " f\"After truncation, the error in our observable is bounded by {metadata.accumulated_error(0):.3e}\"\n",
+ ")\n",
+ "print(\n",
+ " f\"Note that backpropagating one more slice would result in {metadata.backpropagation_history[-1].num_paulis[0]} terms \"\n",
+ " f\"across {metadata.backpropagation_history[-1].num_qwc_groups} groups.\"\n",
+ ")\n",
+ "print(\"The remaining circuit after backpropagation looks as follows:\")\n",
+ "bp_circuit_trunc.draw(\"mpl\", scale=0.6)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "1e258829-af9f-43ad-8c79-9455243d7ced",
+ "metadata": {},
+ "source": [
+ "### Now that we have our reduced ansatze and expanded observables, we can transpile our experiments to the backend.\n",
+ "\n",
+ "Here we will use the 14-qubit [FakeMelbourneV2](/docs/api/qiskit-ibm-runtime/fake-provider-fake-melbourne-v2) from [qiskit-ibm-runtime](/docs/api/qiskit-ibm-runtime) to demonstrate how to transpile to a QPU backend."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "b219035b-c5e7-4a08-a8df-cd275bdfe7a0",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager\n",
+ "from qiskit_ibm_runtime.fake_provider import FakeMelbourneV2\n",
+ "\n",
+ "# Specify a backend and a pass manager for transpilation\n",
+ "backend = FakeMelbourneV2()\n",
+ "pm = generate_preset_pass_manager(backend=backend, optimization_level=1)\n",
+ "\n",
+ "# Transpile original experiment\n",
+ "circuit_isa = pm.run(circuit)\n",
+ "observable_isa = observable.apply_layout(circuit_isa.layout)\n",
+ "\n",
+ "# Transpile backpropagated experiment\n",
+ "bp_circuit_isa = pm.run(bp_circuit)\n",
+ "bp_obs_isa = bp_obs.apply_layout(bp_circuit_isa.layout)\n",
+ "\n",
+ "# Transpile the backpropagated experiment with truncated observable terms\n",
+ "bp_circuit_trunc_isa = pm.run(bp_circuit_trunc)\n",
+ "bp_obs_trunc_isa = bp_obs_trunc.apply_layout(bp_circuit_trunc_isa.layout)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "966bcc9b-48a4-4a6a-8b03-e5aded1ab9df",
+ "metadata": {},
+ "source": [
+ "## Step 3: Execute quantum experiments\n",
+ "### Calculate expectation value\n",
+ "\n",
+ "Finally, we can run the backpropagated experiments and compare them with the full experiment using the noiseless [StatevectorEstimator](/docs/api/qiskit/qiskit.primitives.StatevectorEstimator). We can see that the backpropagated expectation value without truncation is equivalent to the exact value within the limits of numerical precision.\n",
+ "\n",
+ "The expectation value on the operator with truncated terms has some error on the order of ``1e-4``, which is within the expected tolerance.\n",
+ "\n",
+ "**Note:** We use a statevector-based ``Estimator`` primitive to illustrate the effect of truncation on the output. To run on the backend to which the experiments were transpiled in Step 2, one would import the [EstimatorV2](/docs/api/qiskit-ibm-runtime/estimator-v2) from ``qiskit-ibm-runtime`` and pass the backend instance into the constructor."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "id": "ae47b4e1-f40e-4308-a32d-2ad7ecc56c03",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Exact expectation value: 0.8854160687717507\n",
+ "Backpropagated expectation value: 0.8854160687717532\n",
+ "Backpropagated expectation value with truncation: 0.8850236647156059\n",
+ " - Expected Error for truncated observable: 4.933e-02\n",
+ " - Observed Error for truncated observable: 3.924e-04\n"
+ ]
+ }
+ ],
+ "source": [
+ "from qiskit.primitives import StatevectorEstimator as Estimator\n",
+ "\n",
+ "estimator = Estimator()\n",
+ "\n",
+ "# Run the experiments using Estimator primitive\n",
+ "result_exact = estimator.run([(circuit_isa, observable_isa)]).result()[0].data.evs.item()\n",
+ "result_bp = estimator.run([(bp_circuit_isa, bp_obs_isa)]).result()[0].data.evs.item()\n",
+ "result_bp_trunc = (\n",
+ " estimator.run([(bp_circuit_trunc_isa, bp_obs_trunc_isa)]).result()[0].data.evs.item()\n",
+ ")\n",
+ "\n",
+ "print(f\"Exact expectation value: {result_exact}\")\n",
+ "print(f\"Backpropagated expectation value: {result_bp}\")\n",
+ "print(f\"Backpropagated expectation value with truncation: {result_bp_trunc}\")\n",
+ "print(f\" - Expected Error for truncated observable: {metadata.accumulated_error(0):.3e}\")\n",
+ "print(f\" - Observed Error for truncated observable: {abs(result_exact - result_bp_trunc):.3e}\")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/addons/qiskit-addon-obp/tutorials/index.mdx b/docs/addons/qiskit-addon-obp/tutorials/index.mdx
new file mode 100644
index 00000000000..92b3236fbdb
--- /dev/null
+++ b/docs/addons/qiskit-addon-obp/tutorials/index.mdx
@@ -0,0 +1,10 @@
+---
+title: "Tutorials - Qiskit addon: operator backpropagation (OBP) 0.3.0"
+---
+
+# Tutorials
+
+This page summarizes the available tutorials.
+
+* [Reducing depth of circuits with operator backpropagation](01-getting-started)
+
diff --git a/docs/api/qiskit-addon-obp/_toc.json b/docs/api/qiskit-addon-obp/_toc.json
index e2bd0263c41..3336d5e5e49 100644
--- a/docs/api/qiskit-addon-obp/_toc.json
+++ b/docs/api/qiskit-addon-obp/_toc.json
@@ -72,5 +72,7 @@
}
],
"collapsed": true,
- "untranslatable": true
+ "untranslatable": true,
+ "parentUrl": "/docs/addons/qiskit-addon-obp",
+ "parentLabel": "Operator backpropagation (OBP)"
}
diff --git a/docs/api/qiskit-addon-obp/qiskit-addon-obp.mdx b/docs/api/qiskit-addon-obp/qiskit-addon-obp.mdx
index 0dc24a61be8..a4334ca7d74 100644
--- a/docs/api/qiskit-addon-obp/qiskit-addon-obp.mdx
+++ b/docs/api/qiskit-addon-obp/qiskit-addon-obp.mdx
@@ -34,8 +34,8 @@ Main operator backpropagation functionality.
**Parameters**
* **observables** ([*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp) *|*[*list*](https://docs.python.org/3/library/stdtypes.html#list)*\[*[*SparsePauliOp*](/docs/api/qiskit/qiskit.quantum_info.SparsePauliOp)*]*) – The observable(s) onto which the circuit is backpropagated.
- * **slices** ([*Sequence*](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence)*\[*[*QuantumCircuit*](/docs/api/qiskit/qiskit.circuit.QuantumCircuit)*]*) – A sequence of `QuantumCircuit` objects representing a single circuit which has been separated into partitions spanning all qubits. **These “slices” will be backpropagated in reverse order.** Each slice must span all qubits. One may use the tools provided in [`qiskit_addon_utils.slicing`](https://qiskit.github.io/qiskit-addon-utils/apidocs/qiskit_addon_utils.slicing.html#module-qiskit_addon_utils.slicing "(in Qiskit addon utilities)") to slice a single [`QuantumCircuit`](/docs/api/qiskit/qiskit.circuit.QuantumCircuit).
- * **truncation\_error\_budget** ([*TruncationErrorBudget*](utils-truncating#truncationerrorbudget "qiskit_addon_obp.utils.truncating.TruncationErrorBudget") *| None*) – The error budget used for truncating Pauli terms. Refer to the [how-to guide](https://qiskit.github.io/qiskit-addon-obp/how_tos/truncate_operator_terms.html) for a detailed discussion on truncating terms from the output operator and bounding the incurred error.
+ * **slices** ([*Sequence*](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence)*\[*[*QuantumCircuit*](/docs/api/qiskit/qiskit.circuit.QuantumCircuit)*]*) – A sequence of `QuantumCircuit` objects representing a single circuit which has been separated into partitions spanning all qubits. **These “slices” will be backpropagated in reverse order.** Each slice must span all qubits. One may use the tools provided in [`qiskit_addon_utils.slicing`](/docs/addons/qiskit-addon-utils/apidocs/qiskit-addon-utils-slicing#module-qiskit_addon_utils.slicing "(in Qiskit addon utilities)") to slice a single [`QuantumCircuit`](/docs/api/qiskit/qiskit.circuit.QuantumCircuit).
+ * **truncation\_error\_budget** ([*TruncationErrorBudget*](utils-truncating#truncationerrorbudget "qiskit_addon_obp.utils.truncating.TruncationErrorBudget") *| None*) – The error budget used for truncating Pauli terms. Refer to the [how-to guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms) for a detailed discussion on truncating terms from the output operator and bounding the incurred error.
* **operator\_budget** ([*OperatorBudget*](utils-simplify#operatorbudget "qiskit_addon_obp.utils.simplify.OperatorBudget") *| None*) – Constraints on how large the operator may grow during backpropagation. If `None`, a default instance of [`OperatorBudget`](utils-simplify#operatorbudget "qiskit_addon_obp.utils.simplify.OperatorBudget") will be used, and no constraints will be placed on the output operator size.
* **max\_seconds** ([*int*](https://docs.python.org/3/library/functions.html#int) *| None*) – The maximum number of seconds to run the backpropagation. If this timeout is triggered before the function returns, the metadata of that moment will be returned. Note, that this metadata may contain only partial information for the last slice being backpropagated.
diff --git a/docs/api/qiskit-addon-obp/utils-truncating.mdx b/docs/api/qiskit-addon-obp/utils-truncating.mdx
index 2e6b97735e1..f53dee4039a 100644
--- a/docs/api/qiskit-addon-obp/utils-truncating.mdx
+++ b/docs/api/qiskit-addon-obp/utils-truncating.mdx
@@ -23,7 +23,7 @@ Functions for truncating Pauli operators within given error budgets.
A class for storing the constants that determine the truncation error budget.
- Refer to the [how-to guide](https://qiskit.github.io/qiskit-addon-obp/how_tos/truncate_operator_terms.html) for a detailed discussion on truncating operator terms during backpropagation and bounding the incurred error.
+ Refer to the [how-to guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms) for a detailed discussion on truncating operator terms during backpropagation and bounding the incurred error.
**Parameters**
@@ -53,7 +53,7 @@ Functions for truncating Pauli operators within given error budgets.
Indicates which Lp-norm is used for calculating truncation errors.
- Refer to the [how-to guide](https://qiskit.github.io/qiskit-addon-obp/how_tos/bound_error_using_p_norm.html) for a detailed conversation on bounding truncation error using higher Lp-norms.
+ Refer to the [how-to guide](/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm) for a detailed conversation on bounding truncation error using higher Lp-norms.
#### per\_slice\_budget
@@ -74,7 +74,7 @@ Functions for truncating Pauli operators within given error budgets.
Calculate the budget available to each slice for observable term truncation.
- This method makes the construction of a [`TruncationErrorBudget`](#qiskit_addon_obp.utils.truncating.TruncationErrorBudget "qiskit_addon_obp.utils.truncating.TruncationErrorBudget") easier for an end-user. This error budget can be provided to the [`backpropagate()`](qiskit-addon-obp#backpropagate "qiskit_addon_obp.backpropagate") method to enable the truncation of low-weight Pauli terms. Refer to the [how-to guide](https://qiskit.github.io/qiskit-addon-obp/how_tos/truncate_operator_terms.html) for a detailed discussion on truncating terms from the output operator and bounding the incurred error.
+ This method makes the construction of a [`TruncationErrorBudget`](#qiskit_addon_obp.utils.truncating.TruncationErrorBudget "qiskit_addon_obp.utils.truncating.TruncationErrorBudget") easier for an end-user. This error budget can be provided to the [`backpropagate()`](qiskit-addon-obp#backpropagate "qiskit_addon_obp.backpropagate") method to enable the truncation of low-weight Pauli terms. Refer to the [how-to guide](/docs/addons/qiskit-addon-obp/how-tos/truncate-operator-terms) for a detailed discussion on truncating terms from the output operator and bounding the incurred error.
**The construction logic is as follows:**
@@ -94,7 +94,7 @@ Functions for truncating Pauli operators within given error budgets.
* **max\_error\_per\_slice** ([*float*](https://docs.python.org/3/library/functions.html#float) *|*[*Sequence*](https://docs.python.org/3/library/collections.abc.html#collections.abc.Sequence)*\[*[*float*](https://docs.python.org/3/library/functions.html#float)*] | None*) – Specifies the maximum error per backpropagated slice. See above for more details.
* **max\_error\_total** ([*float*](https://docs.python.org/3/library/functions.html#float) *| None*) – Specifies the total maximum error for the entire backpropagation. See above for more details.
* **num\_slices** ([*int*](https://docs.python.org/3/library/functions.html#int) *| None*) – The number of slices over which to distribute the budget. See above for more details.
- * **p\_norm** ([*int*](https://docs.python.org/3/library/functions.html#int)) – The Lp norm of the error. This affects the gradual distribution of `max_error_total` in the case of `num_slices` also being set (see above). Refer to the [how-to guide](https://qiskit.github.io/qiskit-addon-obp/how_tos/bound_error_using_p_norm.html) for a detailed conversation on bounding truncation error using higher Lp-norms.
+ * **p\_norm** ([*int*](https://docs.python.org/3/library/functions.html#int)) – The Lp norm of the error. This affects the gradual distribution of `max_error_total` in the case of `num_slices` also being set (see above). Refer to the [how-to guide](/docs/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm) for a detailed conversation on bounding truncation error using higher Lp-norms.
**Returns**
diff --git a/docs/ruff.toml b/docs/ruff.toml
index 50ec7749e1f..71708e929c0 100644
--- a/docs/ruff.toml
+++ b/docs/ruff.toml
@@ -1 +1,2 @@
line-length=78
+exclude = ["addons"]
diff --git a/package.json b/package.json
index fd0672cd084..fe4da73a53a 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,7 @@
"check:qiskit-deprecation": "tsx scripts/js/commands/api/checkQiskitDeprecation.ts",
"check:qiskit-versions": "tsx scripts/js/commands/checkQiskitApiVersions.ts",
"regen-apis": "tsx scripts/js/commands/api/regenerateApiDocs.ts",
- "gen-api": "tsx scripts/js/commands/api/updateApiDocs.ts",
+ "gen-docs": "tsx scripts/js/commands/updateDocs.ts",
"generate-historical-redirects": "tsx scripts/js/commands/api/generateHistoricalRedirects.ts",
"save-internal-links": "tsx scripts/js/commands/saveInternalLinks.ts"
},
diff --git a/public/docs/api/qiskit-addon-aqc-tensor/objects.inv b/public/docs/api/qiskit-addon-aqc-tensor/objects.inv
index 3c3bda03e70..719ec51e4df 100644
Binary files a/public/docs/api/qiskit-addon-aqc-tensor/objects.inv and b/public/docs/api/qiskit-addon-aqc-tensor/objects.inv differ
diff --git a/public/docs/api/qiskit-addon-cutting/objects.inv b/public/docs/api/qiskit-addon-cutting/objects.inv
index 0282f2a012e..ac2216e9465 100644
Binary files a/public/docs/api/qiskit-addon-cutting/objects.inv and b/public/docs/api/qiskit-addon-cutting/objects.inv differ
diff --git a/public/docs/api/qiskit-addon-mpf/objects.inv b/public/docs/api/qiskit-addon-mpf/objects.inv
index 7eff45d1f48..217e3c58088 100644
Binary files a/public/docs/api/qiskit-addon-mpf/objects.inv and b/public/docs/api/qiskit-addon-mpf/objects.inv differ
diff --git a/public/docs/api/qiskit-addon-obp/objects.inv b/public/docs/api/qiskit-addon-obp/objects.inv
index c5791a33f25..a50859addb6 100644
Binary files a/public/docs/api/qiskit-addon-obp/objects.inv and b/public/docs/api/qiskit-addon-obp/objects.inv differ
diff --git a/public/docs/api/qiskit-addon-sqd/objects.inv b/public/docs/api/qiskit-addon-sqd/objects.inv
index da335cc5086..055c9347037 100644
Binary files a/public/docs/api/qiskit-addon-sqd/objects.inv and b/public/docs/api/qiskit-addon-sqd/objects.inv differ
diff --git a/public/docs/api/qiskit-addon-utils/objects.inv b/public/docs/api/qiskit-addon-utils/objects.inv
index b4ff59622be..a249b3d9965 100644
Binary files a/public/docs/api/qiskit-addon-utils/objects.inv and b/public/docs/api/qiskit-addon-utils/objects.inv differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/d6f281d4-706e-41ad-b7b5-bc7ebe106996-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/d6f281d4-706e-41ad-b7b5-bc7ebe106996-0.avif
new file mode 100644
index 00000000000..1f66445a312
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/d6f281d4-706e-41ad-b7b5-bc7ebe106996-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/fe7f454c-7d79-4c61-b1dd-6a98564fb5f1-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/fe7f454c-7d79-4c61-b1dd-6a98564fb5f1-0.avif
new file mode 100644
index 00000000000..069e3cfbcbd
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/fe7f454c-7d79-4c61-b1dd-6a98564fb5f1-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/ff72e77f-e2c0-4cce-8575-c5aa587f78fb-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/ff72e77f-e2c0-4cce-8575-c5aa587f78fb-0.avif
new file mode 100644
index 00000000000..722ad405b96
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/bound-error-using-p-norm/extracted-outputs/ff72e77f-e2c0-4cce-8575-c5aa587f78fb-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp/extracted-outputs/8758f393-8691-4116-94ca-1185c6188bb4-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp/extracted-outputs/8758f393-8691-4116-94ca-1185c6188bb4-0.avif
new file mode 100644
index 00000000000..c6f6d183e97
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp/extracted-outputs/8758f393-8691-4116-94ca-1185c6188bb4-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp/extracted-outputs/dadc314d-452f-465c-a382-d54685ce3f2a-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp/extracted-outputs/dadc314d-452f-465c-a382-d54685ce3f2a-0.avif
new file mode 100644
index 00000000000..a7f9f56ec92
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/simulating-circuits-with-obp/extracted-outputs/dadc314d-452f-465c-a382-d54685ce3f2a-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/297fc3b0-d3b4-432d-96cd-7e175b7e6a52-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/297fc3b0-d3b4-432d-96cd-7e175b7e6a52-0.avif
new file mode 100644
index 00000000000..1f66445a312
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/297fc3b0-d3b4-432d-96cd-7e175b7e6a52-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/4c36768e-03e7-4e1b-81b3-59b6e9eab43e-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/4c36768e-03e7-4e1b-81b3-59b6e9eab43e-0.avif
new file mode 100644
index 00000000000..d7759b6a948
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/4c36768e-03e7-4e1b-81b3-59b6e9eab43e-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/51d36d7e-0bd5-4388-bbb8-6831bf1f02e6-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/51d36d7e-0bd5-4388-bbb8-6831bf1f02e6-0.avif
new file mode 100644
index 00000000000..2619a764523
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/51d36d7e-0bd5-4388-bbb8-6831bf1f02e6-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/68f118df-6718-49b2-ba80-c37ef461f71a-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/68f118df-6718-49b2-ba80-c37ef461f71a-0.avif
new file mode 100644
index 00000000000..518f1a7f9fc
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/68f118df-6718-49b2-ba80-c37ef461f71a-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/69c9069a-9038-4319-8dc6-37a1be973ebf-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/69c9069a-9038-4319-8dc6-37a1be973ebf-0.avif
new file mode 100644
index 00000000000..acab16b05d8
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/69c9069a-9038-4319-8dc6-37a1be973ebf-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7-0.avif
new file mode 100644
index 00000000000..04a0c5eda7b
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/82271b0d-ddaf-4ad2-b5c6-e9e4c2a4cff7-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/b0bf5050-2928-41a1-9c3d-c9d79e918f6f-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/b0bf5050-2928-41a1-9c3d-c9d79e918f6f-0.avif
new file mode 100644
index 00000000000..e417e70524d
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/b0bf5050-2928-41a1-9c3d-c9d79e918f6f-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/b7167f80-5d29-4c32-be97-cba733f18b1d-0.avif b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/b7167f80-5d29-4c32-be97-cba733f18b1d-0.avif
new file mode 100644
index 00000000000..5b4f58f8b26
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/how-tos/truncate-operator-terms/extracted-outputs/b7167f80-5d29-4c32-be97-cba733f18b1d-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/1d68f197-ffa4-49de-9fe8-243b1facbd00-0.avif b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/1d68f197-ffa4-49de-9fe8-243b1facbd00-0.avif
new file mode 100644
index 00000000000..d09a869e3cb
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/1d68f197-ffa4-49de-9fe8-243b1facbd00-0.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/5e8bae1a-ef18-4eb0-9d2a-1ac7bbdced3b-1.avif b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/5e8bae1a-ef18-4eb0-9d2a-1ac7bbdced3b-1.avif
new file mode 100644
index 00000000000..9cee419e7c2
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/5e8bae1a-ef18-4eb0-9d2a-1ac7bbdced3b-1.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/65ec9cb1-a4ed-497b-a616-180e9659956f-1.avif b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/65ec9cb1-a4ed-497b-a616-180e9659956f-1.avif
new file mode 100644
index 00000000000..8d491d84737
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/65ec9cb1-a4ed-497b-a616-180e9659956f-1.avif differ
diff --git a/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/c79b8484-7de0-47cb-be7b-147b502ad1e5-0.avif b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/c79b8484-7de0-47cb-be7b-147b502ad1e5-0.avif
new file mode 100644
index 00000000000..8626c7192e7
Binary files /dev/null and b/public/docs/images/addons/qiskit-addon-obp/tutorials/01-getting-started/extracted-outputs/c79b8484-7de0-47cb-be7b-147b502ad1e5-0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-aqc-tensor/aqc-compression.avif b/public/docs/images/api/qiskit-addon-aqc-tensor/aqc-compression.avif
new file mode 100644
index 00000000000..729a6dfbf2e
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-aqc-tensor/aqc-compression.avif differ
diff --git a/public/docs/images/api/qiskit-addon-aqc-tensor/how-tos_01_quimb_tnoptimizer_7_0.avif b/public/docs/images/api/qiskit-addon-aqc-tensor/how-tos_01_quimb_tnoptimizer_7_0.avif
new file mode 100644
index 00000000000..6d479b3c4b6
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-aqc-tensor/how-tos_01_quimb_tnoptimizer_7_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-aqc-tensor/tutorials_01_initial_state_aqc_15_0.avif b/public/docs/images/api/qiskit-addon-aqc-tensor/tutorials_01_initial_state_aqc_15_0.avif
new file mode 100644
index 00000000000..3a6c0552553
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-aqc-tensor/tutorials_01_initial_state_aqc_15_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-aqc-tensor/tutorials_01_initial_state_aqc_26_0.avif b/public/docs/images/api/qiskit-addon-aqc-tensor/tutorials_01_initial_state_aqc_26_0.avif
new file mode 100644
index 00000000000..2232a740a86
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-aqc-tensor/tutorials_01_initial_state_aqc_26_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_2_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_2_0.avif
new file mode 100644
index 00000000000..02fd34d4350
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_2_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_4_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_4_0.avif
new file mode 100644
index 00000000000..d03d4e44b5e
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_4_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_5_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_5_0.avif
new file mode 100644
index 00000000000..573b2332ad6
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_generate_exact_sampling_coefficients_5_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_provide_multiple_observables_2_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_provide_multiple_observables_2_0.avif
new file mode 100644
index 00000000000..dc4133dc92a
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_provide_multiple_observables_2_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_17_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_17_0.avif
new file mode 100644
index 00000000000..8cf1ce24529
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_17_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_18_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_18_0.avif
new file mode 100644
index 00000000000..ca2c92dccf6
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_18_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_3_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_3_0.avif
new file mode 100644
index 00000000000..211ace31c5a
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_3_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_5_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_5_0.avif
new file mode 100644
index 00000000000..f6bf05eafaf
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_5_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_9_0.avif b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_9_0.avif
new file mode 100644
index 00000000000..918c5622977
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/how-tos_how_to_specify_cut_wires_9_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/index-1.svg b/public/docs/images/api/qiskit-addon-cutting/index-1.svg
new file mode 100644
index 00000000000..54eda97e5d2
--- /dev/null
+++ b/public/docs/images/api/qiskit-addon-cutting/index-1.svg
@@ -0,0 +1,2865 @@
+
+
+
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_10_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_10_0.avif
new file mode 100644
index 00000000000..8783c1ed965
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_10_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_2_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_2_0.avif
new file mode 100644
index 00000000000..dc4133dc92a
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_2_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_9_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_9_0.avif
new file mode 100644
index 00000000000..7ded7cd2ded
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_01_gate_cutting_to_reduce_circuit_width_9_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_11_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_11_0.avif
new file mode 100644
index 00000000000..33ee54b14e6
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_11_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_17_2.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_17_2.avif
new file mode 100644
index 00000000000..79972a3cc66
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_17_2.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_2_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_2_0.avif
new file mode 100644
index 00000000000..9fa7706d29f
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_2_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_9_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_9_0.avif
new file mode 100644
index 00000000000..8789a195c2b
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_02_gate_cutting_to_reduce_circuit_depth_9_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_14_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_14_0.avif
new file mode 100644
index 00000000000..38e454af7f3
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_14_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_15_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_15_0.avif
new file mode 100644
index 00000000000..9218e55ed07
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_15_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_3_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_3_0.avif
new file mode 100644
index 00000000000..f6bf05eafaf
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_3_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_7_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_7_0.avif
new file mode 100644
index 00000000000..3305a535e8b
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_03_wire_cutting_via_move_instruction_7_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_10_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_10_0.avif
new file mode 100644
index 00000000000..eafd867f2a0
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_10_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_11_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_11_0.avif
new file mode 100644
index 00000000000..0cbd5fce57b
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_11_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_2_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_2_0.avif
new file mode 100644
index 00000000000..026a7cef875
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_2_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_4_1.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_4_1.avif
new file mode 100644
index 00000000000..c364d22db11
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_4_1.avif differ
diff --git a/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_6_0.avif b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_6_0.avif
new file mode 100644
index 00000000000..3191b9095df
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-cutting/tutorials_04_automatic_cut_finding_6_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-mpf/explanations_mpf_stability_8_0.avif b/public/docs/images/api/qiskit-addon-mpf/explanations_mpf_stability_8_0.avif
new file mode 100644
index 00000000000..289bf7e1f25
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-mpf/explanations_mpf_stability_8_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_26_0.avif b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_26_0.avif
new file mode 100644
index 00000000000..8174319c875
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_26_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_27_0.avif b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_27_0.avif
new file mode 100644
index 00000000000..72c80267835
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_27_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_28_0.avif b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_28_0.avif
new file mode 100644
index 00000000000..2a62759c1b4
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_28_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_4_0.avif b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_4_0.avif
new file mode 100644
index 00000000000..7917ba8a5a0
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-mpf/tutorials_01_getting_started_4_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-10.avif b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-10.avif
index 7400e97e1b8..406f193ef56 100644
Binary files a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-10.avif and b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-10.avif differ
diff --git a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-12.avif b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-12.avif
index 42178152132..91f24887429 100644
Binary files a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-12.avif and b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-12.avif differ
diff --git a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-14.avif b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-14.avif
index 73e9ef64255..d126a4d7f92 100644
Binary files a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-14.avif and b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-14.avif differ
diff --git a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-2.avif b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-2.avif
index 248ca15aa5f..7cc0233c6ed 100644
Binary files a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-2.avif and b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-2.avif differ
diff --git a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-4.avif b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-4.avif
index 0c4991e782e..6085b852f82 100644
Binary files a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-4.avif and b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-4.avif differ
diff --git a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-6.avif b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-6.avif
index a1b8aa55dff..9c0e343c812 100644
Binary files a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-6.avif and b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-6.avif differ
diff --git a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-8.avif b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-8.avif
index ce2d163fbb5..15c5864f9a3 100644
Binary files a/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-8.avif and b/public/docs/images/api/qiskit-addon-obp/qiskit_addon_obp-utils-visualization-8.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/how_tos_benchmark_pauli_projection_12_0.avif b/public/docs/images/api/qiskit-addon-sqd/how_tos_benchmark_pauli_projection_12_0.avif
new file mode 100644
index 00000000000..50e80759124
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/how_tos_benchmark_pauli_projection_12_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/how_tos_benchmark_pauli_projection_8_0.avif b/public/docs/images/api/qiskit-addon-sqd/how_tos_benchmark_pauli_projection_8_0.avif
new file mode 100644
index 00000000000..f71d718c740
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/how_tos_benchmark_pauli_projection_8_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/how_tos_choose_subspace_dimension_8_0.avif b/public/docs/images/api/qiskit-addon-sqd/how_tos_choose_subspace_dimension_8_0.avif
new file mode 100644
index 00000000000..d7bc5a1192a
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/how_tos_choose_subspace_dimension_8_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/lucj_ansatz_zig_zag_pattern.avif b/public/docs/images/api/qiskit-addon-sqd/lucj_ansatz_zig_zag_pattern.avif
new file mode 100644
index 00000000000..4490f3900e8
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/lucj_ansatz_zig_zag_pattern.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/sqd_diagram.avif b/public/docs/images/api/qiskit-addon-sqd/sqd_diagram.avif
new file mode 100644
index 00000000000..c13fa299cd7
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/sqd_diagram.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/tutorials_01_chemistry_hamiltonian_19_1.avif b/public/docs/images/api/qiskit-addon-sqd/tutorials_01_chemistry_hamiltonian_19_1.avif
new file mode 100644
index 00000000000..ad877634350
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/tutorials_01_chemistry_hamiltonian_19_1.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_17_0.avif b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_17_0.avif
new file mode 100644
index 00000000000..3cdf97a6047
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_17_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_22_1.avif b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_22_1.avif
new file mode 100644
index 00000000000..9d97b5f9fb5
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_22_1.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_8_0.avif b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_8_0.avif
new file mode 100644
index 00000000000..d550ad169fb
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_8_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_9_0.avif b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_9_0.avif
new file mode 100644
index 00000000000..9196405a6ba
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-sqd/tutorials_02_fermionic_lattice_hamiltonian_9_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_10_1.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_10_1.avif
new file mode 100644
index 00000000000..c7509568f7a
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_10_1.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_3_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_3_0.avif
new file mode 100644
index 00000000000..311adfef85c
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_3_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_6_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_6_0.avif
new file mode 100644
index 00000000000..c45bcab464e
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_6_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_8_1.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_8_1.avif
new file mode 100644
index 00000000000..0e39125fed9
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_color_device_edges_to_improve_depth_8_1.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_11_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_11_0.avif
new file mode 100644
index 00000000000..a2204a9eea8
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_11_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_13_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_13_0.avif
new file mode 100644
index 00000000000..61cf4299d12
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_13_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_15_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_15_0.avif
new file mode 100644
index 00000000000..987ea6af65f
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_15_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_16_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_16_0.avif
new file mode 100644
index 00000000000..044488f7c26
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_16_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_17_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_17_0.avif
new file mode 100644
index 00000000000..8ec7afe076c
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_17_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_3_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_3_0.avif
new file mode 100644
index 00000000000..972add14f61
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_3_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_5_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_5_0.avif
new file mode 100644
index 00000000000..703822aae56
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_5_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_7_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_7_0.avif
new file mode 100644
index 00000000000..d40018ce2fc
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_7_0.avif differ
diff --git a/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_9_0.avif b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_9_0.avif
new file mode 100644
index 00000000000..0d5a240702c
Binary files /dev/null and b/public/docs/images/api/qiskit-addon-utils/how_tos_create_circuit_slices_9_0.avif differ
diff --git a/scripts/config/historical-pages-to-latest.json b/scripts/config/historical-pages-to-latest.json
index 9323e61f359..d9d00bfd17a 100644
--- a/scripts/config/historical-pages-to-latest.json
+++ b/scripts/config/historical-pages-to-latest.json
@@ -1482,6 +1482,17 @@
"0.16": {},
"0.17": {}
},
+ "qiskit-c": {
+ "2.0": {},
+ "2.1": {},
+ "2.2": {
+ "qk-vf-2-layout-result": "/"
+ },
+ "2.3": {},
+ "dev": {
+ "qk-param": "/"
+ }
+ },
"qiskit-addon-aqc-tensor": {
"0.1": {}
},
@@ -1508,16 +1519,5 @@
"0.2": {
"noise-management-post-selection": "/"
}
- },
- "qiskit-c": {
- "2.0": {},
- "2.1": {},
- "2.2": {
- "qk-vf-2-layout-result": "/"
- },
- "2.3": {},
- "dev": {
- "qk-param": "/"
- }
}
}
diff --git a/scripts/js/commands/api/regenerateApiDocs.ts b/scripts/js/commands/api/regenerateApiDocs.ts
index fa13ada3716..a22e8466570 100644
--- a/scripts/js/commands/api/regenerateApiDocs.ts
+++ b/scripts/js/commands/api/regenerateApiDocs.ts
@@ -10,15 +10,16 @@
// copyright notice, and modified files need to carry a notice indicating
// that they have been altered from the originals.
-import { readdir } from "fs/promises";
-
import yargs from "yargs/yargs";
import { hideBin } from "yargs/helpers";
import { $ } from "zx";
import { Pkg } from "../../lib/api/Pkg.js";
import { zxMain } from "../../lib/zx.js";
-import { getDevVersion, getReleasedVersions } from "../../lib/apiVersions.js";
+import { getDevVersion, getReleasedVersions, parseMinorVersion } from "../../lib/apiVersions.js";
+import { pathExists, rmFilesInFolder } from "../../lib/fs.js";
+import { downloadSphinxArtifact } from "../../lib/api/sphinxArtifacts.js";
+import { runConversionPipeline } from "../../lib/api/conversionPipeline.js";
import { generateHistoricalRedirects } from "./generateHistoricalRedirects.js";
interface Arguments {
@@ -75,8 +76,6 @@ zxMain(async () => {
);
}
- await generateHistoricalRedirects();
-
console.log(`Each regenerated version has been saved as a distinct commit. If the changes are
too large for one single PR, consider splitting it up into multiple PRs by using
git cherry-pick or git rebase -i so each PR only has the commits it wants to target.`);
@@ -109,16 +108,23 @@ async function regenerateVersion(
skipDownload: boolean,
typeArgument?: "historical" | "dev",
): Promise {
- const command = ["npm", "run", "gen-api", "--", "-p", pkgName, "-v", version];
- if (typeArgument) {
- command.push(`--${typeArgument}`);
- }
- if (skipDownload) {
- command.push("--skip-download");
- }
-
try {
- await $`${command}`;
+ const type = typeArgument ?? "latest";
+ const minorVersion = parseMinorVersion(version)!;
+ const pkg = await Pkg.fromArgs(pkgName, version, minorVersion, type);
+ const artifactFolder = pkg.sphinxArtifactFolder();
+ if (skipDownload && (await pathExists(`${artifactFolder}/artifact`))) {
+ console.log(`Skip downloading sources for ${pkgName}:${minorVersion}`);
+ } else {
+ await downloadSphinxArtifact(pkg, artifactFolder);
+ }
+ const markdownDir = pkg.apiOutputDir("docs");
+ if (await pathExists(markdownDir)) await rmFilesInFolder(markdownDir);
+ const imagesDir = pkg.apiOutputDir("public/docs/images");
+ if (await pathExists(imagesDir)) await rmFilesInFolder(imagesDir);
+ console.log(`Run pipeline for ${pkgName}:${minorVersion}`);
+ await runConversionPipeline(`${artifactFolder}/artifact`, "docs", "public/docs", pkg);
+ await generateHistoricalRedirects();
if ((await gitStatus()) !== "") {
await gitCommit(`Regenerate ${pkgName} ${version}`);
console.log(`🚀 ${pkgName} ${version} regenerated correctly`);
diff --git a/scripts/js/commands/api/syncDevDocs.ts b/scripts/js/commands/api/syncDevDocs.ts
index e7d17ce3e09..e066c8f5b85 100644
--- a/scripts/js/commands/api/syncDevDocs.ts
+++ b/scripts/js/commands/api/syncDevDocs.ts
@@ -15,12 +15,11 @@ import fs from "fs/promises";
import { Pkg } from "../../lib/api/Pkg.js";
import { zxMain } from "../../lib/zx.js";
-import { readApiFullVersion } from "../../lib/apiVersions.js";
-import {
- generateVersion,
- determineMinorVersion,
- Arguments,
-} from "./updateApiDocs.js";
+import { readApiFullVersion, parseMinorVersion } from "../../lib/apiVersions.js";
+import { pathExists, rmFilesInFolder } from "../../lib/fs.js";
+import { downloadSphinxArtifact } from "../../lib/api/sphinxArtifacts.js";
+import { runConversionPipeline } from "../../lib/api/conversionPipeline.js";
+import { generateHistoricalRedirects } from "./generateHistoricalRedirects.js";
// Found with `gh api repos/qiskit/${repo}/actions/workflows`
const QISKIT_WORKFLOW_ID = "66225883";
@@ -101,15 +100,16 @@ async function updateHtmlArtifacts(args: {
await fs.writeFile(path, JSON.stringify(prior, null, 2) + "\n");
}
-async function regenDocs(pkg: string, version: string): Promise {
- const args: Arguments = {
- package: pkg,
- version: version,
- dev: true,
- historical: false,
- skipDownload: false,
- };
- const minorVersion = determineMinorVersion(args);
- const pkgObj = await Pkg.fromArgs(pkg, version, minorVersion, "dev");
- await generateVersion(pkgObj, args);
+async function regenDocs(pkgName: string, version: string): Promise {
+ const minorVersion = parseMinorVersion(version)!;
+ const pkg = await Pkg.fromArgs(pkgName, version, minorVersion, "dev");
+ const artifactFolder = pkg.sphinxArtifactFolder();
+ await downloadSphinxArtifact(pkg, artifactFolder);
+ const markdownDir = pkg.apiOutputDir("docs");
+ if (await pathExists(markdownDir)) await rmFilesInFolder(markdownDir);
+ const imagesDir = pkg.apiOutputDir("public/docs/images");
+ if (await pathExists(imagesDir)) await rmFilesInFolder(imagesDir);
+ console.log(`Run pipeline for ${pkgName}:${minorVersion}`);
+ await runConversionPipeline(`${artifactFolder}/artifact`, "docs", "public/docs", pkg);
+ await generateHistoricalRedirects();
}
diff --git a/scripts/js/commands/api/updateApiDocs.ts b/scripts/js/commands/updateDocs.ts
similarity index 56%
rename from scripts/js/commands/api/updateApiDocs.ts
rename to scripts/js/commands/updateDocs.ts
index edac31e251b..9736acb8ec4 100644
--- a/scripts/js/commands/api/updateApiDocs.ts
+++ b/scripts/js/commands/updateDocs.ts
@@ -1,6 +1,6 @@
// This code is a Qiskit project.
//
-// (C) Copyright IBM 2023.
+// (C) Copyright IBM 2024.
//
// This code is licensed under the Apache License, Version 2.0. You may
// obtain a copy of this license in the LICENSE file in the root directory
@@ -12,22 +12,24 @@
import yargs from "yargs/yargs";
import { hideBin } from "yargs/helpers";
+import { readFile, writeFile } from "fs/promises";
-import { Pkg } from "../../lib/api/Pkg.js";
-import { zxMain } from "../../lib/zx.js";
-import { parseMinorVersion, isValidVersion } from "../../lib/apiVersions.js";
-import { pathExists, rmFilesInFolder } from "../../lib/fs.js";
-import { downloadSphinxArtifact } from "../../lib/api/sphinxArtifacts.js";
-import { runConversionPipeline } from "../../lib/api/conversionPipeline.js";
-import { generateHistoricalRedirects } from "./generateHistoricalRedirects.js";
+import { $ } from "zx";
+import { mkdirp } from "mkdirp";
-export interface Arguments {
- [x: string]: unknown;
+import { Pkg } from "../lib/api/Pkg.js";
+import { pathExists, rmFilesInFolder } from "../lib/fs.js";
+import { downloadSphinxArtifact } from "../lib/api/sphinxArtifacts.js";
+import { runConversionPipeline } from "../lib/api/conversionPipeline.js";
+import { runSphinxPipeline } from "../lib/sphinx/conversionPipeline.js";
+
+import { zxMain } from "../lib/zx.js";
+import { isValidVersion, parseMinorVersion } from "../lib/apiVersions.js";
+
+interface Arguments {
package: string;
version: string;
- historical: boolean;
- dev: boolean;
- skipDownload: boolean;
+ skipDownload?: boolean;
sphinxArtifactFolder?: string;
}
@@ -55,21 +57,11 @@ const readArgs = (): Arguments => {
return version;
},
})
- .option("historical", {
- type: "boolean",
- default: false,
- description: "Is this a prior release?",
- })
- .option("dev", {
- type: "boolean",
- default: false,
- description: "Is this a dev release?",
- })
.option("skip-download", {
type: "boolean",
default: false,
description:
- "Rather than downloading the artifact from Box, reuse what is already downloaded. This can save time, but it risks using an outdated version of the docs.",
+ "Rather than downloading the artifact from Box, reuse what is already downloaded.",
})
.option("sphinx-artifact-folder", {
alias: "a",
@@ -82,34 +74,23 @@ const readArgs = (): Arguments => {
.parseSync();
};
-export async function generateVersion(
- pkg: Pkg,
- args: Arguments,
-): Promise {
+async function generateVersion(pkg: Pkg, args: Arguments): Promise {
const sphinxArtifactFolder = await prepareSphinxFolder(pkg, args);
- await deleteExistingFiles(pkg);
+
+ await deleteExistingApiFiles(pkg);
console.log(`Run pipeline for ${pkg.name}:${pkg.versionWithoutPatch}`);
await runConversionPipeline(sphinxArtifactFolder, "docs", "public/docs", pkg);
- await generateHistoricalRedirects();
-}
-export function determineMinorVersion(args: Arguments): string {
- const minorVersion = parseMinorVersion(args.version);
- if (minorVersion === null) {
- throw new Error(
- `Invalid --version. Expected the format 0.44.0, but received ${args.version}`,
+ if (pkg.isAddon()) {
+ await deleteExistingAddonFiles(pkg);
+ await runSphinxPipeline(
+ sphinxArtifactFolder,
+ "docs/addons",
+ "public/docs",
+ pkg,
);
}
-
- const devRegex = /[0-9](rc|-dev)/;
- if (args.dev && !args.version.match(devRegex)) {
- throw new Error(
- `${args.package} ${args.version} is not a correct dev version. Please make sure the version has one of the following suffixes immediately following the patch version: rc, -dev. e.g. 1.0.0rc1 or 1.0.0-dev`,
- );
- }
-
- return minorVersion;
}
async function prepareSphinxFolder(pkg: Pkg, args: Arguments): Promise {
@@ -135,38 +116,57 @@ async function prepareSphinxFolder(pkg: Pkg, args: Arguments): Promise {
return `${sphinxArtifactFolder}/artifact`;
}
-async function deleteExistingFiles(pkg: Pkg): Promise {
- const markdownDir = pkg.outputDir("docs");
+async function deleteExistingApiFiles(pkg: Pkg): Promise {
+ const markdownDir = pkg.apiOutputDir("docs");
if (await pathExists(markdownDir)) {
await rmFilesInFolder(markdownDir);
}
- const imagesDir = pkg.outputDir("public/docs/images");
+ const imagesDir = pkg.apiOutputDir("public/docs/images");
if (await pathExists(imagesDir)) {
await rmFilesInFolder(imagesDir);
}
console.log(
- `Deleted existing markdown & images for ${pkg.name}:${pkg.versionWithoutPatch}`,
+ `Deleted existing docs & images for ${pkg.name}:${pkg.versionWithoutPatch}`,
);
}
-if (import.meta.url === `file://${process.argv[1]}`) {
- zxMain(async () => {
- const args = readArgs();
-
- if (args.historical && args.dev) {
- throw new Error(
- `${args.package} ${args.version} cannot be historical and dev at the same time. Please remove at least only one of these two arguments: --historical, --dev.`,
- );
+async function deleteExistingAddonFiles(pkg: Pkg): Promise {
+ const markdownDir = pkg.outputDir("docs/addons");
+ if (await pathExists(markdownDir)) {
+ // Preserve the hand-authored _toc.json.
+ const tocPath = `${markdownDir}/_toc.json`;
+ const tocContent = (await pathExists(tocPath))
+ ? await readFile(tocPath, "utf-8")
+ : null;
+ await $`rm -rf ${markdownDir}`;
+ if (tocContent !== null) {
+ await mkdirp(markdownDir);
+ await writeFile(tocPath, tocContent);
}
+ }
+ const imagesDir = pkg.outputDir("public/docs/images/addons");
+ if (await pathExists(imagesDir)) {
+ await $`rm -rf ${imagesDir}`;
+ }
+ console.log(
+ `Deleted existing docs & images for ${pkg.name}:${pkg.versionWithoutPatch}`,
+ );
+}
- const minorVersion = determineMinorVersion(args);
- const type = args.historical ? "historical" : args.dev ? "dev" : "latest";
- const pkg = await Pkg.fromArgs(
- args.package,
- args.version,
- minorVersion,
- type,
+zxMain(async () => {
+ const args = readArgs();
+ const minorVersion = parseMinorVersion(args.version);
+ if (minorVersion === null) {
+ throw new Error(
+ `Invalid --version. Expected the format 0.44.0, but received ${args.version}`,
);
- await generateVersion(pkg, args);
- });
-}
+ }
+
+ const pkg = await Pkg.fromArgs(
+ args.package,
+ args.version,
+ minorVersion,
+ "latest",
+ );
+ await generateVersion(pkg, args);
+});
diff --git a/scripts/js/lib/Notebooks.ts b/scripts/js/lib/Notebooks.ts
new file mode 100644
index 00000000000..5870dcbc7e2
--- /dev/null
+++ b/scripts/js/lib/Notebooks.ts
@@ -0,0 +1,17 @@
+export type NotebookCell = {
+ id?: string;
+ cell_type: "code" | "markdown" | "raw";
+ source: string | string[];
+ metadata: Record;
+ outputs?: unknown[];
+ execution_count?: number | null;
+};
+
+export type Notebook = {
+ nbformat: number;
+ nbformat_minor: number;
+ metadata: Record;
+ cells: NotebookCell[];
+};
+
+export type NotebookWithUrl = Notebook & { url: string };
diff --git a/scripts/js/lib/api/Pkg.ts b/scripts/js/lib/api/Pkg.ts
index 9b3b1145f86..78c3c7c9484 100644
--- a/scripts/js/lib/api/Pkg.ts
+++ b/scripts/js/lib/api/Pkg.ts
@@ -58,17 +58,21 @@ export class Pkg {
readonly artifactPackageName: string;
readonly hasRootNamespaceFile: boolean;
- static VALID_NAMES = [
- "qiskit",
- "qiskit-ibm-runtime",
- "qiskit-ibm-transpiler",
+ static ADDON_NAMES = [
"qiskit-addon-aqc-tensor",
"qiskit-addon-obp",
"qiskit-addon-mpf",
"qiskit-addon-sqd",
"qiskit-addon-cutting",
"qiskit-addon-utils",
+ ];
+
+ static VALID_NAMES = [
+ "qiskit",
+ "qiskit-ibm-runtime",
+ "qiskit-ibm-transpiler",
"qiskit-c",
+ ...Pkg.ADDON_NAMES,
];
constructor(kwargs: {
@@ -255,6 +259,10 @@ export class Pkg {
}
outputDir(parentDir: string): string {
+ return join(parentDir, this.name);
+ }
+
+ apiOutputDir(parentDir: string): string {
let path = join(parentDir, "api", this.name);
if (this.isHistorical()) {
path = join(path, this.versionWithoutPatch);
@@ -284,6 +292,10 @@ export class Pkg {
return this.language === "C";
}
+ isAddon(): boolean {
+ return Pkg.ADDON_NAMES.includes(this.name);
+ }
+
isProblematicLegacyQiskit(): boolean {
return this.name === "qiskit" && +this.versionWithoutPatch < 0.45;
}
diff --git a/scripts/js/lib/api/conversionPipeline.test.ts b/scripts/js/lib/api/conversionPipeline.test.ts
index 90d1c91bd21..9171b925cae 100644
--- a/scripts/js/lib/api/conversionPipeline.test.ts
+++ b/scripts/js/lib/api/conversionPipeline.test.ts
@@ -66,7 +66,7 @@ test("qiskit-sphinx-theme", async ({}, testInfo) => {
releaseNotesConfig: new ReleaseNotesConfig({ enabled: false }),
kebabCaseAndShortenUrls: false,
});
- const markdownFolder = pkg.outputDir(docsBaseFolder);
+ const markdownFolder = pkg.apiOutputDir(docsBaseFolder);
await runConversionPipeline(
"scripts/js/lib/api/testdata/qiskit-sphinx-theme",
diff --git a/scripts/js/lib/api/conversionPipeline.ts b/scripts/js/lib/api/conversionPipeline.ts
index d672da35b17..034bf951b51 100644
--- a/scripts/js/lib/api/conversionPipeline.ts
+++ b/scripts/js/lib/api/conversionPipeline.ts
@@ -68,7 +68,7 @@ export async function runConversionPipeline(
// Warning: the sequence of operations often matters.
await writeMarkdownResults(pkg, docsBaseFolder, results);
await copyImages(pkg, htmlPath, "public", results);
- await maybeObjectsInv?.write(pkg.outputDir(publicBaseFolder));
+ await maybeObjectsInv?.write(pkg.apiOutputDir(publicBaseFolder));
await maybeUpdateReleaseNotesFolder(pkg, markdownPath);
await writeTocFile(pkg, markdownPath, results);
await writeVersionFile(pkg, markdownPath);
@@ -92,7 +92,7 @@ async function determineFilePaths(
cwd: htmlPath,
},
);
- const markdownPath = pkg.outputDir(docsBaseFolder);
+ const markdownPath = pkg.apiOutputDir(docsBaseFolder);
await mkdirp(markdownPath);
return [files, markdownPath, maybeObjectsInv];
}
@@ -111,7 +111,7 @@ async function convertFilesToMarkdown(
html,
fileName: file,
determineGithubUrl: pkg.determineGithubUrlFn(),
- imageDestination: pkg.outputDir(`${DOCS_BASE_PATH}/images`),
+ imageDestination: pkg.apiOutputDir(`${DOCS_BASE_PATH}/images`),
releaseNotesTitle: pkg.releaseNotesTitle(),
hasSeparateReleaseNotes: pkg.hasSeparateReleaseNotes(),
isCApi: pkg.isCApi(),
@@ -161,7 +161,7 @@ async function postProcessResults(
{
kebabCaseAndShorten: pkg.kebabCaseAndShortenUrls,
pkgName: pkg.name,
- pkgOutputDir: pkg.outputDir(DOCS_BASE_PATH),
+ pkgOutputDir: pkg.apiOutputDir(DOCS_BASE_PATH),
},
maybeObjectsInv,
);
diff --git a/scripts/js/lib/api/generateToc.ts b/scripts/js/lib/api/generateToc.ts
index 2001244c4b9..ef4636ba252 100644
--- a/scripts/js/lib/api/generateToc.ts
+++ b/scripts/js/lib/api/generateToc.ts
@@ -35,6 +35,8 @@ type Toc = {
children: TocEntry[];
collapsed: boolean;
untranslatable?: boolean;
+ parentUrl?: string;
+ parentLabel?: string;
};
export function generateToc(pkg: Pkg, results: HtmlToMdResultWithUrl[]): Toc {
@@ -71,6 +73,10 @@ export function generateToc(pkg: Pkg, results: HtmlToMdResultWithUrl[]): Toc {
children: orderedEntries,
collapsed: true,
untranslatable: true,
+ ...(pkg.isAddon() && {
+ parentUrl: `/docs/addons/${pkg.name}`,
+ parentLabel: pkg.title,
+ }),
};
}
@@ -226,7 +232,7 @@ function ensureIndexPage(
pkg: Pkg,
tocModules: TocEntry[],
): TocEntry | undefined {
- const docsFolder = pkg.outputDir(`${DOCS_BASE_PATH}/`);
+ const docsFolder = pkg.apiOutputDir(`${DOCS_BASE_PATH}/`);
return tocModules.some((entry) => entry.url === docsFolder)
? undefined
: {
diff --git a/scripts/js/lib/api/htmlToMd.ts b/scripts/js/lib/api/htmlToMd.ts
index 7abf0b466bf..ad8ecced71b 100644
--- a/scripts/js/lib/api/htmlToMd.ts
+++ b/scripts/js/lib/api/htmlToMd.ts
@@ -243,7 +243,7 @@ function buildAdmonition(
handlers: Record,
): MdxJsxFlowElement {
const titleNode = findNodeWithProperty(node.children, "admonition-title");
- const children: Array = without(node.children, titleNode).map(
+ const children: Array = without(node.children, titleNode ?? undefined).map(
(node: any) => toMdast(node, { handlers }),
);
@@ -261,7 +261,7 @@ function buildAdmonition(
{
type: "mdxJsxAttribute",
name: "title",
- value: toText(titleNode),
+ value: titleNode ? toText(titleNode) : "",
},
{
type: "mdxJsxAttribute",
diff --git a/scripts/js/lib/api/objectsInv.ts b/scripts/js/lib/api/objectsInv.ts
index 93100542c9b..b39faf41525 100644
--- a/scripts/js/lib/api/objectsInv.ts
+++ b/scripts/js/lib/api/objectsInv.ts
@@ -190,6 +190,58 @@ export class ObjectsInv {
return uri;
}
+ /**
+ * Load all published objects.inv files from public/docs/api/ and return
+ * a map of package name → ObjectsInv. These inventories have already been
+ * normalized by the API pipeline so URIs are ready to use directly.
+ */
+ static async loadPublishedApis(
+ publicBaseFolder: string,
+ ): Promise