{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# QLOQ (Qubit Logic on Qudits) – A Hands-On Tutorial\n", "\n", "Welcome to this tutorial, where we explore **QLOQ** (Qubit Logic on Qudits) for linear optical quantum computing. We will break down the core concepts behind encoding multiple qubits into a single photon (qudit), demonstrate how **intra-group** gates (like CNOT and single-qubit rotations) are implemented **without** the usual success-probability issues, and show how **Ralph CZ** gates can link multiple qudit blocks.\n", "\n", "## References\n", "\n", "[1] L. Lysaght, T. Goubault, P. Sinnott, S. Mansfield, P-E. Emeriau, \"Quantum circuit compression using qubit logic on qudits,\" https://arxiv.org/abs/2411.03878v1 (2024).\n", "\n", "\n", "## Contents\n", "\n", "1. [Introduction to QLOQ]\n", "2. [Qudits & CNOT in a Single Photon]\n", "3. [Applying Rotations in a Qudit Group]\n", "4. [Building QLOQ Circuits with Perceval]\n", "5. [Summary]\n", "\n", "\n", "\n", "\n", "## 1. Introduction to QLOQ\n", "\n", "**QLOQ** (Qubit Logic on Qudits) is a specialized architecture in linear optics that encodes multiple qubits **within a single photon**. Traditionally, a 2-qubit operation in linear optics involves two separate photons interfering at a beam splitter with a probabilistic success rate. However, **QLOQ** circumvents that for intra-group gates by confining both qubits to one photon’s modes.\n", "\n", "- **Inter-group** entangling gates (between different photons) still rely on a *Ralph CZ* gate, which is post-selected (probabilistic). Inter-group entangling gates are accomplished via an unbalanced Ralph CZ to accomplish a multi-controlled Z operation. A balanced Ralph CZ has the same success probability(1/9) for each input state. An **unbalanced** version has different success probability for each input. It was chosen because it performs empirically better than the standard CCCZ and requires less post-selected modes.\n", "- **Intra-group** gates (like CNOT, CZ, single-qubit rotations) become deterministic mode permutations and transformations." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. Qudits & CNOT in a Single Photon\n", "\n", "### Qudits\n", "A **qubit** is a 2-level system $|0\\rangle$ or $|1\\rangle$. A **qudit** extends this to $d$ levels. For a **2-qubit** block, $d = 4$. We treat each logical basis state as a unique optical mode:\n", "\n", "$|00\\rangle \\rightarrow$ mode 0 \n", "$|01\\rangle \\rightarrow$ mode 1 \n", "$|10\\rangle \\rightarrow$ mode 2 \n", "$|11\\rangle \\rightarrow$ mode 3 \n", "\n", "A single photon occupying exactly one of these four modes represents any superposition of the 2-qubit space.\n", "\n", "### CNOT Within a Qudit\n", "In standard linear optics, a **CNOT** between two separate photons is probabilistic. In QLOQ, if both qubits are in the *same* photon, a CNOT is merely a **mode permutation**:\n", "\n", "- $|10\\rangle \\rightarrow |11\\rangle$\n", "- $|00\\rangle$ and $|01\\rangle$ remain the same\n", "\n", "The complete mapping is:\n", "\n", "Mode Index | Binary State | CNOT Output\n", "-----------|--------------------|-------------\n", "0 | $\\lvert 00\\rangle$ | $\\lvert 00\\rangle$\n", "1 | $\\lvert 01\\rangle$ | $\\lvert 01\\rangle$\n", "2 | $\\lvert 10\\rangle$ | $\\lvert 11\\rangle$\n", "3 | $\\lvert 11\\rangle$ | $\\lvert 10\\rangle$\n", "\n", "This operation is **deterministic** because it's implemented entirely within the single photon's modes, bypassing the usual success probability constraints.\n", "\n", "> **CZ** is similarly done by a mode permutation + Hadamards on the target qubit." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. Applying Rotations in a Qudit Group\n", "When **multiple qubits** are encoded into a **single photon** (a qudit), each logical qubit corresponds to a specific **pairing of modes**. For a **2-qubit** group (4 modes total):\n", "\n", "- **Modes**: \n", " $0 \\rightarrow |00\\rangle$ \n", " $1 \\rightarrow |01\\rangle$ \n", " $2 \\rightarrow |10\\rangle$ \n", " $3 \\rightarrow |11\\rangle$\n", "\n", "- **Second qubit** flips between $|0\\rangle$ and $|1\\rangle$ in the *rightmost bit*, so to rotate it, we **pair**:\n", " - $(0,1) \\rightarrow |00\\rangle, |01\\rangle$\n", " - $(2,3) \\rightarrow |10\\rangle, |11\\rangle$\n", "\n", "- **First qubit** flips in the *leftmost bit*, so to rotate it, we **pair**:\n", " - $(0,2) \\rightarrow |00\\rangle, |10\\rangle$\n", " - $(1,3) \\rightarrow |01\\rangle, |11\\rangle$\n", "\n", "- So in practice we would apply a 2 mode parametrized beamsplitter for the specific rotation we want to achieve ($R_x$, $R_y$, $R_z$ etc) for each combination corresponding to the qubit we wish to act on.\n", "\n", "- Thankfully all this logic and all relevant swaps have been pre-coded into the ansatz builder so the user need not worry too much about them.\n", "\n", "> **Key Insight**: **Intra-group operations** are layerwise, meaning you stack them: first apply a rotation on the second qubit via parametrized beamsplitters on the correct pairs of modes, then do some internal mode swap, then apply a rotation on the first qubit by doing similarly, etc." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Building QLOQ Circuits with Perceval\n", "\n", "Perceval provides a `QLOQ ansatz` that helps you define:\n", "\n", "1. **Group Sizes**: e.g., `[Encoding.QUDIT2, Encoding.QUDIT2]`\n", " - can do Encoding.DUAL_RAIL, Encoding.QUDIT3 etc\n", "2. **Layers**: e.g., `[\"Y\"]` for Ry rotations\n", "3. **Phases**: the numerical angles for each rotation (or `None` for symbolic)\n", "4. **Entangling Gate** (`ctype`): either `\"cx\"` or `\"cz\"` inside each group\n", "\n", "Below is a minimal code snippet showing how to construct a QLOQ circuit (as a `Processor`) and display it." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "from perceval import LogicalState, pdisplay, catalog, Encoding\n", "import perceval as pcvl\n", "import numpy as np\n", "from scipy.optimize import minimize\n", "from typing import List, Dict, Tuple, Optional\n", "import matplotlib.pyplot as plt" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of required phases: 16\n" ] }, { "data": { "image/svg+xml": [ "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "CPLX\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "POSTPROCESSEDCZ\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "CZ2\n", "\n", "\n", "\n", "\n", "\n", "RYQUDIT2\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "[Group 0]\n", "\n", "[Group 1]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "\n", "[Group 0]\n", "\n", "[Group 1]\n", "\n", "[herald0]\n", "0\n", "\n", "[herald1]\n", "0\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "0\n", "1\n", "2\n", "3\n", "4\n", "5\n", "6\n", "7\n", "" ], "text/plain": [ "" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "# Example: Building a QLOQ circuit in Perceval\n", "# 1) Get the QLOQ ansatz from Perceval's catalog\n", "ansatz = catalog[\"qloq ansatz\"]\n", "\n", "# 2) Define groups of qubits (each group is a qudit)\n", "# For demonstration: one 2-qubit group (QUDIT2) + another 2 qubit group\n", "group_sizes = [Encoding.QUDIT2, Encoding.QUDIT2]\n", "\n", "# 3) Choose the single-qubit rotation layers we want (Y, X, or Z)\n", "layers = [\"Y\"] # e.g., apply RY rotations in each group\n", "#generally RY rotations are sufficient\n", "\n", "# 4) Compute how many parameter phases are needed\n", "nb_phases = ansatz.get_parameter_nb(group_sizes, len(layers))\n", "print(\"Number of required phases:\", nb_phases)\n", "\n", "# 5) (Optional) Provide numeric phases or use None for symbolic placeholders\n", "phases = None # Use symbolic parameters for visualization\n", "\n", "# 6) Build the QLOQ processor with 'ctype=\"cx\"'\n", "circ = ansatz.build_processor(\n", " group_sizes=group_sizes,\n", " layers=layers,\n", " phases=phases,\n", " ctype=\"cz\"\n", ")\n", "\n", "# 7) Display the resulting circuit\n", "pdisplay(circ, recursive=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The ansatz in the qubit picture will then have this form, with layers of 2 qubit blocks linked by a multi-controlled Z gate which takes the form of an unbalanced CCCZ here using the ralph CZ." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "![qloqansatz.png](../_static/img/qloqansatz.png)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. Summary\n", "\n", "**QLOQ** offers a unique way to encode **multiple qubits** in **one photon** (creating qudits). The major advantage is that **CNOTs and single-qubit rotations** within that qudit can be executed deterministically without the usual success probability of two-photon gates. When linking multiple qudit blocks, **Ralph CZ** gates are used, which are **probabilistic** and post-selected.\n", "\n", "### Key Points\n", "- **Qudits**: 2 qubits = 4 modes in a single photon, 3 qubits = 8 modes, etc.\n", "- **Intra-group** gates (e.g., CNOT, Ry, Rz, Rx → Mode permutations and layers of beamsplitters.\n", "- **Inter-group** gates → Ralph CZ (post-selected) forming an unbalanced multi-controlled Z.\n", "- **Layerwise approach**: We apply rotations/cnot gates in a sequence of “layers,” possibly swapping modes to target the correct qubit.\n", "\n", "In upcoming sections, we will:\n", "- **Integrate** a classical optimizer (e.g., COBYLA) to do VQE-like or QAOA-like tasks on these QLOQ circuits.\n", "- Explore **QUBO** matrices and measure the resulting cost function from the photonic simulator." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Encoding Functions Explained\n", "\n", "These two functions, `to_fock_state` and `fock_to_qubit_state`, convert between:\n", "\n", "1. A **bitstring representation** of qubits (e.g., `\"0101\"`)\n", "2. A **Fock-state representation** (a list of occupation numbers, eventually wrapped in a `pcvl.BasicState`)\n", "\n", "### Key Idea\n", "- **Bitstring**: Represents qubits in the usual binary sense, e.g. `\"00\"` or `\"01\"`.\n", "- **Fock State**: In Perceval, a `BasicState` is a list of photon occupation numbers for each mode. A single photon occupying one of $2^n$ possible modes (for an $n$-qubit group) is stored as a one-hot vector (e.g., `[0,1,0,0]` for mode 1 out of 4).\n", "\n", "These functions handle situations where **multiple groups** of qubits are each encoded in a **qudit**. For example:\n", "\n", "- A group of **size=2** means 4 modes\n", "- A group of **size=3** means 8 modes, etc." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "def fock_to_qubit_state(fock_state: List[int], group_sizes: List[int]) -> Optional[str]:\n", " \"\"\"\n", " Convert a Perceval Fock-state representation back to a multi-qubit bitstring.\n", "\n", " Args:\n", " fock_state: a one-hot vector for all groups (concatenated)\n", " group_sizes: each integer indicates how many qubits are in that group\n", "\n", " Returns:\n", " A bitstring (e.g. \"0101\"), or None if the Fock state is invalid.\n", " \"\"\"\n", "\n", " fock_state = [i for i in fock_state]\n", "\n", " # Expected total length = sum of (2^group_size) for each group\n", " expected_length = sum([2 ** size for size in group_sizes])\n", " if len(fock_state) != expected_length:\n", " return None\n", "\n", " offset = 0\n", " qubit_state_binary = \"\"\n", "\n", " for size in group_sizes:\n", " group_length = 2 ** size\n", " group_fock_state = fock_state[offset : offset + group_length]\n", "\n", " # We expect exactly one '1' in the chunk (indicating the photon mode)\n", " if group_fock_state.count(1) != 1:\n", " return None\n", "\n", " state_index = group_fock_state.index(1)\n", " # Convert index to binary (of width 'size' bits)\n", " binary_state = format(state_index, f'0{size}b')\n", " qubit_state_binary += binary_state\n", "\n", " offset += group_length\n", "\n", " return qubit_state_binary" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### How `fock_to_qubit_state` Works\n", "\n", "1. **Check** the total length needed: sum of $(2^{\\text{group\\_size}})$\n", "2. **Slice** each group's chunk out of the big `fock_state`\n", "3. Within that chunk, ensure exactly **one** entry is `1`\n", "4. The **index** of that `1` is the integer representation of the bits for that group\n", " - Convert that index to a binary string of width `n`\n", "5. Append all these binary substrings together, forming the **full** qubit bitstring\n", "\n", "#### Example \n", "- `fock_state = [1,0,0,0, 0,0,0,1]` (length=8)\n", "- `group_sizes = [2,2]`\n", "\n", "**Step by Step**:\n", "\n", "- Group A chunk: `[1,0,0,0]` → exactly one '1' at index=0 → binary of `0` with width=2 → `\"00\"`\n", "- Group B chunk: `[0,0,0,1]` → index=3 → binary= `\"11\"`\n", "\n", "Concatenate = `\"00\" + \"11\"` = `\"0011\"`\n", "\n", "## Edge Cases\n", "\n", "1. **Invalid Fock State**\n", " If a chunk has more than one `1` or none at all, `fock_to_qubit_state` returns `None`.\n", " This ensures we only accept states with exactly **one** photon per group.\n", " Thus, we have post-selected\n", "\n", "2. **Bitstring Offsets**\n", " The variable `offset` keeps track of how many bits we've already consumed from `qubit_state`.\n", " This ensures we map each portion of the bitstring to its corresponding qudit group.\n", "\n", "---\n", "\n", "By using the helper function:\n", "\n", "- `fock_to_qubit_state`: you can interpret the circuit's **measurement results** (a one-hot outcome) back into a **bitstring** for classical post-processing or optimization" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## QUBO Example with a Simple QLOQ ansatz Circuit (CVaR-VQE Approach)\n", "\n", "In this section of the notebook, we demonstrate how to tackle a **QUBO** (Quadratic Unconstrained Binary Optimization) problem using a **CVar-VQE** style approach in Perceval. This can also be verified with the other more photonic QUBO approach which uses the same QUBO matrix. We’ll show:\n", "\n", "1. **Building a simple QLOQ circuit** (e.g., with Ry layers) to produce a variational ansatz.\n", "2. **Sampling** from the circuit to get a distribution of bitstrings.\n", "3. **Computing CVaR** to measure the cost (objective function) and performing classical optimization (COBYLA).\n", "4. **Identifying the best bitstring** based on the final solution.\n", "5. **Optional**: Plotting the final probability distribution as a histogram for visual insight." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## What is CVaR-VQE?\n", "\n", "**CVaR-VQE** (Conditional Value-at-Risk Variational Quantum Eigensolver) is a hybrid quantum-classical optimization technique that **goes beyond** the usual average-cost minimization seen in standard VQE:\n", "\n", "1. **VQE Recap**\n", " - In a typical VQE, you prepare a **parametrized quantum circuit** (ansatz).\n", " - You **sample** from it to estimate the **average** energy (or cost).\n", " - A **classical optimizer** tunes the circuit parameters to **minimize** this average cost.\n", "\n", "2. **Why CVaR?**\n", " - In many problems, you don’t just care about the **average** cost. You also want to avoid **worst-case** outcomes.\n", " - **CVaR** (Conditional Value-at-Risk) focuses on the **worst $\\alpha$-fraction** of possible outcomes in your distribution.\n", " - Practically, we *sort* outcomes by cost and *average* the top $\\alpha$ portion (highest costs). If $\\alpha = 0.5$, that means we look at the top 50% of the distribution by cost. By **minimizing** that portion, we make sure the algorithm consistently avoids very high-cost states.\n", "\n", "3. **Combining CVaR with VQE**\n", " - We still build a **variational circuit** and measure its outputs, but instead of updating parameters to reduce the simple average cost, we **focus on the worst tail**.\n", " - This ensures the final circuit is **less likely** to produce very bad solutions.\n", "\n", "In short, **CVaR-VQE** aims to push the distribution of measured bitstrings toward reliably low-cost outcomes, rather than just optimizing the mean. This can be extremely useful for **QUBO** (Quadratic Unconstrained Binary Optimization) problems, where you want to avoid sampling high-cost bitstrings even occasionally.\n", "\n" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def compute_cvar(probabilities: List[float], values: List[float], alpha: float) -> float:\n", " \"\"\"\n", " Compute the Conditional Value at Risk (CVaR).\n", " Given a list of probabilities and corresponding values (costs),\n", " we take the worst alpha portion of outcomes and average them\n", " weighted by their probabilities.\n", " \"\"\"\n", " sorted_indices = np.argsort(values) # sort by ascending value\n", " probs = np.array(probabilities)[sorted_indices]\n", " vals = np.array(values)[sorted_indices]\n", " cvar = 0\n", " total_prob = 0\n", "\n", " for p, v in zip(probs, vals):\n", " if p >= alpha - total_prob:\n", " p = alpha - total_prob\n", " total_prob += p\n", " cvar += p * v\n", "\n", " return cvar / total_prob\n", "\n", "def expectation_value(vec_state: np.ndarray, qubo_matrix: np.ndarray) -> float:\n", " \"\"\"\n", " Compute the expectation value for a given state with respect to a QUBO matrix.\n", " Here, vec_state is a binary vector (e.g., [0,1,0,1]) converted to float,\n", " and qubo_matrix is the NxN matrix of the QUBO.\n", " \"\"\"\n", " return np.dot(vec_state.conjugate(), np.dot(qubo_matrix, vec_state))\n", "\n", "def extract_probability_distribution(job_results: Dict, group_sizes: List[int]) -> Tuple[Dict[str, float], int]:\n", " \"\"\"\n", " Extract probability distribution from sampling results.\n", " Returns:\n", " output_dict = {bitstring: probability}\n", " sum_valid_outputs = sum of all valid counts (used for normalization)\n", " \"\"\"\n", " output_dict = {}\n", " sum_valid_outputs = 0\n", "\n", " # First pass: count how many valid outputs (bitstrings)\n", " for res in job_results['results']:\n", " qb_state = fock_to_qubit_state(res, group_sizes)\n", " if qb_state:\n", " sum_valid_outputs += job_results['results'][res]\n", " output_dict[qb_state] = job_results['results'][res]\n", " \n", " divisor = sum_valid_outputs\n", " output_dict = {k: v/divisor for k, v in output_dict.items()}\n", " #compute probabilities by dividing by sum\n", "\n", " return output_dict" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "def build_circuit(phases: List[float], group_sizes: List[int], layers: List[str]) -> pcvl.Circuit:\n", " \"\"\"\n", " Build the quantum circuit (QLOQ ansatz) with specified phases.\n", " \n", " \"\"\"\n", " ansatz = catalog[\"qloq ansatz\"]\n", " group_sizes_p = [Encoding.DUAL_RAIL if x == 1 else eval(f\"Encoding.QUDIT{x}\") for x in group_sizes]\n", " proc = ansatz.build_processor(\n", " group_sizes=group_sizes_p,\n", " layers=layers,\n", " phases=phases,\n", " ctype=\"cz\" #can be cx too\n", " )\n", " return proc" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "def create_objective_function(qubo_matrix: np.ndarray,\n", " input_state: str,\n", " group_sizes: List[int],\n", " layers: List[str],\n", " sampling_size: int,\n", " alpha: float,\n", " verbose):\n", " \"\"\"\n", " Create the CVaR-VQE objective function for optimization.\n", " - qubo_matrix: the QUBO cost matrix\n", " - input_state: initial computational basis string (e.g. \"000000\")\n", " - group_sizes: list of integers, each representing # of qubits in that group\n", " - layers: e.g. [\"Y\"] or [\"Y\",\"X\"]\n", " - sampling_size: how many shots to gather per evaluation\n", " - alpha: the fraction for CVaR computation\n", "\n", " Returns:\n", " objective_function (callable): to be passed into an optimizer\n", " best_result (list reference): to track the best (lowest) loss + best bitstring\n", " \"\"\"\n", " best_result = [None] # store (loss, bitstring)\n", " iteration = [0] # track iteration count\n", "\n", " def objective_function(phases: np.ndarray) -> float:\n", " # Build circuit (processor)\n", " circ = build_circuit(phases.tolist(), group_sizes, layers)\n", " circ.with_input(LogicalState(input_state))\n", " \n", " # Sample from the circuit\n", " sampler = pcvl.algorithm.Sampler(circ, max_shots_per_call=sampling_size)\n", " job_results = sampler.sample_count(sampling_size)\n", "\n", " # Extract distribution\n", " output_dict = extract_probability_distribution(job_results, group_sizes)\n", " if not output_dict:\n", " return float('inf') # if no valid results, large penalty\n", "\n", " # Convert bitstrings to vectors => compute QUBO cost => gather CVaR\n", " probabilities = list(output_dict.values())\n", " values = [expectation_value(np.array(list(state)).astype(int), qubo_matrix)\n", " for state in output_dict.keys()]\n", "\n", " loss = compute_cvar(probabilities, values, alpha)\n", "\n", " # Track the best result\n", " bitstring = max(output_dict, key=output_dict.get)\n", " if best_result[0] is None or loss < best_result[0][0]:\n", " best_result[0] = (loss, bitstring)\n", "\n", " iteration[0] += 1\n", " if verbose == True:\n", " print(f\"Iteration {iteration[0]}: Loss = {loss}, Best bitstring = {bitstring}\")\n", "\n", " return loss\n", "\n", " return objective_function, best_result" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "def optimize_qubo(qubo_matrix: np.ndarray,\n", " input_state: str,\n", " group_sizes: List[int],\n", " layers: List[str],\n", " sampling_size: int = 100000,\n", " alpha: float = 0.5,\n", " maxiter: int = 50,\n", " verbose=False) -> Tuple[float, str, np.ndarray]:\n", " \"\"\"\n", " Run the optimization using COBYLA to find phases that minimize CVaR for the given QUBO problem.\n", " - qubo_matrix: your QUBO cost matrix\n", " - input_state: initial bitstring\n", " - group_sizes: e.g. [2,2,2]\n", " - layers: e.g. [\"Y\"]\n", " - sampling_size: how many shots per circuit evaluation\n", " - alpha: fraction for CVaR\n", " - maxiter: max COBYLA iterations\n", "\n", " Returns:\n", " final_loss (float): best CVaR found\n", " best_bitstring (str): best measured bitstring\n", " optimal_phases (np.ndarray): the phase vector that achieved the best result\n", " \"\"\"\n", " ansatz = catalog[\"qloq ansatz\"]\n", " \n", " group_sizes_p = [Encoding.DUAL_RAIL if x == 1 else eval(f\"Encoding.QUDIT{x}\") for x in group_sizes]\n", " nb_phases = ansatz.get_parameter_nb(group_sizes_p, len(layers))\n", "\n", " # Random initial guess for the phases\n", " initial_phases = np.random.uniform(0, 2*np.pi, nb_phases)\n", "\n", " # Build the objective function\n", " objective_function, best_result = create_objective_function(\n", " qubo_matrix, input_state, group_sizes, layers, sampling_size, alpha, verbose=verbose)\n", "\n", " # Minimize\n", " result = minimize(\n", " objective_function,\n", " initial_phases,\n", " method='cobyla',\n", " options={\n", " 'maxiter': maxiter,\n", " }\n", " )\n", "\n", " best_loss = result.fun\n", " best_bitstring = best_result[0][1]\n", " return best_loss, best_bitstring, result.x" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [], "source": [ "def plot_bitstring_distribution(prob_dict: Dict[str, float], top_n: int = 10, title: str = \"Top Bitstring Probabilities\"):\n", " \"\"\"\n", " Plot a histogram of the top N most probable bitstrings.\n", "\n", " Args:\n", " prob_dict: e.g. {\"000000\": 0.25, \"100100\": 0.15, ...}\n", " top_n: how many top states to display\n", " title: plot title\n", " \"\"\"\n", " # Convert dict to list of (bitstring, probability) pairs\n", " items = list(prob_dict.items())\n", "\n", " # Sort descending by probability\n", " items.sort(key=lambda x: x[1], reverse=True)\n", "\n", " # Take the top 'top_n' states\n", " items = items[:top_n]\n", "\n", " # Unzip into two lists\n", " bitstrings, probs = zip(*items)\n", "\n", " plt.figure(figsize=(8, 4))\n", " plt.bar(bitstrings, probs, color='darkviolet')\n", " plt.ylabel(\"Probability\")\n", " plt.xlabel(\"Bitstring\")\n", " plt.title(title)\n", " plt.xticks(rotation=45, ha='right')\n", " plt.show()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Putting It All Together\n", "\n", "Below is a complete **example** usage, showing how to:\n", "\n", "1. Define a sample QUBO matrix (6-qubit problem).\n", "2. Optimize using Ry layers in a QLOQ circuit.\n", "3. Print the final result and best bitstring.\n", "4. Optionally, sample the final circuit once more to plot the output distribution." ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "=== Final Optimization Results ===\n", "Final CVaR Loss: -27.1173748\n", "Best Bitstring: 010001\n", "Optimal Phases: [1.79428709 6.42920804 6.50974881 1.33076923 6.54604002 4.42032268\n", " 2.76595847 1.05846287 6.5138662 4.25633883 4.4176755 4.25184957\n", " 1.33692428 3.82872455 5.19343458 3.11518911 5.0788073 3.33595093\n", " 3.58811962 7.09218881 2.64311878 4.98881303 4.4733493 4.57674368]\n" ] }, { "data": { "image/png": "iVBORw0KGgoAAAANSUhEUgAAArwAAAGqCAYAAADp3ZdYAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAS/1JREFUeJzt3Qm8TfX+//HPMc9DZEyGEDJGpEiDollJuIroaqKU6KZbKHVJKiWl2zwpDbd5UCmNUoYSRZFCmWWeWf/H+/t7rP3fezvnOI7j7LPWej0fj83Za++zz/rutfZan/1dn+/nm+Z5nmcAAABASOVL9QoAAAAAhxIBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8QcL///rulpaXZ008/fUj/To0aNeyyyy4L7PqL/ob+lv5mfLvOOeccyw1Tp051f1//52WbN2+2f/7zn1apUiW3vtdff71FyfDhw127c5I+O9rXcluq/i6Q1xDwAnmcH6Sld7v55pstr4lfvwIFCthhhx1mzZs3twEDBthPP/2UY3/n4YcfzpUgOWzrJi1btnTb55FHHkn38f/85z9u/a+++mp77rnn7NJLL7Wvv/7aBYLr16/P9fX96quv7IILLrCKFSta4cKFXQB35ZVX2pIlS7L9mlu3bnXtyetfPrLir7/+cm35/vvvU70qQJ6V5nmel+qVAJAxBR69e/e2O+64w2rWrJnwWMOGDa1Jkya2Y8cOK1iwoOXPn/+QrYeCjJNPPnm/gZwCqdNPP9169uxpOrxs2LDBfvjhB3vllVdsy5Ytdvfdd9vAgQNjz9dzsrP+anv58uUPKGDZs2eP7dq1ywVNfg+e2qXXeuedd7L8Otldt71799rOnTutUKFCli9favobfv31V6tbt65rd9WqVe3LL7/c5znHH3+8+7IS/9iYMWNs8ODBtnjx4lztMRw3bpz7slSrVi3XW1m5cmX7+eef7fHHH3ePv/fee3bCCScc8OuuWbPGDj/8cBs2bJgLFuPt3r3b3YoUKZJj7dB+p+2vfS+nzZgxw4477jh76qmn9rkKcyj/LhAkBVK9AgCy5swzz7QWLVqk+1hOnphzggKqSy65JGHZqFGj7Nxzz7Ubb7zR6tWrZ2eddZZbrsDzUK+/Au3ixYu7gPpQfinYHwW5qd5Wzz//vFWoUMHuvfdeu+iii1x6R3IAu2rVKmvQoEGurI96WosVK5Zhz67SKdq0aWMffPBBwvPU+3ziiSe6NsybN8/Kli2bY+ukYF+3nKQvdKmQqr8L5Dnq4QWQdz311FO6CuN999136T6+ePFi97ie5+vVq5dXvHhxb9myZd7555/vfi5fvrx34403ert37074/Xvuucdr3bq1d9hhh3lFihTxjj32WO+VV17Z5+9Ur17dve7+aF369euX7mN//PGHV6BAAe+EE07IdP2XL1/uXXbZZV7VqlW9QoUKeZUqVfLOO+8891x/XfQ78bd27dolvF9Tp071rr76au/www/3ypQpk/CY/zr+a5199tne5MmTvSZNmniFCxf26tev77322msJ6z5s2DD3u8mSXzOzdfv000/dff0f7+WXX3bvu97/cuXKeT169HDbLt6BbNPM1K5d27vmmmu8HTt2uPflrrvuij3mr1/yTX87veXx7+Nzzz0Xa0PZsmW9rl27ekuWLEn423ofjjnmGG/GjBle27ZtvaJFi3oDBgzIcF07dOjg5c+f3/vtt9/SffyZZ55x6zFy5Mh93qdFixZ5Z5xxhlesWDGvcuXK3u233+7t3bs3YZ9LvmkbZ7St/f1a20r7h9p5/PHHe3PmzHGPT5gwwTvqqKPc/qN2xr83/npp34h/L9Jbh/jPwtq1a932bdiwoWtTyZIlvY4dO3rff//9freZ/xrJf1c2b97sDRw40DviiCPc56tu3bruOOC/P8ltfv31191203MbNGjgvf/++xluMyCvoocXCAilBugybDxdNs/s8n2HDh2sVatW7nL0xx9/7Hr1jjrqKNc75nvggQfsvPPOsx49erjL7S+99JJ16dLFXeI/++yzc7QNRx55pLVr184+/fRT27hxo5UqVSrd53Xu3Nn12l177bWu91E9jh999JHL2dT9sWPHusdKlChh//73v93vKL8z3jXXXOMuWQ8dOtT18O7vMn/Xrl3tqquusl69erlLw3oP1Kuo9IwDkZV1Sy9lRZekR44caStXrnTbRL2bs2fPtjJlyhzwNs3I9OnTbeHCha59Squ48MIL7YUXXrBbbrnFPV6/fn2Xs3vDDTfYEUcc4XrjpVGjRm7fePHFF+3++++P7Xd6f+Wuu+6y2267zS6++GI32G316tUuFeGkk07apw1r1651Vyu6devmrgJk9N6o53fKlCnWtm3bfVJ5fNpmV1xxhdtX4/PZ9T517NjRpWaMHj3abUelLihNQalBWm/lL+s9U26w3gdp3Lhxpu/fF198YW+99Zb169fP3df20oDHm266yeVta5/7+++/3d/s06ePffLJJxm+lvYNvVfJve+TJ092PfDy22+/2RtvvOH2Rb0H2jceffRR9xlSPnyVKlXcNlObtJ/rvdD7JRmleSiO1eddn8HLL7/cmjZt6v6m0lX+/PNPt33jKa3lf//7n2tbyZIl7cEHH3SfT30Wy5Url+n7BeQpqY64AWTO70FM75ZZD6+W3XHHHQmv1axZM6958+YJy7Zu3Zpwf+fOna5H6dRTT83xHl5Rj56e88MPP6S7/n///be7rx6nzKjHye85Te/9atOmzT49nxn18GpZfI/uhg0bXK+g3q8D7eHNbN2Se3j1XleoUMG939u2bYs975133nHPGzp0aLa2aUb69+/vVatWLdaT9+GHH7rXnD17dsLz/F7veNoeye2U33//3fXCxvcUy48//uh68+OX+72a6g3dH/Vi6rmZ9QBL48aN3dWJ5Pfp2muvjS1Te9Ue9VCuXr3aLdP/8b268TLq4VXvbXz7H330UbdcVyA2btwYWz5kyJB93qv0elrjffXVV17BggW9Pn36xJZt377d27NnT8Lz9Jpaj/j9QFd/ko8BGf3dN954wz33zjvvTHjeRRdd5KWlpXkLFy5MaLPes/hl+txq+bhx4zJsC5AXUaUBCIjx48e7Xs742/6oxzKeen/UaxSvaNGisZ/VO6WeZD1v1qxZdiio51M2bdqU7uNaH/U+asCX1ie7+vbtm+V8XfWUqafPp55nDbpT7+SKFSvsUNFgI/Veq/csPrdXPevKc3733XeztU3To97NSZMmuV5Rf8Deqaee6noT1cubXer906Ao9e7qCoR/U0mzOnXquJ7EeBo8pR7t/fH3D/UqZkaP62pBsv79+8d+Vnt1X73U6hXPrtNOOy0h31k97aIez/j19JdnZbuI9jHlIqu3VT3F8e+VP7hRvdbqHdfn5+ijj87251OD/PS5uO666xKWqzdfMe7777+fsLx9+/buCoJPveD6fGS1bUBeQUoDEBAqJZXRoLX0KIDyLzn7NLAnOYjU5eA777zTlTRStQRfTtchja/xmlkgo5O8KjnoBKzL3bosrcvGCkAVRGVVRpfB01O7du192quBd6JBXQfydw/EH3/84f5XAJNMAW9yBYWsbtP0fPjhhy7VQPuR0hp8p5xyiktV0HuencoRSgdRoKTgNiuDplQZQl9o9sffPzL6YuTT48n7ktqhqg4Zbc+DScmJV7p0afd/tWrV0l2ele2iLyL6sqCAVl8e4qsp6IuE0lsUBKs6hp7jy246gfY5fcFLfs+UGuE/nlmbD2SfA/ISAl4gpLLSu6mcROXzKddSJ1WVfFKAohzPiRMnHpL1mjt3rlu3zAJSjcxXRQflLyq/UPmhypdUTmSzZs2y9Hfie65zQkZfAOKDkEPtYCpM+L24Cq7S89lnn7ng90ApKNN7o57B9NbP79E/0O2iLyGqlDBnzpwMn6MvaAsWLDigL4KH4v3PaHlWqn4qd3batGmu51l508n1kLXvKx94xIgRrqa1gnl9PvS+54aDaRuQlxDwAhH22muvuV5DBZXxPUsKeA8FDXRRYNW6dev9XqrWZVT18uqmXkRd7tUALQ3syekeaPV46gQe/5q//PKL+9+/hO2XvdLEC/GDsJJ7xA5k3apXr+7+V9Cm9IJ4WuY/frA0aO/NN9906Qy6dJ5Ml7cVEGcW8GbUJm0nvXf6AuP3ouYElZHT+uhLjt7j9N6Ll19+2QW9yTPlKRjUJff49UnenofqCsaB0ABRDXLUTQPRkr366qvuPXjiiScSlmsfjB+weiBt0fuo4Dq5Z3z+/Pmxx4EwIocXiDD13uhkGd9LqUu+6lnNaevWrbPu3bu7v+VXL8hodP727dv3Cap0co5PuVBAlFOzfmmmqtdffz12Xzmhzz77rAuy/XQGP4/x888/Twgkn3nmmX1eL6vrpp5J5dBOmDAhoW3qLdXkCjlVJUNt07qquoAC3uSbAkZ9+Ylfh/TaJMntUoUD7Ue33377Pr1+uq+80+y69dZb3WtoMoVt27YlPKZL/KqOoKsSmnUt2UMPPZSwHrqvqxfKwxW/pm8qZo7zr3SoSoMqVWhijfTofU1+TzWBi6opZGXbpEf1r/UZjH9/RNUZdCxQBQ0gjOjhBSJMAdV9993nSjj94x//cAOoNDhOl5Mzu5S8P+pNU0+sTtYKHv2Z1pS/6/+9zH5XQYkuvWvyA13WVsCmkkwqZeXTdMUqLaX8Y62vAsfkXtKsUk+gSjR99913Lm/4ySefdH8vvqf7jDPOcPmMep4uQysY0fOUU5s8xW1W100BmHJnNYhLPXz6QuCXJVNPpMqD5QT13irnM6NSVUpreeyxx9wgOb9EVzK1SfRlRdtB6660E30RUDuHDBnivix16tTJfTlRQKrtplJZgwYNytZ6K9VG5dc0M58GS/kzrak3UuurnlwNwkqedEJXLVSKTCXmNIBMXyDUNpVf83OglVqh/UsD+bT9lS6gGfJ0yw3+wD210b9q4dN2Ug6yvoio5Jieq2U//vij25bJ+cnaBrrqoC9Oeu8VAKvd6aUNaZup11jbUdtLMzUqv1tXAJQqET9ADQiVVJeJAHDoJp7ISrmlJ554wqtTp44rdVSvXj33Ouk970DKkvm3fPnyuckNVDpL5aXmzZu33/Vfs2aNK2umdVEbSpcu7bVq1coV/I+3YsUKV2pKxfjTm3givfdrfxNPqMSV/z6kN/nGzJkz3bqoVNORRx7p3Xfffem+ZkbrltHEE5MmTXLvkf62SmxlNvFEsozKpflWrlzpyoNdeumlGT5Hpek0QcMFF1yQ8J4kGzFihJsMRNs1uc0q66ZScFpH3fQeajsuWLBgn4knDtTnn3/uJtvQRBsq3aX3vm/fvq4kWrL0Jp6oWLGie5+SS3x9/fXXrqSbtmdWJ55Ib99NLqHnb+f4fSi5PFh6E5QkTxqhsmSaeEIl8jRJx4knnuhNmzbNvY/JZe/efPNNNymEtvX+Jp7YtGmTd8MNN3hVqlRx76c+/5lNPJEsq8cCIC9J0z+pDroBAMgJ6gVW7qtfDQQAhBxeAAAAhBoBLwAAAEKNgBcAAAChRg4vAAAAQo0eXgAAAIQadXjTodqOKkSveoZ5YTYeAAAAJFKSgmYNrFKlipt2OzMEvOlQsFutWrVUrwYAAAD2Y+nSpXbEEUdk+hwC3nT484vrDSxVqlSqVwcAAABJNJOnOij9uC0zBLzp8NMYFOwS8AIAAORdWUk/ZdAaAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgVSPUK4P+MT5trQdTPa5jqVQAAAMgUPbwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8AAABCjYAXAAAAoUbACwAAgFAj4AUAAECoEfACAAAg1Ah4AQAAEGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINTyRMA7fvx4q1GjhhUpUsRatWpl3377bYbPfeyxx6xt27ZWtmxZd2vfvv0+z/c8z4YOHWqVK1e2okWLuuf8+uuvudASAAAA5DUpD3gnTZpkAwcOtGHDhtmsWbOsSZMm1qFDB1u1alW6z586dap1797dPv30U5s2bZpVq1bNzjjjDPvzzz9jzxk9erQ9+OCDNmHCBJs+fboVL17cveb27dtzsWUAAADIC9I8dYemkHp0jzvuOHvooYfc/b1797og9tprr7Wbb755v7+/Z88e19Or3+/Zs6fr3a1SpYrdeOONNmjQIPecDRs2WMWKFe3pp5+2bt267fMaO3bscDffxo0b3Tro90qVKmW5YXzaXAuifl7DVK8CAACIoI0bN1rp0qWzFK+ltId3586dNnPmTJdyEFuhfPncffXeZsXWrVtt165ddthhh7n7ixcvthUrViS8pt4MBdYZvebIkSPdc/ybgl0AAACEQ0oD3jVr1rgeWvW+xtN9Ba1Z8a9//cv16PoBrv97B/KaQ4YMcd8O/NvSpUuz2SIAAADkNQUswEaNGmUvvfSSy+vVgLfsKly4sLsBAAAgfFLaw1u+fHnLnz+/rVy5MmG57leqVCnT3x0zZowLeD/88ENr3LhxbLn/e9l5TQAAAIRPSgPeQoUKWfPmzW3KlCmxZRq0pvutW7fO8PdUhWHEiBH2wQcfWIsWLRIeq1mzpgts419TSc2q1pDZawIAACCcUp7SoJJkvXr1coFry5YtbezYsbZlyxbr3bu3e1yVF6pWreoGlsndd9/tauxOnDjR1e7183JLlCjhbmlpaXb99dfbnXfeaXXq1HEB8G233ebyfDt16pTStgIAACCCAW/Xrl1t9erVLohV8Nq0aVPXc+sPOluyZImr3OB75JFHXHWHiy66KOF1VMd3+PDh7uebbrrJBc1XXHGFrV+/3tq0aeNe82DyfAEAABBMKa/DG/S6bjmFOrwAAAAhrMMLAAAAHGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8AAABCjYAXAAAAoUbACwAAgFAj4AUAAECoEfACAAAg1Ah4AQAAEGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQaAS8AAABCjYAXAAAAoUbACwAAgFAj4AUAAECoEfACAAAg1Ah4AQAAEGoEvAAAAAg1Al4AAACEGgEvAAAAQo2AFwAAAKFGwAsAAIBQI+AFAABAqKU84B0/frzVqFHDihQpYq1atbJvv/02w+fOmzfPOnfu7J6flpZmY8eO3ec5w4cPd4/F3+rVq3eIWwEAAIC8KqUB76RJk2zgwIE2bNgwmzVrljVp0sQ6dOhgq1atSvf5W7dutVq1atmoUaOsUqVKGb7uMcccY8uXL4/dvvzyy0PYCgAAAORlKQ1477vvPuvbt6/17t3bGjRoYBMmTLBixYrZk08+me7zjzvuOLvnnnusW7duVrhw4Qxft0CBAi4g9m/ly5c/hK0AAABAXpaygHfnzp02c+ZMa9++/f9fmXz53P1p06Yd1Gv/+uuvVqVKFdcb3KNHD1uyZEmmz9+xY4dt3Lgx4QYAAIBwSFnAu2bNGtuzZ49VrFgxYbnur1ixItuvqzzgp59+2j744AN75JFHbPHixda2bVvbtGlThr8zcuRIK126dOxWrVq1bP99AAAA5C0pH7SW084880zr0qWLNW7c2OUDv/fee7Z+/Xp7+eWXM/ydIUOG2IYNG2K3pUuX5uo6AwAA4NApYCmivNr8+fPbypUrE5brfmYD0g5UmTJlrG7durZw4cIMn6N84MxyggEAABBcKevhLVSokDVv3tymTJkSW7Z37153v3Xr1jn2dzZv3myLFi2yypUr59hrAgAAIDhS1sMrKknWq1cva9GihbVs2dLV1d2yZYur2iA9e/a0qlWruhxbf6DbTz/9FPv5zz//tO+//95KlChhtWvXdssHDRpk5557rlWvXt3++usvV/JMPcndu3dPYUsBAAAQyYC3a9eutnr1ahs6dKgbqNa0aVM32MwfyKbqCqrc4FMA26xZs9j9MWPGuFu7du1s6tSpbtmyZctccLt27Vo7/PDDrU2bNvbNN9+4nwEAABA9aZ7nealeibxGZclUrUED2EqVKpUrf3N82lwLon5ew1SvAgAAiKCNBxCvha5KAwAAABCPgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUshXwfvrppzm/JgAAAEBeCXg7duxoRx11lN155522dOnSnF8rAAAAIJUBr2Y469+/v7366qtWq1Yt69Chg7388stu9jMAAAAg8AFv+fLl7YYbbnDT+k6fPt3q1q1r11xzjVWpUsWuu+46++GHH3J+TQEAAIBUDFo79thjbciQIa7Hd/Pmzfbkk09a8+bNrW3btjZv3ryDfXkAAAAgNQHvrl27XErDWWedZdWrV7fJkyfbQw89ZCtXrrSFCxe6ZV26dDm4tQMAAAAOUoHs/NK1115rL774onmeZ5deeqmNHj3aGjZsGHu8ePHiNmbMGJfiAAAAAAQu4P3pp59s3LhxduGFF1rhwoUzzPOlfBkAAAACmdIwbNgwl66QHOzu3r3bPv/8c/dzgQIFrF27djmzlgAAAEBuBrynnHKKrVu3bp/lGzZscI8BAAAAgQ54lbublpa2z/K1a9e6/F0AAAAgkDm8ytkVBbuXXXZZQkrDnj17bM6cOXbCCSfk/FoCAAAAuRHwli5dOtbDW7JkSStatGjssUKFCtnxxx9vffv2ze66AAAAAKkNeJ966in3f40aNWzQoEGkLwAAACCcZclUpQEAAAAIVcCrKYSnTJliZcuWtWbNmqU7aM03a9asnFo/AAAAIHcC3vPPPz82SK1Tp04H91cBAACAXJLmaQQaEmzcuNEN0FNd4VKlSuXK3xyfNteCqJ/3/6eUBgAAyIvxWrbq8AIAAAChS2lQ7m5mebvx0puFDQAAAMjTAe/YsWMP7ZoAAAAAqQx4e/XqdSj+PgAAAJA3Al4lBvsJwfo5M7k10AsAAADI0Rze5cuXW4UKFaxMmTLp5vOq4IOW79mzJ6svCwAAAOSNgPeTTz6xww47zP386aefHsp1AgAAAHI/4G3Xrl26PwMAAAChCHiT/f333/bEE0/Yzz//7O43aNDAevfuHesFBgAAAPKCbE088fnnn1uNGjXswQcfdIGvbvq5Zs2a7jEAAAAg0D28/fr1s65du9ojjzxi+fPnd8s0UO2aa65xj/344485vZ4AAABA7vXwLly40G688cZYsCv6eeDAge4xAAAAINAB77HHHhvL3Y2nZU2aNMmJ9QIAAAByN6Vhzpw5sZ+vu+46GzBggOvNPf74492yb775xsaPH2+jRo3KmTUDAAAAckCap9kisiBfvnxuUon9PT0ME09oJrnSpUvbhg0bcm3WuPFpcy2I+nkNU70KAAAggjYeQLyW5R7exYsX58S6AQAAALkqywFv9erVD+2aAAAAAHlp4gn56aefbMmSJbZz586E5eedd97BrhcAAACQuoD3t99+swsuuMDV243P69XPEvQcXgAAAES8LJkqNGhWtVWrVlmxYsVs3rx5boa1Fi1a2NSpU3N+LQEAAIDc7OGdNm2affLJJ1a+fHlXvUG3Nm3a2MiRI13JstmzZ2d3fQAAAIDU9/AqZaFkyZLuZwW9f/31V2xg24IFC3J2DQEAAIDc7uFt2LCh/fDDDy6toVWrVjZ69GgrVKiQ/fe//7VatWodzPoAAAAAqQ94b731VtuyZYv7+Y477rBzzjnH2rZta+XKlbNJkybl7BoCAAAAuR3wdujQIfZz7dq1bf78+bZu3TorW7ZsrFIDAAAAEPg6vLJ06VL3f7Vq1XJifQAAAIDUD1rbvXu33XbbbW7+4ho1aribflaqw65du3J2DQEAAIDc7uG99tpr7X//+58brNa6detYqbLhw4fb2rVr7ZFHHjmYdQIAAABSG/BOnDjRXnrpJTvzzDNjyxo3buzSGrp3707ACwAAgGCnNBQuXNilMSRTmTKVJwMAAAACHfD279/fRowYYTt27Igt08933XWXewwAAAAIXErDhRdemHD/448/tiOOOMKaNGni7msiip07d9ppp52W82sJAAAAHOqAV1UY4nXu3DnhPmXJAAAAEOiA96mnnjokKzB+/Hi75557bMWKFa63eNy4cdayZct0nztv3jwbOnSozZw50/744w+7//777frrrz+o1wQAAEC4ZSuH17d69Wr78ssv3U0/HyhNQzxw4EAbNmyYzZo1ywWnmsVt1apV6T5/69atVqtWLRs1apRVqlQpR14TAAAA4ZatgHfLli3Wp08fq1y5sp100knuVqVKFbv88stdUJpV9913n/Xt29d69+5tDRo0sAkTJlixYsXsySefTPf5xx13nOu57datm6sUkROv6Q+427hxY8INAAAAEQ541YP62Wef2dtvv23r1693tzfffNMtu/HGG7P0GhrgptSE9u3b//+VyZfP3dckFtmR3dccOXKky1H2b+QjAwAARDzgfe211+yJJ55wE0+UKlXK3c466yx77LHH7NVXX83Sa6xZs8b27NljFStWTFiu+8q9zY7svuaQIUNsw4YNsdvSpUuz9fcBAAAQkpnWlLaQHFRKhQoVDiilIa9QekRGKRIAAACIYA9v69at3aCw7du3x5Zt27bNbr/9dvdYVpQvX97y589vK1euTFiu+xkNSEvFawIAACCCAe/YsWPtq6++chNPaKIJ3ZT3+vXXX9sDDzyQpdfQFMTNmze3KVOmxJbt3bvX3c9q0JwbrwkAAIAIpjQ0atTIfv31V3vhhRds/vz5bln37t2tR48eVrRo0QMa/NarVy9r0aKFq5OrQFoVIFRhQXr27GlVq1Z1g8r8QWk//fRT7Oc///zTvv/+eytRooTVrl07S68JAACAaDnggHfXrl1Wr149e+edd1z5r4PRtWtXV79Xk0loUFnTpk3tgw8+iOUHL1myxFVZ8P3111/WrFmz2P0xY8a4W7t27Wzq1KlZek0AAABES5rned6B/pJ6XT/++GOrX7++hZHq8Ko8mSo2qAJFbhifNteCqJ/XMNWrAAAAImjjAcRr2crh7devn9199922e/fu7K4jAAAAkHdzeL/77js3EOzDDz90+bzFixdPePx///tfTq0fAAAAkPsBb5kyZaxz584H95cBAACAvBbwqsTXPffcY7/88ourknDqqafa8OHDD6gyAwAAAJCbDiiH96677rJbbrnFlQHTwLUHH3zQ5fMCAAAAoQh4n332WXv44Ydt8uTJ9sYbb9jbb7/tavGq5xcAAAAIfMCrurhnnXVW7H779u0tLS3N1ccFAAAAAh/wqgxZkSJFEpYVLFjQTUYBAAAABH7QmuaouOyyy6xw4cKxZdu3b7errroqoTQZZckAAAAQyIC3V69e+yy75JJLcnJ9AAAAgNQFvE899VTO/nUAAADgEMvW1MIAAABAUBDwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhNoBVWkADtb4tLkWRP28hqleBQAAkE308AIAACDUCHgBAAAQagS8AAAACDUCXgAAAIQag9aAHMbAPAAA8hZ6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YcPc73Djxo0bN27cuHGzQN2WLl2635gzpT28a9assT179rje13i6P3/+/HR/Z8WKFek+X8v9x/1lGT0n2ZAhQ1xahW/v3r22bt06K1eunKWlpVmQ6dtPtWrVbOnSpVaqVCkLqyi0MwptjEo7o9DGqLQzCm2MSjuj0MawtVM9u5s2bbIqVark/ZSGvKBw4cLuFq9MmTIWJtqpg75jZ0UU2hmFNkalnVFoY1TaGYU2RqWdUWhjmNpZunTpvD9orXz58pY/f35buXJlwnLdr1SpUrq/o+WZPd///0BeEwAAAOGV0oC3UKFC1rx5c5syZUpCOoHut27dOt3f0fL454sGrfnPr1mzpgts45+j7ntVa8joNQEAABBeKU9pUO5sr169rEWLFtayZUtXYWHLli2xqg09e/a0qlWr2siRI939AQMGWLt27ezee++1s88+21566SWbMWOG/fe//3WPK+f2+uuvtzvvvNPq1KnjAuDbbrvN5XeofFnUKFVDJd+SUzbCJgrtjEIbo9LOKLQxKu2MQhuj0s4otDFK7UyWppFrqV4JlRS755573KCypk2buhq6moBCTj75ZDcpxdNPP50w8YTKjv3+++8uqFXd3bPOOiv2uJqkjakgeP369damTRt7+OGHrW7duilpHwAAACIe8AIAAAChnWkNAAAAOJQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAChlvKJJ3DgVElOE2wg+FavXm3r1q2znTt32jHHHGP58vEdNKh27dplBQsWtDD7+++/3b66bds2Vx89rJYvX+7qvKutDRo0sMMPPzx0x94o7K9APM6uAfPzzz/bXXfdZZs3b7Yw0wk17ObMmWPHH3+8de7c2Zo0aWIXXXSRPffccxY2mtpbtzCbP3++DRo0yH0+w7y/tm3b1k477TQ7+uij7corr7RPPvnEwubHH390s35effXVdsopp1iXLl1iM30q2A1D6foo7K/y22+/uUmrNPPqDz/84DoYJAzbMGrH2ByhiScQDL/++qt3+OGHe2lpaV6/fv28bdu2eWE0d+5cr3Hjxt7bb7/thdWKFSu86tWrezfeeKP3888/e1OnTvU6derkHXvssd6IESO8sJg3b55Xr14974EHHvA2b97shdHChQu9qlWrus/lhRde6O6HzbJly7zKlSu7/fXzzz/3Xn31Va9Zs2beqaee6j355JNeWKxdu9arW7eud8MNN3jLly/3Zs6c6X6uWbOmd9VVV8Wet3fvXi+oorC/ypw5c7xy5cp5J5xwgnfkkUd6VapU8f7xj394s2bNCvw2jNoxNqcQ8AaEdmQdcLt27eo9//zzXtGiRb0rrrgidEHvH3/84TVo0MArXbq0d9hhh3nvvvuuF0ZfffWVV79+fe+vv/6KLVu0aJH3r3/9y7V/zJgxXtAtXbrUa9KkiXfEEUd4xYsX9x5++OHQHZD1+VMQqBPpxx9/7Pbbc845JyGICMOJ9a233vIaNmzobdq0Kbbshx9+8Lp16+adeOKJ3sSJE70w+Omnn1zwoP99q1at8h555BEXMCn4DbKo7K9btmzx2rdv71133XWxc+Tjjz/unXXWWV7Lli296dOnh6KtUTjG5iRSGgKUb1W/fn13+btHjx725ptvusvfAwYMsO3bt1sY7N6927WrTp069tVXX7m2du3a1d5//30Lm6JFi7rLaz/99FNsWa1atax///52xhln2Ouvv27Tpk2zoNq7d69NnTrVqlevbt9++60NHDjQrr32Wnv22Wdty5YtFhbqNNDl73POOcdd6v/uu+/siy++sOuvv94WLVrknhOGnM/ChQu7XPM//vgjtn0bN25sQ4cOtfLly9vEiRNjjwVZsWLFbNWqVTZr1qzYMuXvdu/e3QYPHmwffvihvfbaaxZUUdlflXu9dOlSa9SokRUpUsQtu/zyy935skKFCjZkyBBbsGBBoNsalWNsjsrR8BmH1OrVqxPuf/DBB/v09O7evdulPgSVLpe+/vrrsfv//Oc/vRIlSoSup3fJkiXukvD111+/zzfyX375xaU7jB492gv6pbb47Xbrrbd6+fPnd70Q8T2FQZfclvnz58d6ztRr7/ck6fJ4kNOMlE7lX3lQe/zesRkzZnjFihXznn76aS/oNm7c6J1//vmuB/S3335LeEwpDm3btnU9pEG2YcOG0O+vW7du9c444wx3xUznxHhvvvmm6+X9z3/+E/he3qgcY3MKAW8epiBWB+B4e/bsSfiAxge969ev9/r37+/SHoJ8WSP5AOQHve+99567v2vXLvez8mCDQgcfra8utfkH4Geffdbl0d1///2uTfEuvfRSl9Mb5IOxJK//bbfdFjsgax/V488999w+wUUQ26b7/rZVXrYfROhnfS4VLP39999e0I49fjvvuecet+1ee+212LHId+aZZ7pjUND4bYtvy/vvv++VKlUqlscbb+DAgV6bNm28nTt3ekE89vjt9M8jYdlf0zN48GB3qf+bb77Z5zFt29q1a+9z3A2K+P01WZiOsTmNgDeP+vHHH903VA3e6tChgzvZ6Fur+B9S/2A9efJkd4DWwArt6EH6Zq78OOUCqr0K2H3JB6K+ffu6oFe5hFdeeaVra/LJKC8PnlCeY506dbxWrVp5V199dezb96hRo7x8+fJ5d911V0J7LrjgAu/aa6/1gkT5yFOmTHED8PyeItFJNf4A7R+QH3roIa9Pnz5exYoVvd9//90LgsWLF3svvfRS7Atlel9I/H1XPWfly5d3A2cKFSoUmM9l8rHn7rvvdsGSaLBswYIF3Qk0vufs9NNP94YOHeoFifJWdVxds2aNu6/2+NtTOcnaR/UZ9Ac5Sa9evdyX0eRew6Ace7T9/G3ptyHo+2vysSc+H1lt1yBEnWPij0E6j2j/DlpAv3379gyD3rAcYw8lAt48SB/YsmXLuiDvv//9r9e9e3c3el9J+H6glBwQqodFByqdrIJCB2MFrsccc4z7cJ599tneU089FXs8+aSi90M9oiVLlvS+++47LygBkk4iGjyhVI1bbrnFpTLoBOQHuBpdq176c88917vkkku8yy+/3LVRl5GDtC01qEcDKNSWFi1auGA+/mAcvz116U3bUl/UdEk8CBYsWOC2i0a4K+DzA4f0gl6/rfrs6nMZlG2Z3rFH+6uOPX6Qf9NNN7kvaTqRqhdNvYF6X9QzGBRKG9Kg2AoVKnh33HGHq86QHPS+8sorrhfw+OOPd18AdOVM+6v29SAfe44++ujY1TF/Pw3q/prRscevdKPzpdIXlCKm92DlypVuub7I6AtAkC776/Olgetff/11bFnysSfox9hDjYA3D9KIYPWs+N/YtFPrMmLz5s3dwdc/8fiXpfwd+/vvv/eCQgceHYR0aUm9gUpR6N27t7sEpd5On/8eKMBXz65OUspbCgqdNFUWxw+ORD0nrVu3du33T7TqpR8yZIjb7go2gnJSFbVBJ1HlI+tnXUIcNmyYO/kMGDAgYVv6ge+gQYNcYBU/Gj4vU0+QvpApt1OpJqqk8cwzz2QY9Oq+3gN9LmfPnu0F/dijL9zaZ/1jj/briy++2PWgqbSVetCClMOqKyiqMHHNNde4tg0fPjzdoFef1UcffdQFuzfffHNojj01atTw1q1bl3AeCeL+mtmxR4G+T59dVd9Qab1TTjnFK1OmTKDaqXOkzo3aPuoUie/wST72BPUYmxsIePMgXY5Q3cB42oEVFB533HFuh/dzyHQwe+yxxwLVsyv6xqkyRyqrEl+S7M4773Q9DPfee2/CB/iFF15wl1KD9k113Lhx7uCaTAch9Tzo5OOnqviCllem3DD10scHPcr/VEBYuHBhN3Ak/uCsy4kFChQITC+9f8lU7fDzyBUAJQe98fT5/PDDDwMVIGXl2NOjR4/YZVV/oGzQSiNqe6lXV7WE/R7r9ILeZEHLp8/s2KPeTX1ZiR/sHMT9NbNjj9Iy4gcYfvTRRy6vVeXJ4lOu8jp93hTQ65jz8ssve7Vq1XJfNjMLeoN4jM0NBLx5iH+Q1QezadOmbjRpfF6OdvwHH3zQPRZ/YMosgT2vUm+0voWrZzOeLrXppKtg+JNPPoktV0CvgDholBenwGjs2LEJJ1H9/M4777jLcBokE9TtKNou2pb6UhJP+6t6x3RZ9cUXX0yoNhJffzgItG1UWSN+G8UHvfH59ck59mE79viXu4O6v/pBUfz2UWqGgl71Dvo9n2pzkAanZefYo0HPQZaVY4/Sj4JM+6ACWL8d6sXeX9Cr82jQjrG5gTq8ecCePXvc/35NQE0zW7x4cRs3blxCnVbVwuzTp48tXLjQPv/889jyfPmCtxkrVqxoJ5xwgr311lu2YsWKhOWqM6y2zpw5M7a8YcOGduSRR1pQ+FNXVq1a1Vq0aOHq6sbXE86fP7+rg7l27VqbMWNGYLejX6dU0yK//PLLNnfu3NhybcNOnTpZmzZt7JtvvoktV93WypUrW1A+l6p3qW2jban/VeNTXnrpJbdf3n333fbKK6/Y+vXr7bbbbrMbb7zRPR6EGp/ZOfaobmtQ91f/c1myZEnXZtU3l9GjR9upp55qb7/9tj344IPumPSvf/3L1d8N2jS0B3LsUR3eoFI7dRzZ37HHP74GbTv6ChYs6Ka4vuSSS9z9Vq1auRr8atc999yTcJ70f9Z5NAjH2NwWvCNWyGgucxWL1ofzlltusenTp7sA4vnnn3dzuqsguIpK+woUKGBNmzZ1QUOQaJ7vlStXuuL1UqlSJbv44ovdB1eFsv/+++/Yc48++mirW7euffrppy7YCIply5bFDjg6mSqYKFGihI0aNcp27NjhDk4KjHwqiH7MMcfYYYcdZkGibbh48WI3T70/iYZOOvPnz7fHH3/cBUU+FXn3C6NrYpEgfi7//e9/u+3qB3iFChWKtWXSpEmuuP2YMWPs3HPPtfvvv98FhkEQlWNP8ucyOZjwjzH6fCoQfPfdd+300093+7ImKAjCF5eoHHt8ap/aqe2nY48mkdjfsScI2zGj86W2pfZT/6bOIj/o1Zc1dSjccMMN1rt3b/fFGxnIlX5kZDjqUiMoVeqmc+fOrrSPch79uemVn6RLF6qHqEttutyoQV4auBWkmnoagKXBE2qL8gDVXv9yqIp/K9dIo2rjy8loQIlG0gblsrAuH6r0i9r3xRdfxJb7l0SXLVvmRrorb1ft1yV+DZhR7UuNGA8K5crpUqgG3B111FFun/XzsLXfVqtWzZVdi699qUF48XnnQfxcFilSxNVNjhc/wl2DYfS5DMrA0agcezL6XCbzj0falhrcpME+QRk4GpVjjyZUUp61nzMfP9ZBFUWUex70Y09650sN5k4e1+EfezRFvcquVapUyaV2BKmUXCoQ8KaQDjoa8R1fuUAVF1TyR6WqRHmD+hAryNCOrZIr8XUh8zrV/tMMTRo8oNHemj1MJbmUW+YPHFAtTI0aPvnkk12NS910Mg7KQDyVF9O6axCIysOphJFmjPP5B1vlrqqtep62Z7t27QITIIkCW5X/0Wh11bvUKHBVDtHoYdXAlOeff94NxNM21nuh/VvbMkij+DP6XPrF3MX/IqZcQU24oIA4KPtrVI49+/tcJtuxY4crCRikbRmVY4+CXZWQ04BmTf7hB73xgaxy6YN+7MnofNmoUaN9ZlD1j0GqOKIvaEHZZ1OJgDeFVM5HB9hk6vVU+RENHPEPxPqAKwk9eea1vE4fWp0o46ezVKCrUcL6IPvTJWt6RJUj0wFbJ9kgfXg1cOC0005z37Y1AC29E0/yN3S9H8nVGfI6DSLUF5X4wRDqaVB7dTLye1a+/fZbN82sSnj9+9//DtzI7/19Lv2pPHXCUcCrerTTp0/3giQKx56sfC6TqRc7vZm58qooHHs0IZGC14suusgNLtS5Q1UL0gt69YUsyMeezM6X9evXj9UR9kvnqfMhaCVJU4mAN4V0eUaXgP/888+Eb2z6AKvAtHZw/7Gg0kwvGimbfOlQJ1D1NKiucDy9B0Ec/R1/wFFA5J94Pvvss30uQwUlTSOZSuKozJFflkrBkE8nXdXDDGrbDvRzGT8rXhDbHIVjT1Y/l0E83kTp2KPto0kzNMOhjjkqKacAUDW+00tvCPP5Uj308fRFJyipN3kBAW8KTZs2zeXqaKYi/5ubv4N//PHH7vJxkIpjx/MPrCobo5mpRo4cGXvMb6M+rJrJyC9ZFcSDcUZUt7Rjx46uiL/f26IDdJB6j3z+dtGsREpf0PSkPj/oVXCknDNNQxv/O0EU5s9llNoY9s9lFNoYP/2xf0xR7/Ttt98eC3r93uqg1YPO7vlSgX/87yDrCmQ0mA05a9GiRfbqq6+6MjgaOXrppZfa8ccfb507d3ajLTXKe8CAAa6UjNSrV8+VB9qyZYsFiUYEqySMRsVqBG2ZMmWsS5cu9t5777l2q8yPP9pdJZ30sz/aPyijaOO3ZY0aNWLlYvzRwyr7c+aZZ7r2qMTRf/7zHzcq+s0337RevXpZUGzbts1tS7VT/2t/vOmmm9xoaI36Hjx4sKtYoFHD5cqVsyOOOMKNLA7qtgzr5zIKbYzK5zIKbYw/j+jYojapQoioJKCqwqhknKjcmiqMDBs2zAYNGuSq/bz22msW5vOl9oEgHWPzlAMIjpFNykfViFgNFNCoS42G1jdw/1KEZhfT8nPPPdddnlJyunJzNBI+/tJpXqeC9Eqg14hg9S5oYJP/rVVTO6r9/ihwn96HMWPGBOYba3rbUm2L7z2JL/L+9ttvuwEFSgUIUp6V2qk0BaWcaCYjVSjQ9LrKp1OvoIr0q5clnvLs/FnVgrotw/a5jEIbo/K5jEIb0zuPKDUjPn3Kb6NSq3QM0pUKjQcpUaKEu3IRFFE4X+Y1BLyHmC63aGfWqGj/soumdtSlCX1Q/YOVAgrlXikBXbOM6YQTpBHRKm+jEbEasa6BBRpgoLZo5LfyrBYvXuxmhtFoU5WJ0awxyhXU7wSlNE5m2/Kkk05KmBlOl6F00+CKkiVLBmoQngZJ6ESp1AVNT6rycDpp/vOf/3Sl4zQTlQJbpTDoYD1q1Cg3cEsnHJW7CoIofC6j0MaofC6j0MbMziMqjRc/06Yf9Gpwl84pQSojF5XzZV5EwJsLlGiu8iLxyfXKeWzcuLF7bNWqVbEPsUZ7a2RpkHpXRB9UDZSIp6lIVbdz0KBBbjCMEu81j7l6B9VDccoppwSq52F/21LfyP2atKITjfKxZsyY4QWJehB0Eo2nqTt18O3Ro4c78eigrFxPbfNTTz3VO++88wJV/icqn8sotDEqn8sotDGj84jKkelLtqbM9anXV0F9sWLFAhXsRul8mdcQ8B5CuuSgb+IqM6JvZz7/8oxOLNrBVYYr6FQ30P8Ax4+YnTBhgjsgjR8/PuH5el+CNMggq9tSl/vjxZeXCVLA27RpUzdILX4Eu+ruqkdpyJAh+/xOkAq7R+FzGYU2RuVzGYU2ZuU8Urx4ce+RRx5x9/3jkq4+BS2oj8L5Mq8i4D2E/Bwb1dZTvlX8TE3+zqtlmnRBBaeDnJOjYvW6fOaXMorPuVKelQ5W8Zekwrwt1c4glwGaNGmSm7XHv6wdvy11wilUqJDLP4sXpHZG4XMZhTZG5XMZhTZm9TyitClNiBJ0YT9f5lX/N/wPOUYjZUWjS/1RlCeffLJdeeWVNnz4cHvxxRfdMo2c9efI1kh3/R/kUZdXXXWVNWvWzI38Xrt2rWvT9u3b3WNXXHGFm7Pdn+s97NtSI9w1sliCtE31BVguvvhi69ixo51//vm2atUq1yaNJva385FHHmmffPJJwu/m9XZG4XMZhTZG5XMZhTZm5zxStmxZmzFjhgVdGM+XQUDAm4Pmzp1r7du3t6VLl7ryITpYiXZe7cR6bODAgTZu3Di3c6vsjz68Olj5pUeC4JdffnFlYXr37m0PPPCA/frrr+4Dq9IwanPXrl1t3bp1sYOxX9JKZVeCIirbUgHt+vXrYydIv50jRoxwga3KVy1btsxtQ9m6dauVLFnSnXiCIgrbMgptjEo7o9DGqJxHotTOQEh1F3NYaFSl8hs10lIlUvwBBPH5OSr5ozJAuiyl52rmFM2bHaQR0RrUotI4Ko/SuXNn97MGLfmX2VQKp2XLll7NmjW9yZMnu9HDStCvVKlSYC7RRGVbapS30hM0Qji9fD9NEXzyySe7Cg2PPvqomyBEJas0gESVHIIgCtsyCm2MSjuj0MaonEei1M6gIODNAcqj0k6qmnpTpkxxI9xV2ie9g5WodNMTTzzhZkzRAS4olGekEil9+/ZNOPh27drVjSJVUOQHUt27d3cH4bp167o6rjNnzvSCICrbUqOdVZpKB19NZdmlS5d0g16VIBs4cKCbalZTB2t2o6CcWKOwLaPQxqi0MwptjMp5JErtDBIC3hwyceLE2JR/GgTStm3bhIOVP5Ag6PO2n3766a52YPJ0iJdddpkrm6NpLeMPyErKX716tRckUdiW77//vvePf/zD++6771w5Ko3yzijolWXLlrmJJ3QLkihsyyi0MSrtjEIbo3IeiVI7g4KA9yDooJNeOSbt2Lrk639DV7Dgf4NX75hqmAaNDrRqa+/evd0lcM1yo3b6B161t3Xr1q5Yti9Io4SjtC1F9Vc//fTT2H3NUOQHvZpNzZfcqxQEUdiWUWhjVNoZhTZG5TwStXYGDQHvQeTmqAi/pl+98sorvXfeeWefHVezUvkHq99++83NXKVaikHqJYufqlI0/WH+/PldWZXk5+ixfPny7VOyKq+L6rb0+QdhzbwV39OrA/bDDz/sffjhh15QRGFbRqGNUWlnFNoYlfNIlNoZVAS82TB//nyXfN6tWzc3iEeDBnQA0qwvyQcrfZPTwB8NQlBtPQ0ECooFCxa4SQg040s8LdMH9bHHHktYrrwj5XoGKZ8s6tsymZ/eoJ4H9U4ULFjQnXCDIArbMgptjEo7o9DGqJxHotTOICPgPUA6AN1yyy0JlyI2btzoRs1qdqr4BHU/cV0HNAUR+jYfFEqu1zrrAKuZteLzinQpTcWx/bm/dXlt7dq17qCtUcP+dKV5HdsyfV9++aV7rn4nKIMnorAto9DGqLQzCm2MynkkSu0MOgLebFDCuS4xxdPBSt/k9A191KhRsYOa5sfWJY2gjGyXzZs3e3369HHt1BSH+qAOHjw44YOpy+DPPPOMK5+iOdvr1avnValSJTABUtS3ZUZBr06smr5UswAF6cQahW0ZlTZGpZ1hb2NUziNRaWcYFEh1HeAg0RcEFec/9thjXfHoBQsW2NFHH+0eUzH+Pn36uGVvvfWW9evXzxUCr1Gjhv38889Wp04dCwoVL2/evLmVK1fOFcUuX768devWzT02ePBgO/zww91zevbsaSeddJItWbLETUjQqFEjq1q1qgUB29Lspptucvfj/fDDD/bFF1/YlClTrEGDBhYEUdiWUWhjVNoZhTZG5TwSpXaGQqoj7iBSTqNql+pb3aZNmxJyrTTPt77hxZcbCSJ9a42nUjlq16BBg2K9gxrBH/Ti2FHflmvWrIn1QPhz1Kv2bhBFYVtGoY1RaWcU2hiV80hU2hl0BLzZpBlRNNONRszGXx5evny5G3zw9ddfe2GgEaX+QVgzbfmXa1Qv8IYbbvAuvPBC92EPckkVtuX/bctOnTp5W7du9YIsCtsyCm2MSjuj0MaonEei1M6gIuA9CG+99ZY7WGkn1jc6zZiiRPTKlSvHCoWHQXz9QLVTI/c161aBAgW82bNne2HAtiwQqPzAqG/LKLQxKu2MQhujch6JUjuDKE3/pDqtIshmzZplAwcOtN9//90KFChg+fPnt5deesmaNWtmYeLvJso9O+200+z777+3qVOnujyksGBbsi2DJAptjEo7o9DGqBx7otTOoCHgzQEbN260devW2aZNm6xy5cr7DAQKiz179rgk/LFjx7oPcOPGjS1s2JbhEYVtGYU2RqWdUWhjVI49UWpnkFClIQeUKlXK3aLgmGOOcb0RYf3wsi3DIwrbMgptjEo7o9DGqBx7otbOoKCHF9kqqYPgY1sCSIWoHHui0s6gIOAFAABAqOVL9QoAAAAAhxIBLwAAAEKNgBcAAAChRsALAACAUCPgBQAAQKgR8AIAACDUCHgBII/SVLOq46mZmnLD008/bWXKlMmVvwUAuYmAFwBS5LLLLnMBrX8rV66cdezY0ebMmeMer1atmi1fvtwaNmzo7k+dOtU9b/369Qf0Nzp16pSl53bt2tV++eWXbLYGAPIuAl4ASCEFuApqdZsyZYoVKFDAzjnnHPdY/vz5rVKlSm7ZobZr1y4rWrSoVahQ4ZD/LQDIbQS8AJBChQsXdkGtbk2bNrWbb77Zli5daqtXr05IadDPp5xyivudsmXLuuXqvZVXX33VGjVq5AJW9RK3b9/etmzZYsOHD7dnnnnG3nzzzVgvsnqJ/dedNGmStWvXzooUKWIvvPDCPikN+n2t03PPPWc1atSw0qVLW7du3WzTpk2x5+jnHj16WPHixa1y5cp2//3328knn2zXX399Ct5NAEgfAS8A5BGbN2+2559/3mrXru0C13hKb3jttdfczwsWLHA9wg888ID7v3v37tanTx/7+eefXUB74YUXmmaNHzRokF188cUJvcgnnHBC7DUVXA8YMMD9XocOHdJdp0WLFtkbb7xh77zzjrt99tlnNmrUqNjjAwcOtK+++sreeust++ijj+yLL76wWbNmHbL3CACy49BfJwMAZEhBZIkSJdzP6pVVL6mW5cuX2B+h9IbDDjvM/ay0A78nVgHp7t27XZBbvXp1t0y9vT71+u7YscP1ICdTL6x+LzN79+51Pb8lS5Z09y+99FKXenHXXXe53l31IE+cONFOO+009/hTTz1lVapUOch3BQByFj28AJBCSlNQyoJu3377retpPfPMM+2PP/7I0u83adLEBZsKcrt06WKPPfaY/f3331n63RYtWuz3OUpl8INdUUC+atUq9/Nvv/3mcn9btmwZe1xpD0cffXSW/j4A5BYCXgBIIeW+KoVBt+OOO84ef/xx19OrwDUr1POrVIL333/fGjRoYOPGjXMB5+LFi7P0t/enYMGCCfeV+6teXwAIEgJeAMhDFFAqnWHbtm37PFaoUCH3/549e/b5nRNPPNFuv/12mz17tnve66+/Hvud5OfnlFq1armA+Lvvvost27BhA6XNAOQ55PACQAopv3bFihXuZ6UiPPTQQ27w2rnnnrvPc5Wjq+BWOb5nnXWWy8+dN2+ey6k944wzXG7v9OnTXYWH+vXrx1ISJk+e7Aa6aSCcUg5yilIdevXqZYMHD3b5xfr7w4YNcwG71hMA8gp6eAEghT744AOXF6tbq1atXG/pK6+84kp7JatatarrxVV1hYoVK1r//v2tVKlS9vnnn7sAuG7dunbrrbfavffe6/KApW/fvi7FQfm6hx9+uKuokJPuu+8+a926tasdrHJo6mlWsK1SZwCQV6R5ql0DAEAOUP6xAnMF3ZdffnmqVwcAHFIaAADZppzh+fPnu0oNyt+944473PLzzz8/1asGADEEvACAgzJmzBiXI6wBcs2bN3eTT5QvXz7VqwUAMaQ0AAAAINQYtAYAAIBQI+AFAABAqBHwAgAAINQIeAEAABBqBLwAAAAINQJeAAAAhBoBLwAAAEKNgBcAAAAWZv8PCO4KZMamVxAAAAAASUVORK5CYII=", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Example QUBO matrix (6-qubit), taken from the other QUBO notebook\n", "H1 = np.array([\n", " [2., 32., -32., -32., 32., 0.],\n", " [0., 1., 32., 0., -32., -32.],\n", " [0., 0., 35., 32., -64., -32.],\n", " [0., 0., 0., 2., -32., 32.],\n", " [0., 0., 0., 0., 35., 32.],\n", " [0., 0., 0., 0., 0., 4.]\n", "])\n", "\n", "# We'll group the 6 qubits into 2 groups of 3 qubits each: [3,3]\n", "# these combinations are also possible [2,2,2], [5,1], [4,2], [3,1,1] etc\n", "# different group combinations may have different alpha value or sample number needs so play around with different hyperparameters\n", "group_sizes = [3,3]\n", "\n", "# Single rotation layer: [\"Y\"]\n", "layers = [\"Y\"]\n", "\n", "# Run the optimization\n", "final_loss, best_bitstring, optimal_phases = optimize_qubo(\n", " qubo_matrix=H1,\n", " input_state=\"000000\", # e.g. 6-qubit input in |000000>\n", " group_sizes=group_sizes,\n", " layers=layers,\n", " sampling_size=10000000, # how many shots for sampling\n", " alpha=0.25, # CVaR parameter\n", " maxiter=600,\n", " verbose=False # displays iterations results if set to True\n", ")\n", "\n", "print(\"\\n=== Final Optimization Results ===\")\n", "print(f\"Final CVaR Loss: {final_loss}\")\n", "print(f\"Best Bitstring: {best_bitstring}\")\n", "print(f\"Optimal Phases: {optimal_phases}\")\n", "\n", "# Sample final circuit with the found phases, then plot the output distribution\n", "circ = build_circuit(list(optimal_phases), group_sizes, layers)\n", "circ.with_input(LogicalState(\"000000\"))\n", "sampler = pcvl.algorithm.Sampler(circ, max_shots_per_call=10000000)\n", "job_results = sampler.sample_count(10000000)\n", "\n", "output_dict = extract_probability_distribution(job_results, group_sizes)\n", "plot_bitstring_distribution(output_dict, title=\"Final Distribution After Optimization\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Explanation\n", "\n", "1. `optimize_qubo` calls our **objective function** repeatedly, adjusting phases to reduce the **CVaR** of the QUBO cost.\n", "2. `alpha` in **CVaR** picks how much “worst tail” of outcomes we average over. $\\alpha = 0.5$ is a middle ground.\n", "3. `best_bitstring` is chosen from the final distribution’s highest-probability outcome (in practice, you might also check the distribution).\n", "4. The **plot** helps visualize which bitstrings are being sampled at the end of optimization.\n", "\n", "---\n", "\n", "With this approach, you have a working **CVaR-VQE** routine for **QUBO** problems using an ansatz in the **QLOQ** framework. You can expand this by:\n", "- Increasing the number of **layers** (e.g., `[\"Y\",\"X\"]`).\n", "- Using **CX** gates instead of CZ inside groups (`ctype=\"cx\"`).\n", "- Changing **group_sizes** or QUBO matrix to match your real problem.\n", "- Exploring advanced optimization methods or different cost functions beyond QUBO.\n", "\n", "This completes our demonstration of a **QUBO** problem solved by a **CVar-VQE**-like approach in **Perceval’s QLOQ** environment. QLOQ can be applied to various problems involving a variational circuit so it is recommended to refactor this example to your needs whether by changing the loss function to match your given problem or the circuit structure itself." ] } ], "metadata": { "language_info": { "name": "python" } }, "nbformat": 4, "nbformat_minor": 1 }