diff --git a/README.md b/README.md index 284c2626..8a32aab3 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ Running notebooks locally requires additional dependencies located in [notebooks | Quantum Circuit Born Machine | [Quantum_Circuit_Born_Machine.ipynb](notebooks/textbook/Quantum_Circuit_Born_Machine.ipynb) | [Benedetti2019](https://www.nature.com/articles/s41534-019-0157-8), [Liu2018](https://journals.aps.org/pra/abstract/10.1103/PhysRevA.98.062324) | | QFT | [Quantum_Fourier_Transform.ipynb](notebooks/textbook/Quantum_Fourier_Transform.ipynb) | [Coppersmith2002](https://arxiv.org/abs/quant-ph/0201067) | | QPE | [Quantum_Phase_Estimation_Algorithm.ipynb](notebooks/textbook/Quantum_Phase_Estimation_Algorithm.ipynb) | [Kitaev1995](https://arxiv.org/abs/quant-ph/9511026) | +| Quantum Counting | [Quantum_Counting_Algorithm.ipynb](notebooks/textbook/Quantum_Counting_Algorithm.ipynb) | [Brassard1998](https://arxiv.org/abs/quant-ph/9805082) | | Quantum Walk | [Quantum_Walk.ipynb](notebooks/textbook/Quantum_Walk.ipynb) | [Childs2002](https://arxiv.org/abs/quant-ph/0209131) | |Shor's| [Shors_Algorithm.ipynb](notebooks/textbook/Shors_Algorithm.ipynb) | [Shor1998](https://arxiv.org/abs/quant-ph/9508027) | | Simon's | [Simons_Algorithm.ipynb](notebooks/textbook/Simons_Algorithm.ipynb) | [Simon1997](https://epubs.siam.org/doi/10.1137/S0097539796298637) | diff --git a/notebooks/advanced_algorithms/adaptive_shot_allocation/1_Shot_Allocation.ipynb b/notebooks/advanced_algorithms/adaptive_shot_allocation/1_Shot_Allocation.ipynb index c8a78f33..8de2d2e2 100644 --- a/notebooks/advanced_algorithms/adaptive_shot_allocation/1_Shot_Allocation.ipynb +++ b/notebooks/advanced_algorithms/adaptive_shot_allocation/1_Shot_Allocation.ipynb @@ -104,11 +104,14 @@ ], "source": [ "# Create a parameterized circuit with random angles\n", - "random_state = (Circuit()\n", - " .ry(angle = np.random.rand()*np.pi, target=0).ry(angle = np.random.rand()*np.pi, target=1)\n", - " .cnot(control=0, target=1)\n", - " .ry(angle = np.random.rand()*np.pi, target=0).ry(angle = np.random.rand()*np.pi, target=1)\n", - " )\n", + "random_state = (\n", + " Circuit()\n", + " .ry(angle=np.random.rand() * np.pi, target=0)\n", + " .ry(angle=np.random.rand() * np.pi, target=1)\n", + " .cnot(control=0, target=1)\n", + " .ry(angle=np.random.rand() * np.pi, target=0)\n", + " .ry(angle=np.random.rand() * np.pi, target=1)\n", + ")\n", "\n", "print(random_state)" ] @@ -159,29 +162,30 @@ "# Set up local simulator device\n", "device = LocalSimulator()\n", "\n", + "\n", "def compute_expectation_value_approximation(state, shot_configuration):\n", " \"\"\"Compute approximate expectation value using specified shot allocation.\n", - " \n", + "\n", " Args:\n", " state (Circuit): Quantum circuit representing the state\n", " shot_configuration (list): Number of shots for each Pauli term\n", - " \n", + "\n", " Returns:\n", " float: Approximated expectation value\n", " \"\"\"\n", - " assert (len(shot_configuration)==len(paulis))\n", + " assert len(shot_configuration) == len(paulis)\n", " expectation_value = 0.0\n", - " \n", + "\n", " for i, term in enumerate(paulis):\n", " # Define a circuit measuring the specific term on the given state\n", " circuit = state.copy().sample(term)\n", - " \n", + "\n", " # Execute measurements and get results (±1)\n", - " measurements = device.run(circuit, shots = shot_configuration[i]).result().values[0]\n", - " \n", + " measurements = device.run(circuit, shots=shot_configuration[i]).result().values[0]\n", + "\n", " # Calculate term's contribution: mean of measurements × coefficient\n", - " expectation_value += coeffs[i]*np.mean(measurements, dtype=float)\n", - " \n", + " expectation_value += coeffs[i] * np.mean(measurements, dtype=float)\n", + "\n", " return expectation_value" ] }, @@ -201,25 +205,26 @@ "source": [ "def get_exact_expectation_value(state):\n", " \"\"\"Compute exact expectation value using state vector simulation.\n", - " \n", + "\n", " Args:\n", " state (Circuit): Quantum circuit representing the state\n", - " \n", + "\n", " Returns:\n", " float: Exact expectation value\n", " \"\"\"\n", " expectation_value = 0.0\n", - " \n", + "\n", " for i, term in enumerate(paulis):\n", " # Calculate exact expectation for each term\n", " circuit = state.copy().expectation(term)\n", - " normalized_expectation = device.run(circuit, shots = 0).result().values[0]\n", - " \n", + " normalized_expectation = device.run(circuit, shots=0).result().values[0]\n", + "\n", " # Add term's contribution: exact expectation × coefficient\n", - " expectation_value += coeffs[i]*normalized_expectation\n", - " \n", + " expectation_value += coeffs[i] * normalized_expectation\n", + "\n", " return expectation_value\n", "\n", + "\n", "# Calculate exact expectation value for reference\n", "exact_expectation = get_exact_expectation_value(random_state)\n", "print(f\"Exact expectation value: {exact_expectation:.6f}\")" @@ -252,12 +257,16 @@ "uniform_shot_allocation = [400, 400, 400]\n", "\n", "# Run multiple trials to get statistics\n", - "uniform_values = [compute_expectation_value_approximation(random_state, uniform_shot_allocation) for _ in range(1000)]\n", + "uniform_values = [\n", + " compute_expectation_value_approximation(random_state, uniform_shot_allocation)\n", + " for _ in range(1000)\n", + "]\n", "uniform_mean = np.mean(uniform_values)\n", "uniform_std = np.std(uniform_values)\n", "\n", - "print(f\"Uniform allocation ({uniform_shot_allocation}):\\n\"\n", - " f\"Mean: {uniform_mean:.6f} ± {uniform_std:.6f}\")" + "print(\n", + " f\"Uniform allocation ({uniform_shot_allocation}):\\nMean: {uniform_mean:.6f} ± {uniform_std:.6f}\"\n", + ")" ] }, { @@ -284,30 +293,47 @@ } ], "source": [ - "def plot_histograms(data, labels, colors, exact_expectation, title, y_max = 150):\n", + "def plot_histograms(data, labels, colors, exact_expectation, title, y_max=150):\n", " num_sets = len(data)\n", - " assert (len(labels) == num_sets)\n", - " assert (len(colors) == num_sets)\n", - " \n", + " assert len(labels) == num_sets\n", + " assert len(colors) == num_sets\n", + "\n", " plt.figure(figsize=(10, 6))\n", " for i in range(num_sets):\n", - " plt.hist(data[i], alpha=0.5, bins=np.arange(exact_expectation-1, exact_expectation+1, 0.025),\n", - " color=colors[i], label=labels[i])\n", - " \n", + " plt.hist(\n", + " data[i],\n", + " alpha=0.5,\n", + " bins=np.arange(exact_expectation - 1, exact_expectation + 1, 0.025),\n", + " color=colors[i],\n", + " label=labels[i],\n", + " )\n", + "\n", " if not y_max:\n", " y_max = int(np.ceil(plt.ylim()[1]))\n", - " \n", - " plt.ylim(0,y_max)\n", - " ev_line_height = (2*y_max//3)\n", - " plt.plot([exact_expectation]*ev_line_height, np.arange(ev_line_height), color=\"black\", label=\"Exact value\")\n", - " \n", + "\n", + " plt.ylim(0, y_max)\n", + " ev_line_height = 2 * y_max // 3\n", + " plt.plot(\n", + " [exact_expectation] * ev_line_height,\n", + " np.arange(ev_line_height),\n", + " color=\"black\",\n", + " label=\"Exact value\",\n", + " )\n", + "\n", " plt.xlabel(\"Expectation Value\")\n", " plt.ylabel(\"Frequency\")\n", " plt.legend()\n", " plt.title(title)\n", " plt.grid(True, alpha=0.3)\n", "\n", - "plot_histograms([uniform_values], [\"Uniform shot allocation\"], [\"red\"], exact_expectation, \"Distribution of Results with Uniform Shot Allocation\")" + "\n", + "plot_histograms(\n", + " [uniform_values],\n", + " [\"Uniform shot allocation\"],\n", + " [\"red\"],\n", + " exact_expectation,\n", + " \"Distribution of Results with Uniform Shot Allocation\",\n", + ")" ] }, { @@ -342,12 +368,17 @@ "weighted_shot_allocation = [100, 100, 1000]\n", "\n", "# Run multiple trials to get statistics\n", - "weighted_values = [compute_expectation_value_approximation(random_state, weighted_shot_allocation) for _ in range(1000)]\n", + "weighted_values = [\n", + " compute_expectation_value_approximation(random_state, weighted_shot_allocation)\n", + " for _ in range(1000)\n", + "]\n", "weighted_mean = np.mean(weighted_values)\n", "weighted_std = np.std(weighted_values)\n", "\n", - "print(f\"Coefficient-weighted allocation ({weighted_shot_allocation}):\\n\"\n", - " f\"Mean: {weighted_mean:.6f} ± {weighted_std:.6f}\")" + "print(\n", + " f\"Coefficient-weighted allocation ({weighted_shot_allocation}):\\n\"\n", + " f\"Mean: {weighted_mean:.6f} ± {weighted_std:.6f}\"\n", + ")" ] }, { @@ -367,8 +398,13 @@ } ], "source": [ - "plot_histograms([uniform_values, weighted_values], [\"Uniform allocation\", \"Coefficient-weighted allocation\"], [\"red\", \"blue\"], \n", - " exact_expectation, \"Comparison of Shot Allocation Strategies\")" + "plot_histograms(\n", + " [uniform_values, weighted_values],\n", + " [\"Uniform allocation\", \"Coefficient-weighted allocation\"],\n", + " [\"red\", \"blue\"],\n", + " exact_expectation,\n", + " \"Comparison of Shot Allocation Strategies\",\n", + ")" ] }, { @@ -425,7 +461,9 @@ ], "source": [ "# Prepare uniform superposition state\n", - "uniform_superposition_state = Circuit().h(0).h(1) # Apply Hadamard gates to create uniform superposition\n", + "uniform_superposition_state = (\n", + " Circuit().h(0).h(1)\n", + ") # Apply Hadamard gates to create uniform superposition\n", "print(uniform_superposition_state)\n", "\n", "exact_expectation = get_exact_expectation_value(uniform_superposition_state)\n", @@ -458,14 +496,24 @@ ], "source": [ "# Compare uniform and weighted allocations\n", - "uniform_values = [compute_expectation_value_approximation(uniform_superposition_state, uniform_shot_allocation) for _ in range(1000)]\n", - "weighted_values = [compute_expectation_value_approximation(uniform_superposition_state, weighted_shot_allocation) for _ in range(1000)]\n", - "\n", - "print(f\"Uniform allocation ({uniform_shot_allocation}):\\n\"\n", - " f\"Mean: {np.mean(uniform_values):.6f} ± {np.std(uniform_values):.6f}\\n\")\n", + "uniform_values = [\n", + " compute_expectation_value_approximation(uniform_superposition_state, uniform_shot_allocation)\n", + " for _ in range(1000)\n", + "]\n", + "weighted_values = [\n", + " compute_expectation_value_approximation(uniform_superposition_state, weighted_shot_allocation)\n", + " for _ in range(1000)\n", + "]\n", + "\n", + "print(\n", + " f\"Uniform allocation ({uniform_shot_allocation}):\\n\"\n", + " f\"Mean: {np.mean(uniform_values):.6f} ± {np.std(uniform_values):.6f}\\n\"\n", + ")\n", "\n", - "print(f\"Coefficient-weighted allocation ({weighted_shot_allocation}):\\n\"\n", - " f\"Mean: {np.mean(weighted_values):.6f} ± {np.std(weighted_values):.6f}\")" + "print(\n", + " f\"Coefficient-weighted allocation ({weighted_shot_allocation}):\\n\"\n", + " f\"Mean: {np.mean(weighted_values):.6f} ± {np.std(weighted_values):.6f}\"\n", + ")" ] }, { @@ -485,8 +533,14 @@ } ], "source": [ - "plot_histograms([uniform_values, weighted_values], [\"Uniform allocation\", \"Coefficient-weighted allocation\"], [\"red\", \"blue\"], \n", - " exact_expectation, \"Comparison for Uniform Superposition State\", y_max = 250)" + "plot_histograms(\n", + " [uniform_values, weighted_values],\n", + " [\"Uniform allocation\", \"Coefficient-weighted allocation\"],\n", + " [\"red\", \"blue\"],\n", + " exact_expectation,\n", + " \"Comparison for Uniform Superposition State\",\n", + " y_max=250,\n", + ")" ] }, { @@ -541,12 +595,16 @@ "source": [ "optimal_shot_allocation = [600, 599, 1]\n", "\n", - "optimal_values = [compute_expectation_value_approximation(uniform_superposition_state, optimal_shot_allocation) for _ in range(1000)]\n", + "optimal_values = [\n", + " compute_expectation_value_approximation(uniform_superposition_state, optimal_shot_allocation)\n", + " for _ in range(1000)\n", + "]\n", "optimal_mean = np.mean(optimal_values)\n", "optimal_std = np.std(optimal_values)\n", "\n", - "print(f\"Optimal allocation ({optimal_shot_allocation}):\\n\"\n", - " f\"Mean: {optimal_mean:.6f} ± {optimal_std:.6f}\")" + "print(\n", + " f\"Optimal allocation ({optimal_shot_allocation}):\\nMean: {optimal_mean:.6f} ± {optimal_std:.6f}\"\n", + ")" ] }, { @@ -566,8 +624,14 @@ } ], "source": [ - "plot_histograms([uniform_values, optimal_values], [\"Uniform allocation\", \"Optimal allocation\"], [\"red\", \"cyan\"], \n", - " exact_expectation, \"Comparison with Optimal Shot Allocation\", y_max = 250)" + "plot_histograms(\n", + " [uniform_values, optimal_values],\n", + " [\"Uniform allocation\", \"Optimal allocation\"],\n", + " [\"red\", \"cyan\"],\n", + " exact_expectation,\n", + " \"Comparison with Optimal Shot Allocation\",\n", + " y_max=250,\n", + ")" ] }, { @@ -586,36 +650,40 @@ "metadata": {}, "outputs": [], "source": [ - "def compute_grouped_expectation_value_approximation(state, observable_index_groups, shot_configuration):\n", + "def compute_grouped_expectation_value_approximation(\n", + " state, observable_index_groups, shot_configuration\n", + "):\n", " \"\"\"Compute expectation value using grouped measurements where possible.\n", - " \n", + "\n", " Args:\n", " state (Circuit): Quantum circuit representing the state\n", " observable_index_groups (list): Groups of observable indices that can be measured together\n", " shot_configuration (list): Number of shots for each group\n", - " \n", + "\n", " Returns:\n", " float: Approximated expectation value\n", " \"\"\"\n", - " assert (len(shot_configuration)==len(observable_index_groups))\n", + " assert len(shot_configuration) == len(observable_index_groups)\n", " expectation_value = 0.0\n", - " \n", + "\n", " for i, group in enumerate(observable_index_groups):\n", " if isinstance(group, int): # Allow single indices to be passed directly\n", " group = [group]\n", - " \n", + "\n", " # Set up circuit to measure all observables in this group\n", " circuit = state.copy()\n", " for term in group:\n", " circuit.sample(paulis[term])\n", - " \n", + "\n", " # Get measurements for all observables in the group\n", - " measurements = device.run(circuit, shots = shot_configuration[i]).result().values\n", - " \n", + " measurements = device.run(circuit, shots=shot_configuration[i]).result().values\n", + "\n", " # Process each observable's measurements\n", " for idx_in_group, idx_global in enumerate(group):\n", - " expectation_value += coeffs[idx_global]*np.mean(measurements[idx_in_group], dtype=float)\n", - " \n", + " expectation_value += coeffs[idx_global] * np.mean(\n", + " measurements[idx_in_group], dtype=float\n", + " )\n", + "\n", " return expectation_value" ] }, @@ -635,14 +703,20 @@ ], "source": [ "# Group ZI and IZ together, measure XX separately\n", - "grouped_optimal_values = [compute_grouped_expectation_value_approximation(uniform_superposition_state, [[0,1], 2], [1199, 1]) \n", - " for _ in range(1000)]\n", + "grouped_optimal_values = [\n", + " compute_grouped_expectation_value_approximation(\n", + " uniform_superposition_state, [[0, 1], 2], [1199, 1]\n", + " )\n", + " for _ in range(1000)\n", + "]\n", "\n", "grouped_mean = np.mean(grouped_optimal_values)\n", "grouped_std = np.std(grouped_optimal_values)\n", "\n", - "print(f\"Grouped optimal allocation ([1199, 1] shots for [[ZI,IZ], XX]):\\n\"\n", - " f\"Mean: {grouped_mean:.6f} ± {grouped_std:.6f}\")" + "print(\n", + " f\"Grouped optimal allocation ([1199, 1] shots for [[ZI,IZ], XX]):\\n\"\n", + " f\"Mean: {grouped_mean:.6f} ± {grouped_std:.6f}\"\n", + ")" ] }, { @@ -662,8 +736,14 @@ } ], "source": [ - "plot_histograms([optimal_values, grouped_optimal_values], [\"Optimal allocation (separate terms)\", \"Optimal allocation (grouped terms)\"],\n", - " [\"cyan\", \"green\"], exact_expectation, \"Comparison of Optimal Strategies\", y_max = 250)" + "plot_histograms(\n", + " [optimal_values, grouped_optimal_values],\n", + " [\"Optimal allocation (separate terms)\", \"Optimal allocation (grouped terms)\"],\n", + " [\"cyan\", \"green\"],\n", + " exact_expectation,\n", + " \"Comparison of Optimal Strategies\",\n", + " y_max=250,\n", + ")" ] }, { @@ -732,14 +812,15 @@ " run_adaptive_allocation,\n", ")\n", "\n", - "estimator = AdaptiveShotAllocator(['IZ', 'ZI', 'XX'], [1.0, 1.0, 10.0])\n", + "estimator = AdaptiveShotAllocator([\"IZ\", \"ZI\", \"XX\"], [1.0, 1.0, 10.0])\n", "\n", "adaptive_values = []\n", "\n", "for _ in tqdm(range(1000)):\n", " estimator.reset()\n", "\n", - " adaptive_values.append(estimator.expectation_from_measurements(\n", + " adaptive_values.append(\n", + " estimator.expectation_from_measurements(\n", " run_adaptive_allocation(device, uniform_superposition_state, estimator, 100, 12)\n", " )\n", " )" @@ -771,8 +852,14 @@ } ], "source": [ - "plot_histograms([grouped_optimal_values, adaptive_values], [\"Optimal allocation\", \"Adaptive allocation\"], [\"green\", \"cyan\"], \n", - " exact_expectation, \"Comparison of Optimal and Adaptive Shot Allocation Strategies\", y_max = 250)" + "plot_histograms(\n", + " [grouped_optimal_values, adaptive_values],\n", + " [\"Optimal allocation\", \"Adaptive allocation\"],\n", + " [\"green\", \"cyan\"],\n", + " exact_expectation,\n", + " \"Comparison of Optimal and Adaptive Shot Allocation Strategies\",\n", + " y_max=250,\n", + ")" ] }, { diff --git a/notebooks/advanced_algorithms/adaptive_shot_allocation/2_Adaptive_Shot_Allocation.ipynb b/notebooks/advanced_algorithms/adaptive_shot_allocation/2_Adaptive_Shot_Allocation.ipynb index 4938fcb3..86c37259 100644 --- a/notebooks/advanced_algorithms/adaptive_shot_allocation/2_Adaptive_Shot_Allocation.ipynb +++ b/notebooks/advanced_algorithms/adaptive_shot_allocation/2_Adaptive_Shot_Allocation.ipynb @@ -123,9 +123,15 @@ "\n", "estimator = AdaptiveShotAllocator(paulis, coeffs)\n", "\n", - "print(\"Identified commuting groups:\", [ [paulis[p] for p in commuting_group] for commuting_group in estimator.cliq])\n", + "print(\n", + " \"Identified commuting groups:\",\n", + " [[paulis[p] for p in commuting_group] for commuting_group in estimator.cliq],\n", + ")\n", "\n", - "print(\"Proposed allocation (shots per group) w/o prior measurement knowledge: \", estimator.incremental_shot_allocation(1200))" + "print(\n", + " \"Proposed allocation (shots per group) w/o prior measurement knowledge: \",\n", + " estimator.incremental_shot_allocation(1200),\n", + ")" ] }, { @@ -178,20 +184,21 @@ " # To that end, we keep track of the measurements in a 2-D array, recording the number of different outcomes.\n", " # DETAILS: `measurements[i][j][(-1,1)]`, for example, records the number of times observables `i` and `j`\n", " # were measured together and recorded values of `-1` adn `1` respectively.\n", - " measurements = [[{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0}\n", - " for _ in range(hamiltonian_terms)]\n", - " for _ in range(hamiltonian_terms)]\n", + " measurements = [\n", + " [{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} for _ in range(hamiltonian_terms)]\n", + " for _ in range(hamiltonian_terms)\n", + " ]\n", "\n", " # STEP 2: Perform the measurements ()\n", " for shot_id in range(shots[0]):\n", " state = np.random.randint(4)\n", - " measurement_IZ = 1 - 2*(state%2)\n", - " measurement_ZI = 1 - 2*(state//2)\n", + " measurement_IZ = 1 - 2 * (state % 2)\n", + " measurement_ZI = 1 - 2 * (state // 2)\n", " measurements[0][0][(measurement_IZ, measurement_IZ)] += 1\n", " measurements[1][1][(measurement_ZI, measurement_ZI)] += 1\n", " measurements[0][1][(measurement_IZ, measurement_ZI)] += 1\n", - " measurements[1][0][(measurement_ZI, measurement_IZ)] += 1 \n", - " \n", + " measurements[1][0][(measurement_ZI, measurement_IZ)] += 1\n", + "\n", " for shot_id in range(shots[1]):\n", " measurement_XX = 1\n", " measurements[2][2][(measurement_XX, measurement_XX)] += 1\n", @@ -225,7 +232,9 @@ } ], "source": [ - "print(f\"Estimated expectation value: {estimator.expectation_from_measurements():.6f} ± {estimator.error_estimate():.6f}\")" + "print(\n", + " f\"Estimated expectation value: {estimator.expectation_from_measurements():.6f} ± {estimator.error_estimate():.6f}\"\n", + ")" ] }, { @@ -333,15 +342,64 @@ ], "source": [ "# Note that, as would be the case in practice, we have removed the IIII term and its cofficient from the calculations below\n", - "paulis = ['IIII', 'XXXI', 'XXXZ', 'XYYI', 'XZXI', 'XZXZ', 'YXYI', 'YXYZ', 'YYXI', 'YZYI', 'YZYZ', 'ZIII',\n", - " 'ZXZI', 'ZXZZ', 'ZXIZ', 'ZZII', 'ZZZI', 'ZZZZ', 'ZIZI', 'ZIZZ', 'IXII', 'IXZI', 'IXIZ', 'IZII',\n", - " 'IZZZ', 'IZIZ', 'IIZI'][1:]\n", - "coeffs = [-14.440090444958097, 0.01396784712709576, 0.0050449972385411684, 0.008922759379312662, 0.004572316311895031, \n", - " 0.004572316311895031, 0.01396784712709576, 0.0050449972385411684, -0.008922759379312662, 0.004572316311895031, \n", - " 0.004572316311895031, 0.37322478932293823, 0.01396784712709576, 0.0050449972385411684, -0.008922759379312663, \n", - " 0.37322478932293823, 0.06754531038829523, 0.06754531038829523, 0.0629729940764002, 0.0629729940764002, \n", - " -0.0050449972385411684, 0.008922759379312663, -0.01396784712709576, 0.1377331477309217, 0.18592768458263145, \n", - " 0.10062930161995226, 0.18592768458263148][1:]\n", + "paulis = [\n", + " \"IIII\",\n", + " \"XXXI\",\n", + " \"XXXZ\",\n", + " \"XYYI\",\n", + " \"XZXI\",\n", + " \"XZXZ\",\n", + " \"YXYI\",\n", + " \"YXYZ\",\n", + " \"YYXI\",\n", + " \"YZYI\",\n", + " \"YZYZ\",\n", + " \"ZIII\",\n", + " \"ZXZI\",\n", + " \"ZXZZ\",\n", + " \"ZXIZ\",\n", + " \"ZZII\",\n", + " \"ZZZI\",\n", + " \"ZZZZ\",\n", + " \"ZIZI\",\n", + " \"ZIZZ\",\n", + " \"IXII\",\n", + " \"IXZI\",\n", + " \"IXIZ\",\n", + " \"IZII\",\n", + " \"IZZZ\",\n", + " \"IZIZ\",\n", + " \"IIZI\",\n", + "][1:]\n", + "coeffs = [\n", + " -14.440090444958097,\n", + " 0.01396784712709576,\n", + " 0.0050449972385411684,\n", + " 0.008922759379312662,\n", + " 0.004572316311895031,\n", + " 0.004572316311895031,\n", + " 0.01396784712709576,\n", + " 0.0050449972385411684,\n", + " -0.008922759379312662,\n", + " 0.004572316311895031,\n", + " 0.004572316311895031,\n", + " 0.37322478932293823,\n", + " 0.01396784712709576,\n", + " 0.0050449972385411684,\n", + " -0.008922759379312663,\n", + " 0.37322478932293823,\n", + " 0.06754531038829523,\n", + " 0.06754531038829523,\n", + " 0.0629729940764002,\n", + " 0.0629729940764002,\n", + " -0.0050449972385411684,\n", + " 0.008922759379312663,\n", + " -0.01396784712709576,\n", + " 0.1377331477309217,\n", + " 0.18592768458263145,\n", + " 0.10062930161995226,\n", + " 0.18592768458263148,\n", + "][1:]\n", "\n", "print(\"4-qubit BeH Hamiltonian (electronic, Bravyi-Kitaev mapping).\")\n", "print(f\"Number of qubits: {len(paulis[0])}\")\n", @@ -500,7 +558,9 @@ "print(f\"Estimated expectation: {e_estimated:.6f}\")\n", "print(f\"Estimated standard error: {error_estimate:.6f}\")\n", "\n", - "print(f\"Actual difference: {abs(e_exact - e_estimated):.6f} ({100*abs(e_exact - e_estimated)/abs(e_exact):.2f}%)\")\n", + "print(\n", + " f\"Actual difference: {abs(e_exact - e_estimated):.6f} ({100 * abs(e_exact - e_estimated) / abs(e_exact):.2f}%)\"\n", + ")\n", "print(f\"Final shot allocation: {estimator.shots}\")\n", "print(f\"Total shots used: {sum(estimator.shots)}\")" ] @@ -592,34 +652,29 @@ "\n", "# Run comparison\n", "num_runs = 50\n", - "results = {\n", - " 'Random': [],\n", - " 'Uniform': [],\n", - " 'Weighted': [],\n", - " 'Adaptive': [] \n", - "}\n", + "results = {\"Random\": [], \"Uniform\": [], \"Weighted\": [], \"Adaptive\": []}\n", "\n", "print(f\"Running {num_runs} trials for each strategy...\")\n", "for i in tqdm(range(num_runs)):\n", " # Reset the allocator every time so that we don't take advantage of prior measurement knowledge\n", " estimator.reset()\n", - " \n", - " results['Adaptive'].append(\n", + "\n", + " results[\"Adaptive\"].append(\n", " estimator.expectation_from_measurements(\n", " run_adaptive_allocation(device, circuit, estimator, shots_per_round, num_rounds)\n", " )\n", " )\n", - " results['Uniform'].append(\n", + " results[\"Uniform\"].append(\n", " estimator.expectation_from_measurements(\n", " run_fixed_allocation(device, circuit, estimator, uniform_shots)\n", " )\n", " )\n", - " results['Random'].append(\n", + " results[\"Random\"].append(\n", " estimator.expectation_from_measurements(\n", " run_fixed_allocation(device, circuit, estimator, random_shots)\n", " )\n", " )\n", - " results['Weighted'].append(\n", + " results[\"Weighted\"].append(\n", " estimator.expectation_from_measurements(\n", " run_fixed_allocation(device, circuit, estimator, weighted_shots)\n", " )\n", @@ -693,14 +748,9 @@ "# Plot results with separate subplots (squashed aspect ratio)\n", "fig, axes = plt.subplots(4, 1, figsize=(10, 6), sharex=True)\n", "\n", - "colors = {\n", - " 'Random': 'red', \n", - " 'Uniform': 'orange',\n", - " 'Weighted': 'blue',\n", - " 'Adaptive': 'green' \n", - "}\n", + "colors = {\"Random\": \"red\", \"Uniform\": \"orange\", \"Weighted\": \"blue\", \"Adaptive\": \"green\"}\n", "\n", - "strategies = ['Random', 'Uniform', 'Weighted', 'Adaptive']\n", + "strategies = [\"Random\", \"Uniform\", \"Weighted\", \"Adaptive\"]\n", "\n", "# First pass: calculate all histograms to find the maximum bar height\n", "bins = np.arange(0.0, 0.05, 0.001)\n", @@ -716,35 +766,44 @@ "for i, strategy in enumerate(strategies):\n", " values = results[strategy]\n", " errors = np.abs(np.array(values) - e_exact)\n", - " \n", + "\n", " # Plot histogram in the corresponding subplot\n", - " axes[i].hist(errors, bins=bins, \n", - " color=colors[strategy], alpha=0.7, edgecolor='black', linewidth=0.5,\n", - " label=strategy)\n", - " \n", + " axes[i].hist(\n", + " errors,\n", + " bins=bins,\n", + " color=colors[strategy],\n", + " alpha=0.7,\n", + " edgecolor=\"black\",\n", + " linewidth=0.5,\n", + " label=strategy,\n", + " )\n", + "\n", " # Add statistics as legend\n", " mean_error = np.mean(errors)\n", " std_error = np.std(errors)\n", - " \n", - " axes[i].set_ylabel('Frequency')\n", + "\n", + " axes[i].set_ylabel(\"Frequency\")\n", " axes[i].grid(True, alpha=0.3)\n", - " \n", + "\n", " # Set aspect ratio and y-axis limits\n", - " axes[i].set_aspect(aspect='auto')\n", + " axes[i].set_aspect(aspect=\"auto\")\n", " axes[i].set_ylim(0, max_height * 1.05) # Add 5% padding at top\n", - " \n", + "\n", " # Set integer y-ticks\n", " axes[i].yaxis.set_major_locator(MaxNLocator(integer=True))\n", - " \n", + "\n", " # Add legend with strategy name and statistics\n", - " axes[i].legend([f'{strategy} (Mean: {mean_error:.6f}, Std: {std_error:.6f})'], \n", - " loc='upper right', framealpha=0.9)\n", + " axes[i].legend(\n", + " [f\"{strategy} (Mean: {mean_error:.6f}, Std: {std_error:.6f})\"],\n", + " loc=\"upper right\",\n", + " framealpha=0.9,\n", + " )\n", "\n", "# Set common x-label only on the bottom subplot\n", - "axes[-1].set_xlabel('Absolute Error')\n", + "axes[-1].set_xlabel(\"Absolute Error\")\n", "\n", "# Add overall title with more space\n", - "fig.suptitle('Distribution of Estimation Errors by Strategy', fontsize=16, y=0.98)\n", + "fig.suptitle(\"Distribution of Estimation Errors by Strategy\", fontsize=16, y=0.98)\n", "\n", "plt.tight_layout()\n", "plt.subplots_adjust(top=0.93) # Add space between suptitle and subplots\n", @@ -767,35 +826,46 @@ "def visualize_shot_allocation_comparison(strategies, allocations, total_shots):\n", " \"\"\"\n", " Visualize different shot allocation strategies for comparison.\n", - " \n", + "\n", " Args:\n", " strategies (list): Names of the strategies to compare\n", " allocations (list): List of shot allocations for each strategy\n", " total_shots (int): Total number of shots used\n", " \"\"\"\n", " plt.figure(figsize=(12, 6))\n", - " \n", + "\n", " # Create a bar chart for each strategy\n", " x = np.arange(len(allocations[0]))\n", " width = 0.8 / len(strategies)\n", - " \n", + "\n", " for i, (strategy, allocation) in enumerate(zip(strategies, allocations)):\n", - " plt.bar(x + i*width - 0.4 + width/2, allocation, width, \n", - " label=strategy, alpha=0.7, color = colors[strategy])\n", - " \n", - " plt.xlabel('Measurement Group')\n", - " plt.ylabel('Number of Shots')\n", - " plt.title(f'Comparison of Shot Allocation Strategies (Total: {total_shots} shots)')\n", - " plt.xticks(x, [f'Group {i+1}' for i in x])\n", + " plt.bar(\n", + " x + i * width - 0.4 + width / 2,\n", + " allocation,\n", + " width,\n", + " label=strategy,\n", + " alpha=0.7,\n", + " color=colors[strategy],\n", + " )\n", + "\n", + " plt.xlabel(\"Measurement Group\")\n", + " plt.ylabel(\"Number of Shots\")\n", + " plt.title(f\"Comparison of Shot Allocation Strategies (Total: {total_shots} shots)\")\n", + " plt.xticks(x, [f\"Group {i + 1}\" for i in x])\n", " plt.legend()\n", " plt.grid(True, alpha=0.3)\n", - " \n", + "\n", " # Add percentages on top of each bar\n", " for i, (strategy, allocation) in enumerate(zip(strategies, allocations)):\n", " for j, shots in enumerate(allocation):\n", " percentage = 100 * shots / total_shots\n", - " plt.text(j + i*width - 0.4 + width/2, shots + 5, \n", - " f'{percentage:.1f}%', ha='center', fontsize=8)" + " plt.text(\n", + " j + i * width - 0.4 + width / 2,\n", + " shots + 5,\n", + " f\"{percentage:.1f}%\",\n", + " ha=\"center\",\n", + " fontsize=8,\n", + " )" ] }, { @@ -815,7 +885,11 @@ } ], "source": [ - "visualize_shot_allocation_comparison([strategy for strategy in results.keys()], [random_shots, uniform_shots, weighted_shots, estimator.shots], sum(uniform_shots))" + "visualize_shot_allocation_comparison(\n", + " [strategy for strategy in results.keys()],\n", + " [random_shots, uniform_shots, weighted_shots, estimator.shots],\n", + " sum(uniform_shots),\n", + ")" ] }, { diff --git a/notebooks/advanced_algorithms/adaptive_shot_allocation/adaptive_allocation_notebook_helpers.py b/notebooks/advanced_algorithms/adaptive_shot_allocation/adaptive_allocation_notebook_helpers.py index dd2299e8..f1f3858a 100644 --- a/notebooks/advanced_algorithms/adaptive_shot_allocation/adaptive_allocation_notebook_helpers.py +++ b/notebooks/advanced_algorithms/adaptive_shot_allocation/adaptive_allocation_notebook_helpers.py @@ -1,4 +1,3 @@ - from typing import List import numpy as np @@ -15,6 +14,7 @@ _localSim = LocalSimulator() + def create_random_state(num_qubits: int = 4) -> Circuit: """ Generate a quantum circuit with random rotations and entanglement. @@ -33,7 +33,7 @@ def create_random_state(num_qubits: int = 4) -> Circuit: # Entangling layer for i in range(num_qubits - 1): - circ.cnot(control=i, target=i+1) + circ.cnot(control=i, target=i + 1) # Second layer of rotations for i in range(num_qubits): @@ -73,10 +73,12 @@ def get_exact_expectation(circuit: Circuit, paulis: List[str], coeffs: List[floa e_exact += c * result.values[0] return e_exact + """ Utilities for allocating measurement shots across different measurement groups. """ + def get_uniform_shots(num_groups: int, total_shots: int) -> List[int]: """ Generate uniform shot allocation across measurement groups. @@ -132,4 +134,3 @@ def get_weighted_shots(cliq: List[List[int]], coeffs: List[float], total_shots: for i in range(remainder): shots[i] += 1 return shots.tolist() - diff --git a/notebooks/textbook/CHSH_Inequality.ipynb b/notebooks/textbook/CHSH_Inequality.ipynb index eeffbfee..b19944d0 100644 --- a/notebooks/textbook/CHSH_Inequality.ipynb +++ b/notebooks/textbook/CHSH_Inequality.ipynb @@ -455,7 +455,7 @@ ], "source": [ "print(\n", - " f\"Estimated cost to run this example: {tracker.qpu_tasks_cost() + tracker.simulator_tasks_cost() :.2f} USD\"\n", + " f\"Estimated cost to run this example: {tracker.qpu_tasks_cost() + tracker.simulator_tasks_cost():.2f} USD\"\n", ")" ] }, diff --git a/notebooks/textbook/Quantum_Counting_Algorithm.ipynb b/notebooks/textbook/Quantum_Counting_Algorithm.ipynb new file mode 100644 index 00000000..eb6b50d9 --- /dev/null +++ b/notebooks/textbook/Quantum_Counting_Algorithm.ipynb @@ -0,0 +1,795 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b7179342", + "metadata": {}, + "source": [ + "# Quantum Counting Algorithm\n", + "\n", + "The Quantum Counting algorithm, introduced by Brassard, Høyer, and Tapp [1], counts the number of marked items $M$ in an unstructured database of $N = 2^n$ elements. It combines **Grover's search operator** with **Quantum Phase Estimation (QPE)** to achieve a quadratic speedup over classical counting, requiring only $O(\\sqrt{N})$ oracle queries instead of the classical $\\Theta(N)$.\n", + "\n", + "**Key idea:** The Grover operator $G = D \\cdot O$ has eigenvalues $e^{\\pm i\\theta}$, where $\\theta$ satisfies $\\sin^2(\\theta/2) = M/N$. By applying QPE to $G$, we estimate $\\theta$ and hence determine $M$." + ] + }, + { + "cell_type": "markdown", + "id": "01fb7ecc", + "metadata": {}, + "source": [ + "## References\n", + "\n", + "[[1] G. Brassard, P. Høyer, and A. Tapp, \"Quantum Counting\", Proceedings of ICALP 1998](https://arxiv.org/abs/quant-ph/9805082)\n", + "\n", + "[[2] M. Nielsen and I. Chuang, Quantum Computation and Quantum Information, Cambridge University Press, 2010](https://www.cambridge.org/highereducation/books/quantum-computation-and-quantum-information/01E10196D0A682A6AEFFEA52D53BE9AE)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "b9bdcb62", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:29.567098Z", + "iopub.status.busy": "2026-02-26T21:20:29.566767Z", + "iopub.status.idle": "2026-02-26T21:20:38.875569Z", + "shell.execute_reply": "2026-02-26T21:20:38.873798Z" + } + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from braket.circuits import Circuit\n", + "from braket.devices import LocalSimulator\n", + "from braket.experimental.algorithms.quantum_counting import (\n", + " build_grover_circuit,\n", + " build_oracle_circuit,\n", + " get_quantum_counting_results,\n", + " quantum_counting_circuit,\n", + " run_quantum_counting,\n", + ")\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "id": "a0df5ef1", + "metadata": {}, + "source": [ + "## Background\n", + "\n", + "### The Grover Operator\n", + "\n", + "Given an oracle $O$ that marks certain states by flipping their phase ($O|x\\rangle = (-1)^{f(x)}|x\\rangle$), and the Grover diffusion operator $D = 2|s\\rangle\\langle s| - I$, the Grover operator is:\n", + "\n", + "$$G = D \\cdot O$$\n", + "\n", + "In the 2D subspace of marked ($|\\beta\\rangle$) and unmarked ($|\\alpha\\rangle$) states, $G$ acts as a rotation by angle $\\theta$:\n", + "\n", + "$$\\sin^2(\\theta/2) = M/N$$\n", + "\n", + "### QPE on the Grover Operator\n", + "\n", + "Within this 2-dimensional solution subspace, the eigenvalues of $G$ are $e^{\\pm i\\theta}$. QPE with $t$ precision (counting) qubits estimates the phase $\\varphi = \\theta/(2\\pi)$ as a $t$-bit fraction $y/2^t$, where $y$ is the integer readout from the counting register. From our estimate of $\\varphi$, we recover the number of marked items:\n", + "\n", + "$$M = N \\cdot \\sin^2(\\pi \\varphi)$$\n", + "\n", + "**Note:** The full $N \\times N$ Grover matrix has additional eigenvalues from the orthogonal complement of the solution subspace, but these do not affect the counting algorithm since the initial uniform superposition $|s\\rangle$ lies entirely within the 2D subspace." + ] + }, + { + "cell_type": "markdown", + "id": "1ff75da5", + "metadata": {}, + "source": [ + "## Build the Oracle and Grover Circuits\n", + "\n", + "We build the oracle and Grover operator as **circuits** using primitives from the `grovers_search` module:\n", + "- `build_oracle` (from `grovers_search`) constructs a phase-flip oracle for a single basis state\n", + "- `amplify` (from `grovers_search`) constructs the diffusion operator $D = H \\cdot O_0 \\cdot H$\n", + "- `build_oracle_circuit` composes oracles for multiple marked states\n", + "- `build_grover_circuit` combines the oracle and diffusion into the full Grover operator $G$" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "49c4b0e1", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:38.880338Z", + "iopub.status.busy": "2026-02-26T21:20:38.879028Z", + "iopub.status.idle": "2026-02-26T21:20:38.915409Z", + "shell.execute_reply": "2026-02-26T21:20:38.913644Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Oracle circuit (marks state |11>):\n", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n", + " \n", + "q0 : ───────────────●───────────────\n", + " │ \n", + " │ \n", + "q1 : ───────────────●───────────────\n", + " │ \n", + " ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ \n", + "q2 : ─┤ X ├─┤ H ├─┤ X ├─┤ H ├─┤ X ├─\n", + " └───┘ └───┘ └───┘ └───┘ └───┘ \n", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │\n", + "\n", + "Grover operator circuit (G = D . O):\n", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n", + " ┌───┐ ┌───┐ ┌───┐ ┌───┐ \n", + "q0 : ───────────────●───┤ H ├─┤ X ├───────────────●───┤ X ├─┤ H ├─\n", + " │ └───┘ └───┘ │ └───┘ └───┘ \n", + " │ ┌───┐ ┌───┐ │ ┌───┐ ┌───┐ \n", + "q1 : ───────────────●───┤ H ├─┤ X ├───────────────●───┤ X ├─┤ H ├─\n", + " │ └───┘ └───┘ │ └───┘ └───┘ \n", + " ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ \n", + "q2 : ─┤ X ├─┤ H ├─┤ X ├─┤ H ├─┤ X ├─┤ X ├─┤ H ├─┤ X ├─┤ H ├─┤ X ├─\n", + " └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ \n", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │\n" + ] + } + ], + "source": [ + "# Search space: n_search = 2 qubits -> N = 4 elements\n", + "# Mark state |3> (binary '11')\n", + "n_search = 2\n", + "marked_states = [3]\n", + "N = 2**n_search\n", + "\n", + "# Build the oracle circuit using primitives from grovers_search\n", + "oracle_circ = build_oracle_circuit(n_search, marked_states)\n", + "print(\"Oracle circuit (marks state |11>):\")\n", + "print(oracle_circ)\n", + "\n", + "# Build the full Grover operator circuit: G = Diffusion . Oracle\n", + "grover_circ = build_grover_circuit(n_search, marked_states)\n", + "print(\"\\nGrover operator circuit (G = D . O):\")\n", + "print(grover_circ)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "c7e1a2b3", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:38.925460Z", + "iopub.status.busy": "2026-02-26T21:20:38.924254Z", + "iopub.status.idle": "2026-02-26T21:20:38.970346Z", + "shell.execute_reply": "2026-02-26T21:20:38.969089Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Grover circuit unitary shape: (8, 8)\n", + "(Includes 1 ancilla qubit(s) from MCZ decomposition)\n", + "\n", + "Non-trivial eigenvalues of G: [-0.5+0.866j -0.5-0.866j]\n", + "\n", + "Grover angle theta = 1.0472 rad\n", + "Ideal eigenvalues in solution subspace: e^(+i*theta) = 0.5000+0.8660j, e^(-i*theta) = 0.5000-0.8660j\n", + "Circuit eigenvalues (with -1 global phase): [-0.5+0.866j -0.5-0.866j]\n" + ] + } + ], + "source": [ + "# Extract the Grover operator unitary from the circuit\n", + "grover_unitary = grover_circ.to_unitary()\n", + "print(f\"Grover circuit unitary shape: {grover_unitary.shape}\")\n", + "print(f\"(Includes {grover_circ.qubit_count - n_search} ancilla qubit(s) from MCZ decomposition)\")\n", + "\n", + "# Eigenvalue analysis of the full circuit unitary\n", + "eigenvalues = np.linalg.eigvals(grover_unitary)\n", + "# Filter out trivial eigenvalues (1.0) to see the interesting ones\n", + "nontrivial = [ev for ev in eigenvalues if abs(ev - 1.0) > 0.01]\n", + "print(f\"\\nNon-trivial eigenvalues of G: {np.round(nontrivial, 4)}\")\n", + "\n", + "# In the 2D subspace spanned by |alpha> (unmarked) and |beta> (marked),\n", + "# the ideal Grover operator has eigenvalues e^{+/- i*theta}.\n", + "# However, the MCZ ancilla decomposition introduces a global phase of -1,\n", + "# so the circuit eigenvalues are -e^{+/- i*theta} = e^{+/- i*(theta + pi)}.\n", + "# This phase shift is corrected in get_quantum_counting_results.\n", + "theta = 2 * np.arcsin(np.sqrt(len(marked_states) / N))\n", + "print(f\"\\nGrover angle theta = {theta:.4f} rad\")\n", + "print(f\"Ideal eigenvalues in solution subspace: \"\n", + " f\"e^(+i*theta) = {np.exp(1j * theta):.4f}, \"\n", + " f\"e^(-i*theta) = {np.exp(-1j * theta):.4f}\")\n", + "print(f\"Circuit eigenvalues (with -1 global phase): {np.round(nontrivial, 4)}\")" + ] + }, + { + "cell_type": "markdown", + "id": "fee13411", + "metadata": {}, + "source": [ + "## Build the Quantum Counting Circuit\n", + "\n", + "The quantum counting circuit consists of four key components:\n", + "\n", + "1. **Initial state preparation**: Apply Hadamard gates to all counting qubits (QPE initialization) and all search qubits (prepare uniform superposition $|s\\rangle$)\n", + "2. **Controlled Grover operators**: Apply controlled-$G^{2^k}$ for each counting qubit $k$ (QPE phase kickback)\n", + "3. **Inverse QFT**: Applied to the counting qubits to extract the phase estimate\n", + "4. **Measurement**: Probability distribution over the counting (QPE) register only" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "d4537c2f", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:38.974567Z", + "iopub.status.busy": "2026-02-26T21:20:38.973858Z", + "iopub.status.idle": "2026-02-26T21:20:39.862187Z", + "shell.execute_reply": "2026-02-26T21:20:39.859359Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Counting qubits (QPE register): [0, 1, 2, 3]\n", + "Search qubits (data register): [4, 5]\n", + "Search space size: N = 4\n", + "Marked states: [3] (M = 1)\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Quantum Counting Circuit:\n", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ 32 │ 33 │ 34 │ 35 │ 36 │ 37 │ 38 │ 39 │ 40 │ 41 │ 42 │ 43 │ 44 │ 45 │ 46 │ 47 │ 48 │ 49 │ 50 │ 51 │ 52 │ 53 │ 54 │ 55 │ 56 │ 57 │ 58 │ 59 │ 60 │ 61 │ 62 │ 63 │ 64 │ 65 │ 66 │ 67 │ 68 │ 69 │ 70 │ 71 │ 72 │ 73 │ 74 │ 75 │ 76 │ 77 │ 78 │ 79 │ 80 │ 81 │ 82 │ 83 │ 84 │ 85 │ 86 │ 87 │ 88 │ 89 │ 90 │ 91 │ 92 │ 93 │ 94 │ 95 │ 96 │ 97 │ 98 │ 99 │ 100 │ 101 │ 102 │ 103 │ 104 │ 105 │ 106 │ 107 │ 108 │ 109 │ 110 │ 111 │ 112 │ 113 │ 114 │ 115 │ 116 │ 117 │ 118 │ 119 │ 120 │ 121 │ 122 │ 123 │ 124 │ 125 │ 126 │ 127 │ 128 │ 129 │ 130 │ 131 │ 132 │ 133 │ 134 │ 135 │ 136 │ 137 │ 138 │ 139 │ 140 │ 141 │ 142 │ 143 │ 144 │ 145 │ 146 │ 147 │ 148 │ 149 │ 150 │ 151 │ 152 │ 153 │ 154 │ 155 │ 156 │ 157 │ 158 │ 159 │ 160 │ 161 │ 162 │ 163 │ 164 │ 165 │ 166 │ 167 │ 168 │ 169 │ 170 │ 171 │ 172 │ 173 │ 174 │ 175 │ 176 │ 177 │ 178 │ 179 │ 180 │ 181 │ 182 │ 183 │ 184 │ 185 │ 186 │ 187 │ 188 │ 189 │ 190 │ 191 │ 192 │ 193 │ 194 │ 195 │ 196 │ 197 │ 198 │ 199 │ 200 │ 201 │ 202 │ 203 │ 204 │ 205 │ 206 │ 207 │ 208 │ 209 │ 210 │ 211 │ 212 │ 213 │ 214 │ 215 │ 216 │ 217 │ 218 │ 219 │ 220 │ 221 │ 222 │ 223 │ 224 │ 225 │ 226 │ 227 │ 228 │ 229 │ 230 │ 231 │ 232 │ 233 │ 234 │ 235 │ 236 │ 237 │ 238 │ 239 │ 240 │ 241 │ 242 │ 243 │ 244 │ 245 │ 246 │ 247 │ 248 │ 249 │ 250 │ 251 │ 252 │ 253 │ 254 │ 255 │ 256 │ 257 │ 258 │ 259 │ 260 │ 261 │ 262 │ 263 │ 264 │ 265 │ 266 │ 267 │ 268 │ 269 │ 270 │ 271 │ 272 │ 273 │ 274 │ 275 │ 276 │ 277 │ 278 │ 279 │ 280 │ 281 │ 282 │ 283 │ 284 │ 285 │ 286 │ 287 │ 288 │ 289 │ 290 │ 291 │ 292 │ 293 │ 294 │ 295 │ 296 │ 297 │ 298 │ 299 │ 300 │ 301 │ 302 │ 303 │ 304 │ 305 │ 306 │ 307 │ 308 │ 309 │ 310 │ 311 │ 312 │ 313 │ 314 │ 315 │ 316 │ 317 │ 318 │ 319 │ 320 │ 321 │ 322 │ 323 │ 324 │ 325 │ 326 │ 327 │ 328 │ 329 │ 330 │ 331 │ 332 │ 333 │ 334 │ 335 │ 336 │ 337 │ 338 │ 339 │ 340 │ 341 │ 342 │ 343 │ 344 │ 345 │ 346 │ 347 │ 348 │ 349 │ 350 │ 351 │ 352 │ 353 │ 354 │ 355 │ 356 │ 357 │ 358 │ 359 │ 360 │ 361 │ 362 │ 363 │ 364 │ 365 │ Result Types │\n", + " ┌───┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌───┐ ┌─────────────┐ \n", + "q0 : ─┤ H ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────●───────────────────────────────────────────────────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●──────x─────────────────────────────────────────────────────────────────────┤ PHASE(-0.39) ├───────┤ PHASE(-0.79) ├─┤ PHASE(-1.57) ├─┤ H ├─┤ Probability ├─\n", + " └───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └───┘ └──────┬──────┘ \n", + " ┌───┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌──────────────┐ │ ┌───┐ │ │ ┌──────┴──────┐ \n", + "q1 : ─┤ H ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────●─────────────────────────────────────────────────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●───────────┼───────────────────────●────────────●──────x───────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼──────┼───────────────────────────────────┤ PHASE(-0.79) ├─┤ PHASE(-1.57) ├────────┼─────────┤ H ├────────┼────────────────●───────────────┤ Probability ├─\n", + " └───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────┬───────┘ └──────┬───────┘ │ └───┘ │ └──────┬──────┘ \n", + " ┌───┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌──────────────┐ ┌───┐ │ │ │ │ ┌──────┴──────┐ \n", + "q2 : ─┤ H ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────●─────────────────────────────────────────────────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●─────────────────────────────●────────────●────────────●─────────────────────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●───────────┼───────────────────────●────────────●────────────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼───────────┼───────────────────────┼────────────┼──────x───────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼──────┼────────────┤ PHASE(-1.57) ├─┤ H ├────────┼────────────────●────────────────┼──────────────────────●────────────────────────────────┤ Probability ├─\n", + " └───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └──────┬───────┘ └───┘ │ │ └──────┬──────┘ \n", + " ┌───┐ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ┌───┐ │ │ │ ┌──────┴──────┐ \n", + "q3 : ─┤ H ├───●───────────────────────●───────────────────────●────────────●────────────●───────────────────────────────────────────●───────────┼───────────────────────●────────────●────────────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼───────────┼───────────────────────┼────────────┼────────────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼───────────┼───────────────────────┼────────────┼──────────────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼────────────┼─────────────────────────────────────┼───────────────────────┼────────────┼────────────┼───────────────────────────────────────────┼─────────────────────────────┼────────────┼──────x─────┤ H ├─────────●──────────────────────●─────────────────────────────────●───────────────────────────────────────────────────────┤ Probability ├─\n", + " └───┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ └───┘ └─────────────┘ \n", + " ┌───┐ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ │ ┌────┐ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ \n", + "q4 : ─┤ H ├───┼─────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├───┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├───┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├───┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼────┤ H ├───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├──────────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼───┤ H ├────┼───┤ Si ├─┤ X ├──────────●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├─┤ X ├─┤ Ti ├───┼────┤ H ├───┤ Si ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", + " └───┘ │ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ │ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ \n", + " ┌───┐ │ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ │ ┌───┐ ┌────┐ ┌───┐ │ │ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌────┐ ┌───┐ ┌────┐ \n", + "q5 : ─┤ H ├───┼─────●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├───┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├─┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├───┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├─┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├───┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├──┤ Ti ├──┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├───●───────────●─────┼───┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├─┤ Ti ├───┼───┤ H ├──┤ Si ├─┤ X ├───●───────────●─────┼───┤ X ├─┤ S ├─┤ H ├─┤ T ├──────────────┤ X ├──┤ Ti ├──┤ H ├───────┤ Si ├─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", + " └───┘ │ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ │ └───┘ └───┘ └───┘ └───┘ └────┘ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ │ └───┘ └───┘ └───┘ └───┘ └────┘ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ │ └───┘ └───┘ └───┘ └───┘ └────┘ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └────┘ │ └───┘ └────┘ └───┘ │ │ │ └───┘ └───┘ └───┘ └───┘ └───┘ └────┘ └───┘ └────┘ \n", + " ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ ┌─┴─┐ │ │ ┌─┴─┐ \n", + "q6 : ───────┤ X ├───┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────┤ X ├─────────────────────────────────────────────────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────┤ X ├─────────────────────────────────────────────────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────┤ X ├───────────────────────────────────────────────────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├───────────────────────────────────────────────────┤ X ├─────────────────┼─────●─────┼───┤ X ├─────────────────────────────────────────────┤ X ├───────────────────────┼─────●─────┼───┤ X ├────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", + " └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ └───┘ │ │ │ └───┘ \n", + " ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ ┌─┴─┐ │ ┌─┴─┐ \n", + "q7 : ─────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├───────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├─────────────────────────────────────────────────────────────────────────────┤ X ├───●───┤ X ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", + " └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ └───┘ │ └───┘ \n", + " ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌─┴─┐ ┌───┐ ┌───┐ \n", + "q8 : ─┤ X ├─┤ H ├───────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├───────┤ X ├─┤ H ├─────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├───────┤ X ├─┤ H ├─────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├───────┤ X ├─┤ H ├───────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├─┤ X ├─┤ H ├───────────────────────────────────────────────────────────┤ X ├─┤ H ├───────┤ X ├──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n", + " └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ └───┘ \n", + "T : │ 0 │ 1 │ 2 │ 3 │ 4 │ 5 │ 6 │ 7 │ 8 │ 9 │ 10 │ 11 │ 12 │ 13 │ 14 │ 15 │ 16 │ 17 │ 18 │ 19 │ 20 │ 21 │ 22 │ 23 │ 24 │ 25 │ 26 │ 27 │ 28 │ 29 │ 30 │ 31 │ 32 │ 33 │ 34 │ 35 │ 36 │ 37 │ 38 │ 39 │ 40 │ 41 │ 42 │ 43 │ 44 │ 45 │ 46 │ 47 │ 48 │ 49 │ 50 │ 51 │ 52 │ 53 │ 54 │ 55 │ 56 │ 57 │ 58 │ 59 │ 60 │ 61 │ 62 │ 63 │ 64 │ 65 │ 66 │ 67 │ 68 │ 69 │ 70 │ 71 │ 72 │ 73 │ 74 │ 75 │ 76 │ 77 │ 78 │ 79 │ 80 │ 81 │ 82 │ 83 │ 84 │ 85 │ 86 │ 87 │ 88 │ 89 │ 90 │ 91 │ 92 │ 93 │ 94 │ 95 │ 96 │ 97 │ 98 │ 99 │ 100 │ 101 │ 102 │ 103 │ 104 │ 105 │ 106 │ 107 │ 108 │ 109 │ 110 │ 111 │ 112 │ 113 │ 114 │ 115 │ 116 │ 117 │ 118 │ 119 │ 120 │ 121 │ 122 │ 123 │ 124 │ 125 │ 126 │ 127 │ 128 │ 129 │ 130 │ 131 │ 132 │ 133 │ 134 │ 135 │ 136 │ 137 │ 138 │ 139 │ 140 │ 141 │ 142 │ 143 │ 144 │ 145 │ 146 │ 147 │ 148 │ 149 │ 150 │ 151 │ 152 │ 153 │ 154 │ 155 │ 156 │ 157 │ 158 │ 159 │ 160 │ 161 │ 162 │ 163 │ 164 │ 165 │ 166 │ 167 │ 168 │ 169 │ 170 │ 171 │ 172 │ 173 │ 174 │ 175 │ 176 │ 177 │ 178 │ 179 │ 180 │ 181 │ 182 │ 183 │ 184 │ 185 │ 186 │ 187 │ 188 │ 189 │ 190 │ 191 │ 192 │ 193 │ 194 │ 195 │ 196 │ 197 │ 198 │ 199 │ 200 │ 201 │ 202 │ 203 │ 204 │ 205 │ 206 │ 207 │ 208 │ 209 │ 210 │ 211 │ 212 │ 213 │ 214 │ 215 │ 216 │ 217 │ 218 │ 219 │ 220 │ 221 │ 222 │ 223 │ 224 │ 225 │ 226 │ 227 │ 228 │ 229 │ 230 │ 231 │ 232 │ 233 │ 234 │ 235 │ 236 │ 237 │ 238 │ 239 │ 240 │ 241 │ 242 │ 243 │ 244 │ 245 │ 246 │ 247 │ 248 │ 249 │ 250 │ 251 │ 252 │ 253 │ 254 │ 255 │ 256 │ 257 │ 258 │ 259 │ 260 │ 261 │ 262 │ 263 │ 264 │ 265 │ 266 │ 267 │ 268 │ 269 │ 270 │ 271 │ 272 │ 273 │ 274 │ 275 │ 276 │ 277 │ 278 │ 279 │ 280 │ 281 │ 282 │ 283 │ 284 │ 285 │ 286 │ 287 │ 288 │ 289 │ 290 │ 291 │ 292 │ 293 │ 294 │ 295 │ 296 │ 297 │ 298 │ 299 │ 300 │ 301 │ 302 │ 303 │ 304 │ 305 │ 306 │ 307 │ 308 │ 309 │ 310 │ 311 │ 312 │ 313 │ 314 │ 315 │ 316 │ 317 │ 318 │ 319 │ 320 │ 321 │ 322 │ 323 │ 324 │ 325 │ 326 │ 327 │ 328 │ 329 │ 330 │ 331 │ 332 │ 333 │ 334 │ 335 │ 336 │ 337 │ 338 │ 339 │ 340 │ 341 │ 342 │ 343 │ 344 │ 345 │ 346 │ 347 │ 348 │ 349 │ 350 │ 351 │ 352 │ 353 │ 354 │ 355 │ 356 │ 357 │ 358 │ 359 │ 360 │ 361 │ 362 │ 363 │ 364 │ 365 │ Result Types │\n" + ] + } + ], + "source": [ + "# Use 4 counting qubits (t = 4) for good precision\n", + "n_counting = 4\n", + "counting_qubits = list(range(n_counting))\n", + "search_qubits = list(range(n_counting, n_counting + n_search))\n", + "\n", + "print(f\"Counting qubits (QPE register): {counting_qubits}\")\n", + "print(f\"Search qubits (data register): {search_qubits}\")\n", + "print(f\"Search space size: N = {N}\")\n", + "print(f\"Marked states: {marked_states} (M = {len(marked_states)})\")\n", + "\n", + "# Build the circuit\n", + "circ = Circuit()\n", + "circ = quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states)\n", + "\n", + "print(\"\\nQuantum Counting Circuit:\")\n", + "print(circ)" + ] + }, + { + "cell_type": "markdown", + "id": "3bc6f5fe", + "metadata": {}, + "source": [ + "## Run on a Local Simulator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8a31859a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:39.865257Z", + "iopub.status.busy": "2026-02-26T21:20:39.864934Z", + "iopub.status.idle": "2026-02-26T21:20:41.511716Z", + "shell.execute_reply": "2026-02-26T21:20:41.495119Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Search space size N = 4\n", + "\n", + "Counting register distribution (top outcomes):\n", + " |0101>: 339 counts -> phase = 0.1875, M ~ 1.2346\n", + " |1011>: 332 counts -> phase = 0.1875, M ~ 1.2346\n", + " |1010>: 109 counts -> phase = 0.1250, M ~ 0.5858\n", + " |0110>: 97 counts -> phase = 0.1250, M ~ 0.5858\n", + " |0100>: 27 counts -> phase = 0.2500, M ~ 2.0000\n", + " |0111>: 21 counts -> phase = 0.0625, M ~ 0.1522\n", + " ... (10 more outcomes)\n", + "\n", + "Best estimate of M: 1.2346331352698203\n", + "\n", + "--- Summary ---\n", + "Actual M = 1\n", + "Estimated M = 1.2346\n", + "Absolute error: 0.2346\n" + ] + } + ], + "source": [ + "device = LocalSimulator()\n", + "task = run_quantum_counting(circ, device, shots=1000)\n", + "\n", + "# Process results - uses only the counting register\n", + "results = get_quantum_counting_results(task, counting_qubits, search_qubits, verbose=True)\n", + "\n", + "print(\"\\n--- Summary ---\")\n", + "print(f\"Actual M = {len(marked_states)}\")\n", + "print(f\"Estimated M = {results['best_estimate']:.4f}\")\n", + "print(f\"Absolute error: {abs(len(marked_states) - results['best_estimate']):.4f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "2ef4d059", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:41.519644Z", + "iopub.status.busy": "2026-02-26T21:20:41.519231Z", + "iopub.status.idle": "2026-02-26T21:20:43.233794Z", + "shell.execute_reply": "2026-02-26T21:20:43.230430Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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f1SFChX0/PPP65tvvtHLL7+s2rVrq3fv3pmua/ny5Tp8+LCGDRuWpaOxBw4cULNmzXTy5EkNHTpUJUqU0Icffqhrr71WM2bM0H/+859s5XTkyBF16tRJPXr00I033qgZM2Zo+PDhio6OVufOnVWzZk2NHj1aTz31lAYOHOhp92bNmp13vdOmTdOxY8d01113yeVy6aWXXlKPHj20c+dOz9Hxr7/+WjfddJOio6M1ZswYHTlyRP369cvwiO3FcBfGoaGhnmnPPfecnnzySd14443q37+/Dh06pLfeekutWrXSr7/+qtDQUCUlJaljx45KTEz0tPVff/2lOXPm6OjRowoJCbmkuMaOHeu57vrxxx+XJIWHh0s6+wXUmDFj1L9/fzVu3FgJCQlavXq1fvnlF1199dWZrnPNmjVKSkrSVVddlekyjz/+uKZMmXLBo91nzpzxfLlwIWFhYcqT53/HSI4dO6bhw4frscceU0RERJbWkRWVKlVSTEyMPvnkE3Xu3FmS9O233yo+Pl4333xzhvkcOXJEKSkpF1x34cKFVbhw4WzFFRISoipVquiHH37Q/fffn611AIDfGQAg4Bw9etQkWffu3c+73LXXXmuSLCEhwczM+vTpYxUrVky33NNPP23n7vJPnjyZbrmOHTta5cqVvaZVrFjRJNmyZcs80w4ePGhBQUH24IMPeqZ99tlnJskWL16cbr2S7Omnn043vWLFitanTx/P60mTJpkk69ixo6Wmpnqmx8TEmMvlsrvvvtszLTk52cqVK2etW7dOt9603njjDZNkX3zxxXmXcxs2bJhJsu+//94z7dixYxYZGWmVKlWylJQUr1h37drl9f7Fixen+xxat25tkmzKlCmeaYmJiRYREWE9e/b0TFu1apVJskmTJqWL69y23bVrl0myEiVK2OHDhz3Tv/zyS5Nks2fP9kyLjo62cuXK2bFjxzzTlixZYpIy7C/nat26tdWoUcMOHTpkhw4dsi1bttjDDz9skqxr166e5Xbv3m158+a15557zuv969evt3z58nmm//rrrybJPvvss0y36c4vo8/i3P6UUVvUqlUrw75Rt25dr5iz6r333jNJtn79+nTzKlas6FnnHXfcYQULFrR9+/aZ2f/6Q9pc3dOy8nNu/3rooYcsMjLSTp8+nW7b2eH+7FatWmVvv/22FStWzLNvuOGGG6xt27aZbse9b7jQT0Zj361IkSJe+4CMdOjQwWrWrJntHAHA3zjSDQAB6NixY5KkYsWKnXc59/xjx45dcNlzFSpUyPP/+Ph4nTlzRq1bt9bcuXMVHx/vdbQxKirK64h7qVKlVL16de3cufOitplV/fr18zr9tEmTJlqxYoX69evnmZY3b141bNhQa9asOe+6EhISJF34s3T75ptv1LhxY7Vo0cIzrWjRoho4cKBGjBihTZs2qXbt2heTjmcdaa+JLlCggBo3bnzJn+FNN92k4sWLe16728m93n379mn9+vV67LHHvK6fbd26taKjoz2fz4Vs2bIl3WUO1157rd5//33P65kzZyo1NVU33nij1zXAERERuvLKK7V48WI99thjnr41d+5cdenSJdtHQbMjNDRUGzdu1LZt23TllVdm+X3//POPJHl91hl54oknNHXqVL3wwgt64403Mlymbt26Fzyd3S3t0ezff/9db7zxhj755JMcvbGZ24033qhhw4Zpzpw56tSpk+bMmXPeI/Yff/yxTp06dcH1Xuq12MWLF9evv/56SesAAH+i6AaAAJS2mD6fY8eOyeVyqWTJkhe9jR9++EFPP/20VqxYoZMnT3rNO7forlChQrr3Fy9eXEeOHLno7WbFudtzx1K+fPl00y8UQ3BwsKQLf5Zuf/zxR4bPBnafKv/HH39kq+guV65cuutYixcvrnXr1l30utI697NyF4Xuz+WPP/6QJFWtWjXde6tWrapffvklS9upVKmSJk6cqNTUVO3YsUPPPfecDh065HX37G3btsnMMi1m3ae7R0ZG6oEHHtBrr72mjz/+WC1bttS1116rXr16XfKp5RcyevRode/eXdWqVVPt2rXVqVMn3X777apTp06W3m8XuIdA5cqVdfvtt+vdd9/Vo48+muEyxYsXz/Ta8PO577771KxZM/Xs2fOi35sVpUqVUmxsrKZNm6aTJ08qJSVF119/fabLN2/e3CdxnMvMsnwNOAAEIopuAAhAISEhKlu27AULsnXr1qlcuXIqUKCAJGX6h+m5113u2LFD7du3V40aNfTaa6+pfPnyKlCggL755hu9/vrr6Z69nNm10BcqQC4ks+tBM9teRtMvFEONGjUkSevXr9d11113cQGeR1Y/azdffYa+Wu+5ihQp4lUoNm/eXFdddZUee+wxz9HQ1NRUuVwuffvttxnGlfZI+6uvvqq+ffvqyy+/1Lx58zR06FCNGTNGK1euzPALCresXEN8Pq1atdKOHTs8233vvff0+uuva8KECerfv3+m7ytRooSks19mlCtX7rzbcN8R/8UXX8ywzyUlJenw4cNZirdUqVLKmzevFi1apO+++04zZ870unlfcnKyTp06pd27dyssLMzzJVN23XrrrRowYIDi4uLUuXNnr+v1z3Xo0KEstUfRokUveJfy8zly5Ei2vlgEgEDB3csBIEBdc8012rVrl5YvX57h/O+//167d+/23DVcOnsE7ejRo+mWdR/tdJs9e7YSExP11Vdf6a677lKXLl0UGxvrdcr5xTrfkaiM4kpKStL+/fuzvb2satGihYoXL65PPvkkSwVCxYoVM3we9pYtWzzzpf8dUT43r3M/64vhi6N57ni3b9+ebl5G07KqTp066tWrl/7v//5Pe/bskSRVqVJFZqbIyEjFxsam+2natKnXOqKjo/XEE09o2bJl+v777/XXX39pwoQJki798z3fZxkWFqY77rhDn3zyifbu3as6deqc927o0v++vNm1a9cFt12lShXPZ5NRH//xxx9VpkyZLP247/ru/ox79OihyMhIz89ff/2lRYsWKTIyUh988MEFY7uQ//znP8qTJ49Wrlx5wbuWN2rUKEs5XOpztnft2pXtmzICQCDgSDcABKiHHnpIU6dO1V133aVly5Z5jrRJ0uHDh3X33XcrODhYgwcP9kyvUqWK4uPjtW7dOs/psvv379cXX3zhtW73Uci0R0Pj4+M1adKkbMdbpEgRSemLJHdcy5Yt85r27rvvXvJRy6woXLiwhg8frkcffVTDhw/Xyy+/nK4g++ijj1StWjU1btxYXbp00dixY7VixQrFxMRIkk6cOKF3331XlSpVUlRUlCcnSVq2bJnq1asn6exR2HfffTfbsZ7vM8yusmXLqnbt2poyZYpGjBjhOeK4dOlSrV+/3lOUZ8cjjzyiKVOm6LXXXtPYsWPVo0cPjRgxQqNGjdJHH33k9TmbmQ4fPqwSJUooISFBhQsX9noedHR0tPLkyaPExERJZy8LKFmypJYtW6Zhw4Z5lsvqnfqLFCmS4ef4zz//eI2lokWLqmrVqukeaXauBg0aqECBAlq9erWuvfbaC27ffW132sfCuWXnmu527dqlG8eSNHDgQFWsWFGPP/64oqOjs7TO8ylatKjGjx+v3bt365prrjnvspfjmu74+Hjt2LFD99xzT7bXAQD+RtENAAGqatWqmjJlim655RZFR0erX79+ioyM1O7du/X+++/ryJEjmj59uiIjIz3vufnmmzV8+HD95z//0dChQ3Xy5EmNHz9e1apV87p2t0OHDipQoICuueYa3XXXXTp+/LgmTpyo0qVLZ/voc7169ZQ3b169+OKLio+PV1BQkOc54P3799fdd9+tnj176uqrr9Zvv/2muXPnXrZTRh9++GFt3LhRr776qhYvXqzrr79eERERiouL06xZs/Tzzz/rxx9/lCQ9+uijnscmDR06VGFhYfrwww+1a9cuff75557HN9WqVUtNmzbViBEjdPjwYYWFhWn69OlKTk7OdpxVqlRRaGioJkyYoGLFiqlIkSJq0qSJVxtnx/PPP6/u3burefPmuuOOO3TkyBG9/fbbql27to4fP57t9UZFRalLly5677339OSTT6pKlSp69tlnNWLECO3evVvXXXedihUrpl27dumLL77QwIED9dBDD2nRokUaPHiwbrjhBlWrVk3JycmaOnWq8ubN63W9cv/+/fXCCy+of//+atiwoZYtW6bff/89S7E1aNBA48eP17PPPquqVauqdOnSateunaKiotSmTRs1aNBAYWFhWr16tWbMmOH15VVGChYsqA4dOmjBggUaPXr0BbfvPtr94YcfppuXnWu6K1SokOG9FYYNG6bw8PB0p7H37dvX028v9lnsffr0ydJy2b2me/bs2frtt98knX182rp16/Tss89KOntzvrTX1y9YsEBmpu7du2drWwAQEPx013QAQBatX7/ebr31VouIiLA8efKYJCtYsKBt3Lgxw+XnzZtntWvXtgIFClj16tXto48+yvCRYV999ZXVqVPHChYsaJUqVbIXX3zRPvjgg3SPKcrskUStW7dO90imiRMnWuXKlS1v3rxej81KSUmx4cOHW8mSJa1w4cLWsWNH2759e6aPDFu1apXXet3xHzp0yGt6nz59rEiRIhf4BP9nxowZ1qFDBwsLC7N8+fJZmTJl7KabbrIlS5Z4Lbdjxw67/vrrLTQ01AoWLGiNGze2OXPmpFvfjh07LDY21oKCgiw8PNwee+wxmz9/foaPDKtVq1a692f0iLcvv/zSoqKiLF++fF6PzMrskWEvv/xy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AAAA4CMU3QAAAAAA+AhFNwAAAAAAPkLRDQAAAACAj1B0AwAAAADgIxTdAAAAAAD4CEU3AAAAAAA+QtENAAAAAICP+LXoHj9+vOrUqaPg4GAFBwcrJiZG3377rWf+6dOnNWjQIJUoUUJFixZVz549deDAAa917NmzR127dlXhwoVVunRpPfzww0pOTr7cqQAAAAAAkI5fi+5y5crphRde0Jo1a7R69Wq1a9dO3bt318aNGyVJ999/v2bPnq3PPvtMS5cu1b59+9SjRw/P+1NSUtS1a1clJSXpxx9/1IcffqjJkyfrqaee8ldKAAAAAAB45PPnxq+55hqv188995zGjx+vlStXqly5cnr//fc1bdo0tWvXTpI0adIk1axZUytXrlTTpk01b948bdq0SQsWLFB4eLjq1aunZ555RsOHD9fIkSNVoEABf6QFAAAAAICkALqmOyUlRdOnT9eJEycUExOjNWvW6MyZM4qNjfUsU6NGDVWoUEErVqyQJK1YsULR0dEKDw/3LNOxY0clJCR4jpYDAAAAAOAvfj3SLUnr169XTEyMTp8+raJFi+qLL75QVFSU1q5dqwIFCig0NNRr+fDwcMXFxUmS4uLivApu93z3vMwkJiYqMTHR8zohISGHsgEAAAAA4H/8fqS7evXqWrt2rX766Sfdc8896tOnjzZt2uTTbY4ZM0YhISGen/Lly/t0ewAAAACAfye/F90FChRQ1apV1aBBA40ZM0Z169bVG2+8oYiICCUlJeno0aNeyx84cEARERGSpIiIiHR3M3e/di+TkREjRig+Pt7zs3fv3pxNCgAAAAAABUDRfa7U1FQlJiaqQYMGyp8/vxYuXOiZt3XrVu3Zs0cxMTGSpJiYGK1fv14HDx70LDN//nwFBwcrKioq020EBQV5HlPm/gEAAAAAIKf59ZruESNGqHPnzqpQoYKOHTumadOmacmSJZo7d65CQkLUr18/PfDAAwoLC1NwcLCGDBmimJgYNW3aVJLUoUMHRUVF6fbbb9dLL72kuLg4PfHEExo0aJCCgoL8mRoAAAAAAP4tug8ePKjevXtr//79CgkJUZ06dTR37lxdffXVkqTXX39defLkUc+ePZWYmKiOHTvqnXfe8bw/b968mjNnju655x7FxMSoSJEi6tOnj0aPHu2vlAAAAAAA8PBr0f3++++fd37BggU1btw4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Tvkd65aammrJycn28MMPW8uWLb0ez+Sef/vtt1vXrl1z1R/S//zzjzVo0MAxY+nPP/+08PBwGzx4sGdacnKymZn9/fffFhISYi+//LK/wrsomfWj5ORkr8L7xIkTduzYMa/r8ANR2n2bmXP2DWaW7mZ9v//+u6fwPnDggJmZHTlyxBYtWuSP8PyKovtfaOvWrfbDDz/Ytm3bPM/5cw949w4gMTHRateubdHR0fbdd99ZTEyMde7cOSB3Yu6jHBlx/4JJSkqyrl27Wv369W3ixIkWExNjjRs3DtibNqTNyR3jli1bLDw83K677jrbsmWL1/IjRoyw7t27e/INRE7LyT2Otm/fbocPHzYzs5UrV1qBAgXsnnvuSfet+5133ml33nmnP0LNMieOpbVr19rXX39t33//veemLhs3brTw8HDr3r07/S4ApG0j99GQc/8ozU39zmm/Y83M4uPjPf93x7h7925r2rSpdejQwebNm+e1/Ouvv26tWrUK6CONac+acLfP6tWrLSgoyO69995cN5b27NljmzZtsqNHj3r25TNnzrS8efN6nXXgbr9u3boF/JcIafvdudz76DNnztigQYOscePG9uSTT1pMTIxVqVIlIPfhGZ2p45Zb9w1p+5274HbfoM9t69atVrFiRWvXrp2tX7/emjZtarfddlvA5uQrFN3/Mu+9956VL1/eypYta5UqVbL27dt7HuGR9ps2s7M7NPcdL2vVqhWQpx59/vnn1r9/f9u6dWumy6T9JrFjx47mcrmsTp06AZmPWcY5uXNYu3athYaG2jXXXGNfffWVJScn25EjR6xly5Z2zz33+CvkC3JaTueOo3bt2tkvv/xiZmazZs2yAgUKWK9evey3336z1NRUO3nypDVu3NiGDx/u58gz58SxNHHiRIuIiLDIyEirWLGiVa1a1ebPn29m9LtAkVEbLV682GuZ3NTvnPY71szs008/tfbt23s9dsld0GzZssWuuuoqi42NtbfeessSExNt//791r59e7v99tv9GfZ5ffnll/bcc895nfLqzmn27NkWFBSUq8bSBx98YFWrVrWIiAirWrWq17587NixlidPHhs+fLj9/fffZna2sKtfv749//zz/gz7vM7tdxlxt1lqaqr169fPXC6XXXXVVZ6xFEhfymXU586V2/YNaftdlSpVbMCAAfb777+bmXfbmJlt377dKlWqZC6Xy2rUqOHJ6d+EovtfZNmyZVa0aFGbOnWq7dy506ZNm2bXXHONFS1a1Ov6ObOzO6rExERr3ry5NWvWzPNHj/vfQPDll19a3rx57YorrrChQ4fatm3bMl02JSXFTp48aS1atLCmTZsGZD5mmeeUmprqiXXDhg3WokULq127tl1xxRXWoEEDi46ODtgdmNNyymwcFSlSxHNt1pIlSzx51K5d22JiYqxWrVoB19/cnDiWfvrpJwsNDbXp06fboUOHbPny5da3b1/Lmzev55E569ats+bNm9Pv/CSzNsqXL59NmTLFa9nc0O+c9jvW7OxzjosWLWqVK1e26667zlavXm1m3vvv7du32+23325XXnmlhYSEWJ06daxevXoBWyjMnDnTXC6XlShRwl555RXPKa9m/4t1+fLlVq5cuVwxlubPn29FihSxiRMn2tq1a23MmDHWrl07q169uud67ilTpljhwoWtefPm1q5dO2vZsqVFRUUFZD5mmfe7jKSmptqxY8esRYsW1rhx44AcS+frc+fKLfuGjPpd+/btrXr16p4vGtOebXDkyBGrV6+eNW/ePGBz8jWK7n+RqVOnWuvWrb2++du9e7f16tXLChUqZGvWrDGz/32LPXjwYAsPD/f84gykwbFv3z7r2LGjPfroo/byyy9b/fr1bdCgQectFoYPH24lS5YMyHzMLpxT2j9yDh48aD/++KONHTvWpk+fHrA7MCfmdL5xVLBgQfvpp5/M7OzjPaZNm2ZPPfWUjRs3LmDzceJYMjP75ptvrH79+l6niCYlJdmjjz5q+fLlsy+//NLMzub/ww8/0O/84HxtlD9/fs+1wu5TFQO93znpd6zZ2fsFdO3a1YYNG2aTJ0+22NhY69atW4aFd0JCgu3evds++ugjmzdvntepv4Hkjz/+sHbt2tnIkSPt4YcftvLly9uLL77oVQS5Y9+zZ0+uGEuvvvqqde/e3Wva8uXLrWvXrhYZGWmbN282s7NnJTz77LM2ZMgQGz16tCePQDsN+0L9LiOjR4+24sWLB+RYykqfSys37BvMMu933bp18+p3qampdvr0aevXr5+VK1cuoHPyNYruf5H/+7//s6JFi6a7Ruavv/6yG264waKiouyvv/4ys7ODZNOmTQH7izMpKck++OADW7JkiZmZvf322xcsFo4cORKw+Zhl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BQV53fXVPZCnTp1qYWFhnuefphXIg90JOWWn36W9sZBboOTkxHHktDZKy52T+3Res7N3vM7oD7f4+Ph0XygEqqzsG8732JxAQhsFvotpo4SEhHRtFEj7hnNje+edd6xMmTKeRzW5paSk2HPPPWdRUVHp7n5tFjg5ZSefjO4iH4jjyin9zml9zszZ/c7f8gi5XmpqqiTJ5XJJkkqVKiUzU2pqqurVq6fhw4erXLlymjBhgj755BOdPHlSrVu31syZM/0Z9nklJyd7/t+iRQvdcMMNeu655/TDDz9IkvLmzStJql+/vvLly6ejR4+mW0e+fPkuS6xZ5bScstPvWrVqla7fBUpOThxHTmsjt+TkZE9OISEhks7mGhkZqXvuuUfdu3fXxIkT9frrrysxMVGxsbGaOHGiP0M+r4vdN8THx/slzotBGzmvjdq3b5+ujQJp3+De37k1a9ZMtWvX1ttvv62NGzd6pufJk0cxMTH666+/Avr3bHbyyajfuds4UDip3zmtz0nO7XcBwb81Py7VjBkz7KabbrLu3bvbsGHD7PDhw55vl9KelrN27Vq79dZbLSYmxipXrmwVK1YMyLsk/vDDD57/p/3mb968edapUydr06aNLViwwDP98OHDFhUVleGzTwOFE3NyWr9zWj5mzswp7Vg633Viu3fvtkcffdSqVatmZcqUsUqVKgVkTk7cN9BGtJE/pN3fpb0L/qeffmr169e33r1726pVqzzL//HHH1a7dm1buXKlv0I+L6flY+a8fufENnJiToGEojsX++ijjywoKMiGDh1qAwYMsAoVKli1atVs1qxZnseUpP3jeuHChZY/f/6AvRPxJ598Yi6Xy1q2bOkpDtLeZOebb76xnj17WunSpe2pp56ycePG2dVXX23169cP2Bs1ODEnp/U7p+Vj5sycMhpL54tx1apVFhISYjExMQGZkxP3DbQRbeQPGe3vqlatarNnzzYzs2nTplnLli2tXr169vbbb9vMmTPt6quvtsaNG6e7ZjgQOC0fM+f1Oye2kRNzCjQU3blQcnKyJSQkWKtWreyFF17wmt6xY0erUaOG/fe///XaQf3999/WsmVLi46ODsgd2MqVK61OnTrWq1cvi46OtjZt2mT4R86WLVvs1VdftSpVqlibNm2sZ8+eAXuHRKfl5LR+57R8zJyZk9n5x1JGsR45csQ6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SpUqSZL+85//yOVyeV6fe3q5e1uvvPKKypQpoxIlSmjQoEE6c+aMZ5n9+/era9euKlSokCIjIzVt2jRVqlRJY8eOPe/nkS9fPkVEROiKK65QbGysbrjhBs2fP99rmffee081a9ZUwYIFVaNGDb3zzjte83/88UfVq1dPBQsWVMOGDTVr1iy5XC6tXbtWUvqj13/88YeuueYaFS9eXEWKFFGtWrX0zTffaPfu3Wrbtq0kqXjx4nK5XOrbt68kKTU1VWPGjFFkZKQKFSqkunXrasaMGZ4Y3Nv49ttv1aBBAwUFBWn58uWZ5t2nTx9Nnz5dp06d8kz74IMP1KdPn3TL7t27VzfeeKNCQ0MVFham7t27a/fu3Z75q1at0tVXX62SJUsqJCRErVu31i+//OKZb2YaOXKkKlSooKCgIJUtW1ZDhw71zM/obIfQ0FBNnjxZ0tkvilwulz799FO1bt1aBQsW1Mcff3zBtnG/77///a9atmypQoUKqVGjRvr999+1atUqNWzYUEWLFlXnzp116NAhr+1nZb0zZ85U27ZtVbhwYdWtW1crVqzwtMUdd9yh+Ph4z1kFafs9AMBPDAAAH/vnn3/M5XLZ888/f97lUlJSrF69etaiRQtbvXq1rVy50ho0aGCtW7f2LNOnTx/r3r271/vuu+8+r2Vat25twcHBNnLkSPv999/tww8/NJfLZfPmzTMzs4MHD5okmzRpku3fv98OHjxoZmZPP/201a1b12tbwcHBdvfdd9vmzZtt9uzZVrhwYXv33Xc9y8TGxlq9evVs5cqVtmbNGmvdurUVKlTIXn/99UzzPHc7u3btslq1all4eLhn2kcffWRlypSxzz//3Hbu3Gmff/65hYWF2eTJk83MLD4+3sLCwqxXr162ceNG++abb6xatWomyX799VczM1u8eLFJsiNHjpiZWdeuXe3qq6+2devW2Y4dO2z27Nm2dOlSS05Ots8//9wk2datW23//v129OhRMzN79tlnrUaNGvbdd9/Zjh07bNKkSRYUFGRLlizx2kadOnVs3rx5tn37dvvnn38yzFuSffHFF1anTh2bOnWqmZn98ccfFhQUZL///rtJssWLF5uZWVJSktWsWdPuvPNOW7dunW3atMluvfVWq169uiUmJpqZ2cKFC23q1Km2efNm27Rpk/Xr18/Cw8MtISHBzMw+++wzCw4Otm+++cb++OMP++mnn7zazh1PWiEhITZp0iRPu0iySpUqedph3759F2wb9/vcn9umTZusadOm1qBBA2vTpo0tX77cfvnlF6tatardfffdWW7ztOudM2eObd261a6//nqrWLGinTlzxhITE23s2LEWHBxs+/fvt/3799uxY8cy7YcAgMuDohsA4HM//fSTSbKZM2eed7l58+ZZ3rx5bc+ePZ5pGzduNEn2888/m1nWi+4WLVp4LdOoUSMbPny453VGBVdGRXfFihUtOTnZM+2GG26wm266yczMNm/ebJJs1apVnvnbtm0zSRcsuvPkyWNFihSxggULmiSTZK+99ppnmSpVqti0adO83vfMM89YTEyMmZmNHz/eSpQoYadOnfLMnzhx4nmL7ujoaBs5cmSGMZ27rJnZ6dOnrXDhwvbjjz96LduvXz+75ZZbvN43a9asTPN1c3/mY8eOtbZt25qZ2ahRo+w///mPHTlyxKvonjp1qlWvXt1SU1M9709MTLRChQrZ3LlzM1x/SkqKFStWzGbPnm1mZq+++qpVq1bNkpKSzhtPWhkV3WPHjvVa5kJt437fe++955n/ySefmCRbuHChZ9qYMWOsevXql7Re9/jYvHmzmZlNmjTJQkJCMswXAOAfXNMNAPA5M8vScps3b1b58uVVvnx5z7SoqCiFhoZq8+bNatSoUZa3WadOHa/XZcqU0cGDB7P8frdatWopb968XutZv369JGnr1q3Kly+frrrqKs/8qlWrqnjx4hdcb/Xq1fXVV1/p9OnT+uijj7R27VoNGTJEknTixAnt2LFD/fr104ABAzzvSU5O9twUbevWrapTp44KFizomd+4cePzbnPo0KG65557NG/ePMXGxqpnz57pPqe0tm/frpMnT+rqq6/2mp6UlKT69et7TWvYsOEFc3br1auXHn30Ue3cuVOTJ0/Wm2++mW6Z3377Tdu3b1exYsW8pp8+fVo7duyQJB04cEBPPPGElixZooMHDyolJUUnT57Unj17JEk33HCDxo4dq8qVK6tTp07q0qWLrrnmmkyvN89M2tyy0jZuaT/b8PBwSVJ0dLTXNHefzO56y5QpI0k6ePCgatSocVF5AQAuD4puAIDPXXnllXK5XDlys7Q8efKkK+LTXmPtdu7Nrlwul1JTUy96ezm1nnMVKFBAVatWlSS98MIL6tq1q0aNGqVnnnlGx48flyRNnDhRTZo08Xpf2i8ALlb//v3VsWNHff3115o3b57GjBmjV1991VPsn8sdx9dff60rrrjCa15QUJDX6yJFimQ5jhIlSqhbt27q16+fTp8+rc6dO+vYsWPptt2gQQPPNdRplSpVStLZ68P/+ecfvfHGG6pYsaKCgoIUExOjpKQkSWdv2rZ161YtWLBA8+fP17333quXX35ZS5cuVf78+eVyubLUl9LmdjFtk7bvuFyuDKe5+9Klrjcn+iQAwDe4kRoAwOfCwsLUsWNHjRs3TidOnEg3332jr5o1a2rv3r3au3evZ96mTZt09OhRRUVFSTpbcO3fv9/r/e4bh12M/PnzKyUl5aLfl1b16tWVnJysX3/91TNt+/btOnLkyEWv64knntArr7yiffv2KTw8XGXLltXOnTtVtWpVr5/IyEjPttevX6/ExETPOlatWnXB7ZQvX1533323Zs6cqQcffFATJ06UdPZLAElen0lUVJSCgoK0Z8+edHGkPRshO+68804tWbJEvXv3zvCLhKuuukrbtm1T6dKl023bfeT3hx9+0NChQ9WlSxfVqlVLQUFB+vvvv73WU6hQIV1zzTV68803tWTJEq1YscJzpsK5fWnbtm06efLkeePOSttkR06tt0CBApfcrwEAOYuiGwBwWYwbN04pKSlq3LixPv/8c23btk2bN2/Wm2++qZiYGElSbGysoqOjddttt+mXX37Rzz//rN69e6t169aeU3zbtWun1atXa8qUKdq2bZuefvppbdiw4aLjqVSpkhYuXKi4uLhsFcmSVKNGDcXGxmrgwIH6+eef9euvv2rgwIEqVKiQ5whkVsXExKhOnTp6/vnnJUmjRo3SmDFj9Oabb+r333/X+vXrNWnSJL322muSpFtvvVWpqakaOHCgNm/erLlz5+qVV16RpEy3PWzYMM2dO1e7du3SL7/8osWLF6tmzZqSpIoVK8rlcmnOnDk6dOiQjh8/rmLFiumhhx7S/fffrw8//FA7duzQL7/8orfeeksffvhhtj4zt06dOunQoUMaPXp0hvNvu+02lSxZUt27d9f333+vXbt2acmSJRo6dKj+/PNPSWfPoJg6dao2b96sn376SbfddpsKFSrkWcfkyZP1/vvva8OGDdq5c6c++ugjFSpUSBUrVpR0ti+9/fbb+vXXX7V69WrdfffdWXoc2IXaJrtyYr2VKlXS8ePHtXDhQv39998X/BIBAOB7FN0AgMuicuXK+uWXX9S2bVs9+OCDql27tq6++motXLhQ48ePl3S2WPzyyy9VvHhxtWrVSrGxsapcubLXs6s7duyoJ598Uo888ogaNWqkY8eOqXfv3hcdz6uvvqr58+erfPny6a5PvhhTpkxReHi4WrVqpf/85z8aMGCAihUr5nWtdVbdf//9eu+997R37171799f7733niZNmqTo6Gi1bt1akydP9hz1DA4O1uzZs7V27VrVq1dPjz/+uJ566ilJynTbKSkpGjRokGrWrKlOnTqpWrVqnkdSXXHFFRo1apQeffRRhYeHa/DgwZKkZ555Rk8++aTGjBnjed/XX399SUd1pbNtXbJkSc8R9nMVLlxYy5YtU4UKFdSjRw/VrFnTczp6cHCwJOn999/XkSNHdNVVV+n222/X0KFDVbp0ac86QkNDNXHiRDVv3lx16tTRggULNHv2bJUoUULS2T5Qvnx5tWzZUrfeeqseeughFS5c+IKxX6htsisn1tusWTPdfffduummm1SqVCm99NJLlxQTAODSuSyrd7cBAAAX9Oeff6p8+fJasGCB2rdvf1m3/fHHH3ue05z2iC8AAPAfbqQGAMAlWLRokY4fP67o6Gjt379fjzzyiCpVqqRWrVr5fNtTpkxR5cqVdcUVV+i3337T8OHDdeONN1JwAwAQQCi6AQC4BGfOnNFjjz2mnTt3qlixYmrWrJk+/vjjLF0bfKni4uL01FNPKS4uTmXKlNENN9yg5557zufbBQAAWcfp5QAAAAAA+Ag3UgMAAAAAwEcougEAAAAA8BGKbgAAAAAAfISiGwAAAAAAH6HoBgAAAADARyi6AQAAAADwEYpuAAAAAAB8hKIbAAAAAAAfoegGAAAAAMBH/h8I5Yb+oPF7bwAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualize counting register distribution\n", + "counting_results = results[\"counting_register_results\"]\n", + "sorted_results = sorted(counting_results.items(), key=lambda x: x[1], reverse=True)\n", + "\n", + "labels = [f\"|{bs}>\" for bs, _ in sorted_results]\n", + "values = [c for _, c in sorted_results]\n", + "\n", + "plt.figure(figsize=(10, 5))\n", + "plt.bar(labels, values, color=\"steelblue\", alpha=0.8)\n", + "plt.xlabel(\"Counting Register Measurement\")\n", + "plt.ylabel(\"Counts\")\n", + "plt.title(f\"Quantum Counting Results (N={N}, M={len(marked_states)})\")\n", + "plt.xticks(rotation=45)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "estimate-accuracy", + "metadata": {}, + "source": [ + "### Understanding the Estimate Accuracy\n", + "\n", + "The estimate $M \\approx 1.23$ rather than exactly $M = 1$ arises for two reasons:\n", + "\n", + "1. **Not in an eigenstate:** QPE works perfectly when the input state is an eigenstate of the operator. However, the uniform superposition $|s\\rangle = H^{\\otimes n}|0\\rangle^{\\otimes n}$ that we prepare is generally **not** an eigenstate of $G$. Instead, it is a superposition of both eigenstates:\n", + "\n", + "$$|s\\rangle = \\sin(\\theta/2)|\\beta\\rangle + \\cos(\\theta/2)|\\alpha\\rangle$$\n", + "\n", + " where $|\\alpha\\rangle$ and $|\\beta\\rangle$ are the eigenstates of $G$ with eigenvalues $e^{+i\\theta}$ and $e^{-i\\theta}$ respectively. Because we start in this superposition, QPE produces **both** phase estimates $\\varphi$ and $1 - \\varphi$, which is why we see two dominant peaks in the histogram.\n", + "\n", + "2. **Finite precision:** When the true phase $\\theta/(2\\pi)$ is not exactly representable as a $t$-bit fraction $y/2^t$, there is a discretization error. Using more counting qubits (larger $t$) reduces this error and gives a more precise estimate of $M$." + ] + }, + { + "cell_type": "markdown", + "id": "fa08c609", + "metadata": {}, + "source": [ + "## Example 2: Counting Multiple Marked Items\n", + "\n", + "Count 2 marked items in an 8-element search space ($N = 8$, $n = 3$)." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "e8901c56", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:43.252843Z", + "iopub.status.busy": "2026-02-26T21:20:43.250737Z", + "iopub.status.idle": "2026-02-26T21:20:50.290397Z", + "shell.execute_reply": "2026-02-26T21:20:50.286177Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quantum Counting Results (N=8, M=2):\n", + "Search space size N = 8\n", + "\n", + "Counting register distribution (top outcomes):\n", + " |10101>: 452 counts -> phase = 0.1562, M ~ 1.7777\n", + " |01011>: 446 counts -> phase = 0.1562, M ~ 1.7777\n", + " |10000>: 177 counts -> phase = 0.0000, M ~ 0.0000\n", + " |10110>: 115 counts -> phase = 0.1875, M ~ 2.4693\n", + " |00000>: 111 counts -> phase = 0.5000, M ~ 8.0000\n", + " |01010>: 104 counts -> phase = 0.1875, M ~ 2.4693\n", + " ... (26 more outcomes)\n", + "\n", + "Best estimate of M: 1.777719067921591\n", + "\n", + "Actual M = 2\n", + "Estimated M = 1.7777\n" + ] + } + ], + "source": [ + "n_counting = 5\n", + "n_search = 3\n", + "marked_states_2 = [2, 5]\n", + "N_2 = 2**n_search\n", + "\n", + "counting_qubits_2 = list(range(n_counting))\n", + "search_qubits_2 = list(range(n_counting, n_counting + n_search))\n", + "\n", + "circ_2 = Circuit()\n", + "circ_2 = quantum_counting_circuit(\n", + " circ_2, counting_qubits_2, search_qubits_2, marked_states_2\n", + ")\n", + "\n", + "device = LocalSimulator()\n", + "task_2 = run_quantum_counting(circ_2, device, shots=2000)\n", + "\n", + "print(f\"Quantum Counting Results (N={N_2}, M={len(marked_states_2)}):\")\n", + "results_2 = get_quantum_counting_results(\n", + " task_2, counting_qubits_2, search_qubits_2, verbose=True\n", + ")\n", + "\n", + "print(f\"\\nActual M = {len(marked_states_2)}\")\n", + "print(f\"Estimated M = {results_2['best_estimate']:.4f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "histogram2", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:50.293744Z", + "iopub.status.busy": "2026-02-26T21:20:50.293511Z", + "iopub.status.idle": "2026-02-26T21:20:51.563858Z", + "shell.execute_reply": "2026-02-26T21:20:51.562244Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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opYtWyaBgYHSsGHDVMc3atRIAgMDZdmyZS7Nf+rUqVKmTBkZOnSoTJgwQQICAuSVV16RyZMnp5j22LFj8uSTT0rz5s1lwoQJUrhwYenevbvs37/fKEv//v1FRGTo0KHGZchVqlRxqWz9+vWTo0ePyqhRo+Txxx+XL774QoYNGyZt27aVhIQE+e9//ysNGjSQcePGyezZs+85r40bN8rly5fl2WeflZw5c9439oULF+Thhx+WlStXyiuvvCLvvfee3LlzRx5//HFZvHixS/UREbly5Yo8+uijUq1aNZkwYYJUrlxZBg8eLD/99JOIiFSpUkVGjx4tIiK9e/c2lmGjRo3uOd958+bJuHHj5MUXX5QxY8bIqVOn5IknnpC4uDhjmh9//FGefvppyZ07t4wdO1aeeOIJ6dGjh+zcudPl+oj8X2KcNAl87733pGvXrvLQQw/JxIkTZcCAAbJ27Vpp1KiRXL16VUTuJnMtW7aULVu2SL9+/WTy5MnSu3dvOXHihDFNRkyaNElKlSollStXNpbj22+/LSJ3f4AaNWqUNG3aVD777DN5++23pXTp0rJr1657znPnzp0SGxsrNWvWTHOat99+W+Lj4+X999+/57zi4uLk0qVL6fpLes98kyZNJDo6Wl588UU5ePCgnD59Wj7//HNZtGiRDBkyxIkllFJgYKCEh4fLN998Ywz76aef5Nq1a9KpU6dUv3PlypV01eHWrVv3jR8ZGSn58uWTfPnyOQz39fWV8uXLy++//56h+gFApsrsU+0AgJSuXr2qIqLt2rW753SPP/64iohGR0erqmq3bt20TJkyKaYbMWKEJu/yb926lWK6li1barly5RyGlSlTRkVEf/31V2NYVFSUenl56euvv24Mu9fl5SKiI0aMSDG8TJky2q1bN+Oz/VLXli1bOlwGHh4erjabTV966SVjWHx8vJYqVeq+lxB//PHHKiK6ePHie05nN2DAABUR/e2334xh169f17Jly2pgYKAmJCQ4lDXpJc2q/3c5cdLl0LhxYxURnTVrljEsJiZG/f39tUOHDsawe11ennzdnjx5UkVEixYtqpcvXzaGL126VEVEly1bZgwLCQnRUqVK6fXr141h69evVxFJtb0k17hxY61cubJevHhRL168qIcOHdI333xTRcThkuNTp05pzpw59b333nP4/t69ezVXrlzG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c4zxSXemy5Yt0/79++szzzyjr732mtNP2r5fndq3b2/Ma8mSJRmKZcWyU1V98cUXtVevXsbn27dv6zfffKN58+Z1uDznk08+0U6dOumjjz6qPXv2dHi/5/1YtY7sr6Po06ePvvHGG1qpUiUdP358qnF2796t7777rvbr109Hjhzp1vXjrnZwv+WWfIdgVjtIerCTmJio165d01q1amnZsmVd2uFYEcuqtuCJbduKPuF+9Unetm/duqWtWrXShx56yKmn6lq5DalmjX7Bnfs8T2oLnrZ/ULWu37bqeOR+dcpubc6q/sfKfi4rHJu6s21b1RY8BUl3FmT/Nahz587aqlUrY7i9ge7YsUPz58+f4kX13377rdON+MiRI9qpUyc9ePCg7tu3T5955hlt0KCBQ4dzr8tM0hsnvXW6131f6Yll5bKLj4/XiIgI7dy5c4r4CxYs0Jw5c6a4pC7pssxq6+jvv//W559/Xv/44w/966+/dPDgwSk60IzGsaodqKZvuSW/fMysdpD011/Vu5eMubLDsSqWFW1B1bPatqp1fUJ66pP8YOfHH380Zf/gjm0oq/ULtAXX4mSn/YMVfamVxyPprVN2anNW9T9WxMlqx6buOiaxqi14CpLuLGzBggXq5eXl8FRWewOdPXu2FilSxHg/aVL3a8T2jc/+r/2Jhap3f/FLrcO5ceOG6xVJIj11uterJdwZJyPLzm7KlClaokQJ4xUMdgkJCfree+9pUFBQqk9mTD6ftMZbtY7sceyX7qnefepsah3otWvX7lv++zGrHbiy3JI+jCd5We4Xxy497SD501rTE8fqWEnjmdUWPK1tW9UnJJ8uPfWJjo5OMV8z9g+ubEOpyUr9gittztPagitxssP+wc7svtTOrOMRVc9rc1bvw63u57LSsWlG27ZV/Y8nIenOYpI2wsjISO3cubM2aNBAN27cqKr/18j37dunxYsX17Vr1zodI7XLhxITE41fo3bv3q2dOnXSBg0a6Lx58/TmzZsaFhamCxcudKVKltTJqjjJl93u3bu1efPm+txzz6V43ca6devU19dXd+3aleE4quato9Q6PnucU6dO6aBBg7RSpUo6ceJEvXPnjtauXVv/97//ZShOdm/bVrUDq2NZ0RY8rW2zfrL+/sET+4XM6LetipPd99+q1h33eFqby6xt1aw4qp7XtlWt6xc8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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Visualize Example 2 results\n", + "counting_results_2 = results_2[\"counting_register_results\"]\n", + "sorted_results_2 = sorted(counting_results_2.items(), key=lambda x: x[1], reverse=True)\n", + "\n", + "labels_2 = [f\"|{bs}>\" for bs, _ in sorted_results_2]\n", + "values_2 = [c for _, c in sorted_results_2]\n", + "\n", + "plt.figure(figsize=(10, 5))\n", + "plt.bar(labels_2, values_2, color=\"steelblue\", alpha=0.8)\n", + "plt.xlabel(\"Counting Register Measurement\")\n", + "plt.ylabel(\"Counts\")\n", + "plt.title(f\"Quantum Counting Results (N={N_2}, M={len(marked_states_2)})\")\n", + "plt.xticks(rotation=45)\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "ba1c59de", + "metadata": {}, + "source": [ + "## Example 3: Edge Case — No Marked Items (M = 0)\n", + "\n", + "When no items are marked, the algorithm should estimate $M \\approx 0$." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1f273c0", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:51.568418Z", + "iopub.status.busy": "2026-02-26T21:20:51.568045Z", + "iopub.status.idle": "2026-02-26T21:20:52.392208Z", + "shell.execute_reply": "2026-02-26T21:20:52.390984Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Search space size N = 4\n", + "\n", + "Counting register distribution (top outcomes):\n", + " |1000>: 1000 counts -> phase = 0.0000, M ~ 0.0000\n", + "\n", + "Best estimate of M: 0.0\n", + "\n", + "Actual M = 0\n", + "Estimated M = 0.0000\n" + ] + } + ], + "source": [ + "n_counting = 4\n", + "n_search = 2\n", + "marked_states_3 = []\n", + "N_3 = 2**n_search\n", + "\n", + "counting_qubits_3 = list(range(n_counting))\n", + "search_qubits_3 = list(range(n_counting, n_counting + n_search))\n", + "\n", + "circ_3 = Circuit()\n", + "circ_3 = quantum_counting_circuit(\n", + " circ_3, counting_qubits_3, search_qubits_3, marked_states_3\n", + ")\n", + "\n", + "device = LocalSimulator()\n", + "task_3 = run_quantum_counting(circ_3, device, shots=1000)\n", + "\n", + "results_3 = get_quantum_counting_results(\n", + " task_3, counting_qubits_3, search_qubits_3, verbose=True\n", + ")\n", + "\n", + "print(\"\\nActual M = 0\")\n", + "print(f\"Estimated M = {results_3['best_estimate']:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "c224af69", + "metadata": {}, + "source": [ + "## Example 4: Edge Case — All Items Marked (M = N)\n", + "\n", + "When all items are marked, $M = N$." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "36cbb24d", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:52.395277Z", + "iopub.status.busy": "2026-02-26T21:20:52.395041Z", + "iopub.status.idle": "2026-02-26T21:20:53.461638Z", + "shell.execute_reply": "2026-02-26T21:20:53.460245Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Search space size N = 4\n", + "\n", + "Counting register distribution (top outcomes):\n", + " |0000>: 1000 counts -> phase = 0.5000, M ~ 4.0000\n", + "\n", + "Best estimate of M: 4.0\n", + "\n", + "Actual M = 4\n", + "Estimated M = 4.0000\n" + ] + } + ], + "source": [ + "n_counting = 4\n", + "n_search = 2\n", + "N_4 = 2**n_search\n", + "marked_states_4 = list(range(N_4))\n", + "\n", + "counting_qubits_4 = list(range(n_counting))\n", + "search_qubits_4 = list(range(n_counting, n_counting + n_search))\n", + "\n", + "circ_4 = Circuit()\n", + "circ_4 = quantum_counting_circuit(\n", + " circ_4, counting_qubits_4, search_qubits_4, marked_states_4\n", + ")\n", + "\n", + "device = LocalSimulator()\n", + "task_4 = run_quantum_counting(circ_4, device, shots=1000)\n", + "\n", + "results_4 = get_quantum_counting_results(\n", + " task_4, counting_qubits_4, search_qubits_4, verbose=True\n", + ")\n", + "\n", + "print(f\"\\nActual M = {N_4}\")\n", + "print(f\"Estimated M = {results_4['best_estimate']:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "5d747213", + "metadata": {}, + "source": [ + "## [Optional] Run on a QPU or Managed Simulator\n", + "\n", + "**Note:** The current implementation builds the controlled-Grover operator from **gate-level primitives** (controlled-H gates and controlled MCZ oracles), rather than using `Circuit.unitary()`. This circuit-based approach is more compatible with QPU execution since it uses native one- and two-qubit gates. However, the circuit depth grows exponentially with the number of counting qubits (as is inherent in QPE), which may exceed the coherence limits of current hardware for larger problem sizes.\n", + "\n", + "[Include estimated price for running in USD using the [cost tracker](https://docs.aws.amazon.com/braket/latest/developerguide/braket-pricing.html#real-time-cost-tracking).]" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "690a8210", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:53.465560Z", + "iopub.status.busy": "2026-02-26T21:20:53.465069Z", + "iopub.status.idle": "2026-02-26T21:20:53.473632Z", + "shell.execute_reply": "2026-02-26T21:20:53.471814Z" + } + }, + "outputs": [], + "source": [ + "# Use Braket SDK Cost Tracking to estimate the cost to run this example\n", + "# from braket.tracking import Tracker\n", + "\n", + "# tracker = Tracker().start()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "2c70f923", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:53.477667Z", + "iopub.status.busy": "2026-02-26T21:20:53.477363Z", + "iopub.status.idle": "2026-02-26T21:20:53.486847Z", + "shell.execute_reply": "2026-02-26T21:20:53.481010Z" + } + }, + "outputs": [], + "source": [ + "# from braket.aws import AwsDevice, AwsSession\n", + "# import boto3\n", + "\n", + "# boto_session = boto3.Session(region_name=\"us-east-1\")\n", + "# aws_session = AwsSession(boto_session=boto_session)\n", + "\n", + "# # Run on managed simulator\n", + "# managed_device = AwsDevice(\n", + "# \"arn:aws:braket:::device/quantum-simulator/amazon/sv1\",\n", + "# aws_session=aws_session\n", + "# )\n", + "# n_counting = 4\n", + "# n_search = 2\n", + "# marked_states = [3]\n", + "# counting_qubits = list(range(n_counting))\n", + "# search_qubits = list(range(n_counting, n_counting + n_search))\n", + "\n", + "# circ = Circuit()\n", + "# circ = quantum_counting_circuit(\n", + "# circ, counting_qubits, search_qubits, marked_states\n", + "# )\n", + "\n", + "# task = run_quantum_counting(circ, managed_device, shots=1000)\n", + "\n", + "# results = get_quantum_counting_results(\n", + "# task, counting_qubits, search_qubits, verbose=True\n", + "# )\n", + "# print(f\"\\nEstimated M = {results['best_estimate']:.4f}\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "1c2e7121", + "metadata": { + "execution": { + "iopub.execute_input": "2026-02-26T21:20:53.490298Z", + "iopub.status.busy": "2026-02-26T21:20:53.489933Z", + "iopub.status.idle": "2026-02-26T21:20:53.494046Z", + "shell.execute_reply": "2026-02-26T21:20:53.492842Z" + } + }, + "outputs": [], + "source": [ + "# print(\"Task Summary\")\n", + "# print(f\"{tracker.quantum_tasks_statistics()} \\n\")\n", + "# print(\n", + "# f\"Estimated cost to run this example: {tracker.qpu_tasks_cost() + tracker.simulator_tasks_cost():.2f} USD\"\n", + "# )" + ] + }, + { + "cell_type": "markdown", + "id": "2ab582fd", + "metadata": {}, + "source": [ + "Note: Charges shown are estimates based on your Amazon Braket simulator and quantum processing unit (QPU) task usage. Estimated charges shown may differ from your actual charges. Estimated charges do not factor in any discounts or credits, and you may experience additional charges based on your use of other services such as Amazon Elastic Compute Cloud (Amazon EC2)." + ] + } + ], + "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.13.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator.py b/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator.py index f1fd03b8..0780e281 100644 --- a/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator.py +++ b/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator.py @@ -33,7 +33,7 @@ def commute(a: str, b: str, qwc: bool = True) -> bool: Raises: ValueError: If the Pauli strigns are of different length. """ - if len(a)!=len(b): + if len(a) != len(b): raise ValueError("Pauli strings must be of the same length.") count = 0 for i in zip(a, b): @@ -42,15 +42,17 @@ def commute(a: str, b: str, qwc: bool = True) -> bool: # Partial functions for specific commutation checks -qwc_commute = partial(commute, qwc=True) # Qubit-wise commutation -gen_commute = partial(commute, qwc=False) # General commutation +qwc_commute = partial(commute, qwc=True) # Qubit-wise commutation +gen_commute = partial(commute, qwc=False) # General commutation # BAYESIAN STATISTICS (closed formulas from Appendix B of arXiv:2110.15339v6) -def term_variance_estimate(term_idx: int, measurements: Union[MeasurementData, None] = None) -> float: +def term_variance_estimate( + term_idx: int, measurements: Union[MeasurementData, None] = None +) -> float: """ - Estimate variance for a single Pauli term. + Estimate variance for a single Pauli term. See Eq 14 in Appendix B of "Adaptive Estimation of Quantum Observables (arXiv:2110.15339v6) Args: @@ -64,10 +66,12 @@ def term_variance_estimate(term_idx: int, measurements: Union[MeasurementData, N if measurements: x0 = measurements[term_idx][term_idx][(1, 1)] x1 = measurements[term_idx][term_idx][(-1, -1)] - return 4*((x0+1)*(x1+1))/((x0+x1+2)*(x0+x1+3)) + return 4 * ((x0 + 1) * (x1 + 1)) / ((x0 + x1 + 2) * (x0 + x1 + 3)) -def terms_covariance_estimate(i: int, j: int, measurements: Union[MeasurementData, None] = None) -> float: +def terms_covariance_estimate( + i: int, j: int, measurements: Union[MeasurementData, None] = None +) -> float: """ Estimate covariance between two Pauli terms. See Eq 25-6 in Appendix B of "Adaptive Estimation of Quantum Observables (arXiv:2110.15339v6) @@ -93,14 +97,17 @@ def terms_covariance_estimate(i: int, j: int, measurements: Union[MeasurementDat xy11 = measurements[i][j][(-1, -1)] # Calculate prior probabilities - p00 = 4*((x0+1)*(y0+1))/((x0+x1+2)*(y0+y1+2)) - p01 = 4*((x0+1)*(y1+1))/((x0+x1+2)*(y0+y1+2)) - p10 = 4*((x1+1)*(y0+1))/((x0+x1+2)*(y0+y1+2)) - p11 = 4*((x1+1)*(y1+1))/((x0+x1+2)*(y0+y1+2)) + p00 = 4 * ((x0 + 1) * (y0 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2)) + p01 = 4 * ((x0 + 1) * (y1 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2)) + p10 = 4 * ((x1 + 1) * (y0 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2)) + p11 = 4 * ((x1 + 1) * (y1 + 1)) / ((x0 + x1 + 2) * (y0 + y1 + 2)) # Return Bayesian covariance estimate - return 4*((xy00+p00)*(xy11+p11) - (xy01+p01)*(xy10+p10)) / \ - ((xy00+xy01+xy10+xy11+4)*(xy00+xy01+xy10+xy11+5)) + return ( + 4 + * ((xy00 + p00) * (xy11 + p11) - (xy01 + p01) * (xy10 + p10)) + / ((xy00 + xy01 + xy10 + xy11 + 4) * (xy00 + xy01 + xy10 + xy11 + 5)) + ) class AdaptiveShotAllocator: @@ -156,11 +163,10 @@ def __init__(self, paulis: List[str], coeffs: List[float]) -> None: raise ValueError("Number of Paulis must match coefficients") # Validate Pauli strings - valid_chars = set('IXYZ') + valid_chars = set("IXYZ") for pauli in paulis: if not set(pauli).issubset(valid_chars): - raise ValueError( - f"Invalid Pauli string: {pauli}. Must only contain I, X, Y, or Z") + raise ValueError(f"Invalid Pauli string: {pauli}. Must only contain I, X, Y, or Z") self.paulis = paulis self.coeffs = coeffs self.graph = self._generate_graph() @@ -179,9 +185,10 @@ def reset(self): - Updates graph weights to initial values """ # Initialize measurement counts for all term pairs - self.measurements = [[{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} - for _ in range(self.num_terms)] - for _ in range(self.num_terms)] + self.measurements = [ + [{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} for _ in range(self.num_terms)] + for _ in range(self.num_terms) + ] self.shots = None # Clear shot allocation history self._update_graph_weights() # Reset graph weights @@ -201,7 +208,9 @@ def _generate_graph(self, commute: Callable[[str, str], bool] = qwc_commute): Edges are added between terms that commute according to the provided function. """ if commute != qwc_commute: - warnings.warn("Braket only supports simultaneous measurement of qubit-wise commuting operators.") + warnings.warn( + "Braket only supports simultaneous measurement of qubit-wise commuting operators." + ) # Create graph and add nodes with Pauli string labels self.graph = nx.Graph() @@ -229,8 +238,9 @@ def _partition_graph(self): _, cliq = approximation.clique_removal(self.graph) self.cliq = [sorted(i) for i in cliq] # Sort cliques for consistency - def visualize_graph(self, node_size: int = 1230, font_size: int = 10, - show_cliques: bool = True) -> None: + def visualize_graph( + self, node_size: int = 1230, font_size: int = 10, show_cliques: bool = True + ) -> None: """ Visualize the graph with colored edges based on clique membership. @@ -242,15 +252,17 @@ def visualize_graph(self, node_size: int = 1230, font_size: int = 10, """ # Generate random colors for each clique - cliq_colors = ["#"+"".join([hex(random.randint(0, 255))[2:].zfill(2) - for _ in range(3)]) for _ in self.cliq] + cliq_colors = [ + "#" + "".join([hex(random.randint(0, 255))[2:].zfill(2) for _ in range(3)]) + for _ in self.cliq + ] # Build edge list and colors if show_cliques: el = [] ec = [] for e in self.graph.edges: - for i,c in enumerate(self.cliq): + for i, c in enumerate(self.cliq): if (e[0] in c) and (e[1] in c): el.append(e) ec.append(cliq_colors[i]) @@ -258,23 +270,32 @@ def visualize_graph(self, node_size: int = 1230, font_size: int = 10, else: el = list(self.graph.edges) # Use gray for all edges when showing full graph - ec = ['gray' for _ in el] + ec = ["gray" for _ in el] # Create layout pos = nx.circular_layout(self.graph) # Draw the graph plt.figure(figsize=(10, 10)) - nx.draw(self.graph, pos=pos, with_labels=False, - node_color='white', node_size=node_size, - edgelist=el, edge_color=ec) + nx.draw( + self.graph, + pos=pos, + with_labels=False, + node_color="white", + node_size=node_size, + edgelist=el, + edge_color=ec, + ) # Add labels - nx.draw_networkx_labels(self.graph, pos, - labels=nx.get_node_attributes( - self.graph, 'label'), - font_size=font_size, font_color="black", - font_family="Times") + nx.draw_networkx_labels( + self.graph, + pos, + labels=nx.get_node_attributes(self.graph, "label"), + font_size=font_size, + font_color="black", + font_family="Times", + ) # Set edge colors ax = plt.gca() @@ -299,9 +320,12 @@ def _update_graph_weights(self) -> None: # Base weight from coefficients weight = self.coeffs[i] * self.coeffs[j] # Multiply by variance/covariance estimate - weight *= (term_variance_estimate(i, measurements) if i == j - else terms_covariance_estimate(i, j, measurements)) - self.graph[i][j]['weight'] = weight + weight *= ( + term_variance_estimate(i, measurements) + if i == j + else terms_covariance_estimate(i, j, measurements) + ) + self.graph[i][j]["weight"] = weight def incremental_shot_allocation(self, num_shots: int) -> List[int]: """ @@ -337,13 +361,14 @@ def incremental_shot_allocation(self, num_shots: int) -> List[int]: current_shots = self.shots if self.shots else [0 for c in self.cliq] # Calculate weighted covariance matrix for error estimates - weighted_covariance = nx.adjacency_matrix( - self.graph, weight="weight").toarray() + weighted_covariance = nx.adjacency_matrix(self.graph, weight="weight").toarray() # Initialize error estimates for each clique using current shots # Error = sqrt(sum of covariances) / number of shots - clique_error = [np.sqrt(weighted_covariance[c, c].sum())/(current_shots[e] or not current_shots[e]) - for e, c in enumerate(self.cliq)] + clique_error = [ + np.sqrt(weighted_covariance[c, c].sum()) / (current_shots[e] or not current_shots[e]) + for e, c in enumerate(self.cliq) + ] # Allocate shots one at a time for _ in range(num_shots): @@ -356,8 +381,7 @@ def incremental_shot_allocation(self, num_shots: int) -> List[int]: # Update error estimate for the chosen clique # New error = Old error * (n/(n+1)) where n+1 is the updated shot count. total_shots = current_shots[cliq_id] + proposed_allocation[cliq_id] - clique_error[cliq_id] *= ((total_shots-1) - or not (total_shots-1))/(total_shots) + clique_error[cliq_id] *= ((total_shots - 1) or not (total_shots - 1)) / (total_shots) return proposed_allocation @@ -379,18 +403,23 @@ def error_estimate(self) -> float: ValueError: If graph weights have not been properly initialized """ # Get weighted covariance matrix - weighted_covariance = nx.adjacency_matrix( - self.graph, weight="weight").toarray() + weighted_covariance = nx.adjacency_matrix(self.graph, weight="weight").toarray() # Sum variance contributions from each clique - variance_estimate = sum([(weighted_covariance[c, c].sum())/(self.shots[e] or not self.shots[e]) - for e, c in enumerate(self.cliq)]) + variance_estimate = sum( + [ + (weighted_covariance[c, c].sum()) / (self.shots[e] or not self.shots[e]) + for e, c in enumerate(self.cliq) + ] + ) return np.sqrt(variance_estimate) - def expectation_from_measurements(self, measurements: Union[MeasurementData, None] = None) -> float: + def expectation_from_measurements( + self, measurements: Union[MeasurementData, None] = None + ) -> float: """ - Calculate the energy expectation value from measurement results + Calculate the energy expectation value from measurement results for the different Pauli string observables. For each Pauli term, computes

= (N++ - N--)/N_total where: @@ -414,17 +443,15 @@ def expectation_from_measurements(self, measurements: Union[MeasurementData, Non expectation = 0.0 for i in range(len(self.coeffs)): # Verify no invalid measurements - assert measurements[i][i][( - 1, -1)] == 0, "Invalid measurement detected: (+1,-1)" - assert measurements[i][i][(-1, 1) - ] == 0, "Invalid measurement detected: (-1,+1)" + assert measurements[i][i][(1, -1)] == 0, "Invalid measurement detected: (+1,-1)" + assert measurements[i][i][(-1, 1)] == 0, "Invalid measurement detected: (-1,+1)" # Calculate expectation for this term - term_shots = measurements[i][i][( - 1, 1)] + measurements[i][i][(-1, -1)] + term_shots = measurements[i][i][(1, 1)] + measurements[i][i][(-1, -1)] if term_shots: term_expect = ( - measurements[i][i][(1, 1)] - measurements[i][i][(-1, -1)]) / term_shots + measurements[i][i][(1, 1)] - measurements[i][i][(-1, -1)] + ) / term_shots expectation += self.coeffs[i] * term_expect return expectation @@ -447,13 +474,11 @@ def _validate_measurements(self, measurements: MeasurementData) -> bool: Raises: AssertionError: If any validation check fails """ - assert len( - measurements) == self.num_terms, "Wrong number of measurement records" + assert len(measurements) == self.num_terms, "Wrong number of measurement records" for c in self.cliq: # Get total shots for this clique - m_cliq = (measurements[c[0]][c[0]][(1, 1)] + - measurements[c[0]][c[0]][(-1, -1)]) + m_cliq = measurements[c[0]][c[0]][(1, 1)] + measurements[c[0]][c[0]][(-1, -1)] # Check consistency within clique for i in c: @@ -463,25 +488,31 @@ def _validate_measurements(self, measurements: MeasurementData) -> bool: assert v >= 0, "Measurement counts should not be negative" # Measurements should be symmetric - assert measurements[i][j][(1,1)]==measurements[j][i][(1,1)],\ - "Measurement should be symmetric" - assert measurements[i][j][(-1,-1)]==measurements[j][i][(-1,-1)],\ - "Measurement should be symmetric" - assert measurements[i][j][(1,-1)]==measurements[j][i][(-1,1)],\ - "Measurement should be symmetric" - assert measurements[i][j][(-1,1)]==measurements[j][i][(1,-1)],\ - "Measurement should be symmetric" + assert measurements[i][j][(1, 1)] == measurements[j][i][(1, 1)], ( + "Measurement should be symmetric" + ) + assert measurements[i][j][(-1, -1)] == measurements[j][i][(-1, -1)], ( + "Measurement should be symmetric" + ) + assert measurements[i][j][(1, -1)] == measurements[j][i][(-1, 1)], ( + "Measurement should be symmetric" + ) + assert measurements[i][j][(-1, 1)] == measurements[j][i][(1, -1)], ( + "Measurement should be symmetric" + ) # All pairs in clique should have same total measurements - assert m_cliq == sum(measurements[i][j].values()), \ - (f"The number of times {i} and {j} were measured together should be " - "equal to the number of measurements of their clique.") + assert m_cliq == sum(measurements[i][j].values()), ( + f"The number of times {i} and {j} were measured together should be " + "equal to the number of measurements of their clique." + ) # Diagonal elements should not have invalid combinations if i == j: - assert measurements[i][i][(1, -1)] == measurements[i][i][(-1, 1)] == 0, \ - ("A measurement of a single term can only contribute to the " - "(1,1) or (-1,-1) counts.") + assert measurements[i][i][(1, -1)] == measurements[i][i][(-1, 1)] == 0, ( + "A measurement of a single term can only contribute to the " + "(1,1) or (-1,-1) counts." + ) return True def shots_from_measurements(self, measurements: MeasurementData) -> List[int]: diff --git a/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator_braket_helpers.py b/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator_braket_helpers.py index 6cb8017a..ea8f2125 100644 --- a/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator_braket_helpers.py +++ b/src/braket/experimental/algorithms/adaptive_shot_allocation/adaptive_allocator_braket_helpers.py @@ -1,5 +1,5 @@ """ -Helper functions to handle quantum measurements and adaptive shot allocation +Helper functions to handle quantum measurements and adaptive shot allocation experiments on Amazon Braket. """ @@ -13,6 +13,7 @@ MeasurementData, ) + def observable_from_string(pauli_string: str) -> Observable: """ Convert Pauli string to Braket observable. @@ -23,15 +24,15 @@ def observable_from_string(pauli_string: str) -> Observable: Returns: Observable: Corresponding Braket observable """ - gates = {"I": Observable.I, "X": Observable.X, - "Y": Observable.Y, "Z": Observable.Z} + gates = {"I": Observable.I, "X": Observable.X, "Y": Observable.Y, "Z": Observable.Z} return Observable.TensorProduct([gates[i[1]](i[0]) for i in enumerate(pauli_string)]) + def run_fixed_allocation( device: Union[LocalSimulator, AwsDevice], circuit: Circuit, estimator: AdaptiveShotAllocator, - shot_allocation: List[int] + shot_allocation: List[int], ) -> MeasurementData: """ Run experiment with a specific shot allocation. @@ -45,7 +46,7 @@ def run_fixed_allocation( Returns: MeasurementData: Measurement outcomes for each term pair """ - + # Step 1. Submit all tasks. tasks = {} for c_idx, c in enumerate(estimator.cliq): @@ -54,41 +55,40 @@ def run_fixed_allocation( measurement_circ = circuit.copy() for p in c: - measurement_circ.sample( - observable_from_string(estimator.paulis[p])) + measurement_circ.sample(observable_from_string(estimator.paulis[p])) + + tasks[c_idx] = device.run(measurement_circ, shots=shot_allocation[c_idx]) - tasks[c_idx] = device.run( - measurement_circ, shots=shot_allocation[c_idx]) - # Step 2. Post-process results. - measurements = [[{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} - for _ in range(len(estimator.paulis))] - for _ in range(len(estimator.paulis))] - - while(tasks): + measurements = [ + [{(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} for _ in range(len(estimator.paulis))] + for _ in range(len(estimator.paulis)) + ] + + while tasks: task_to_process = None - + for c_idx in tasks: # Check task status state = tasks[c_idx].state() - assert state in ["CREATED", "QUEUED", "RUNNING", "COMPLETED"], \ + assert state in ["CREATED", "QUEUED", "RUNNING", "COMPLETED"], ( f"Encountered quantum task failure (status: {state})." + ) if state == "COMPLETED": task_to_process = c_idx break - + if task_to_process is None: continue - + # Task is ready for post-processing result = tasks[task_to_process].result() c = estimator.cliq[task_to_process] for i_idx, i in enumerate(c): for j_idx, j in enumerate(c): for s in range(len(result.values[i_idx])): - measurements[i][j][(result.values[i_idx][s], - result.values[j_idx][s])] += 1 + measurements[i][j][(result.values[i_idx][s], result.values[j_idx][s])] += 1 # Remove task from the queue tasks.pop(task_to_process) @@ -101,7 +101,7 @@ def run_adaptive_allocation( estimator: AdaptiveShotAllocator, shots_per_round: int, num_rounds: int, - verbose: bool = False + verbose: bool = False, ) -> MeasurementData: """ Run adaptive shot allocation process. @@ -118,14 +118,12 @@ def run_adaptive_allocation( MeasurementData: Final measurement outcomes """ if verbose: - print( - f"Running {num_rounds} rounds with {shots_per_round} shots each:") + print(f"Running {num_rounds} rounds with {shots_per_round} shots each:") for i in range(num_rounds): if verbose: - print(f"Round {i+1}/{num_rounds}...") + print(f"Round {i + 1}/{num_rounds}...") shots_to_run = estimator.incremental_shot_allocation(shots_per_round) - new_measurements = run_fixed_allocation( - device, circuit, estimator, shots_to_run) + new_measurements = run_fixed_allocation(device, circuit, estimator, shots_to_run) estimator.update_measurements(new_measurements) return estimator.measurements diff --git a/src/braket/experimental/algorithms/quantum_counting/__init__.py b/src/braket/experimental/algorithms/quantum_counting/__init__.py new file mode 100644 index 00000000..afc94115 --- /dev/null +++ b/src/braket/experimental/algorithms/quantum_counting/__init__.py @@ -0,0 +1,20 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from braket.experimental.algorithms.quantum_counting.quantum_counting import ( # noqa: F401, E501 + build_grover_circuit, + build_oracle_circuit, + get_quantum_counting_results, + quantum_counting_circuit, + run_quantum_counting, +) diff --git a/src/braket/experimental/algorithms/quantum_counting/quantum_counting.md b/src/braket/experimental/algorithms/quantum_counting/quantum_counting.md new file mode 100644 index 00000000..9a78a589 --- /dev/null +++ b/src/braket/experimental/algorithms/quantum_counting/quantum_counting.md @@ -0,0 +1,7 @@ +The Quantum Counting algorithm, introduced by Brassard, Høyer, and Tapp (1998), solves a generalization of the quantum search problem. Instead of merely detecting whether a marked item exists in an unstructured database of N elements, quantum counting determines the number of marked items M. The algorithm combines Grover's search operator with Quantum Phase Estimation (QPE): it prepares the Grover operator G = D·O (oracle followed by diffusion), whose eigenvalues encode θ such that M = N·sin²(θ/2), then uses QPE to estimate θ with t precision qubits. The algorithm achieves a quadratic speedup over classical counting, requiring only O(√N) oracle queries instead of the classical Θ(N). + + diff --git a/src/braket/experimental/algorithms/quantum_counting/quantum_counting.py b/src/braket/experimental/algorithms/quantum_counting/quantum_counting.py new file mode 100644 index 00000000..68aadb0f --- /dev/null +++ b/src/braket/experimental/algorithms/quantum_counting/quantum_counting.py @@ -0,0 +1,483 @@ +"""Quantum Counting Algorithm implementation using the Amazon Braket SDK. + +The quantum counting algorithm combines Grover's search operator with Quantum +Phase Estimation (QPE) to count the number of marked items in an unstructured +search space of N = 2^n elements. + +Reference: + G. Brassard, P. Høyer, and A. Tapp, "Quantum Counting", + Proceedings of ICALP 1998. arXiv:quant-ph/9805082 +""" + +import math +from typing import Any, Dict, List, Tuple + +import numpy as np + +from braket.circuits import Circuit +from braket.circuits.qubit_set import QubitSetInput +from braket.devices import Device +from braket.experimental.algorithms.grovers_search.grovers_search import amplify, build_oracle +from braket.tasks import QuantumTask + + +def build_oracle_circuit( + n_qubits: int, + marked_states: List[int], + decompose_ccnot: bool = False, +) -> Circuit: + """Build an oracle circuit that flips the phase of marked states. + + Composes individual oracle circuits from grovers_search.build_oracle + for each marked state. Each call flips the phase of one basis state, + and their composition marks all specified states. + + Args: + n_qubits (int): Number of qubits in the search register. + marked_states (List[int]): Indices of marked computational-basis states. + decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates. + + Returns: + Circuit: Oracle circuit that flips the phase of all marked states. + + Raises: + ValueError: If a marked state index is out of range. + """ + dim = 2**n_qubits + for state in marked_states: + if state < 0 or state >= dim: + raise ValueError( + f"Marked state {state} is out of range for {n_qubits} qubits " + f"(must be in [0, {dim - 1}])." + ) + + oracle_circ = Circuit() + for state in marked_states: + bitstring = format(state, f"0{n_qubits}b") + oracle_circ.add_circuit(build_oracle(bitstring, decompose_ccnot)) + return oracle_circ + + +def build_grover_circuit( + n_qubits: int, + marked_states: List[int], + decompose_ccnot: bool = False, +) -> Circuit: + """Build the Grover operator G = D · O as a circuit. + + Uses circuit primitives from grovers_search: build_oracle for the phase + oracle and amplify for the diffusion operator (H · oracle_0 · H). + + Args: + n_qubits (int): Number of qubits in the search register. + marked_states (List[int]): Indices of marked states. + decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates. + + Returns: + Circuit: Circuit implementing the Grover operator G. + """ + oracle_circ = build_oracle_circuit(n_qubits, marked_states, decompose_ccnot) + diffusion_circ = amplify(n_qubits, decompose_ccnot) + + grover_circ = Circuit() + grover_circ.add_circuit(oracle_circ) + grover_circ.add_circuit(diffusion_circ) + return grover_circ + + +def _controlled_oracle_circuit( + control: int, + search_qubits: QubitSetInput, + marked_states: List[int], + decompose_ccnot: bool = False, +) -> Circuit: + """Build a controlled oracle circuit using gate-level primitives. + + Makes the phase-flip oracle controlled on the given qubit by prepending + "1" to each marked-state bitstring, so the MCZ gate gains an extra + control qubit. + + Args: + control (int): Index of the control qubit. + search_qubits (QubitSetInput): Indices of the search register qubits. + marked_states (List[int]): Indices of marked computational-basis states. + decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates. + + Returns: + Circuit: Controlled oracle circuit. + """ + n_search = len(search_qubits) + circ = Circuit() + + for state in marked_states: + bitstring = format(state, f"0{n_search}b") + # Prepend "1" so the MCZ is controlled on the QPE qubit + controlled_bitstring = "1" + bitstring + oracle_sub = build_oracle(controlled_bitstring, decompose_ccnot) + # Map the oracle qubits: qubit 0 -> control, qubits 1..n -> search_qubits + target_mapping = [control] + list(search_qubits) + # The oracle may use ancilla qubits beyond n_search+1; they are + # automatically placed by add_circuit's target mapping. + all_targets = target_mapping + list( + range( + max(target_mapping) + 1, + max(target_mapping) + 1 + oracle_sub.qubit_count - len(target_mapping), + ) + ) + circ.add_circuit(oracle_sub, target=all_targets) + + return circ + + +def _controlled_diffusion_circuit( + control: int, + search_qubits: QubitSetInput, + decompose_ccnot: bool = False, +) -> Circuit: + """Build a controlled diffusion (amplitude amplification) circuit. + + The diffusion operator is D = H^⊗n · O_0 · H^⊗n, where O_0 flips the + phase of the |0⟩ state. To make it controlled: + - H gates become controlled-H (CH) gates + - O_0 becomes controlled by prepending "1" to the "00...0" bitstring + + Args: + control (int): Index of the control qubit. + search_qubits (QubitSetInput): Indices of the search register qubits. + decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates. + + Returns: + Circuit: Controlled diffusion circuit. + """ + n_search = len(search_qubits) + circ = Circuit() + + # First set of controlled-H gates on search qubits + for sq in search_qubits: + _add_controlled_h(circ, control, sq) + + # Controlled O_0: flip phase of |0⟩, controlled on QPE qubit + # O_0 uses build_oracle("00...0"). Controlled version: build_oracle("1" + "00...0") + controlled_zero_bitstring = "1" + "0" * n_search + zero_oracle = build_oracle(controlled_zero_bitstring, decompose_ccnot) + target_mapping = [control] + list(search_qubits) + all_targets = target_mapping + list( + range( + max(target_mapping) + 1, + max(target_mapping) + 1 + zero_oracle.qubit_count - len(target_mapping), + ) + ) + circ.add_circuit(zero_oracle, target=all_targets) + + # Second set of controlled-H gates on search qubits + for sq in search_qubits: + _add_controlled_h(circ, control, sq) + + # Apply Z gate to correct the -1 global phase difference between + # I - 2|s> None: + """Add a controlled-Hadamard gate to the circuit. + + Decomposition: CH(c, t) = S†(t) · H(t) · T†(t) · CX(c,t) · T(t) · H(t) · S(t) + + Args: + circ (Circuit): Circuit to append the controlled-H to. + control (int): Control qubit index. + target (int): Target qubit index. + """ + circ.s(target) + circ.h(target) + circ.t(target) + circ.cnot(control, target) + circ.ti(target) + circ.h(target) + circ.si(target) + + +def controlled_grover_circuit( + control: int, + search_qubits: QubitSetInput, + marked_states: List[int], + power: int = 1, + decompose_ccnot: bool = False, +) -> Circuit: + """Build a controlled Grover operator G^power as a gate-level circuit. + + Constructs the controlled Grover operator C-G = C-D · C-O entirely from + gate-level primitives: + - The oracle is made controlled by prepending "1" to each marked-state + bitstring, adding the control qubit as an extra MCZ control. + - The diffusion operator is made controlled by using controlled-H (CH) + gates, a controlled zero-oracle, and a Z gate on the control qubit + to correct the inherent -1 global phase of the diffusion operator. + + Args: + control (int): Index of the QPE control qubit. + search_qubits (QubitSetInput): Indices of the search register qubits. + marked_states (List[int]): Indices of marked computational-basis states. + power (int): Number of times to apply the Grover operator (default 1). + decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates. + + Returns: + Circuit: Circuit implementing the controlled G^power operator. + """ + circ = Circuit() + + for _ in range(power): + # Controlled oracle + circ.add_circuit( + _controlled_oracle_circuit(control, search_qubits, marked_states, decompose_ccnot) + ) + + # Controlled diffusion + circ.add_circuit( + _controlled_diffusion_circuit(control, search_qubits, decompose_ccnot) + ) + + return circ + + +def inverse_qft_for_counting(qubits: QubitSetInput) -> Circuit: + """Inverse Quantum Fourier Transform applied to the given qubits. + + Args: + qubits (QubitSetInput): Qubits on which to apply the inverse QFT. + + Returns: + Circuit: Circuit implementing the inverse QFT. + """ + qft_circ = Circuit() + num_qubits = len(qubits) + + # SWAP gates to reverse qubit order + for i in range(math.floor(num_qubits / 2)): + qft_circ.swap(qubits[i], qubits[-i - 1]) + + # Controlled phase rotations + Hadamard + for k in reversed(range(num_qubits)): + for j in reversed(range(1, num_qubits - k)): + angle = -2 * math.pi / (2 ** (j + 1)) + qft_circ.cphaseshift(qubits[k + j], qubits[k], angle) + qft_circ.h(qubits[k]) + + return qft_circ + + +def quantum_counting_circuit( + counting_circ: Circuit, + counting_qubits: QubitSetInput, + search_qubits: QubitSetInput, + marked_states: List[int], + decompose_ccnot: bool = False, +) -> Circuit: + """Create the full quantum counting circuit with result types. + + Builds the controlled Grover operator as a gate-level circuit and + applies QPE. + + Args: + counting_circ (Circuit): Initial circuit (may contain setup operations). + counting_qubits (QubitSetInput): Qubits for the counting (precision) register. + search_qubits (QubitSetInput): Qubits for the search register. + marked_states (List[int]): Indices of marked computational-basis states. + decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates. + + Returns: + Circuit: The complete quantum counting circuit with result types. + """ + new_circuit = Circuit().add_circuit(counting_circ) + new_circuit.add_circuit( + quantum_counting( + counting_qubits, search_qubits, marked_states, decompose_ccnot + ) + ) + return new_circuit.probability(counting_qubits) + + +def quantum_counting( + counting_qubits: QubitSetInput, + search_qubits: QubitSetInput, + marked_states: List[int], + decompose_ccnot: bool = False, +) -> Circuit: + """Build the core quantum counting circuit using QPE on the Grover operator. + + Constructs the controlled Grover operator as a gate-level circuit from + grovers_search primitives (build_oracle for the phase oracle and + controlled-H gates for the diffusion operator). + + The circuit structure: + 1. Apply H to all counting qubits + 2. Apply H to all search qubits (prepare uniform superposition |s⟩) + 3. Apply controlled-G^(2^k) for each counting qubit k + 4. Apply inverse QFT to counting qubits + + Args: + counting_qubits (QubitSetInput): Qubits for the counting (precision) register. + search_qubits (QubitSetInput): Qubits for the search register. + marked_states (List[int]): Indices of marked computational-basis states. + decompose_ccnot (bool): Whether to decompose CCNOT (Toffoli) gates. + + Returns: + Circuit: Circuit implementing the quantum counting algorithm. + """ + qc_circ = Circuit() + + # Hadamard on counting qubits + qc_circ.h(counting_qubits) + + # Hadamard on search qubits (prepare |s⟩) + qc_circ.h(search_qubits) + + # Controlled-G^(2^k) + for ii, qubit in enumerate(reversed(counting_qubits)): + power = 2**ii + qc_circ.add_circuit( + controlled_grover_circuit( + qubit, search_qubits, marked_states, power, decompose_ccnot + ) + ) + + # Inverse QFT on counting qubits + qc_circ.add_circuit(inverse_qft_for_counting(counting_qubits)) + + return qc_circ + + +def run_quantum_counting( + circuit: Circuit, + device: Device, + shots: int = 1000, +) -> QuantumTask: + """Run the quantum counting circuit on a device. + + Args: + circuit (Circuit): The quantum counting circuit to run. + device (Device): Braket device backend. + shots (int): Number of measurement shots (default is 1000). + + Returns: + QuantumTask: Task from running quantum counting. + """ + task = device.run(circuit, shots=shots) + return task + + +def get_quantum_counting_results( + task, + counting_qubits: QubitSetInput, + search_qubits: QubitSetInput, + verbose: bool = False, +) -> Dict[str, Any]: + """Post-process quantum counting results to estimate the number of marked items. + + After measuring the counting qubits, the most likely outcome y gives + an estimate of the phase φ = y / 2^t. The number of marked items M is: + M = N · sin²(π · φ) + where N = 2^n_search. + + Args: + task (QuantumTask): The task holding quantum counting results. + counting_qubits (QubitSetInput): Qubits of the counting register. + search_qubits (QubitSetInput): Qubits of the search register. + verbose (bool): If True, print detailed results (default False). + + Returns: + Dict[str, Any]: Aggregate measurement results including: + - measurement_counts: raw measurement counts + - counting_register_results: counts collapsed to the counting register + - phases: estimated phases φ for each measured bitstring + - estimated_counts: estimated M for each measured bitstring + - best_estimate: best estimate of M (from most frequent outcome) + - search_space_size: N = 2^n_search + """ + result = task.result() + measurement_counts = result.measurement_counts + n_counting = len(counting_qubits) + n_search = len(search_qubits) + N = 2**n_search + + # Aggregate results on counting register (trace out search qubits) + counting_register_results: Dict[str, int] = {} + if measurement_counts: + for key in measurement_counts.keys(): + counting_bits = key[:n_counting] + counting_register_results[counting_bits] = ( + counting_register_results.get(counting_bits, 0) + measurement_counts[key] + ) + + # Convert counting register bitstrings to phase estimates and M estimates + phases, estimated_counts = _get_counting_estimates(counting_register_results, n_counting, N) + + # Best estimate from most frequent outcome + if counting_register_results: + best_key = max(counting_register_results, key=counting_register_results.get) + best_y = int(best_key, 2) + raw_phase = best_y / (2**n_counting) + best_phase = raw_phase + best_M = N * (np.sin(np.pi * best_phase) ** 2) + else: + best_key = None + best_M = None + + aggregate_results = { + "measurement_counts": measurement_counts, + "counting_register_results": counting_register_results, + "phases": phases, + "estimated_counts": estimated_counts, + "best_estimate": best_M, + "search_space_size": N, + } + + if verbose: + print(f"Search space size N = {N}") + sorted_cr = sorted( + counting_register_results.items(), key=lambda x: x[1], reverse=True + ) + print("\nCounting register distribution (top outcomes):") + for bitstring, count in sorted_cr[:6]: + y_val = int(bitstring, 2) + raw_ph = y_val / (2**n_counting) + ph = raw_ph + m_est = N * (np.sin(np.pi * ph) ** 2) + print(f" |{bitstring}>: {count} counts -> phase = {ph:.4f}, M ~ {m_est:.4f}") + if len(sorted_cr) > 6: + print(f" ... ({len(sorted_cr) - 6} more outcomes)") + print(f"\nBest estimate of M: {best_M}") + + return aggregate_results + + +def _get_counting_estimates( + counting_register_results: Dict[str, int], + n_counting: int, + N: int, +) -> Tuple[List[float], List[float]]: + """Convert counting register bitstrings to phase and count estimates. + + Args: + counting_register_results (Dict[str, int]): Measurement results on the counting register. + n_counting (int): Number of counting qubits. + N (int): Search space size. + + Returns: + Tuple[List[float], List[float]]: (phases, estimated_counts) lists, one entry per unique + measured bitstring with counts > 0. + """ + phases = [] + estimated_counts = [] + + for bitstring in counting_register_results: + y = int(bitstring, 2) + raw_phase = y / (2**n_counting) + phase = raw_phase + M_est = N * (np.sin(np.pi * phase) ** 2) + phases.append(phase) + estimated_counts.append(M_est) + + return phases, estimated_counts diff --git a/test/unit_tests/braket/experimental/algorithms/adaptive_shot_allocation/test_adaptive_allocator.py b/test/unit_tests/braket/experimental/algorithms/adaptive_shot_allocation/test_adaptive_allocator.py index 3d02a80a..9a52d7e2 100644 --- a/test/unit_tests/braket/experimental/algorithms/adaptive_shot_allocation/test_adaptive_allocator.py +++ b/test/unit_tests/braket/experimental/algorithms/adaptive_shot_allocation/test_adaptive_allocator.py @@ -1,13 +1,17 @@ -import pytest -import networkx as nx import unittest +from unittest.mock import patch + +import networkx as nx +import pytest from braket.experimental.algorithms.adaptive_shot_allocation.adaptive_allocator import ( - commute, qwc_commute, gen_commute, - term_variance_estimate, terms_covariance_estimate, - AdaptiveShotAllocator + AdaptiveShotAllocator, + commute, + gen_commute, + qwc_commute, + term_variance_estimate, + terms_covariance_estimate, ) -from unittest.mock import patch # Fixtures for common test setups @@ -30,14 +34,15 @@ def mock_measurements_2terms(): return [ [ {(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}, - {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4} + {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4}, ], [ {(1, 1): 3, (1, -1): 1, (-1, 1): 2, (-1, -1): 4}, - {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4} - ] + {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}, + ], ] + # 1. Tests for Helper Functions @@ -77,13 +82,12 @@ def test_partial_commute_functions(): def test_term_variance_estimate(): # Test with no measurements (prior only) # With no measurements, the formula gives 4*(1*1)/(2*3) = 4/6 = 2/3 - assert abs(term_variance_estimate(0) - 2/3) < 1e-10 + assert abs(term_variance_estimate(0) - 2 / 3) < 1e-10 # Test with mock measurements mock_measurements = [[{(1, 1): 10, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}]] - expected_variance = 4 * ((10+1) * (5+1)) / ((10+5+2) * (10+5+3)) - assert abs(term_variance_estimate( - 0, mock_measurements) - expected_variance) < 1e-10 + expected_variance = 4 * ((10 + 1) * (5 + 1)) / ((10 + 5 + 2) * (10 + 5 + 3)) + assert abs(term_variance_estimate(0, mock_measurements) - expected_variance) < 1e-10 def test_terms_covariance_estimate(): @@ -94,19 +98,18 @@ def test_terms_covariance_estimate(): # Test with mock measurements mock_measurements = [ [{(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}], - [{(1, 1): 7, (1, -1): 0, (-1, 1): 0, (-1, -1): 3}] + [{(1, 1): 7, (1, -1): 0, (-1, 1): 0, (-1, -1): 3}], ] # Add cross-measurements - mock_measurements[0].append( - {(1, 1): 4, (1, -1): 1, (-1, 1): 2, (-1, -1): 3}) - mock_measurements[1].insert( - 0, {(1, 1): 4, (1, -1): 2, (-1, 1): 1, (-1, -1): 3}) + mock_measurements[0].append({(1, 1): 4, (1, -1): 1, (-1, 1): 2, (-1, -1): 3}) + mock_measurements[1].insert(0, {(1, 1): 4, (1, -1): 2, (-1, 1): 1, (-1, -1): 3}) # Calculate covariance result = terms_covariance_estimate(0, 1, mock_measurements) assert isinstance(result, float) assert -1.0 <= result <= 1.0 # Covariance should be in this range + # 2. Tests for AdaptiveShotAllocator Class @@ -139,9 +142,11 @@ def test_reset_method(simple_allocator): # Reset and check state simple_allocator.reset() assert simple_allocator.shots is None - assert all(all(outcome == 0 for outcome in simple_allocator.measurements[i][j].values()) - for i in range(simple_allocator.num_terms) - for j in range(simple_allocator.num_terms)) + assert all( + all(outcome == 0 for outcome in simple_allocator.measurements[i][j].values()) + for i in range(simple_allocator.num_terms) + for j in range(simple_allocator.num_terms) + ) def test_generate_graph(): @@ -163,8 +168,7 @@ def test_generate_graph(): def test_partition_graph(): # Create a simple graph with known clique structure - allocator = AdaptiveShotAllocator( - ["II", "IX", "IZ", "ZI"], [1.0, 1.0, 1.0, 1.0]) + allocator = AdaptiveShotAllocator(["II", "IX", "IZ", "ZI"], [1.0, 1.0, 1.0, 1.0]) # II, IX, ZI should form one clique (all commute with each other) # IZ should be in a separate clique @@ -195,8 +199,7 @@ def test_error_estimate(simple_allocator): simple_allocator.shots = [10, 15] # Mock measurements to update graph weights - mock_measurements = [ - [{(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}] * 2] * 2 + mock_measurements = [[{(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}] * 2] * 2 simple_allocator.update_measurements(mock_measurements) # Calculate error estimate @@ -228,12 +231,12 @@ def test_expectation_from_measurements(): mock_measurements = [ [ {(1, 1): 8, (1, -1): 0, (-1, 1): 0, (-1, -1): 2}, - {(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} + {(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0}, ], [ {(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0}, - {(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0} - ] + {(1, 1): 0, (1, -1): 0, (-1, 1): 0, (-1, -1): 0}, + ], ] # Expected: 0.5 * 0.6 + (-0.3) * (0.0) = 0.3 + 0.0 = 0.0 @@ -250,22 +253,18 @@ def test_validate_measurements(): valid_measurements = [ [ {(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}, - {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4} + {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4}, ], [ {(1, 1): 3, (1, -1): 1, (-1, 1): 2, (-1, -1): 4}, - {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4} - ] + {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}, + ], ] assert allocator._validate_measurements(valid_measurements) == True # Invalid measurements (wrong size) - invalid_measurements = [ - [ - {(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5} - ] - ] + invalid_measurements = [[{(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}]] with pytest.raises(AssertionError): allocator._validate_measurements(invalid_measurements) @@ -274,12 +273,12 @@ def test_validate_measurements(): invalid_measurements = [ [ {(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}, - {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 3} # Sum is 9, not 10 + {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 3}, # Sum is 9, not 10 ], [ {(1, 1): 3, (1, -1): 1, (-1, 1): 2, (-1, -1): 4}, - {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4} - ] + {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}, + ], ] with pytest.raises(AssertionError): @@ -289,12 +288,12 @@ def test_validate_measurements(): invalid_measurements = [ [ {(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}, - {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4} + {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4}, ], [ {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4}, - {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4} - ] + {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}, + ], ] with pytest.raises(AssertionError): @@ -304,12 +303,12 @@ def test_validate_measurements(): invalid_measurements = [ [ {(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}, - {(1, 1): 3, (1, -1): 4, (-1, 1): -1, (-1, -1): 4} + {(1, 1): 3, (1, -1): 4, (-1, 1): -1, (-1, -1): 4}, ], [ {(1, 1): 3, (1, -1): -1, (-1, 1): 4, (-1, -1): 4}, - {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4} - ] + {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}, + ], ] with pytest.raises(AssertionError): @@ -323,20 +322,21 @@ def test_shots_from_measurements(): mock_measurements = [ [ {(1, 1): 5, (1, -1): 0, (-1, 1): 0, (-1, -1): 5}, # 10 shots - {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4} + {(1, 1): 3, (1, -1): 2, (-1, 1): 1, (-1, -1): 4}, ], [ {(1, 1): 3, (1, -1): 1, (-1, 1): 2, (-1, -1): 4}, - {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4} # 10 shots - ] + {(1, 1): 6, (1, -1): 0, (-1, 1): 0, (-1, -1): 4}, # 10 shots + ], ] shots = allocator.shots_from_measurements(mock_measurements) assert len(shots) == len(allocator.cliq) assert shots[0] == 10 # First clique should have 10 shots + class VisualizationTest(unittest.TestCase): - @patch('matplotlib.pyplot.show') + @patch("matplotlib.pyplot.show") def test_graph(*args): paulis = ["XX", "IZ", "ZI", "YY", "XI"] coeffs = [0.5, 0.3, -0.2, 1.0, 2.0] diff --git a/test/unit_tests/braket/experimental/algorithms/quantum_counting/test_quantum_counting.py b/test/unit_tests/braket/experimental/algorithms/quantum_counting/test_quantum_counting.py new file mode 100644 index 00000000..784b6131 --- /dev/null +++ b/test/unit_tests/braket/experimental/algorithms/quantum_counting/test_quantum_counting.py @@ -0,0 +1,380 @@ +# Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. + +from unittest.mock import MagicMock + +import numpy as np + +from braket.circuits import Circuit +from braket.devices import LocalSimulator +from braket.experimental.algorithms.quantum_counting import quantum_counting as qc + +# Oracle / circuit construction tests + + +def test_oracle_circuit_single_marked(): + """Oracle circuit should flip the phase of the marked state.""" + oracle = qc.build_oracle_circuit(2, [3]) + assert len(oracle.instructions) > 0 + assert oracle.qubit_count >= 2 + + +def test_oracle_circuit_multiple_marked(): + """Oracle with multiple marked states should compose individual oracles.""" + oracle = qc.build_oracle_circuit(2, [0, 2]) + oracle_single = qc.build_oracle_circuit(2, [0]) + assert len(oracle.instructions) > len(oracle_single.instructions) + + +def test_oracle_circuit_no_marked(): + """Oracle with no marked states should be an empty circuit.""" + oracle = qc.build_oracle_circuit(2, []) + assert len(oracle.instructions) == 0 + + +def test_grover_circuit_construction(): + """Grover circuit should combine oracle and diffusion.""" + grover = qc.build_grover_circuit(2, [1]) + assert len(grover.instructions) > 0 + assert grover.qubit_count >= 2 + + +def test_grover_circuit_unitarity(): + """Grover circuit unitary should be unitary.""" + grover = qc.build_grover_circuit(2, [1]) + u = grover.to_unitary() + product = u @ u.conj().T + np.testing.assert_array_almost_equal(product, np.eye(len(u)), decimal=10) + + +# Circuit construction tests + + +def test_quantum_counting_circuit_construction(): + """Circuit should have expected gate structure.""" + counting_qubits = [0, 1] + search_qubits = [2] + marked_states = [0] + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + assert len(circ.instructions) > 0 + assert len(circ.result_types) > 0 + + +def test_inverse_qft_for_counting_2_qubits(): + """Inverse QFT on 2 qubits should produce correct gate count.""" + qubits = [0, 1] + qft_circ = qc.inverse_qft_for_counting(qubits) + + # 2-qubit inverse QFT: 1 SWAP + 1 CPHASE + 2 H = 4 instructions + assert len(qft_circ.instructions) == 4 + + +# End-to-end counting tests + + +def _expected_M(n_counting: int, M_true: int, N: int) -> float: + """Compute the expected M estimate given finite counting-qubit precision. + + With n_counting qubits the QPE can only resolve phases in multiples of + 1/2^n_counting. This helper finds the closest representable phase to the + true Grover angle and returns the corresponding M estimate, which is what + an ideal (noiseless, infinite-shot) quantum counting run would produce. + """ + if M_true == 0: + return 0.0 + if M_true == N: + return float(N) + import math + + theta = math.asin(math.sqrt(M_true / N)) # Grover angle + exact_phase = theta / math.pi + # Closest representable phase with n_counting bits + y = round(exact_phase * (2**n_counting)) + approx_phase = y / (2**n_counting) + return N * (math.sin(math.pi * approx_phase) ** 2) + + +def test_count_1_of_4(): + """Quantum counting should estimate M ≈ 1 for 1 marked item out of 4. + + With 4 counting qubits and N=4, the discretization error gives + M_expected ≈ 1.235 (from the closest phase 3/16 to the exact 1/6). + We allow a tolerance of 0.5 around the discretized expected value. + """ + counting_qubits = [0, 1, 2, 3] + search_qubits = [4, 5] + marked_states = [3] + N = 2 ** len(search_qubits) + M_exp = _expected_M(len(counting_qubits), len(marked_states), N) + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + device = LocalSimulator() + task = device.run(circ, shots=1000) + + count_estimates = qc.get_quantum_counting_results( + task, counting_qubits, search_qubits, verbose=True + ) + + assert count_estimates["best_estimate"] is not None + assert count_estimates["search_space_size"] == N + assert abs(count_estimates["best_estimate"] - M_exp) < 0.5 + + +def test_count_0_of_4(): + """Quantum counting should estimate M ≈ 0 when no items are marked. + + With no marked items, the Grover operator is the identity (up to global + phase). QPE should measure phase 0, giving M = 0 exactly. + """ + counting_qubits = [0, 1, 2, 3] + search_qubits = [4, 5] + marked_states = [] + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + device = LocalSimulator() + task = device.run(circ, shots=1000) + + count_estimates = qc.get_quantum_counting_results( + task, counting_qubits, search_qubits, verbose=True + ) + + assert count_estimates["best_estimate"] is not None + assert abs(count_estimates["best_estimate"] - 0.0) < 0.01 + + +def test_count_4_of_4(): + """Quantum counting should estimate M ≈ 4 when all items are marked. + + With all items marked, the oracle flips every amplitude equally, so + QPE resolves the phase exactly and M = N. + """ + counting_qubits = [0, 1, 2, 3] + search_qubits = [4, 5] + marked_states = [0, 1, 2, 3] + N = 2 ** len(search_qubits) + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + device = LocalSimulator() + task = device.run(circ, shots=1000) + + count_estimates = qc.get_quantum_counting_results( + task, counting_qubits, search_qubits, verbose=True + ) + + assert count_estimates["best_estimate"] is not None + assert abs(count_estimates["best_estimate"] - float(N)) < 0.01 + + +def test_count_3_of_4(): + """Quantum counting should estimate M ≈ 3 for 3 marked items out of 4. + + Complements the 1-of-4 test by verifying a non-trivial fraction (3/4). + With 4 counting qubits, the discretization error is small enough that + the estimate should be within 0.5 of the expected value. + """ + counting_qubits = [0, 1, 2, 3] + search_qubits = [4, 5] + marked_states = [0, 1, 3] + N = 2 ** len(search_qubits) + M_exp = _expected_M(len(counting_qubits), len(marked_states), N) + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + device = LocalSimulator() + task = device.run(circ, shots=1000) + + count_estimates = qc.get_quantum_counting_results( + task, counting_qubits, search_qubits, verbose=True + ) + + assert count_estimates["best_estimate"] is not None + assert abs(count_estimates["best_estimate"] - M_exp) < 0.5 + + +def test_count_2_of_8(): + """Quantum counting should estimate M ≈ 2 for 2 marked items out of 8. + + With 5 counting qubits and N=8, the discretization error is small. + """ + counting_qubits = [0, 1, 2, 3, 4] + search_qubits = [5, 6, 7] + marked_states = [2, 5] + N = 2 ** len(search_qubits) + M_exp = _expected_M(len(counting_qubits), len(marked_states), N) + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + device = LocalSimulator() + task = device.run(circ, shots=2000) + + count_estimates = qc.get_quantum_counting_results( + task, counting_qubits, search_qubits, verbose=True + ) + + assert count_estimates["best_estimate"] is not None + assert count_estimates["search_space_size"] == N + assert abs(count_estimates["best_estimate"] - M_exp) < 0.5 + + +def test_count_with_marked_initial_state(): + """QPE should estimate M correctly when search register starts in a marked state. + + Instead of the standard uniform superposition |s⟩ = H^⊗n|0⟩^⊗n, we + prepare the search register directly in a marked state |β⟩ = |11⟩. + Since |β⟩ is a superposition of both Grover eigenstates (with equal + amplitude), QPE still resolves the correct phase and produces the same + M estimate as the standard |s⟩ initialization. + + This validates that the algorithm works for initial states within the + Grover eigenspace beyond just the uniform superposition. + """ + counting_qubits = [0, 1, 2, 3] + search_qubits = [4, 5] + marked_states = [3] # Mark |11⟩ + N = 2 ** len(search_qubits) + M_exp = _expected_M(len(counting_qubits), len(marked_states), N) + + # Build custom QPE circuit with marked-state initialization + circ = Circuit() + + # QPE: Hadamard on counting qubits + circ.h(counting_qubits) + + # Prepare |β⟩ = |11⟩ (marked state) instead of uniform superposition + circ.x(search_qubits) + + # Controlled-G^(2^k) using gate-level circuit + for ii, qubit in enumerate(reversed(counting_qubits)): + power = 2 ** ii + circ.add_circuit( + qc.controlled_grover_circuit(qubit, search_qubits, marked_states, power) + ) + + # Inverse QFT on counting qubits + circ.add_circuit(qc.inverse_qft_for_counting(counting_qubits)) + + # Measurement on counting register + circ.probability(counting_qubits) + + device = LocalSimulator() + task = device.run(circ, shots=1000) + + count_estimates = qc.get_quantum_counting_results( + task, counting_qubits, search_qubits, verbose=True + ) + + assert count_estimates["best_estimate"] is not None + assert abs(count_estimates["best_estimate"] - M_exp) < 0.5 + + +def test_oracle_invalid_state_raises(): + """Building oracle with out-of-range state should raise ValueError.""" + try: + qc.build_oracle_circuit(2, [4]) # 4 is out of range for 2 qubits (max=3) + assert False, "Should have raised ValueError" + except ValueError: + pass + + +def test_counting_estimates_have_correct_keys(): + """Counting estimates dict should contain all expected keys.""" + counting_qubits = [0, 1, 2] + search_qubits = [3] + marked_states = [0] + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + device = LocalSimulator() + task = device.run(circ, shots=100) + + count_estimates = qc.get_quantum_counting_results(task, counting_qubits, search_qubits) + + expected_keys = { + "measurement_counts", + "counting_register_results", + "phases", + "estimated_counts", + "best_estimate", + "search_space_size", + } + assert set(count_estimates.keys()) == expected_keys + + +def test_get_quantum_counting_results_empty_counts(): + """Test get_quantum_counting_results with empty measurement counts.""" + mock_task = MagicMock() + mock_result = MagicMock() + mock_result.measurement_counts = {} + mock_task.result.return_value = mock_result + + counting_qubits = [0, 1] + search_qubits = [2] + + count_estimates = qc.get_quantum_counting_results(mock_task, counting_qubits, search_qubits) + + assert count_estimates["measurement_counts"] == {} + assert count_estimates["counting_register_results"] == {} + assert count_estimates["phases"] == [] + assert count_estimates["estimated_counts"] == [] + assert count_estimates["best_estimate"] is None + + +def test_run_quantum_counting(): + """run_quantum_counting should run the circuit and return a task.""" + counting_qubits = [0, 1, 2] + search_qubits = [3] + marked_states = [0] + + circ = Circuit() + circ = qc.quantum_counting_circuit(circ, counting_qubits, search_qubits, marked_states) + + device = LocalSimulator() + task = qc.run_quantum_counting(circ, device, shots=100) + + count_estimates = qc.get_quantum_counting_results(task, counting_qubits, search_qubits) + + assert count_estimates["best_estimate"] is not None + assert count_estimates["search_space_size"] == 2 + + +def test_controlled_grover_circuit_construction(): + """Controlled Grover circuit should produce a non-empty circuit.""" + control = 0 + search_qubits = [1, 2] + marked_states = [1] + + circ = qc.controlled_grover_circuit(control, search_qubits, marked_states) + assert len(circ.instructions) > 0 + + +def test_controlled_grover_circuit_power(): + """Controlled Grover circuit with power > 1 should have more gates.""" + control = 0 + search_qubits = [1, 2] + marked_states = [1] + + circ_p1 = qc.controlled_grover_circuit(control, search_qubits, marked_states, power=1) + circ_p2 = qc.controlled_grover_circuit(control, search_qubits, marked_states, power=2) + assert len(circ_p2.instructions) > len(circ_p1.instructions)