From be7f2cc8f09b7d5b0b8af777cbaf1fcecc50c0d6 Mon Sep 17 00:00:00 2001 From: Chuan Zhang Date: Fri, 12 Jun 2026 12:52:49 -0700 Subject: [PATCH] feat: add Quantum Functions QAOA example (#58) --- README.md | 2 + examples/README.md | 30 ++++++ examples/quantum_function_qaoa.py | 149 ++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 examples/README.md create mode 100644 examples/quantum_function_qaoa.py diff --git a/README.md b/README.md index 992bbde..fc383f3 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,8 @@ probs = get_job_probabilities.sync(uuid=job.id, client=client) print(probs.additional_properties) ``` +See the [examples/](examples/README.md) directory for more advanced use cases, including a hybrid parameter optimization workflow using Quantum Functions. + Each generated endpoint module exposes four callables: `sync`, `sync_detailed`, `asyncio`, and `asyncio_detailed`. The `sync` and `asyncio` variants return the parsed body; the `_detailed` variants return a `Response[T]` with the status code, headers, and parsed body. For options (`api_key`, `base_url`, `max_retries`, `timeout`, `extension`), error classes, retry behavior, pagination, polling, sessions, and downstream-SDK extension hooks, see the [API reference](https://ionq.github.io/ionq-core-python/). diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f1f3acc --- /dev/null +++ b/examples/README.md @@ -0,0 +1,30 @@ +# IonQ Core Examples + +This directory contains usage examples for the `ionq-core` API. + +## Running the Examples + +1. **Install dependencies** + Ensure you have installed the `ionq-core` package. If running from the repository source: + ```sh + pip install -e . + ``` + You will also need `scipy` for the classical optimization example: + ```sh + pip install scipy + ``` + +2. **Set your API Key** + The IonQ client requires an API key to communicate with the cloud endpoints. Export it in your shell: + ```sh + export IONQ_API_KEY="your_api_key_here" + ``` + +3. **Run the script** + ```sh + python examples/quantum_function_qaoa.py + ``` + +## Example Index + +- **`quantum_function_qaoa.py`**: Demonstrates the Hosted Hybrid Service (Quantum Functions) by minimizing a Max Cut Hamiltonian energy via the free simulator. It uses a custom OpenQASM ansatz (1 layer QAOA) and a client-side `scipy.optimize` loop. diff --git a/examples/quantum_function_qaoa.py b/examples/quantum_function_qaoa.py new file mode 100644 index 0000000..cb7e93e --- /dev/null +++ b/examples/quantum_function_qaoa.py @@ -0,0 +1,149 @@ +# SPDX-FileCopyrightText: 2026 IonQ, Inc. +# SPDX-License-Identifier: Apache-2.0 + +""" +Quantum Function QAOA Example +============================= + +This example demonstrates how to use IonQ's Hosted Hybrid Service (Quantum Functions) +to solve a Max Cut problem using the Quantum Approximate Optimization Algorithm (QAOA). + +- Problem: Max Cut on a 3-vertex graph with edges (0, 1) and (1, 2) +- Graph (3 nodes, 2 edges): 0 -- 1 -- 2 +- Objective Function: max \sum_{i, j \in E} (x_i x_j - x_i - x_j) +- Hamiltonian: H_c = 0.5 * (Z_0 Z_1 + Z_1 Z_2) (Energy minimization ignoring constant terms) +- Ansatz: 1 layer QAOA with Pauli X mixer +- Optimizer: SLSQP with bounds via scipy.optimize +""" + +import math +import os +import time + +import scipy.optimize # type: ignore + +from ionq_core import IonQClient, wait_for_job +from ionq_core.api.default import create_job +from ionq_core.models import ( + Ansatz, + HamiltonianEnergyData, + HamiltonianEnergyInput, + HamiltonianEnergyInputData, + HamiltonianPauliTerm, + QuantumFunctionJobCreationPayload, +) + +# 1. Initialize the IonQ client +client = IonQClient() + + +# 2. Define the Max Cut problem Hamiltonian (Hc) +hamiltonian_terms = [ + HamiltonianPauliTerm(pauli_string="Z0 Z1", coefficient=0.5), + HamiltonianPauliTerm(pauli_string="Z1 Z2", coefficient=0.5), +] + + +# 3. Define the Ansatz with QASM 3.0 parameters +# We define `p0` (gamma) and `p1` (2*beta) as inputs. +qasm_str = """OPENQASM 3.0; +include "stdgates.inc"; + +input float[64] p0; +input float[64] p1; + +qubit[3] q; + +h q[0]; +h q[1]; +h q[2]; + +rzz(p0) q[0], q[1]; +rzz(p0) q[1], q[2]; + +rx(p1) q[0]; +rx(p1) q[1]; +rx(p1) q[2]; +""" + +ansatz = Ansatz(data=qasm_str) +energy_data = HamiltonianEnergyData(hamiltonian=hamiltonian_terms, ansatz=ansatz) +input_data = HamiltonianEnergyInputData(type_="hamiltonian-energy", data=energy_data) + + +# 4. Define the objective function (Energy evaluation) +def evaluate_energy(params: list[float]) -> float: + """ + Evaluates the energy of the QAOA ansatz for the given parameters. + + Args: + params: A list of two floats [gamma, beta] for QAOA p=1. + + Returns: + The expected energy value. + """ + gamma, beta = params + + # We pass the parameters natively through the Quantum Function's `params` field. + energy_input = HamiltonianEnergyInput( + data=input_data, + params=[gamma, 2.0 * beta], + ) + + payload = QuantumFunctionJobCreationPayload( + backend="simulator", # Using the free cloud simulator + type_="quantum-function", + input_=energy_input, + ) + + max_retries = 3 + for attempt in range(1, max_retries + 1): + try: + # Submit the job to the IonQ API + job = create_job.sync(client=client, body=payload) + if job is None or not hasattr(job, "id"): + raise RuntimeError(f"Job creation failed or returned malformed response: {job}") + + # Wait for completion and parse result + # `wait_for_job` will raise exceptions on timeouts or if the job transitions to 'failed' + completed = wait_for_job(client, job.id) + if completed.status == "canceled": + raise RuntimeError("Job was unexpectedly canceled by the backend.") + + # For Quantum Function jobs, the algorithmic result is returned in the flexible `output` block + energy = float(completed.output["energy"]) + + print(f"Iter: gamma={gamma:.4f}, beta={beta:.4f} | Energy: {energy:.4f}") + return energy + + except Exception as e: + print(f" [Warning] Attempt {attempt} failed: {e}") + if attempt == max_retries: + print(" [Error] Max retries reached. Aborting optimization.") + raise + + time.sleep(2) + + raise RuntimeError("Unreachable") + + +if __name__ == "__main__": + # Ensure API Key is available before starting + if not os.getenv("IONQ_API_KEY"): + raise RuntimeError("Please set the IONQ_API_KEY environment variable.") + + # Initial guess for [gamma, beta] + initial_params = [0.5, 0.5] + + # Use a classical optimizer to minimize the energy callback + # Note: OpenQASM 2.0 rx/rz gates expect angles in radians. + bounds = [(0.0, 2 * math.pi), (0.0, math.pi)] + + print(f"Starting optimization with initial parameters: {initial_params}, expecting minimum energy -2") + + result = scipy.optimize.minimize( + evaluate_energy, initial_params, method="SLSQP", bounds=bounds, options={"maxiter": 20} + ) + + print(f"Optimal Parameters (gamma, beta): {result.x}") + print(f"Minimum Energy Found: {result.fun}")