diff --git a/applications/optimization/adaptive_qaoa_lib.ipynb b/applications/optimization/adaptive_qaoa_lib.ipynb index ae41772b5..6b6272642 100644 --- a/applications/optimization/adaptive_qaoa_lib.ipynb +++ b/applications/optimization/adaptive_qaoa_lib.ipynb @@ -1316,7 +1316,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "adaptiveqaoa", "language": "python", "name": "python3" }, @@ -1330,7 +1330,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.4" + "version": "3.12.12" } }, "nbformat": 4, diff --git a/applications/optimization/robust_posture_optimization/posture_optimization.metadata.json b/applications/optimization/robust_posture_optimization/posture_optimization.metadata.json new file mode 100644 index 000000000..2d0b4c972 --- /dev/null +++ b/applications/optimization/robust_posture_optimization/posture_optimization.metadata.json @@ -0,0 +1,7 @@ +{ + "friendly_name": "Robust Posture Optimization", + "description": "Use Hybrid quantum algorithm for solving the inverse kinematic problem of Robotic arm", + "problem_domain_tags": ["optimization"], + "qmod_type": ["application"], + "level": ["demos"] +} diff --git a/applications/optimization/robust_posture_optimization/posture_optimization.qmod b/applications/optimization/robust_posture_optimization/posture_optimization.qmod new file mode 100644 index 000000000..cfbfac679 --- /dev/null +++ b/applications/optimization/robust_posture_optimization/posture_optimization.qmod @@ -0,0 +1,26 @@ +qfunc q0_rotate(theta: real[], q0: qbit) { + RZ(theta[2], q0); + RY(theta[1], q0); + RX(theta[0], q0); +} + +qfunc q1_rotate(theta: real[], q1: qbit) { + RZ(theta[5], q1); + RY(theta[4], q1); + RX(theta[3], q1); +} + +qfunc pauli_Y_measure(q: qbit[]) { + S(q[0]); + H(q[0]); + S(q[1]); + H(q[1]); +} + +qfunc main(theta: real[6], output q: qbit[2]) { + allocate(q); + q0_rotate(theta, q[0]); + q1_rotate(theta, q[1]); + q0_rotate(theta, q[1]); + pauli_Y_measure(q); +} diff --git a/algorithms/qaoa/grover_mixer_qaoa/gm_qaoa.synthesis_options.json b/applications/optimization/robust_posture_optimization/posture_optimization.synthesis_options.json similarity index 92% rename from algorithms/qaoa/grover_mixer_qaoa/gm_qaoa.synthesis_options.json rename to applications/optimization/robust_posture_optimization/posture_optimization.synthesis_options.json index d4d6192c9..dc38a58d6 100644 --- a/algorithms/qaoa/grover_mixer_qaoa/gm_qaoa.synthesis_options.json +++ b/applications/optimization/robust_posture_optimization/posture_optimization.synthesis_options.json @@ -8,26 +8,26 @@ "basis_gates": [ "h", "rx", - "u", - "s", - "cx", - "sx", "rz", - "cy", + "u1", + "r", + "z", + "cx", + "u2", + "cz", "t", "y", - "sdg", + "u", + "sxdg", "p", - "id", + "sdg", "tdg", - "u2", - "z", - "r", - "u1", - "sxdg", - "cz", + "cy", + "x", "ry", - "x" + "id", + "s", + "sx" ], "is_symmetric_connectivity": true }, @@ -36,7 +36,7 @@ "optimization_level": 1, "output_format": ["qasm"], "pretty_qasm": true, - "random_seed": 3858854528, + "random_seed": 181731280, "synthesize_all_separately": false, "timeout_seconds": 300, "transpilation_option": "auto optimize" diff --git a/applications/optimization/robust_posture_optimization/robust_posture_optimization.ipynb b/applications/optimization/robust_posture_optimization/robust_posture_optimization.ipynb new file mode 100644 index 000000000..44f3f845e --- /dev/null +++ b/applications/optimization/robust_posture_optimization/robust_posture_optimization.ipynb @@ -0,0 +1,450 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "# Quantum computation for robot posture optimization" + ] + }, + { + "cell_type": "markdown", + "id": "1", + "metadata": {}, + "source": [ + "* This tutorial proposes a novel quantum computing approach to solving the inverse kinematics problem of a robotic arm [[1](#QOA)]. The method performs forward kinematics—computing the end-effector position from given joint angles—using a quantum circuit, and then feeds the result into a **classical optimization algorithm** to find the joint angles that minimize the error with respect to a target position. By adopting this **hybrid framework** that combines quantum and classical computation, the aim is to efficiently solve inverse kinematics.\n", + "\n", + "* The algorithm is based on variational optimization methods similar to the Variational Quantum Eigensolver (VQE) and the Quantum Approximate Optimization Algorithm (QAOA). The quantum circuit performs the forward kinematics, while the classical optimizer—COBYLA (Constrained Optimization BY Linear Approximation)—evaluates the error and updates the parameters iteratively. This classical optimization loop provides flexibility to incorporate additional objective terms such as energy minimization or obstacle avoidance.\n", + "\n", + "* The proposed quantum circuit ansatz represents each robotic link using one qubit. The orientation of each link is encoded on the Bloch sphere using single-qubit rotation gates (RX, RY, RZ). The expectation values $(⟨X⟩, ⟨Y⟩, ⟨Z⟩)$ are multiplied by the corresponding link lengths to compute the end-effector position. Furthermore, an entangled circuit structure using RXX, RYY, and RZZ gates is introduced, capturing the parent–child link dependencies in orientation. This entanglement enhances both convergence speed and solution accuracy.\n", + "\n", + "This method is implemented on Qmod and validated through both simulation and real quantum hardware. The results demonstrate that introducing entanglement enables faster and more accurate inverse kinematics solutions compared to unentangled cases, thereby confirming the effectiveness of quantum computation in robotic applications.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "2", + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import scipy\n", + "from tqdm import tqdm\n", + "\n", + "from classiq import *" + ] + }, + { + "cell_type": "markdown", + "id": "3", + "metadata": {}, + "source": [ + "# Ansatz circuit without entanglement" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "4", + "metadata": {}, + "outputs": [], + "source": [ + "@qfunc\n", + "def q0_rotate(theta: CArray[CReal], q0: QBit):\n", + " RZ(theta[2], q0)\n", + " RY(theta[1], q0)\n", + " RX(theta[0], q0)\n", + "\n", + "\n", + "@qfunc\n", + "def q1_rotate(theta: CArray[CReal], q1: QBit):\n", + " RZ(theta[5], q1)\n", + " RY(theta[4], q1)\n", + " RX(theta[3], q1)\n", + "\n", + "\n", + "@qfunc\n", + "def pauli_Y_measure(q: QArray):\n", + " S(q[0])\n", + " H(q[0])\n", + " S(q[1])\n", + " H(q[1])\n", + "\n", + "\n", + "def main_project_measure(pauli_term):\n", + " @qfunc\n", + " def main(theta: CArray[CReal, 6], q: Output[QArray[QBit, 2]]):\n", + " allocate(q)\n", + " q0_rotate(theta, q[0])\n", + " q1_rotate(theta, q[1])\n", + " q0_rotate(theta, q[1])\n", + " if pauli_term == \"Z\":\n", + " pass\n", + " elif pauli_term == \"X\":\n", + " H(q[0])\n", + " H(q[1])\n", + " elif pauli_term == \"Y\":\n", + " pauli_Y_measure(q)\n", + "\n", + " return main" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "qmod = create_model(main_project_measure(\"Y\"))\n", + "write_qmod(qmod, \"posture_optimization\")\n", + "qprog = synthesize(qmod)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Quantum program link: https://platform.classiq.io/circuit/36pWVSeOsQDhSnq57rtTm14J45R\n" + ] + } + ], + "source": [ + "show(qprog)" + ] + }, + { + "cell_type": "markdown", + "id": "7", + "metadata": {}, + "source": [ + "## Post Processing" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "8", + "metadata": {}, + "outputs": [], + "source": [ + "NUM_SHOTS = 1000\n", + "MAX_ITERATIONS = 30\n", + "DOF = 6\n", + "# robot link\n", + "L1, L2 = 1.0, 1.0\n", + "target_pos = np.array([0.6, 1.0, 0.2])" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "9", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[0. 0.62831853 1.25663706 1.88495559 2.51327412 3.14159265]\n" + ] + } + ], + "source": [ + "initial_params = (np.linspace(0, 1, DOF)) * math.pi\n", + "print(initial_params)" + ] + }, + { + "cell_type": "markdown", + "id": "10", + "metadata": {}, + "source": [ + "## Create cost function" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "Target position $(X,Y,Z) = (0.6,1.0,0.2)$" + ] + }, + { + "cell_type": "markdown", + "id": "12", + "metadata": {}, + "source": [ + "### Pauli based measurment" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "13", + "metadata": {}, + "outputs": [], + "source": [ + "# X pauli measurment\n", + "qmod_X = create_model(main_project_measure(\"X\"))\n", + "qprog_X = synthesize(qmod_X)\n", + "# Y pauli measurment\n", + "qmod_Y = create_model(main_project_measure(\"Y\"))\n", + "qprog_Y = synthesize(qmod_Y)\n", + "# Z pauli measurment\n", + "qmod_Z = create_model(main_project_measure(\"Z\"))\n", + "qprog_Z = synthesize(qmod_Z)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "es_X = ExecutionSession(\n", + " qprog_X, execution_preferences=ExecutionPreferences(num_shots=NUM_SHOTS)\n", + ")\n", + "es_Y = ExecutionSession(\n", + " qprog_Y, execution_preferences=ExecutionPreferences(num_shots=NUM_SHOTS)\n", + ")\n", + "es_Z = ExecutionSession(\n", + " qprog_Z, execution_preferences=ExecutionPreferences(num_shots=NUM_SHOTS)\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "15", + "metadata": {}, + "outputs": [], + "source": [ + "def expected_valu(counts):\n", + " expect_value_qubit0 = (\n", + " (counts.get(\"00\", 0) + counts.get(\"01\", 0))\n", + " - (counts.get(\"10\", 0) + counts.get(\"11\", 0))\n", + " ) / NUM_SHOTS\n", + " expect_value_qubit1 = (\n", + " (counts.get(\"00\", 0) + counts.get(\"10\", 0))\n", + " - (counts.get(\"01\", 0) + counts.get(\"11\", 0))\n", + " ) / NUM_SHOTS\n", + " return expect_value_qubit0, expect_value_qubit1" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "cost_trace = []\n", + "cost_trace = []\n", + "\n", + "\n", + "def evaluate_params(es_X, es_Y, es_Z, params):\n", + " X_sample = es_X.sample(parameters={\"theta\": params.tolist()})\n", + " Y_sample = es_Y.sample(parameters={\"theta\": params.tolist()})\n", + " Z_sample = es_Z.sample(parameters={\"theta\": params.tolist()})\n", + " X0_expect, X1_expect = expected_valu(X_sample.counts)\n", + " Y0_expect, Y1_expect = expected_valu(Y_sample.counts)\n", + " Z0_expect, Z1_expect = expected_valu(Z_sample.counts)\n", + " # location of QOA\n", + " v0 = np.array([X0_expect, Y0_expect, Z0_expect])\n", + " v1 = np.array([X1_expect, Y1_expect, Z1_expect])\n", + " # final output\n", + " estimate_pos = L1 * v0 + L2 * v1\n", + " cost_estimation = np.sum((target_pos - estimate_pos) ** 2)\n", + " cost_trace.append(float(cost_estimation))\n", + " return cost_estimation" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "17", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Optimization Progress: 50%|████████████████████████████████████████████████████████████████████████████████████████████▌ | 15/30 [01:53<01:53, 7.54s/it]" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Optimized parameters: [0.5538562527520599, 0.5180382038302478, 2.6520717446034245, 1.6766637933338149, 3.93410941527406, 4.178340080781159]\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/plain": [ + "Text(0.5, 1.0, 'Cost convergence')" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAisAAAHHCAYAAAB+wBhMAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQAAUDZJREFUeJzt3Xl4U2X+NvD7ZO2atKU7FFooW9lURARkEZBtZFAcQccZWdxQcGQcdcQZFWZUHLcXdBgddRTHnwuCIu6MgIALIPu+L1Khe23TdEnT5Hn/SM9pQ1uapklO0t6f68rV9uQk59tjtLfPKgkhBIiIiIiClEbtAoiIiIguhmGFiIiIghrDChEREQU1hhUiIiIKagwrREREFNQYVoiIiCioMawQERFRUGNYISIioqDGsEJERERBjWGFiIiIghrDCpHKTp48ibvuugtdu3ZFWFgYTCYThg0bhqVLl6KystLn16uoqMDChQuxceNGn783EZE/6NQugKg9+/zzz3HjjTfCaDTi1ltvRd++fVFdXY3vvvsODz74IA4ePIhXX33Vp9esqKjAokWLAACjRo3y6XsTEfkDwwqRSk6fPo2bbroJXbp0wYYNG5CSkqI8N3fuXJw4cQKff/65ihW2feXl5YiMjFS7DCJqBruBiFTyzDPPwGq14j//+Y9bUJFlZmbivvvuU36uqanB3//+d3Tr1g1GoxHp6el45JFHYLPZ3F63Y8cOjB8/HvHx8QgPD0dGRgZmz54NADhz5gwSEhIAAIsWLYIkSZAkCQsXLrxorSUlJfjjH/+I9PR0GI1GdOrUCbfeeisKCwuVc/Lz83HbbbchKSkJYWFhGDBgAN566y239zlz5gwkScJzzz2HV199VfldBg0ahO3btyvnPffcc5AkCT/99FODWhYsWACDwYBffvlFObZt2zZMmDABZrMZERERGDlyJL7//nu31y1cuBCSJOHQoUP47W9/i9jYWFx11VUAAKfTiYULFyI1NRURERG4+uqrcejQIaSnp2PmzJkN7sX8+fORlpYGo9GIzMxM/OMf/4DT6Wzx7yk7cuQIpk2bhoSEBISHh6Nnz574y1/+4nbOuXPnMHv2bCQlJcFoNKJPnz544403mvpHRtSmsGWFSCWffvopunbtiqFDh3p0/u2334633noLv/nNb/CnP/0J27Ztw+LFi3H48GGsXr0agCswjBs3DgkJCXj44YcRExODM2fO4KOPPgIAJCQk4OWXX8bdd9+N66+/HlOnTgUA9O/fv8nrWq1WDB8+HIcPH8bs2bNx2WWXobCwEJ988gl+/vlnxMfHo7KyEqNGjcKJEycwb948ZGRkYOXKlZg5cyZKSkrcQhcAvPvuuygrK8Ndd90FSZLwzDPPYOrUqTh16hT0ej2mTZuGhx56CB988AEefPBBt9d+8MEHGDduHGJjYwEAGzZswMSJEzFw4EA8/vjj0Gg0ePPNNzF69Gh8++23uOKKK9xef+ONN6J79+546qmnIIQA4ApAzzzzDCZPnozx48dj7969GD9+PKqqqtxeW1FRgZEjR+LcuXO466670LlzZ/zwww9YsGABcnJysGTJkhb9ngCwb98+DB8+HHq9HnfeeSfS09Nx8uRJfPrpp3jyyScBAHl5ebjyyishSRLmzZuHhIQEfPnll7jttttgsVgwf/78i352iEKeIKKAKy0tFQDElClTPDp/z549AoC4/fbb3Y4/8MADAoDYsGGDEEKI1atXCwBi+/btTb5XQUGBACAef/xxj6792GOPCQDio48+avCc0+kUQgixZMkSAUD83//9n/JcdXW1GDJkiIiKihIWi0UIIcTp06cFANGhQwdRXFysnLtmzRoBQHz66afKsSFDhoiBAwe6Xe/HH38UAMR///tf5frdu3cX48ePV2oRQoiKigqRkZEhrrnmGuXY448/LgCIm2++2e09c3NzhU6nE9ddd53b8YULFwoAYsaMGcqxv//97yIyMlIcO3bM7dyHH35YaLVacfbs2Rb/niNGjBDR0dHip59+avTeCiHEbbfdJlJSUkRhYaHbOTfddJMwm82ioqJCELVl7AYiUoHFYgEAREdHe3T+F198AQC4//773Y7/6U9/AgBlbEtMTAwA4LPPPoPdbvdFqfjwww8xYMAAXH/99Q2ekyRJqS85ORk333yz8pxer8cf/vAHWK1WbNq0ye1106dPV1pGAGD48OEAgFOnTrmds3PnTpw8eVI5tmLFChiNRkyZMgUAsGfPHhw/fhy//e1vUVRUhMLCQhQWFqK8vBxjxozB5s2b3bpnAGDOnDluP69fvx41NTW455573I7fe++9DX7flStXYvjw4YiNjVWuVVhYiLFjx8LhcGDz5s0t+j0LCgqwefNmzJ49G507d3Z7rXxv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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "with tqdm(total=MAX_ITERATIONS, desc=\"Optimization Progress\", leave=True) as pbar:\n", + "\n", + " def progress_bar(xk: np.ndarray) -> None:\n", + " pbar.update(1) # increment progress bar\n", + "\n", + " final_params = scipy.optimize.minimize(\n", + " fun=lambda params: evaluate_params(es_X, es_Y, es_Z, params),\n", + " x0=initial_params,\n", + " method=\"COBYLA\",\n", + " options={\"maxiter\": MAX_ITERATIONS},\n", + " callback=progress_bar,\n", + " ).x.tolist()\n", + "\n", + "print(f\"Optimized parameters: {final_params}\")\n", + "plt.plot(cost_trace)\n", + "plt.xlabel(\"Iterations\")\n", + "plt.ylabel(\"Cost\")\n", + "plt.title(\"Cost convergence\")" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "es = ExecutionSession(qprog, execution_preferences=ExecutionPreferences(num_shots=1000))\n", + "res_qaoa = es.sample({\"theta\": final_params})\n", + "es.close()" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "## Define the function for extracting the predicted position from the quantum algorithm results" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "20", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.806, 1.216, 0.204])" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def estimate_arm_pos(params):\n", + " X_sample = es_X.sample(parameters={\"theta\": params.tolist()})\n", + " Y_sample = es_Y.sample(parameters={\"theta\": params.tolist()})\n", + " Z_sample = es_Z.sample(parameters={\"theta\": params.tolist()})\n", + " X0_expect, X1_expect = expected_valu(X_sample.counts)\n", + " Y0_expect, Y1_expect = expected_valu(Y_sample.counts)\n", + " Z0_expect, Z1_expect = expected_valu(Z_sample.counts)\n", + " v0 = np.array([X0_expect, Y0_expect, Z0_expect])\n", + " v1 = np.array([X1_expect, Y1_expect, Z1_expect])\n", + " estimate_pos = L1 * v0 + L2 * v1\n", + " return estimate_pos\n", + "\n", + "\n", + "estimate_arm_pos(np.array(final_params))" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "21", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "predict_arm_position : [0.79 1.202 0.142]\n", + "target_arm_position : [0.6 1. 0.2]\n" + ] + } + ], + "source": [ + "print(\"predict_arm_position :\", estimate_arm_pos(np.array(final_params)))\n", + "print(\"target_arm_position :\", target_pos)" + ] + }, + { + "cell_type": "markdown", + "id": "22", + "metadata": {}, + "source": [ + "## Reference\n", + "[1]: [T. Otani, A. Takanishi, N. Hara, Y.Takita and K.Kimura. \"Quantum computation for robot posture optimization.\" Scientific Reports volume 15, 28508 (2025).](https://www.nature.com/articles/s41598-025-12109-0)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.4" + } + }, + "nbformat": 4, + "nbformat_minor": 9 +} diff --git a/tests/notebooks/test_robust_posture_optimization.py b/tests/notebooks/test_robust_posture_optimization.py new file mode 100644 index 000000000..117afb7f2 --- /dev/null +++ b/tests/notebooks/test_robust_posture_optimization.py @@ -0,0 +1,20 @@ +from tests.utils_for_testbook import ( + validate_quantum_program_size, + validate_quantum_model, + wrap_testbook, +) +from testbook.client import TestbookNotebookClient + + +@wrap_testbook("robust_posture_optimization", timeout_seconds=300) +def test_notebook(tb: TestbookNotebookClient) -> None: + # test quantum programs + validate_quantum_program_size( + tb.ref_pydantic("qprog"), + expected_width=None, + expected_depth=None, + expected_cx_count=None, + ) + + # test notebook content + pass # Todo