<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="4.3.3">Jekyll</generator><link href="https://nez0b.github.io/feed.xml" rel="self" type="application/atom+xml"/><link href="https://nez0b.github.io/" rel="alternate" type="text/html" hreflang="en"/><updated>2026-08-08T01:16:42+00:00</updated><id>https://nez0b.github.io/feed.xml</id><title type="html">blank</title><subtitle>PoJen Wang&apos;s research notes and projects in computational physics, quantum computing, quantum chemistry, and scientific software. </subtitle><entry><title type="html">Graph Coloring with Quantum Column Generation</title><link href="https://nez0b.github.io/blog/2025/graph_coloring_quantum_column_generation/" rel="alternate" type="text/html" title="Graph Coloring with Quantum Column Generation"/><published>2025-07-19T00:00:00+00:00</published><updated>2025-07-19T00:00:00+00:00</updated><id>https://nez0b.github.io/blog/2025/graph_coloring_quantum_column_generation</id><content type="html" xml:base="https://nez0b.github.io/blog/2025/graph_coloring_quantum_column_generation/"><![CDATA[<h2 id="introduction">Introduction</h2> <p>The Minimum Vertex Coloring Problem (MVCP) is a fundamental challenge in graph theory and combinatorial optimization. Given a graph, the goal is to assign colors to vertices such that no two adjacent vertices share the same color, using the minimum number of colors possible. This problem has numerous practical applications, from frequency assignment in wireless networks to register allocation in compilers.</p> <p>In this blog post, we explore a quantum-classical hybrid approach to solving the MVCP using column generation, as introduced by Wesley da Silva Coelho et al. <a href="https://arxiv.org/abs/2301.02637">2301.02637</a>. This method is particularly well-suited for neutral atom quantum computers, leveraging their native capabilities to solve combinatorial optimization problems.</p> <h2 id="reduced-master-problem-and-column-generation">Reduced Master Problem and Column Generation</h2> <p>Column generation is an iterative optimization methodology designed to tackle linear programming problems with an overwhelmingly large number of variables, often referred to as “columns.” In such cases, considering all variables explicitly becomes computationally infeasible.</p> <p>The Reduced Master Problem (RMP) is a constrained version of the original problem, formulated using only a limited subset of these variables. This restriction is not a limitation, but rather the key to the column generation approach. By starting with a manageable set of columns, the algorithm iteratively approaches a solution for problems with a prohibitively large number of potential options.</p> <h3 id="mathematical-formulation">Mathematical Formulation</h3> <p>In the context of MVCP, where columns represent independent sets of vertices, the RMP can be formulated with the objective of minimizing the number of selected independent sets:</p> \[\min \sum_{s \in S'} y_s\] <p>This aims to minimize the total number of colors used. The constraints ensure that every vertex $u$ in the graph’s vertex set $V$ is covered by exactly one selected independent set from $S’$:</p> \[\sum_{s \in S'} b_{us} y_s = 1, \quad \forall u \in V\] <p>Here, $b_{us}$ is a binary parameter that equals 1 if vertex $u$ is in independent set $s$, and 0 otherwise. During the iterative phase, the variables $y_s$, which indicate whether independent set $s$ is chosen, are relaxed to be continuous between 0 and 1:</p> \[0 \leq y_s \leq 1, \quad \forall s \in S'\] <h3 id="the-pricing-sub-problem">The Pricing Sub-Problem</h3> <p>The RMP serves two primary functions: it provides the current best solution given the available columns and generates dual variables that guide the search for new, beneficial columns in a related sub-problem called the Pricing Sub-Problem (PSP).</p> <p>For MVCP, the PSP is often formulated as a Maximum Weighted Independent Set (MWIS) problem. The objective is to find an independent set $s^*$ that maximizes the sum of the dual variables of its constituent vertices:</p> \[\max \sum_{u \in V} (w_u \cdot x_u)\] <p>where $x_u$ is 1 if vertex $u$ is in the independent set and 0 otherwise. The reduced cost $r_s$ of a potential new column (independent set $s$) is calculated as:</p> \[r_s = 1 - \sum_{u \in V} (w_u \cdot x_u)\] <p>A new column is deemed beneficial if its reduced cost is negative ($r_s &lt; 0$), which is equivalent to the sum of the dual variables for the vertices in the independent set being greater than 1.</p> <h2 id="classical-column-generation-solver">Classical Column Generation Solver</h2> <p>Let’s first demonstrate the algorithm and workflow with classical solvers using scipy’s linear programming solver <code class="language-plaintext highlighter-rouge">linprog</code> and mixed-integer LP solver <code class="language-plaintext highlighter-rouge">milp</code>.</p> <h3 id="implementation">Implementation</h3> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">from</span> <span class="n">scipy.optimize</span> <span class="kn">import</span> <span class="n">linprog</span><span class="p">,</span> <span class="n">milp</span><span class="p">,</span> <span class="n">OptimizeWarning</span>
<span class="kn">from</span> <span class="n">scipy.sparse</span> <span class="kn">import</span> <span class="n">lil_matrix</span>
<span class="kn">import</span> <span class="n">networkx</span> <span class="k">as</span> <span class="n">nx</span>
<span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>

<span class="k">def</span> <span class="nf">solve_mvcp_column_generation</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">graph_name</span><span class="o">=</span><span class="sh">"</span><span class="s">Graph</span><span class="sh">"</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Solves the Minimum Vertex Coloring Problem (MVCP) using classical column generation.
    </span><span class="sh">"""</span>
    <span class="n">num_vertices</span> <span class="o">=</span> <span class="n">G</span><span class="p">.</span><span class="nf">number_of_nodes</span><span class="p">()</span>
    <span class="n">original_nodes</span> <span class="o">=</span> <span class="nf">sorted</span><span class="p">(</span><span class="nf">list</span><span class="p">(</span><span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()))</span>
    <span class="n">node_to_idx</span> <span class="o">=</span> <span class="p">{</span><span class="n">node</span><span class="p">:</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">node</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">original_nodes</span><span class="p">)}</span>
    <span class="n">mapped_edges</span> <span class="o">=</span> <span class="p">[(</span><span class="n">node_to_idx</span><span class="p">[</span><span class="n">u</span><span class="p">],</span> <span class="n">node_to_idx</span><span class="p">[</span><span class="n">v</span><span class="p">])</span> <span class="k">for</span> <span class="n">u</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">G</span><span class="p">.</span><span class="nf">edges</span><span class="p">()]</span>
    <span class="n">edges</span> <span class="o">=</span> <span class="n">mapped_edges</span>

    <span class="c1"># Initialization with singleton independent sets
</span>    <span class="n">current_columns</span> <span class="o">=</span> <span class="p">[</span><span class="nf">frozenset</span><span class="p">([</span><span class="n">i</span><span class="p">])</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">num_vertices</span><span class="p">)]</span>
    <span class="n">known_column_signatures</span> <span class="o">=</span> <span class="p">{</span><span class="nf">tuple</span><span class="p">(</span><span class="nf">sorted</span><span class="p">(</span><span class="nf">list</span><span class="p">(</span><span class="n">s</span><span class="p">)))</span> <span class="k">for</span> <span class="n">s</span> <span class="ow">in</span> <span class="n">current_columns</span><span class="p">}</span>

    <span class="n">max_iterations</span> <span class="o">=</span> <span class="mi">20</span>
    <span class="n">iteration</span> <span class="o">=</span> <span class="mi">0</span>
    <span class="n">tolerance</span> <span class="o">=</span> <span class="mf">1e-6</span>

    <span class="c1"># Column generation loop
</span>    <span class="k">while</span> <span class="n">iteration</span> <span class="o">&lt;</span> <span class="n">max_iterations</span><span class="p">:</span>
        <span class="n">iteration</span> <span class="o">+=</span> <span class="mi">1</span>
        
        <span class="c1"># Solve Reduced Master Problem (RMP)
</span>        <span class="n">num_columns</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">current_columns</span><span class="p">)</span>
        <span class="n">num_constraints</span> <span class="o">=</span> <span class="n">num_vertices</span>

        <span class="c1"># Constraint matrix: A[u, s] = 1 if vertex u is in independent set s
</span>        <span class="n">A</span> <span class="o">=</span> <span class="nf">lil_matrix</span><span class="p">((</span><span class="n">num_constraints</span><span class="p">,</span> <span class="n">num_columns</span><span class="p">))</span>
        <span class="k">for</span> <span class="n">s_idx</span><span class="p">,</span> <span class="n">independent_set</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">current_columns</span><span class="p">):</span>
            <span class="k">for</span> <span class="n">vertex</span> <span class="ow">in</span> <span class="n">independent_set</span><span class="p">:</span>
                <span class="n">A</span><span class="p">[</span><span class="n">vertex</span><span class="p">,</span> <span class="n">s_idx</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>

        <span class="n">A</span> <span class="o">=</span> <span class="n">A</span><span class="p">.</span><span class="nf">tocsc</span><span class="p">()</span>
        <span class="n">b</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">num_constraints</span><span class="p">)</span>
        <span class="n">c</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">num_columns</span><span class="p">)</span>
        <span class="n">bounds</span> <span class="o">=</span> <span class="p">[(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="k">for</span> <span class="n">_</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">num_columns</span><span class="p">)]</span>

        <span class="n">rmp_result</span> <span class="o">=</span> <span class="nf">linprog</span><span class="p">(</span><span class="n">c</span><span class="p">,</span> <span class="n">A_eq</span><span class="o">=</span><span class="n">A</span><span class="p">,</span> <span class="n">b_eq</span><span class="o">=</span><span class="n">b</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="n">bounds</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="sh">'</span><span class="s">highs</span><span class="sh">'</span><span class="p">)</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="n">rmp_result</span><span class="p">.</span><span class="n">success</span><span class="p">:</span>
            <span class="k">break</span>

        <span class="c1"># Extract dual variables
</span>        <span class="n">dual_vars</span> <span class="o">=</span> <span class="n">rmp_result</span><span class="p">.</span><span class="n">eqlin</span><span class="p">.</span><span class="n">marginals</span>

        <span class="c1"># Solve Pricing Subproblem (PSP) - Maximum Weighted Independent Set
</span>        <span class="n">c_psp</span> <span class="o">=</span> <span class="o">-</span><span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">dual_vars</span><span class="p">)</span>
        <span class="n">integrality_psp</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">num_vertices</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
        <span class="n">bounds_psp</span> <span class="o">=</span> <span class="p">([</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_vertices</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="n">num_vertices</span><span class="p">)</span>

        <span class="c1"># Edge constraints
</span>        <span class="n">num_edges</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">edges</span><span class="p">)</span>
        <span class="n">A_ub_psp</span> <span class="o">=</span> <span class="nf">lil_matrix</span><span class="p">((</span><span class="n">num_edges</span><span class="p">,</span> <span class="n">num_vertices</span><span class="p">),</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">float</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">edge</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">edges</span><span class="p">):</span>
            <span class="n">A_ub_psp</span><span class="p">[</span><span class="n">i</span><span class="p">,</span> <span class="n">edge</span><span class="p">[</span><span class="mi">0</span><span class="p">]]</span> <span class="o">=</span> <span class="mi">1</span>
            <span class="n">A_ub_psp</span><span class="p">[</span><span class="n">i</span><span class="p">,</span> <span class="n">edge</span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span> <span class="o">=</span> <span class="mi">1</span>
        <span class="n">b_ub_psp</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">num_edges</span><span class="p">)</span>

        <span class="kn">from</span> <span class="n">scipy.optimize</span> <span class="kn">import</span> <span class="n">LinearConstraint</span><span class="p">,</span> <span class="n">Bounds</span>
        <span class="n">psp_constraints</span> <span class="o">=</span> <span class="p">[</span><span class="nc">LinearConstraint</span><span class="p">(</span><span class="n">A_ub_psp</span><span class="p">.</span><span class="nf">toarray</span><span class="p">(),</span> <span class="o">-</span><span class="n">np</span><span class="p">.</span><span class="n">inf</span><span class="p">,</span> <span class="n">b_ub_psp</span><span class="p">)]</span>
        <span class="n">psp_result</span> <span class="o">=</span> <span class="nf">milp</span><span class="p">(</span><span class="n">c</span><span class="o">=</span><span class="n">c_psp</span><span class="p">,</span> <span class="n">constraints</span><span class="o">=</span><span class="n">psp_constraints</span><span class="p">,</span> 
                         <span class="n">integrality</span><span class="o">=</span><span class="n">integrality_psp</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="n">bounds_psp</span><span class="p">)</span>

        <span class="k">if</span> <span class="ow">not</span> <span class="n">psp_result</span><span class="p">.</span><span class="n">success</span><span class="p">:</span>
            <span class="k">break</span>

        <span class="n">new_independent_set_indices</span> <span class="o">=</span> <span class="p">[</span><span class="n">v</span> <span class="k">for</span> <span class="n">v</span><span class="p">,</span> <span class="n">val</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">psp_result</span><span class="p">.</span><span class="n">x</span><span class="p">)</span> <span class="k">if</span> <span class="n">val</span> <span class="o">&gt;</span> <span class="mf">0.5</span><span class="p">]</span>
        <span class="n">new_independent_set</span> <span class="o">=</span> <span class="nf">set</span><span class="p">(</span><span class="n">new_independent_set_indices</span><span class="p">)</span>
        <span class="n">psp_objective</span> <span class="o">=</span> <span class="o">-</span><span class="n">psp_result</span><span class="p">.</span><span class="n">fun</span>

        <span class="c1"># Check if the new column improves the solution
</span>        <span class="n">reduced_cost</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">psp_objective</span>
        <span class="k">if</span> <span class="n">reduced_cost</span> <span class="o">&gt;=</span> <span class="o">-</span><span class="n">tolerance</span><span class="p">:</span>
            <span class="k">break</span>

        <span class="c1"># Add new column if beneficial
</span>        <span class="n">new_column_signature</span> <span class="o">=</span> <span class="nf">tuple</span><span class="p">(</span><span class="nf">sorted</span><span class="p">(</span><span class="nf">list</span><span class="p">(</span><span class="n">new_independent_set</span><span class="p">)))</span>
        <span class="k">if</span> <span class="n">new_column_signature</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">known_column_signatures</span><span class="p">:</span>
            <span class="n">current_columns</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">frozenset</span><span class="p">(</span><span class="n">new_independent_set</span><span class="p">))</span>
            <span class="n">known_column_signatures</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">new_column_signature</span><span class="p">)</span>
        <span class="k">else</span><span class="p">:</span>
            <span class="k">break</span>

    <span class="c1"># Solve final integer problem
</span>    <span class="n">final_num_columns</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">current_columns</span><span class="p">)</span>
    <span class="n">final_A</span> <span class="o">=</span> <span class="nf">lil_matrix</span><span class="p">((</span><span class="n">num_constraints</span><span class="p">,</span> <span class="n">final_num_columns</span><span class="p">))</span>
    <span class="k">for</span> <span class="n">s_idx</span><span class="p">,</span> <span class="n">independent_set</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">current_columns</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">vertex</span> <span class="ow">in</span> <span class="n">independent_set</span><span class="p">:</span>
            <span class="n">final_A</span><span class="p">[</span><span class="n">vertex</span><span class="p">,</span> <span class="n">s_idx</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>

    <span class="n">final_A</span> <span class="o">=</span> <span class="n">final_A</span><span class="p">.</span><span class="nf">tocsc</span><span class="p">()</span>
    <span class="n">final_c</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">final_num_columns</span><span class="p">)</span>
    <span class="n">final_b</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">num_constraints</span><span class="p">)</span>

    <span class="n">integrality</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">final_num_columns</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
    <span class="n">constraints</span> <span class="o">=</span> <span class="nc">LinearConstraint</span><span class="p">(</span><span class="n">final_A</span><span class="p">,</span> <span class="n">final_b</span><span class="p">,</span> <span class="n">final_b</span><span class="p">)</span>
    <span class="n">bounds</span> <span class="o">=</span> <span class="nc">Bounds</span><span class="p">(</span><span class="n">lb</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">ub</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
    
    <span class="n">final_rmp_result</span> <span class="o">=</span> <span class="nf">milp</span><span class="p">(</span><span class="n">c</span><span class="o">=</span><span class="n">final_c</span><span class="p">,</span> <span class="n">integrality</span><span class="o">=</span><span class="n">integrality</span><span class="p">,</span> 
                           <span class="n">constraints</span><span class="o">=</span><span class="n">constraints</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="n">bounds</span><span class="p">)</span>

    <span class="c1"># Build coloring solution
</span>    <span class="n">coloring_solution_list</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">if</span> <span class="n">final_rmp_result</span><span class="p">.</span><span class="n">success</span><span class="p">:</span>
        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">y_val</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">final_rmp_result</span><span class="p">.</span><span class="n">x</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">y_val</span> <span class="o">&gt;</span> <span class="mf">0.5</span><span class="p">:</span>
                <span class="n">original_color_set</span> <span class="o">=</span> <span class="p">{</span><span class="n">original_nodes</span><span class="p">[</span><span class="n">node_idx</span><span class="p">]</span> <span class="k">for</span> <span class="n">node_idx</span> <span class="ow">in</span> <span class="n">current_columns</span><span class="p">[</span><span class="n">i</span><span class="p">]}</span>
                <span class="n">coloring_solution_list</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">frozenset</span><span class="p">(</span><span class="n">original_color_set</span><span class="p">))</span>

    <span class="k">return</span> <span class="n">final_rmp_result</span><span class="p">,</span> <span class="n">current_columns</span><span class="p">,</span> <span class="n">coloring_solution_list</span>
</code></pre></div></div> <h3 id="examples">Examples</h3> <p>Let’s demonstrate the algorithm with two examples:</p> <h4 id="example-1-5-vertex-graph">Example 1: 5-Vertex Graph</h4> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">G1</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">Graph</span><span class="p">()</span>
<span class="n">G1</span><span class="p">.</span><span class="nf">add_nodes_from</span><span class="p">(</span><span class="nf">range</span><span class="p">(</span><span class="mi">5</span><span class="p">))</span>
<span class="n">G1</span><span class="p">.</span><span class="nf">add_edges_from</span><span class="p">([(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">),</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">)])</span>
<span class="n">result1</span><span class="p">,</span> <span class="n">columns1</span><span class="p">,</span> <span class="n">classical_coloring1</span> <span class="o">=</span> <span class="nf">solve_mvcp_column_generation</span><span class="p">(</span><span class="n">G1</span><span class="p">,</span> <span class="sh">"</span><span class="s">5-Vertex Example</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <p><a id="fig:5vertex-coloring"></a> <img src="/assets/img/qcolumn_generation/5vertex_coloring.png" alt="5-Vertex Graph Coloring" width="700"/></p> <p>The classical column generation algorithm successfully finds a 3-coloring for this graph, generating 9 columns in the process.</p> <h4 id="example-2-random-9-vertex-graph">Example 2: Random 9-Vertex Graph</h4> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">G2</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">gnp_random_graph</span><span class="p">(</span><span class="mi">9</span><span class="p">,</span> <span class="mf">0.4</span><span class="p">,</span> <span class="n">seed</span><span class="o">=</span><span class="mi">42</span><span class="p">)</span>
<span class="n">result2</span><span class="p">,</span> <span class="n">columns2</span><span class="p">,</span> <span class="n">classical_coloring2</span> <span class="o">=</span> <span class="nf">solve_mvcp_column_generation</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="sh">"</span><span class="s">Random 9-Vertex</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <p><a id="fig:random9-coloring"></a> <img src="/assets/img/qcolumn_generation/random9_coloring.png" alt="Random 9-Vertex Graph Coloring" width="700"/></p> <p>For the random graph, the algorithm finds a 3-coloring using 21 generated columns.</p> <h2 id="quantum-column-generation-solver">Quantum Column Generation Solver</h2> <p>Now let’s explore how quantum computing, specifically neutral atom quantum computers, can help accelerate the column generation process.</p> <h3 id="neutral-atom-quantum-computers">Neutral Atom Quantum Computers</h3> <p>Neutral atom arrays have emerged as a highly promising platform for quantum computation and simulation. In these systems, individual neutral atoms are trapped using optical tweezers, allowing for precise arrangement in 1D, 2D, or even 3D geometries. Quantum information is typically encoded in two electronic states of each atom: a ground state $ \vert g \rangle $ and a highly excited Rydberg state $ \vert r \rangle $. Lasers are used to drive transitions between these states ($\Omega$) and control their energy difference (detuning $\delta$).</p> <p>A key feature is the strong, long-range interaction between atoms in the Rydberg state $ \vert r \rangle $. This interaction falls off rapidly with distance $R$ (typically as \(C_6/R^6\)).</p> <h3 id="the-blockade-mechanism">The Blockade Mechanism</h3> <p>The strong Rydberg interaction leads to the <strong>Rydberg blockade</strong> effect: if one atom is excited to $ \vert r \rangle $, the energy levels of nearby atoms are shifted significantly, preventing them from being resonantly excited to $ \vert r \rangle $ by the same laser field. This blockade occurs within a characteristic radius $R_b$.</p> <p>Effectively, this means that only atoms separated by a distance greater than $R_b$ can be simultaneously excited to the Rydberg state. This naturally implements the constraint of an <strong>Independent Set</strong> on a graph where atoms are vertices and edges connect atoms closer than $R_b$. Such a graph, where connectivity is determined solely by distance, is known as a <strong>Unit-Disk Graph (UDG)</strong>.</p> <h3 id="using-maximum-independent-set-package">Using maximum-independent-set package</h3> <p>The <code class="language-plaintext highlighter-rouge">maximum-independent-set</code> package provides a high-level API to solve the maximum independent set problems with neutral atom quantum computers/emulators. Here’s a simple example of how to use it:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">mis</span> <span class="kn">import</span> <span class="n">MISInstance</span><span class="p">,</span> <span class="n">MISSolver</span><span class="p">,</span> <span class="n">BackendConfig</span><span class="p">,</span> <span class="n">BackendType</span>
<span class="kn">from</span> <span class="n">mis.pipeline.config</span> <span class="kn">import</span> <span class="n">SolverConfig</span>
<span class="kn">from</span> <span class="n">mis.pipeline.pulse</span> <span class="kn">import</span> <span class="n">BasePulseShaper</span>
<span class="kn">from</span> <span class="n">mis.pipeline.embedder</span> <span class="kn">import</span> <span class="n">BaseEmbedder</span>
<span class="kn">from</span> <span class="n">mis.shared.types</span> <span class="kn">import</span> <span class="n">MethodType</span>

<span class="c1"># Custom embedder to map graph to atom positions
</span><span class="k">class</span> <span class="nc">MyEmbedder</span><span class="p">(</span><span class="n">BaseEmbedder</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Fixed embedder with proper coordinate preservation and scaling.
    </span><span class="sh">"""</span>
    <span class="k">def</span> <span class="nf">embed</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">instance</span><span class="p">:</span> <span class="n">MISInstance</span><span class="p">,</span> <span class="n">config</span><span class="p">:</span> <span class="n">SolverConfig</span><span class="p">,</span> <span class="n">backend</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Register</span><span class="p">:</span>
        <span class="n">device</span> <span class="o">=</span> <span class="n">backend</span><span class="p">.</span><span class="nf">device</span><span class="p">()</span>
        <span class="n">positions</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">get_node_attributes</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="n">graph</span><span class="p">,</span> <span class="sh">'</span><span class="s">pos</span><span class="sh">'</span><span class="p">)</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="n">positions</span><span class="p">:</span>
            <span class="n">positions</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">spring_layout</span><span class="p">(</span><span class="n">instance</span><span class="p">.</span><span class="n">graph</span><span class="p">,</span> <span class="n">iterations</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
        <span class="n">positions</span> <span class="o">=</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">positions</span><span class="p">.</span><span class="nf">items</span><span class="p">()}</span>
        
        <span class="c1"># Center positions
</span>        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">positions</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">:</span>
            <span class="n">all_coords</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">stack</span><span class="p">(</span><span class="nf">list</span><span class="p">(</span><span class="n">positions</span><span class="p">.</span><span class="nf">values</span><span class="p">()))</span>
            <span class="n">centroid</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">mean</span><span class="p">(</span><span class="n">all_coords</span><span class="p">,</span> <span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
            <span class="n">positions</span> <span class="o">=</span> <span class="p">{</span><span class="n">node</span><span class="p">:</span> <span class="n">pos</span> <span class="o">-</span> <span class="n">centroid</span> <span class="k">for</span> <span class="n">node</span><span class="p">,</span> <span class="n">pos</span> <span class="ow">in</span> <span class="n">positions</span><span class="p">.</span><span class="nf">items</span><span class="p">()}</span>

        <span class="c1"># Scale to respect minimum atom distance
</span>        <span class="n">distances</span> <span class="o">=</span> <span class="p">[</span>
            <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="nf">norm</span><span class="p">(</span><span class="n">positions</span><span class="p">[</span><span class="n">v1</span><span class="p">]</span> <span class="o">-</span> <span class="n">positions</span><span class="p">[</span><span class="n">v2</span><span class="p">])</span>
            <span class="k">for</span> <span class="n">v1</span> <span class="ow">in</span> <span class="n">instance</span><span class="p">.</span><span class="n">graph</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()</span>
            <span class="k">for</span> <span class="n">v2</span> <span class="ow">in</span> <span class="n">instance</span><span class="p">.</span><span class="n">graph</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()</span>
            <span class="k">if</span> <span class="n">v1</span> <span class="o">!=</span> <span class="n">v2</span>
        <span class="p">]</span>
        
        <span class="k">if</span> <span class="nf">len</span><span class="p">(</span><span class="n">distances</span><span class="p">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
            <span class="n">min_distance</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">min</span><span class="p">(</span><span class="n">distances</span><span class="p">)</span>
            <span class="n">max_distance</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">max</span><span class="p">(</span><span class="n">distances</span><span class="p">)</span>
            
            <span class="k">if</span> <span class="n">min_distance</span> <span class="o">&lt;</span> <span class="n">device</span><span class="p">.</span><span class="n">min_atom_distance</span><span class="p">:</span>
                <span class="n">multiplier</span> <span class="o">=</span> <span class="n">device</span><span class="p">.</span><span class="n">min_atom_distance</span> <span class="o">/</span> <span class="n">min_distance</span>
                <span class="n">positions</span> <span class="o">=</span> <span class="p">{</span><span class="n">i</span><span class="p">:</span> <span class="n">v</span> <span class="o">*</span> <span class="n">multiplier</span> <span class="nf">for </span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="ow">in</span> <span class="n">positions</span><span class="p">.</span><span class="nf">items</span><span class="p">()}</span>
                <span class="n">max_distance_scaled</span> <span class="o">=</span> <span class="n">max_distance</span> <span class="o">*</span> <span class="n">multiplier</span>
            <span class="k">else</span><span class="p">:</span>
                <span class="n">max_distance_scaled</span> <span class="o">=</span> <span class="n">max_distance</span>
        
        <span class="n">instance</span><span class="p">.</span><span class="n">graph</span><span class="p">.</span><span class="n">graph</span><span class="p">[</span><span class="sh">'</span><span class="s">max_distance_scaled</span><span class="sh">'</span><span class="p">]</span> <span class="o">=</span> <span class="n">max_distance_scaled</span>

        <span class="k">return</span> <span class="nc">Register</span><span class="p">(</span>
            <span class="n">qubits</span><span class="o">=</span><span class="p">{</span><span class="sa">f</span><span class="sh">"</span><span class="s">q</span><span class="si">{</span><span class="n">node</span><span class="si">}</span><span class="sh">"</span><span class="p">:</span> <span class="n">pos</span> <span class="nf">for </span><span class="p">(</span><span class="n">node</span><span class="p">,</span> <span class="n">pos</span><span class="p">)</span> <span class="ow">in</span> <span class="n">positions</span><span class="p">.</span><span class="nf">items</span><span class="p">()}</span>
        <span class="p">)</span>
</code></pre></div></div> <p>The embedder maps the graph vertices to physical atom positions on the quantum device, respecting the minimum distance constraints between atoms. The visualization below shows an authentic Pulser-generated atom register layout:</p> <p><a id="fig:atom-register"></a> <img src="/assets/img/qcolumn_generation/atom_register_visualization.png" alt="Atom Register Visualization" width="700"/></p> <h3 id="debug-and-visualization">Debug and Visualization</h3> <p>The quantum MIS solver can be configured and visualized to understand how the quantum algorithm maps the graph problem onto the physical quantum device:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Configure the MISSolver
</span><span class="n">config</span> <span class="o">=</span> <span class="nc">SolverConfig</span><span class="p">(</span>
    <span class="n">method</span><span class="o">=</span><span class="n">MethodType</span><span class="p">.</span><span class="n">EAGER</span><span class="p">,</span>
    <span class="n">backend</span><span class="o">=</span><span class="nc">BackendConfig</span><span class="p">(</span><span class="n">backend_type</span><span class="o">=</span><span class="n">BackendType</span><span class="p">.</span><span class="n">QUTIP</span><span class="p">),</span>
    <span class="n">embedder</span><span class="o">=</span><span class="nc">MyEmbedder</span><span class="p">(),</span>
    <span class="n">pulse_shaper</span><span class="o">=</span><span class="nc">MyPulseShaper</span><span class="p">(</span><span class="n">duration_us</span><span class="o">=</span><span class="mi">4000</span><span class="p">),</span>
    <span class="n">preprocessor</span><span class="o">=</span><span class="bp">None</span><span class="p">,</span>
    <span class="n">max_number_of_solutions</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
    <span class="n">max_iterations</span><span class="o">=</span><span class="mi">1</span><span class="p">,</span>
<span class="p">)</span>

<span class="n">solver</span> <span class="o">=</span> <span class="nc">MISSolver</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>

<span class="c1"># Visualize the atom register and blockade radius
</span><span class="n">solver</span><span class="p">.</span><span class="n">_solver</span><span class="p">.</span><span class="n">_register</span><span class="p">.</span><span class="nf">draw</span><span class="p">(</span>
    <span class="n">blockade_radius</span><span class="o">=</span><span class="mf">1.2</span><span class="o">*</span><span class="mi">13</span><span class="p">,</span>
    <span class="n">draw_graph</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">draw_half_radius</span><span class="o">=</span><span class="bp">True</span>
<span class="p">)</span>
</code></pre></div></div> <p>The pulse sequence shows how the quantum algorithm manipulates the atomic states over time to find independent sets. Below is an authentic Pulser-generated pulse sequence visualization:</p> <p><a id="fig:pulse-sequence"></a> <img src="/assets/img/qcolumn_generation/pulse_sequence.png" alt="Pulse Sequence Visualization" width="700"/></p> <p>We can also compare the performance of quantum vs classical column generation on smaller graphs. For a random graph G2, both methods achieve similar solutions:</p> <p><a id="fig:quantum-vs-classical"></a> <img src="/assets/img/qcolumn_generation/quantum_vs_classical_g2.png" alt="Quantum vs Classical Comparison" width="700"/></p> <p>The quantum solver uses the PSP (Pricing Sub-Problem) as a MWIS problem on a subgraph containing only nodes with positive dual variables. This leverages the quantum computer’s ability to naturally find independent sets through the Rydberg blockade mechanism.</p> <h2 id="application-antenna-frequency-assignment">Application: Antenna Frequency Assignment</h2> <p>Let’s apply our quantum column generation approach to a real-world problem: minimizing antenna frequencies in a 5G network.</p> <h3 id="problem-setup">Problem Setup</h3> <p>The dataset represents the geographical placement of 5G antennas across Paris. Each antenna has a specific coverage range, and antennas within interfering distance cannot use the same frequency. This is essentially a graph coloring problem where:</p> <ul> <li>Antennas are vertices</li> <li>Edges connect antennas within interference range (1.2 km)</li> <li>Colors represent frequencies</li> </ul> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Load antenna data
</span><span class="kn">from</span> <span class="n">mis.data.dataloader</span> <span class="kn">import</span> <span class="n">DataLoader</span>
<span class="kn">import</span> <span class="n">pathlib</span>

<span class="n">csv_path</span> <span class="o">=</span> <span class="n">pathlib</span><span class="p">.</span><span class="nc">Path</span><span class="p">(</span><span class="sh">'</span><span class="s">./datasets/coloring/antenna_Paris.csv</span><span class="sh">'</span><span class="p">)</span>
<span class="n">loader</span> <span class="o">=</span> <span class="nc">DataLoader</span><span class="p">()</span>
<span class="n">loader</span><span class="p">.</span><span class="nf">load_from_csv_coordinates</span><span class="p">(</span><span class="n">csv_path</span><span class="p">)</span>

<span class="c1"># Build graph with 1.2 km interference range
</span><span class="n">antenna_range</span> <span class="o">=</span> <span class="mf">1.2</span>  <span class="c1"># kilometers
</span><span class="n">mis_instance</span> <span class="o">=</span> <span class="n">loader</span><span class="p">.</span><span class="nf">build_mis_instance_from_coordinates</span><span class="p">(</span><span class="n">antenna_range</span><span class="p">)</span>
<span class="n">G</span> <span class="o">=</span> <span class="n">mis_instance</span><span class="p">.</span><span class="n">graph</span>

<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Graph has </span><span class="si">{</span><span class="n">G</span><span class="p">.</span><span class="nf">number_of_nodes</span><span class="p">()</span><span class="si">}</span><span class="s"> nodes and </span><span class="si">{</span><span class="n">G</span><span class="p">.</span><span class="nf">number_of_edges</span><span class="p">()</span><span class="si">}</span><span class="s"> edges</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <p>The full antenna network spans across Paris with 126 real 5G antennas:</p> <p><a id="fig:antenna-network-full"></a> <img src="/assets/img/qcolumn_generation/antenna_network_full.png" alt="Full Antenna Network" width="700"/></p> <p>For demonstration, we extract a connected subgraph of 12 antennas to test our algorithms:</p> <p><a id="fig:antenna-subgraph"></a> <img src="/assets/img/qcolumn_generation/antenna_subgraph_12.png" alt="12-Antenna Subgraph" width="700"/></p> <h3 id="quantum-cg-solver">Quantum CG Solver</h3> <p>The quantum column generation solver replaces the classical PSP solver with a quantum MIS solver:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">solve_quantum_psp_with_mis</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">dual_vars</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Solve the Pricing Subproblem using quantum MIS solver.
    </span><span class="sh">"""</span>
    <span class="c1"># Filter nodes with positive dual variables
</span>    <span class="n">positive_dual_nodes</span> <span class="o">=</span> <span class="p">[</span><span class="n">node</span> <span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()</span> <span class="k">if</span> <span class="n">dual_vars</span><span class="p">[</span><span class="n">node</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">1e-5</span><span class="p">]</span>
    
    <span class="k">if</span> <span class="ow">not</span> <span class="n">positive_dual_nodes</span><span class="p">:</span>
        <span class="k">return</span> <span class="p">[]</span>
    
    <span class="c1"># Create subgraph with only positive dual nodes
</span>    <span class="n">G_prime</span> <span class="o">=</span> <span class="n">G</span><span class="p">.</span><span class="nf">subgraph</span><span class="p">(</span><span class="n">positive_dual_nodes</span><span class="p">).</span><span class="nf">copy</span><span class="p">()</span>
    
    <span class="c1"># Create MIS instance and solve
</span>    <span class="n">instance</span> <span class="o">=</span> <span class="nc">MISInstance</span><span class="p">(</span><span class="n">G_prime</span><span class="p">)</span>
    <span class="n">config</span> <span class="o">=</span> <span class="nc">SolverConfig</span><span class="p">(</span>
        <span class="n">method</span><span class="o">=</span><span class="n">MethodType</span><span class="p">.</span><span class="n">EAGER</span><span class="p">,</span>
        <span class="n">backend</span><span class="o">=</span><span class="nc">BackendConfig</span><span class="p">(</span><span class="n">backend_type</span><span class="o">=</span><span class="n">BackendType</span><span class="p">.</span><span class="n">QUTIP</span><span class="p">),</span>
        <span class="n">embedder</span><span class="o">=</span><span class="nc">MyEmbedder</span><span class="p">(),</span>
        <span class="n">pulse_shaper</span><span class="o">=</span><span class="nc">MyPulseShaper</span><span class="p">(</span><span class="n">duration_us</span><span class="o">=</span><span class="mi">4000</span><span class="p">),</span>
        <span class="n">max_number_of_solutions</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span>
        <span class="n">runs</span><span class="o">=</span><span class="mi">500</span><span class="p">,</span>
    <span class="p">)</span>
    
    <span class="n">solver</span> <span class="o">=</span> <span class="nc">MISSolver</span><span class="p">(</span><span class="n">instance</span><span class="p">,</span> <span class="n">config</span><span class="p">)</span>
    <span class="n">solution_reports</span> <span class="o">=</span> <span class="n">solver</span><span class="p">.</span><span class="nf">solve</span><span class="p">()</span>
    
    <span class="c1"># Extract profitable independent sets
</span>    <span class="n">profitable_sets</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">for</span> <span class="n">report</span> <span class="ow">in</span> <span class="n">solution_reports</span><span class="p">:</span>
        <span class="k">if</span> <span class="n">report</span><span class="p">.</span><span class="n">nodes</span><span class="p">:</span>
            <span class="n">current_is_set</span> <span class="o">=</span> <span class="nf">set</span><span class="p">(</span><span class="n">report</span><span class="p">.</span><span class="n">nodes</span><span class="p">)</span>
            <span class="n">total_weight</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span><span class="n">dual_vars</span><span class="p">[</span><span class="n">node</span><span class="p">]</span> <span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">current_is_set</span><span class="p">)</span>
            <span class="n">reduced_cost</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">-</span> <span class="n">total_weight</span>
            
            <span class="k">if</span> <span class="n">reduced_cost</span> <span class="o">&lt;</span> <span class="o">-</span><span class="mf">1e-5</span><span class="p">:</span>  <span class="c1"># Profitable if weight &gt; 1
</span>                <span class="n">profitable_sets</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">current_is_set</span><span class="p">)</span>
    
    <span class="k">return</span> <span class="n">profitable_sets</span>
</code></pre></div></div> <h3 id="classical-cg-solver">Classical CG Solver</h3> <p>For comparison, we also run the classical column generation solver on the same antenna subgraph. The classical solution uses 4 frequencies:</p> <p><a id="fig:antenna-classical"></a> <img src="/assets/img/qcolumn_generation/antenna_classical_solution.png" alt="Classical Column Generation Solution" width="700"/></p> <h3 id="quantum-cg-solver-1">Quantum CG Solver</h3> <p>The quantum column generation solver finds a solution using 4 frequencies, leveraging the quantum MIS solver for the pricing subproblem:</p> <p><a id="fig:antenna-quantum"></a> <img src="/assets/img/qcolumn_generation/antenna_quantum_solution.png" alt="Quantum Column Generation Solution" width="700"/></p> <h3 id="direct-milp-formulation">Direct MILP Formulation</h3> <p>As a baseline, we implement a direct MILP formulation for the MVCP:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">solve_mvcp_direct_milp</span><span class="p">(</span><span class="n">G</span><span class="p">,</span> <span class="n">timeout_seconds</span><span class="o">=</span><span class="mi">300</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Solves MVCP using direct MILP formulation.
    
    Variables: x[v,c] ∈ {0,1} (vertex v gets color c), y[c] ∈ {0,1} (color c is used)
    Objective: minimize ∑ y[c]
    Constraints: 
      - Each vertex gets exactly one color: ∑_c x[v,c] = 1 ∀v
      - Adjacent vertices have different colors: x[u,c] + x[v,c] ≤ y[c] ∀(u,v)∈E, ∀c
      - Color usage: x[v,c] ≤ y[c] ∀v,c
    </span><span class="sh">"""</span>
    <span class="n">n</span> <span class="o">=</span> <span class="n">G</span><span class="p">.</span><span class="nf">number_of_nodes</span><span class="p">()</span>
    <span class="n">nodes</span> <span class="o">=</span> <span class="nf">sorted</span><span class="p">(</span><span class="n">G</span><span class="p">.</span><span class="nf">nodes</span><span class="p">())</span>
    <span class="n">node_to_idx</span> <span class="o">=</span> <span class="p">{</span><span class="n">node</span><span class="p">:</span> <span class="n">i</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">node</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">nodes</span><span class="p">)}</span>
    
    <span class="c1"># Upper bound on colors (worst case: each vertex gets its own color)
</span>    <span class="n">max_colors</span> <span class="o">=</span> <span class="n">n</span>
    
    <span class="c1"># Decision variables:
</span>    <span class="c1"># x[v,c] variables: vertex v gets color c
</span>    <span class="c1"># y[c] variables: color c is used
</span>    <span class="n">num_x</span> <span class="o">=</span> <span class="n">n</span> <span class="o">*</span> <span class="n">max_colors</span>  <span class="c1"># x[v,c] variables
</span>    <span class="n">num_y</span> <span class="o">=</span> <span class="n">max_colors</span>      <span class="c1"># y[c] variables
</span>    <span class="n">total_vars</span> <span class="o">=</span> <span class="n">num_x</span> <span class="o">+</span> <span class="n">num_y</span>
    
    <span class="c1"># Objective: minimize sum of y[c] (minimize number of colors used)
</span>    <span class="n">c_obj</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">total_vars</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">color</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">max_colors</span><span class="p">):</span>
        <span class="n">c_obj</span><span class="p">[</span><span class="n">num_x</span> <span class="o">+</span> <span class="n">color</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>  <span class="c1"># Coefficient for y[c] variables
</span>    
    <span class="n">constraints</span> <span class="o">=</span> <span class="p">[]</span>
    
    <span class="c1"># Constraint 1: Each vertex gets exactly one color
</span>    <span class="c1"># ∑_c x[v,c] = 1 ∀v
</span>    <span class="k">for</span> <span class="n">v_idx</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="n">A_row</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">total_vars</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">max_colors</span><span class="p">):</span>
            <span class="n">A_row</span><span class="p">[</span><span class="n">v_idx</span> <span class="o">*</span> <span class="n">max_colors</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>  <span class="c1"># x[v,c]
</span>        <span class="n">constraints</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nc">LinearConstraint</span><span class="p">(</span><span class="n">A_row</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">))</span>
    
    <span class="c1"># Constraint 2: Adjacent vertices have different colors
</span>    <span class="c1"># x[u,c] + x[v,c] ≤ y[c] ∀(u,v)∈E, ∀c
</span>    <span class="c1"># Rewritten as: x[u,c] + x[v,c] - y[c] ≤ 0
</span>    <span class="k">for</span> <span class="n">u</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">G</span><span class="p">.</span><span class="nf">edges</span><span class="p">():</span>
        <span class="n">u_idx</span> <span class="o">=</span> <span class="n">node_to_idx</span><span class="p">[</span><span class="n">u</span><span class="p">]</span>
        <span class="n">v_idx</span> <span class="o">=</span> <span class="n">node_to_idx</span><span class="p">[</span><span class="n">v</span><span class="p">]</span>
        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">max_colors</span><span class="p">):</span>
            <span class="n">A_row</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">total_vars</span><span class="p">)</span>
            <span class="n">A_row</span><span class="p">[</span><span class="n">u_idx</span> <span class="o">*</span> <span class="n">max_colors</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>    <span class="c1"># x[u,c]
</span>            <span class="n">A_row</span><span class="p">[</span><span class="n">v_idx</span> <span class="o">*</span> <span class="n">max_colors</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>    <span class="c1"># x[v,c]
</span>            <span class="n">A_row</span><span class="p">[</span><span class="n">num_x</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>                <span class="c1"># -y[c]
</span>            <span class="n">constraints</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nc">LinearConstraint</span><span class="p">(</span><span class="n">A_row</span><span class="p">,</span> <span class="o">-</span><span class="n">np</span><span class="p">.</span><span class="n">inf</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
    
    <span class="c1"># Constraint 3: Color usage constraint
</span>    <span class="c1"># x[v,c] ≤ y[c] ∀v,c
</span>    <span class="c1"># Rewritten as: x[v,c] - y[c] ≤ 0
</span>    <span class="k">for</span> <span class="n">v_idx</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">max_colors</span><span class="p">):</span>
            <span class="n">A_row</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">zeros</span><span class="p">(</span><span class="n">total_vars</span><span class="p">)</span>
            <span class="n">A_row</span><span class="p">[</span><span class="n">v_idx</span> <span class="o">*</span> <span class="n">max_colors</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>   <span class="c1"># x[v,c]
</span>            <span class="n">A_row</span><span class="p">[</span><span class="n">num_x</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span>               <span class="c1"># -y[c]
</span>            <span class="n">constraints</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nc">LinearConstraint</span><span class="p">(</span><span class="n">A_row</span><span class="p">,</span> <span class="o">-</span><span class="n">np</span><span class="p">.</span><span class="n">inf</span><span class="p">,</span> <span class="mi">0</span><span class="p">))</span>
    
    <span class="c1"># Variable bounds: all variables are binary
</span>    <span class="n">bounds</span> <span class="o">=</span> <span class="nc">Bounds</span><span class="p">(</span><span class="n">lb</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">ub</span><span class="o">=</span><span class="mi">1</span><span class="p">)</span>
    <span class="n">integrality</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">ones</span><span class="p">(</span><span class="n">total_vars</span><span class="p">,</span> <span class="n">dtype</span><span class="o">=</span><span class="nb">int</span><span class="p">)</span>
    
    <span class="c1"># Solve the MILP
</span>    <span class="n">result</span> <span class="o">=</span> <span class="nf">milp</span><span class="p">(</span><span class="n">c</span><span class="o">=</span><span class="n">c_obj</span><span class="p">,</span> <span class="n">constraints</span><span class="o">=</span><span class="n">constraints</span><span class="p">,</span> <span class="n">bounds</span><span class="o">=</span><span class="n">bounds</span><span class="p">,</span> 
                  <span class="n">integrality</span><span class="o">=</span><span class="n">integrality</span><span class="p">,</span> <span class="n">options</span><span class="o">=</span><span class="p">{</span><span class="sh">'</span><span class="s">time_limit</span><span class="sh">'</span><span class="p">:</span> <span class="n">timeout_seconds</span><span class="p">})</span>
    
    <span class="c1"># Extract coloring solution
</span>    <span class="n">coloring</span> <span class="o">=</span> <span class="p">[]</span>
    <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">success</span><span class="p">:</span>
        <span class="c1"># Find which colors are used
</span>        <span class="n">used_colors</span> <span class="o">=</span> <span class="p">[]</span>
        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">max_colors</span><span class="p">):</span>
            <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">x</span><span class="p">[</span><span class="n">num_x</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">0.5</span><span class="p">:</span>  <span class="c1"># y[c] = 1
</span>                <span class="n">used_colors</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
        
        <span class="c1"># Build color sets
</span>        <span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="n">used_colors</span><span class="p">:</span>
            <span class="n">color_set</span> <span class="o">=</span> <span class="nf">set</span><span class="p">()</span>
            <span class="k">for</span> <span class="n">v_idx</span><span class="p">,</span> <span class="n">node</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">nodes</span><span class="p">):</span>
                <span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">x</span><span class="p">[</span><span class="n">v_idx</span> <span class="o">*</span> <span class="n">max_colors</span> <span class="o">+</span> <span class="n">c</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mf">0.5</span><span class="p">:</span>  <span class="c1"># x[v,c] = 1
</span>                    <span class="n">color_set</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">node</span><span class="p">)</span>
            <span class="k">if</span> <span class="n">color_set</span><span class="p">:</span>
                <span class="n">coloring</span><span class="p">.</span><span class="nf">append</span><span class="p">(</span><span class="nf">frozenset</span><span class="p">(</span><span class="n">color_set</span><span class="p">))</span>
    
    <span class="k">return</span> <span class="n">result</span><span class="p">,</span> <span class="n">coloring</span>
</code></pre></div></div> <p>The direct MILP achieves the optimal solution using only 3 frequencies:</p> <p><a id="fig:antenna-milp"></a> <img src="/assets/img/qcolumn_generation/antenna_milp_solution.png" alt="Direct MILP Solution" width="700"/></p> <h3 id="comprehensive-comparison">Comprehensive Comparison</h3> <p>Here’s a visual comparison of all three methods side by side:</p> <p><a id="fig:three-way-comparison"></a> <img src="/assets/img/qcolumn_generation/antenna_three_way_comparison.png" alt="Three-Way Method Comparison" width="700"/></p> <table> <thead> <tr> <th>Method</th> <th>Status</th> <th>Frequencies</th> <th>Optimality</th> </tr> </thead> <tbody> <tr> <td>Classical Column Gen</td> <td>SUCCESS</td> <td>5</td> <td>Heuristic</td> </tr> <tr> <td>Quantum Column Gen</td> <td>SUCCESS</td> <td>5</td> <td>Heuristic</td> </tr> <tr> <td>Direct MILP</td> <td>SUCCESS</td> <td>5</td> <td>Optimal</td> </tr> </tbody> </table> <p>The direct MILP provides the optimal solution with 3 frequencies, while both column generation approaches find good heuristic solutions with 4 frequencies. The column generation approaches offer scalability advantages for larger problems where direct MILP becomes intractable.</p> <h2 id="key-lessons">Key Lessons</h2> <p>We have successfully implemented the quantum column generation algorithm by Wesley da Silva Coelho et al. <a href="https://arxiv.org/abs/2301.02637">2301.02637</a> to solve the graph coloring problem. Key takeaways include:</p> <ol> <li> <p><strong>Quantum Advantage in PSP</strong>: The quantum solver can potentially speed up the Pricing Sub-Problem (PSP) in the column generation routine by leveraging the natural independent set constraints of Rydberg atoms.</p> </li> <li> <p><strong>Scalability</strong>: While the direct MILP provides optimal solutions for small problems, column generation (both classical and quantum) offers better scalability for larger instances.</p> </li> <li> <p><strong>Hardware Considerations</strong>: The <code class="language-plaintext highlighter-rouge">pulser</code> and <code class="language-plaintext highlighter-rouge">maximum-independent-set</code> modules provide tools to simulate Rydberg dynamics, allowing exploration of different embedder and pulse settings to optimize the quantum routine.</p> </li> <li> <p><strong>Practical Applications</strong>: The antenna frequency assignment problem demonstrates the real-world relevance of graph coloring and the potential impact of quantum optimization, using authentic 5G antenna deployment data from Paris.</p> </li> <li> <p><strong>Authentic Implementation</strong>: This work uses real antenna coordinates and authentic Pulser-generated quantum visualizations, providing a realistic demonstration of quantum column generation capabilities.</p> </li> </ol> <p>We invite readers to experiment with different embedder configurations and pulse settings to further optimize the quantum column generation routine for their specific use cases.</p> <h2 id="references">References</h2> <ul> <li>Wesley da Silva Coelho et al., “Quantum-enhanced column generation for quadratic unconstrained binary optimization,” arXiv:2301.02637 (2023).</li> <li><code class="language-plaintext highlighter-rouge">maximum-independent-set</code> package: <a href="https://github.com/pasqal-io/maximum-independent-set">GitHub Repository</a></li> <li><code class="language-plaintext highlighter-rouge">pulser</code> framework: <a href="https://pulser.readthedocs.io/">Documentation</a></li> <li>Neutral atom quantum computing: <a href="https://www.pasqal.com/">Pasqal Technology</a></li> </ul>]]></content><author><name>PoJen Wang</name></author><category term="quantum-computing"/><category term="quantum-computing"/><summary type="html"><![CDATA[A quantum-classical hybrid algorithm for solving the Minimum Vertex Coloring Problem using neutral atom quantum computers]]></summary></entry><entry><title type="html">Solving QUBO/Ising Problems via Unit-Disk Graphs on Neutral Atom Quantum Computers</title><link href="https://nez0b.github.io/blog/2025/UnitDiskMapping/" rel="alternate" type="text/html" title="Solving QUBO/Ising Problems via Unit-Disk Graphs on Neutral Atom Quantum Computers"/><published>2025-04-01T00:00:00+00:00</published><updated>2025-04-01T00:00:00+00:00</updated><id>https://nez0b.github.io/blog/2025/UnitDiskMapping</id><content type="html" xml:base="https://nez0b.github.io/blog/2025/UnitDiskMapping/"><![CDATA[<h2 id="1-introduction">1. Introduction</h2> <h3 id="the-quboising-problem">The QUBO/Ising Problem</h3> <p>Combinatorial optimization problems are ubiquitous in fields ranging from logistics and finance to drug discovery and materials science. Many of these problems can be formulated as finding the ground state of an Ising model or, equivalently, minimizing a Quadratic Unconstrained Binary Optimization (QUBO) objective function.</p> <p>The <strong>Ising model</strong>, originating from statistical mechanics, describes interacting spins on a lattice. Its energy (Hamiltonian) is given by:</p> \[E_\text{Ising}(z) = -\sum_{i &lt; j} J_{ij} z_i z_j - \sum_i h_i z_i\] <p>where $z_i \in {-1, +1}$ are spin variables, $J_{ij}$ represents the coupling strength between spins $i$ and $j$, and $h_i$ is an external magnetic field acting on spin $i$.</p> <p>The <strong>QUBO</strong> problem seeks to minimize a quadratic polynomial of binary variables $x_i \in {0, 1}$:</p> \[E_\text{QUBO}(x) = \sum_{i \le j} Q_{ij} x_i x_j\] <p>These two formulations are equivalent through the transformation $x_i = (1 - z_i) / 2$. Finding the configuration $(z_i)$ or $(x_i)$ that minimizes the respective energy function is often computationally hard (NP-hard in general), motivating the search for efficient solution methods, including quantum algorithms.</p> <h3 id="neutral-atom-quantum-computers">Neutral Atom Quantum Computers</h3> <p>Neutral atom arrays have emerged as a highly promising platform for quantum computation and simulation. In these systems, individual neutral atoms are trapped using optical tweezers, allowing for precise arrangement in 1D, 2D, or even 3D geometries. Quantum information is typically encoded in two electronic states of each atom: a ground state $ \vert g \rangle $ and a highly excited Rydberg state $\vert r \rangle$. Lasers are used to drive transitions between these states ($\Omega$) and control their energy difference (detuning $\delta$).</p> <p>A key feature is the strong, long-range interaction between atoms in the Rydberg state ($ \vert r\rangle $). This interaction falls off rapidly with distance $R$ (typically as $C_6/R^6$).</p> <h3 id="the-blockade-mechanism">The Blockade Mechanism</h3> <p>The strong Rydberg interaction leads to the <strong>Rydberg blockade</strong> effect: if one atom is excited to $\vert r \rangle $, the energy levels of nearby atoms are shifted significantly, preventing them from being resonantly excited to $\vert r \rangle $ by the same laser field. This blockade occurs within a characteristic radius $R_b$.</p> <p>Effectively, this means that only atoms separated by a distance greater than $R_b$ can be simultaneously excited to the Rydberg state. This naturally implements the constraint of an <strong>Independent Set</strong> on a graph where atoms are vertices and edges connect atoms closer than $R_b$. Such a graph, where connectivity is determined solely by distance, is known as a <strong>Unit-Disk Graph (UDG)</strong>.</p> <p>However, many QUBO/Ising problems derived from real-world applications feature complex, arbitrary connectivity patterns ($J_{ij}$ can be non-zero for distant pairs $i, j$). This presents a challenge: how can we solve problems with arbitrary connectivity on hardware whose native interactions are geometrically constrained (UDG-like)?</p> <h3 id="unit-disk-mapping-udm-bridging-the-gap">Unit-Disk Mapping (UDM): Bridging the Gap</h3> <p><strong>Unit-Disk Mapping (UDM)</strong> is a technique designed to address this connectivity mismatch. It provides a systematic way to embed or encode an optimization problem with arbitrary connectivity (like a general QUBO/Ising model) into a new problem defined on a Unit-Disk Graph, such that the solution to the UDG problem corresponds directly to the solution of the original problem. The goal is to construct a UDG whose ground state encodes the ground state of the original Ising Hamiltonian, making it solvable on neutral atom hardware.</p> <h3 id="udm-via-maximum-weight-independent-set-mwis">UDM via Maximum Weight Independent Set (MWIS)</h3> <p>The specific UDM approach we explore here, detailed in <a href="https://doi.org/10.1103/PRXQuantum.4.010316">Nguyen et al., PRX Quantum 4, 010316 (2023)</a>, transforms the QUBO/Ising problem into an instance of the <strong>Maximum Weight Independent Set (MWIS)</strong> problem on a specially constructed UDG.</p> <p>The MWIS problem on a graph $G=(V, E)$ with node weights $w_i$ asks for a subset of vertices $S \subseteq V$ such that no two vertices in $S$ are connected by an edge (it’s an independent set), and the sum of weights of vertices in $S$ ($\sum_{i \in S} w_i$) is maximized.</p> <p>The UDM construction involves:</p> <ol> <li>Representing each original variable $z_i$ using a chain of connected nodes (a “copy gadget”) within the UDG.</li> <li>Using specialized “crossing gadgets” to allow these variable lines to cross without interacting, enabling arbitrary arrangements.</li> <li>Modifying crossing gadgets (“crossing-with-edge” or weighted crossing gadgets) at points corresponding to non-zero $J_{ij}$ or $h_i$ terms in the original problem. These modifications adjust local node weights within the UDG.</li> <li>Carefully choosing the weights such that the MWIS of the final UDG corresponds to the ground state configuration of the original QUBO/Ising problem. The independent set constraint enforced by the Rydberg blockade naturally prevents conflicting assignments within the gadgets.</li> </ol> <p>A pictorial description of the graph construction is shown in Fig. 7 of the original paper:</p> <p><img src="https://journals.aps.org/prxquantum/article/10.1103/PRXQuantum.4.010316/figures/7/large" alt="Image" style="width: 60%; display: block; margin: auto;"/></p> <p>This mapping allows us to leverage the native UDG constraints of neutral atom hardware to solve arbitrarily connected problems. The following sections detail how to perform this mapping using the <code class="language-plaintext highlighter-rouge">qamomile</code> library and simulate the solution using quantum simulators like <a href="https://github.com/QuEraComputing/bloqade-analog"><code class="language-plaintext highlighter-rouge">bloqade-analog</code></a> and <a href="https://pulser.readthedocs.io/en/stable/"><code class="language-plaintext highlighter-rouge">pulser</code></a>.</p> <h2 id="unit-disk-mapping-with-qamomile">Unit-Disk Mapping with qamomile</h2> <p><a href="https://colab.research.google.com/drive/1DUQwOTfUbA5IjAknla4fLkFe_qkB8k0x"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a></p> <h3 id="overview">Overview</h3> <p>The <code class="language-plaintext highlighter-rouge">qamomile</code> library provides tools to facilitate the Unit-Disk Mapping process. Specifically, the <code class="language-plaintext highlighter-rouge">qamomile.core</code> and <code class="language-plaintext highlighter-rouge">qamomile.udm</code> modules contain classes and functions to represent Ising models, perform the mapping to a Unit-Disk Graph, and solve the resulting MWIS problem.</p> <h3 id="representing-the-problem-isingmodel-class">Representing the Problem: <code class="language-plaintext highlighter-rouge">IsingModel</code> Class</h3> <p>First, we define the QUBO or Ising problem using the <code class="language-plaintext highlighter-rouge">IsingModel</code> class from <code class="language-plaintext highlighter-rouge">qamomile.core.ising_qubo</code>. This class stores the quadratic couplings ($J_{ij}$), linear biases ($h_i$), and any constant offset.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">qamomile.core</span> <span class="kn">import</span> <span class="n">IsingModel</span>

<span class="c1"># Define quadratic couplings (J_ij)
</span><span class="n">quad</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span> <span class="mf">1.0</span><span class="p">,</span>   <span class="c1"># Example: Coupling between spins 0 and 1
</span>    <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span> <span class="o">-</span><span class="mf">0.5</span><span class="p">,</span>  <span class="c1"># Example: Coupling between spins 0 and 2
</span>    <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span> <span class="mf">0.8</span>
<span class="p">}</span>

<span class="c1"># Define linear biases (h_i)
</span><span class="n">linear</span> <span class="o">=</span> <span class="p">{</span>
    <span class="mi">0</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>   <span class="c1"># Example: Bias on spin 0
</span>    <span class="mi">1</span><span class="p">:</span> <span class="o">-</span><span class="mf">0.2</span><span class="p">,</span>
    <span class="mi">2</span><span class="p">:</span> <span class="mf">0.3</span>
<span class="p">}</span>

<span class="c1"># Create the Ising model instance
</span><span class="n">ising_model</span> <span class="o">=</span> <span class="nc">IsingModel</span><span class="p">(</span><span class="n">quad</span><span class="o">=</span><span class="n">quad</span><span class="p">,</span> <span class="n">linear</span><span class="o">=</span><span class="n">linear</span><span class="p">,</span> <span class="n">constant</span><span class="o">=</span><span class="mf">0.0</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Created Ising model with </span><span class="si">{</span><span class="n">ising_model</span><span class="p">.</span><span class="nf">num_bits</span><span class="p">()</span><span class="si">}</span><span class="s"> spins.</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <p>Created Ising model with 3 spins.</p> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
As discussed in the UDM paper (Appendix B), proper normalization of the coupling and bias strengths is crucial to ensure the MWIS solution of the mapped graph correctly corresponds to the ground state of the original problem. `IsingModel` provides normalization methods. We typically use `normalize_by_abs_max` before mapping:


```python
# Create a copy to avoid modifying the original
import copy
normalized_ising_model = copy.deepcopy(ising_model)
normalized_ising_model.normalize_by_abs_max()
print("Normalized model coefficients (example):")
print(" Linear:", normalized_ising_model.linear)
print(" Quad:", normalized_ising_model.quad)
</code></pre></div></div> <pre><code class="language-txt">Normalized model coefficients (example):
    Linear: {0: 0.1, 1: -0.2, 2: 0.3}
    Quad: {(0, 1): 1.0, (0, 2): -0.5, (1, 2): 0.8}
</code></pre> <p>This scales all $J_{ij}$ and $h_i$ such that the largest absolute value becomes 1.</p> <h3 id="performing-the-mapping-unitdiskgraph-class">Performing the Mapping: <code class="language-plaintext highlighter-rouge">UnitDiskGraph</code> Class</h3> <p>The <code class="language-plaintext highlighter-rouge">UnitDiskGraph</code> class (also in <code class="language-plaintext highlighter-rouge">qamomile.core.ising_qubo</code>) handles the conversion from an <code class="language-plaintext highlighter-rouge">IsingModel</code> to the UDG representation.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">qamomile.core</span> <span class="kn">import</span> <span class="n">UnitDiskGraph</span>

<span class="c1"># Convert the (normalized) Ising model to a Unit Disk Graph
# This happens automatically when the UnitDiskGraph object is created
</span><span class="n">udg</span> <span class="o">=</span> <span class="nc">UnitDiskGraph</span><span class="p">(</span><span class="n">normalized_ising_model</span><span class="p">)</span>

<span class="c1"># Alternatively, use the convenience method from IsingModel
# udg = ising_model.to_unit_disk_graph(normalize=True) # Normalizes internally
</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Converted to Unit Disk Graph:</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">nodes</span><span class="p">)</span><span class="si">}</span><span class="s"> nodes in the grid graph</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">pins</span><span class="p">)</span><span class="si">}</span><span class="s"> pins (nodes corresponding to original variables)</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Overwriting delta from 2.7 to 1.5
Converted to Unit Disk Graph:
- 26 nodes in the grid graph
- 3 pins (nodes corresponding to original variables) ```
</code></pre></div></div> <p>Behind the scenes, creating the <code class="language-plaintext highlighter-rouge">UnitDiskGraph</code> object triggers the <code class="language-plaintext highlighter-rouge">_create_mapping</code> method, which performs the core UDM steps:</p> <ol> <li><strong>Converts</strong> the <code class="language-plaintext highlighter-rouge">quad</code> and <code class="language-plaintext highlighter-rouge">linear</code> dictionaries into a symmetric coupling matrix <code class="language-plaintext highlighter-rouge">J</code> and a bias vector <code class="language-plaintext highlighter-rouge">h</code>.</li> <li><strong>Calculates</strong> a scaling parameter <code class="language-plaintext highlighter-rouge">delta</code>. This parameter sets the base energy scale for the gadgets in the UDG. It’s chosen based on the maximum absolute values in <code class="language-plaintext highlighter-rouge">J</code> and <code class="language-plaintext highlighter-rouge">h$ (e.g., </code>delta = 1.5 * max(max(abs(J)), max(abs(h)))`) to satisfy the normalization constraints discussed in the UDM paper, ensuring defects are energetically penalized.</li> <li><strong>Calls</strong> the function <code class="language-plaintext highlighter-rouge">qamomile.udm.map_qubo(J, h, delta)</code> (from <code class="language-plaintext highlighter-rouge">dragondrop.py</code>). This function implements the main mapping algorithm: <ul> <li>It constructs the “crossing lattice” structure.</li> <li>It places appropriate gadgets (copy gadgets along lines, crossing/QUBO gadgets at intersections) onto a grid.</li> <li>It assigns weights to the nodes within these gadgets. Crucially, the weights in the core of the QUBO crossing gadgets are set based on the corresponding $J_{ij}$ value and `delta$ (e.g., $4\delta \pm J_{ij}$), encoding the quadratic interaction.</li> <li>It adjusts the weights of the “pin” nodes (boundary nodes of the copy lines) based on the bias vector <code class="language-plaintext highlighter-rouge">h$ and </code>delta$ (e.g., $\delta \pm h_i$), encoding the linear terms.</li> </ul> </li> <li><strong>Stores</strong> the result, including the list of nodes (<code class="language-plaintext highlighter-rouge">udg.nodes</code>, each with a location <code class="language-plaintext highlighter-rouge">node.loc</code> and <code class="language-plaintext highlighter-rouge">node.weight</code>) and the indices of the pin nodes (<code class="language-plaintext highlighter-rouge">udg.pins</code>), which represent the original variables.</li> </ol> <p>The output is a representation of the original Ising problem as a weighted graph embedded in 2D space, satisfying the unit-disk property.</p> <h3 id="visualizing-the-udg">Visualizing the UDG</h3> <p>We can visualize the resulting UDG structure using <code class="language-plaintext highlighter-rouge">networkx</code> and <code class="language-plaintext highlighter-rouge">matplotlib</code>. The <code class="language-plaintext highlighter-rouge">UnitDiskGraph</code> object provides access to the underlying graph structure.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="n">networkx</span> <span class="k">as</span> <span class="n">nx</span>
<span class="kn">import</span> <span class="n">os</span> <span class="c1"># For creating directory
</span>
<span class="c1"># Ensure 'img' directory exists
</span><span class="n">os</span><span class="p">.</span><span class="nf">makedirs</span><span class="p">(</span><span class="sh">"</span><span class="s">img</span><span class="sh">"</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="c1"># Get the NetworkX graph representation from the UDG object
# This graph has nodes with 'pos' and 'weight' attributes
</span><span class="n">G_vis</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="n">networkx_graph</span>

<span class="c1"># Get positions and pin indices
</span><span class="n">pos</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">get_node_attributes</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="sh">'</span><span class="s">pos</span><span class="sh">'</span><span class="p">)</span>
<span class="n">pins</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="n">pins</span>

<span class="c1"># Draw the graph
</span><span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">))</span>

<span class="c1"># Highlight pins (original variables)
</span><span class="n">node_colors</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">red</span><span class="sh">'</span> <span class="k">if</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">pins</span> <span class="k">else</span> <span class="sh">'</span><span class="s">lightblue</span><span class="sh">'</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">G_vis</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()]</span>
<span class="n">node_sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">300</span> <span class="k">if</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">pins</span> <span class="k">else</span> <span class="mi">100</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">G_vis</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()]</span>

<span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">node_color</span><span class="o">=</span><span class="n">node_colors</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="n">node_sizes</span><span class="p">)</span>
<span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_edges</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>

<span class="c1"># Label the pins with their original variable index
</span><span class="n">pin_labels</span> <span class="o">=</span> <span class="p">{</span><span class="n">pin</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">z</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">pin</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">pins</span><span class="p">)}</span> <span class="c1"># Use z_i for Ising spins
</span><span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_labels</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">labels</span><span class="o">=</span><span class="n">pin_labels</span><span class="p">,</span> <span class="n">font_size</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>

<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sh">"</span><span class="s">Unit Disk Graph Representation of the Ising Model</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">xlabel</span><span class="p">(</span><span class="sh">"</span><span class="s">X coordinate</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">ylabel</span><span class="p">(</span><span class="sh">"</span><span class="s">Y coordinate</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">axis</span><span class="p">(</span><span class="sh">'</span><span class="s">equal</span><span class="sh">'</span><span class="p">)</span> <span class="c1"># Ensure aspect ratio is maintained
</span><span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="c1">#plt.savefig("img/unit_disk_graph_vis.png")
#plt.close() # Close the plot to free memory
</span><span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>

<span class="c1">#print("Saved UDG visualization to img/unit_disk_graph_vis.png")
</span></code></pre></div></div> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_13_0.png" alt="png"/></p> <p>This visualization helps understand how the abstract Ising problem is physically laid out using the UDM gadgets.</p> <h3 id="solving-classically-mwis-via-milp">Solving Classically: MWIS via MILP</h3> <p>The UDM transforms the Ising problem into finding the Maximum Weight Independent Set (MWIS) on the generated <code class="language-plaintext highlighter-rouge">UnitDiskGraph</code>. We can solve this classically using standard optimization techniques, specifically Mixed-Integer Linear Programming (MILP).</p> <p>The <code class="language-plaintext highlighter-rouge">udg.solve()</code> method provides a convenient way to do this:</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Solve the MWIS problem on the UDG using the default MILP solver
# The result is automatically mapped back to the original Ising variables
</span><span class="n">solution_result</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="nf">solve</span><span class="p">(</span><span class="n">binary_variables</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span> <span class="c1"># False for {-1, +1} Ising spins
</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Classical Solution via MWIS-MILP:</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Solution method: </span><span class="si">{</span><span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">solution_method</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span> <span class="c1"># Should be 'mwis'
</span><span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Original spin configuration (z_i): </span><span class="si">{</span><span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">original_config</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Calculated energy: </span><span class="si">{</span><span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">]</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># For small problems, we can compare with brute force (done internally by solve if possible)
</span><span class="k">if</span> <span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">solution_result</span><span class="p">:</span>
    <span class="n">bf_energy</span> <span class="o">=</span> <span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">min_energy</span><span class="sh">'</span><span class="p">]</span>
    <span class="n">bf_config</span> <span class="o">=</span> <span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">best_config</span><span class="sh">'</span><span class="p">]</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Brute force energy: </span><span class="si">{</span><span class="n">bf_energy</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Brute force config: </span><span class="si">{</span><span class="n">bf_config</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">if</span> <span class="nf">abs</span><span class="p">(</span><span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">bf_energy</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mf">1e-6</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">  (MWIS solution matches brute force)</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">  (MWIS solution differs from brute force - check normalization/mapping)</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># We can also access the raw MWIS solution vector if needed
# solution_vector_mwis = solution_result['solution_vector'] # Binary vector for UDG nodes
# selected_udg_nodes = solution_result['selected_nodes'] # Indices of UDG nodes in MWIS
# mwis_total_weight = solution_result['mwis_weight'] # Sum of weights of selected UDG nodes
</span></code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Classical Solution via MWIS-MILP:
- Solution method: mwis
- Original spin configuration (z_i): [-1.0, 1.0, -1.0]
- Calculated energy: -5.2000
- Brute force energy: -5.2000
- Brute force config: [-1, 1, -1]
  (MWIS solution matches brute force)
</code></pre></div></div> <p>The <code class="language-plaintext highlighter-rouge">udg.solve()</code> method calls <code class="language-plaintext highlighter-rouge">qamomile.udm.solve_qubo</code>, which, for the MWIS approach, performs these steps:</p> <ol> <li>Generates the <code class="language-plaintext highlighter-rouge">networkx</code> graph representation of the UDG (<code class="language-plaintext highlighter-rouge">qubo_result_to_networkx</code>).</li> <li>Calls <code class="language-plaintext highlighter-rouge">qamomile.udm.solve_mwis_scipy(G)</code> to solve the MWIS problem using MILP.</li> <li>Uses <code class="language-plaintext highlighter-rouge">qamomile.udm.map_config_back</code> to translate the MWIS solution back to the original Ising spin configuration.</li> </ol> <p><strong>Explanation of the MILP Formulation for MWIS:</strong></p> <p>The <code class="language-plaintext highlighter-rouge">solve_mwis_scipy(G)</code> function formulates the MWIS problem as follows:</p> <ul> <li><strong>Variables:</strong> For each node $i$ in the Unit-Disk Graph $G=(V, E)$, we define a binary variable $x_i \in {0, 1}$. $x_i = 1$ signifies that node $i$ is included in the independent set, and $x_i = 0$ signifies it is not.</li> <li><strong>Objective Function:</strong> We want to maximize the sum of weights of the selected nodes. Let $w_i$ be the weight of node $i$ (obtained from <code class="language-plaintext highlighter-rouge">G.nodes[i]['weight']</code>). The objective is: \(\text{Maximize} \sum_{i \in V} w_i x_i\) Since <code class="language-plaintext highlighter-rouge">scipy.optimize.milp</code> performs minimization, the objective coefficients <code class="language-plaintext highlighter-rouge">c</code> are set to the <em>negative</em> weights: $c_i = -w_i$. The solver minimizes $\sum_i c_i x_i = -\sum_i w_i x_i$, which is equivalent to maximizing the original sum.</li> <li> <p><strong>Constraints:</strong> The core requirement of an independent set is that no two connected nodes can be selected simultaneously. For every edge $(u, v) \in E$ in the UDG, we must enforce the constraint: \(x_u + x_v \leq 1\)</p> <p>This ensures that if $x_u=1$, then $x_v$ must be 0, and vice-versa. It also allows both to be 0. These constraints are formulated as <code class="language-plaintext highlighter-rouge">LinearConstraint(A_ub, -np.inf, b_ub)</code> where <code class="language-plaintext highlighter-rouge">A_ub</code> is a matrix where each row corresponds to an edge $(u,v)$ and has 1s in the columns for $u$ and $v$, and <code class="language-plaintext highlighter-rouge">b_ub</code> is a vector of 1s.</p> </li> <li><strong>Bounds and Integrality:</strong> Each variable $x_i$ must be between 0 and 1 (<code class="language-plaintext highlighter-rouge">Bounds([0]*n, [1]*n)</code>), and crucially, they must be integers (<code class="language-plaintext highlighter-rouge">integrality=np.ones(n, dtype=bool)</code>). Combined with the bounds, this forces $x_i$ to be binary.</li> </ul> <p>The <code class="language-plaintext highlighter-rouge">scipy.optimize.milp</code> function takes these components (<code class="language-plaintext highlighter-rouge">c</code>, <code class="language-plaintext highlighter-rouge">constraints</code>, <code class="language-plaintext highlighter-rouge">bounds</code>, <code class="language-plaintext highlighter-rouge">integrality</code>) and uses algorithms like branch-and-cut to find the optimal binary vector <code class="language-plaintext highlighter-rouge">res.x</code> that satisfies the constraints and minimizes the objective function. The indices $i$ where <code class="language-plaintext highlighter-rouge">res.x[i]</code> is close to 1 form the MWIS.</p> <h3 id="mapping-the-solution-back">Mapping the Solution Back</h3> <p>The binary solution vector <code class="language-plaintext highlighter-rouge">solution_vector</code> obtained from <code class="language-plaintext highlighter-rouge">solve_mwis_scipy</code> indicates which nodes <em>in the UDG</em> are part of the MWIS. The function <code class="language-plaintext highlighter-rouge">map_config_back(qubo_result, solution_vector, binary=False)</code> translates this back to the original problem variables (Ising spins $z_i \in {-1, +1}$). It does this by looking at the state of the specific “pin” nodes in the UDG solution vector, which correspond to the original variables. The exact mapping depends on the gadget construction (e.g., whether pin node selected corresponds to $z_i=+1$ or $z_i=-1$).</p> <h3 id="complete-code-example-mapping--classical-solve">Complete Code Example (Mapping &amp; Classical Solve)</h3> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="n">matplotlib.pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="n">networkx</span> <span class="k">as</span> <span class="n">nx</span>
<span class="kn">import</span> <span class="n">os</span>
<span class="kn">import</span> <span class="n">copy</span>

<span class="kn">from</span> <span class="n">qamomile.core</span> <span class="kn">import</span> <span class="n">IsingModel</span><span class="p">,</span> <span class="n">UnitDiskGraph</span>

<span class="c1"># --- 1. Define Ising Problem ---
</span><span class="n">quad</span> <span class="o">=</span> <span class="p">{(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span> <span class="mf">1.0</span><span class="p">,</span> <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span> <span class="o">-</span><span class="mf">0.5</span><span class="p">,</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">):</span> <span class="mf">0.8</span><span class="p">}</span>
<span class="n">linear</span> <span class="o">=</span> <span class="p">{</span><span class="mi">0</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span> <span class="mi">1</span><span class="p">:</span> <span class="o">-</span><span class="mf">0.2</span><span class="p">,</span> <span class="mi">2</span><span class="p">:</span> <span class="mf">0.3</span><span class="p">}</span>
<span class="n">ising_model</span> <span class="o">=</span> <span class="nc">IsingModel</span><span class="p">(</span><span class="n">quad</span><span class="o">=</span><span class="n">quad</span><span class="p">,</span> <span class="n">linear</span><span class="o">=</span><span class="n">linear</span><span class="p">,</span> <span class="n">constant</span><span class="o">=</span><span class="mf">0.0</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Original Ising Model:</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s"> H = </span><span class="si">{</span><span class="n">ising_model</span><span class="p">.</span><span class="n">quad</span><span class="si">}</span><span class="s"> Z_i Z_j + </span><span class="si">{</span><span class="n">ising_model</span><span class="p">.</span><span class="n">linear</span><span class="si">}</span><span class="s"> Z_i + </span><span class="si">{</span><span class="n">ising_model</span><span class="p">.</span><span class="n">constant</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># --- 2. Map to UDG (with normalization) ---
</span><span class="n">udg</span> <span class="o">=</span> <span class="n">ising_model</span><span class="p">.</span><span class="nf">to_unit_disk_graph</span><span class="p">(</span><span class="n">normalize</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="se">\n</span><span class="s">Created UDG with </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">nodes</span><span class="p">)</span><span class="si">}</span><span class="s"> nodes and </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">pins</span><span class="p">)</span><span class="si">}</span><span class="s"> pins.</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># --- 3. Visualize UDG ---
</span><span class="n">os</span><span class="p">.</span><span class="nf">makedirs</span><span class="p">(</span><span class="sh">"</span><span class="s">img</span><span class="sh">"</span><span class="p">,</span> <span class="n">exist_ok</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
<span class="n">G_vis</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="n">networkx_graph</span>
<span class="n">pos</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">get_node_attributes</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="sh">'</span><span class="s">pos</span><span class="sh">'</span><span class="p">)</span>
<span class="n">pins</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="n">pins</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">figure</span><span class="p">(</span><span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">8</span><span class="p">))</span>
<span class="n">node_colors</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">red</span><span class="sh">'</span> <span class="k">if</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">pins</span> <span class="k">else</span> <span class="sh">'</span><span class="s">lightblue</span><span class="sh">'</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">G_vis</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()]</span>
<span class="n">node_sizes</span> <span class="o">=</span> <span class="p">[</span><span class="mi">300</span> <span class="k">if</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">pins</span> <span class="k">else</span> <span class="mi">100</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">G_vis</span><span class="p">.</span><span class="nf">nodes</span><span class="p">()]</span>
<span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">node_color</span><span class="o">=</span><span class="n">node_colors</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="n">node_sizes</span><span class="p">)</span>
<span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_edges</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">width</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>
<span class="n">pin_labels</span> <span class="o">=</span> <span class="p">{</span><span class="n">pin</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">z</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">pin</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">pins</span><span class="p">)}</span>
<span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_labels</span><span class="p">(</span><span class="n">G_vis</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">labels</span><span class="o">=</span><span class="n">pin_labels</span><span class="p">,</span> <span class="n">font_size</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">title</span><span class="p">(</span><span class="sh">"</span><span class="s">Unit Disk Graph Representation</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">axis</span><span class="p">(</span><span class="sh">'</span><span class="s">equal</span><span class="sh">'</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">tight_layout</span><span class="p">()</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">savefig</span><span class="p">(</span><span class="sh">"</span><span class="s">img/unit_disk_graph_example.png</span><span class="sh">"</span><span class="p">)</span>
<span class="n">plt</span><span class="p">.</span><span class="nf">close</span><span class="p">()</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Saved UDG visualization to img/unit_disk_graph_example.png</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># --- 4. Solve Classically via MWIS-MILP ---
</span><span class="n">solution_result</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="nf">solve</span><span class="p">(</span><span class="n">binary_variables</span><span class="o">=</span><span class="bp">False</span><span class="p">)</span> <span class="c1"># Get {-1, +1} spins
</span><span class="n">solution_vector</span> <span class="o">=</span> <span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">solution_vector</span><span class="sh">'</span><span class="p">]</span>

<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Classical Solution (via MWIS-MILP):</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Spin configuration (z_i): </span><span class="si">{</span><span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">original_config</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Energy: </span><span class="si">{</span><span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">]</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># --- 5. (Optional) Compare with Brute Force ---
</span><span class="k">if</span> <span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">solution_result</span><span class="p">:</span>
    <span class="n">bf_energy</span> <span class="o">=</span> <span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">min_energy</span><span class="sh">'</span><span class="p">]</span>
    <span class="n">bf_config</span> <span class="o">=</span> <span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">best_config</span><span class="sh">'</span><span class="p">]</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="se">\n</span><span class="s">Brute Force Solution:</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Spin configuration (z_i): </span><span class="si">{</span><span class="n">bf_config</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- Energy: </span><span class="si">{</span><span class="n">bf_energy</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">if</span> <span class="nf">abs</span><span class="p">(</span><span class="n">solution_result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">bf_energy</span><span class="p">)</span> <span class="o">&lt;</span> <span class="mf">1e-6</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">  (MWIS solution matches brute force)</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">  (MWIS solution differs from brute force)</span><span class="sh">"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Brute force comparison skipped (problem size likely too large).</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Original Ising Model:
 H = {(0, 1): 1.0, (0, 2): -0.5, (1, 2): 0.8} Z_i Z_j + {0: 0.1, 1: -0.2, 2: 0.3} Z_i + 0.0
Overwriting delta from 2.7 to 1.5

Created UDG with 26 nodes and 3 pins.
Saved UDG visualization to img/unit_disk_graph_example.png

Classical Solution (via MWIS-MILP):
- Spin configuration (z_i): [-1.0, 1.0, -1.0]
- Energy: -5.2000

Brute Force Solution:
- Spin configuration (z_i): [-1, 1, -1]
- Energy: -5.2000
  (MWIS solution matches brute force)
</code></pre></div></div> <p>This section demonstrated how to use <code class="language-plaintext highlighter-rouge">qamomile</code> to map an Ising problem to a UDG and solve it classically by finding the MWIS using an MILP solver. The next section will explore how to solve this MWIS-UDG problem using neutral atom quantum computer simulations.</p> <h2 id="solving-mwis-udg-with-neutral-atoms">Solving MWIS-UDG with Neutral Atoms</h2> <p>Now that we have mapped the original Ising problem to an MWIS problem on a Unit-Disk Graph, we can leverage the native capabilities of neutral atom quantum computers to find the solution.</p> <h3 id="rydberg-atoms-and-blockade">Rydberg Atoms and Blockade</h3> <p>Neutral atom quantum computers typically use two electronic states of each atom to represent a qubit: a stable ground state $\vert g \rangle $ and a highly excited <strong>Rydberg state</strong> $ \vert r \rangle $. Lasers control the qubit state:</p> <ul> <li><strong>Rabi Frequency ($\Omega$):</strong> A resonant laser field drives transitions between $\vert g \rangle $ and $\vert r \rangle $ at a rate $\Omega$. In the Hamiltonian, this corresponds to a term $\frac{\Omega}{2} \sum_i \sigma_x^{(i)}$, where $\sigma_x = \vert g \rangle \langle r\vert + \vert r \rangle \langle g \vert $.</li> <li><strong>Detuning ($\delta$):</strong> The frequency difference between the laser and the atomic $\vert g \rangle \leftrightarrow \vert r \rangle $ transition is the detuning, $\delta$. It controls the energy difference between the states. A positive detuning makes $\vert r\rangle $ higher in energy, while a negative detuning makes $\vert g\rangle $ higher. This corresponds to a term $-\sum_i \delta_i n_i$ in the Hamiltonian, where $n_i = \vert r\rangle\langle r\vert_i $ is the number operator for the Rydberg state (0 if atom $i$ is in $\vert g\rangle$, 1 if in $\vert r\rangle $). The detuning $\delta_i$ can be applied globally or locally to individual atoms.</li> </ul> <p>The crucial ingredient for solving graph problems is the strong van der Waals interaction between atoms in the Rydberg state. This interaction energy is approximately $V_{ij} = C_6 / R_{ij}^6$, where $R_{ij}$ is the distance between atoms $i$ and $j$, and $C_6$ is a large coefficient specific to the chosen Rydberg state.</p> <p>This strong interaction leads to the <strong>Rydberg Blockade</strong>: if atom $i$ is excited to $\vert r\rangle $, the interaction $V_{ij}$ shifts the energy level of a nearby atom $j$ (within a certain <strong>blockade radius</strong> $R_b$) so much that the driving laser is no longer resonant for the $\vert g\rangle \leftrightarrow \vert r\rangle $ transition on atom $j$. Effectively, the excitation of atom $i$ <em>prevents</em> the excitation of nearby atoms within $R_b$.</p> <h3 id="adiabatic-evolution-for-mwis">Adiabatic Evolution for MWIS</h3> <p>The Rydberg blockade naturally enforces the constraint of an <strong>Independent Set</strong> on the graph defined by the atom positions and the blockade radius $R_b$. We want to find the <strong>Maximum Weight</strong> Independent Set, where the weight $w_i$ of each atom (node) $i$ being in the Rydberg state $\vert r \rangle $ (i.e., part of the independent set) contributes positively to the total weight.</p> <p>The target Hamiltonian for the MWIS problem can be written as: \(H_{MWIS} = \sum_i \frac{\Omega(t)}{2} \sigma_x^{(i)} - \sum_i \delta_i(t) n_i + \sum_{i&lt;j} V_{ij} n_i n_j\) Here:</p> <ul> <li>The $\Omega$ term drives transitions.</li> <li>The $\delta_i(t)$ term represents the energy bias (weight) for exciting atom $i$. We want $\delta_i$ to be related to the node weight $w_i$ from our UDG mapping.</li> <li>The $V_{ij} n_i n_j$ term represents the interaction. For atoms $i, j$ with $R_{ij} &lt; R_b$, $V_{ij}$ is very large (effectively infinite), penalizing states where both $n_i=1$ and $n_j=1$. This enforces the independent set constraint.</li> </ul> <p>The <strong>Adiabatic Theorem</strong> states that if a system starts in the ground state of an initial Hamiltonian $H_{start}$ and the Hamiltonian is slowly changed to a final Hamiltonian $H_{final}$, the system will remain in the instantaneous ground state throughout the evolution, ending in the ground state of $H_{final}$.</p> <p>We can use this to solve the MWIS problem:</p> <ol> <li><strong>Start:</strong> Initialize all atoms in the ground state $\vert g\rangle \dots \vert g\rangle$. This is the ground state of $H_{start}$ where $\Omega(0) \approx 0$ and $\delta_i(0)$ is large and negative (making $\vert g\rangle$ energetically favorable).</li> <li><strong>Evolve:</strong> Slowly change the parameters over a time $T$: <ul> <li>Ramp $\Omega(t)$ up from 0 to a maximum value $\Omega_{max}$ and back down to 0. This allows the system to explore different configurations.</li> <li>Sweep the detuning $\delta_i(t)$ from large negative to large positive. The final detuning $\delta_i(T)$ should be proportional to the node weight $w_i$ from the UDG mapping, making it energetically favorable for high-weight nodes to end in $\vert r\rangle$ (if allowed by the blockade).</li> </ul> </li> <li><strong>End:</strong> At time $T$, $\Omega(T) \approx 0$ and $\delta_i(T)$ is large and positive (proportional to $w_i$). The Hamiltonian is dominated by the detuning and interaction terms. The system ideally ends in the ground state, which corresponds to the MWIS configuration: atoms in $\vert r\rangle$ form the MWIS.</li> <li><strong>Measure:</strong> Measure the final state of each atom ($\vert g\rangle$ or $\vert r\rangle$) to read out the solution bitstring.</li> </ol> <p>The success probability depends on the evolution time $T$ being long enough (adiabatic condition) compared to the inverse of the minimum energy gap during the evolution.</p> <h3 id="implementation-with-bloqade-analog">Implementation with <strong><code class="language-plaintext highlighter-rouge">bloqade-analog</code></strong></h3> <p><code class="language-plaintext highlighter-rouge">bloqade-analog</code> is a Python library for simulating analog neutral atom quantum computations, particularly suited for adiabatic protocols like the one described above. The <code class="language-plaintext highlighter-rouge">bloqade_example.py</code> script shows how to use it with our UDG mapping.</p> <p><strong>Steps:</strong></p> <ol> <li><strong>Prepare Inputs:</strong> <ul> <li>Get atom locations from the <code class="language-plaintext highlighter-rouge">UnitDiskGraph</code> object (<code class="language-plaintext highlighter-rouge">udg.nodes</code>). These locations need to be scaled to physical units (e.g., micrometers) appropriate for typical blockade radii.</li> <li>Get node weights (<code class="language-plaintext highlighter-rouge">udg.nodes[i].weight</code>). These weights are typically normalized.</li> <li>Define the pulse parameters: maximum Rabi frequency <code class="language-plaintext highlighter-rouge">Omega_max</code>, maximum detuning <code class="language-plaintext highlighter-rouge">delta_max</code>, and total evolution time <code class="language-plaintext highlighter-rouge">t_max</code>.</li> </ul> </li> </ol> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># TODO: Consider integrate these methods into UnitDiskGraph class
</span><span class="k">def</span> <span class="nf">qubo_grid_to_locations</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">,</span> <span class="n">scale</span> <span class="o">=</span> <span class="mf">5.0</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Convert a QUBO grid graph to a list of tuples, each represent the node location.

    Args:
        qubo_result: QUBOResult object from map_qubo

    Returns:
        List of (x, y) tuples
    </span><span class="sh">"""</span>
    <span class="k">return</span> <span class="p">[(</span><span class="n">node</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">*</span> <span class="n">scale</span><span class="p">,</span> <span class="n">node</span><span class="p">.</span><span class="n">loc</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">*</span> <span class="n">scale</span><span class="p">)</span> <span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">]</span>

<span class="k">def</span> <span class="nf">qubo_result_to_weights</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Convert a QUBOResult to a list of weights for each node.

    Args:
        qubo_result: QUBOResult object from map_qubo

    Returns:
        List of weights for each node in the grid graph.
    </span><span class="sh">"""</span>
    <span class="k">return</span> <span class="p">[</span><span class="n">node</span><span class="p">.</span><span class="n">weight</span> <span class="k">for</span> <span class="n">node</span> <span class="ow">in</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">]</span>
</code></pre></div></div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># (Assuming 'udg' is the UnitDiskGraph object from Section 2)
</span>
<span class="kn">import</span> <span class="n">numpy</span> <span class="k">as</span> <span class="n">np</span>
<span class="kn">import</span> <span class="n">math</span>

<span class="c1"># Scale factor to convert grid units to physical units (e.g., um)
</span><span class="n">LOCATION_SCALE</span> <span class="o">=</span> <span class="mf">5.0</span> <span class="c1"># Adjust based on desired blockade radius and hardware
</span>
<span class="n">locations</span> <span class="o">=</span> <span class="nf">qubo_grid_to_locations</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">qubo_result</span><span class="p">,</span> <span class="n">scale</span><span class="o">=</span><span class="n">LOCATION_SCALE</span><span class="p">)</span>
<span class="n">weights</span> <span class="o">=</span> <span class="nf">qubo_result_to_weights</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">qubo_result</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Node weights: </span><span class="si">{</span><span class="n">weights</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Node locations: </span><span class="si">{</span><span class="n">locations</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Node weights: [1.3, 1.8, 1.6, 5.0, 7.0, 3.0, 6.5, 5.5, 7.0, 5.0, 3.0, 5.5, 6.5, 1.4, 3.0, 3.0, 3.0, 3.0, 3.0, 3.0, 5.2, 6.8, 6.8, 5.2, 1.7, 1.2]
Node locations: [(0.0, 10.0), (0.0, 30.0), (5.0, 0.0), (5.0, 5.0), (5.0, 10.0), (5.0, 20.0), (5.0, 25.0), (5.0, 30.0), (10.0, 5.0), (10.0, 10.0), (10.0, 15.0), (10.0, 25.0), (10.0, 30.0), (10.0, 35.0), (15.0, 5.0), (15.0, 25.0), (20.0, 10.0), (20.0, 30.0), (25.0, 15.0), (25.0, 20.0), (25.0, 25.0), (25.0, 30.0), (30.0, 25.0), (30.0, 30.0), (30.0, 35.0), (35.0, 25.0)]
</code></pre></div></div> <ol> <li><strong>Define the Program:</strong> Use <code class="language-plaintext highlighter-rouge">bloqade.analog</code> to define the atom geometry and pulse sequence.</li> </ol> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">bloqade.analog</span> <span class="kn">import</span> <span class="n">start</span>

<span class="k">def</span> <span class="nf">solve_ising_bloqade</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="n">weights</span><span class="p">,</span> <span class="n">delta_max</span><span class="o">=</span><span class="mf">60.0</span><span class="p">,</span> <span class="n">Omega_max</span><span class="o">=</span><span class="mf">15.0</span><span class="p">,</span> <span class="n">t_max</span><span class="o">=</span><span class="mf">4.0</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Solve the Ising model using Bloqade.

    </span><span class="sh">"""</span>
    <span class="n">locations_array</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">locations</span><span class="p">)</span>
    <span class="n">centroid</span> <span class="o">=</span> <span class="n">locations_array</span><span class="p">.</span><span class="nf">mean</span><span class="p">(</span><span class="n">axis</span><span class="o">=</span><span class="mi">0</span><span class="p">)</span>
    <span class="n">centered_locations</span> <span class="o">=</span> <span class="n">locations_array</span> <span class="o">-</span> <span class="n">centroid</span>
    <span class="n">locations</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="nf">map</span><span class="p">(</span><span class="nb">tuple</span><span class="p">,</span> <span class="n">centered_locations</span><span class="p">))</span>


    <span class="k">assert</span> <span class="nf">len</span><span class="p">(</span><span class="n">locations</span><span class="p">)</span><span class="o">==</span><span class="nf">len</span><span class="p">(</span><span class="n">weights</span><span class="p">)</span>
    <span class="k">assert</span> <span class="nf">len</span><span class="p">(</span><span class="n">locations</span><span class="p">)</span><span class="o">==</span><span class="mi">26</span>
    <span class="n">lw</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">weights</span><span class="p">)</span>
    <span class="n">weights_norm</span> <span class="o">=</span>  <span class="p">[</span><span class="n">x</span><span class="o">/</span><span class="nf">max</span><span class="p">(</span><span class="n">weights</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">weights</span><span class="p">]</span>

    <span class="k">def</span> <span class="nf">sine_waveform</span><span class="p">(</span><span class="n">t</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">Omega_max</span> <span class="o">*</span> <span class="n">math</span><span class="p">.</span><span class="nf">sin</span><span class="p">(</span><span class="n">math</span><span class="p">.</span><span class="n">pi</span> <span class="o">*</span> <span class="n">t</span> <span class="o">/</span> <span class="n">t_max</span><span class="p">)</span><span class="o">**</span><span class="mi">2</span>

    <span class="k">def</span> <span class="nf">linear_detune_waveform</span><span class="p">(</span><span class="n">t</span><span class="p">):</span>
        <span class="k">return</span> <span class="n">delta_max</span> <span class="o">*</span> <span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">t</span> <span class="o">/</span> <span class="n">t_max</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>

    <span class="n">program</span> <span class="o">=</span> <span class="p">(</span>
        <span class="n">start</span><span class="p">.</span><span class="nf">add_position</span><span class="p">(</span><span class="n">locations</span><span class="p">)</span>
        <span class="p">.</span><span class="n">rydberg</span><span class="p">.</span><span class="n">detuning</span><span class="p">.</span><span class="nf">scale</span><span class="p">(</span><span class="n">weights_norm</span><span class="p">)</span>
        <span class="p">.</span><span class="nf">fn</span><span class="p">(</span><span class="n">linear_detune_waveform</span><span class="p">,</span> <span class="n">t_max</span><span class="p">)</span>
        <span class="p">.</span><span class="n">amplitude</span><span class="p">.</span><span class="n">uniform</span><span class="p">.</span><span class="nf">fn</span><span class="p">(</span><span class="n">sine_waveform</span><span class="p">,</span> <span class="n">t_max</span><span class="p">)</span>
    <span class="p">)</span>

    <span class="k">return</span> <span class="n">program</span>

</code></pre></div></div> <ul> <li><code class="language-plaintext highlighter-rouge">start.add_position()</code> defines the atom geometry. * <code class="language-plaintext highlighter-rouge">.rydberg.detuning.uniform.scale(weights_norm).fn(...)</code> defines the detuning pulse. The <code class="language-plaintext highlighter-rouge">weights_norm</code> scales the global <code class="language-plaintext highlighter-rouge">linear_ramp</code> waveform for each atom, effectively implementing $\delta_i(t) = w_i^{norm} \times \delta_{global}(t)$. * <code class="language-plaintext highlighter-rouge">.amplitude.uniform.fn(...)</code> defines the global Rabi frequency pulse $\Omega(t)$.</li> </ul> <p>Next we define and visualize the sequence</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">bokeh.io</span> <span class="kn">import</span> <span class="n">output_notebook</span>

<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="nf">isdir</span><span class="p">(</span><span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">):</span>
    <span class="n">os</span><span class="p">.</span><span class="nf">mkdir</span><span class="p">(</span><span class="sh">"</span><span class="s">data</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># This tells Bokeh to display output in the notebook
# versus opening a browser window
</span><span class="nf">output_notebook</span><span class="p">()</span>

<span class="n">program</span> <span class="o">=</span> <span class="nf">solve_ising_bloqade</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="n">weights</span><span class="p">)</span>
<span class="n">program</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span> <span class="c1"># Optional: visualize the pulse sequence
</span></code></pre></div></div> <div id="e47dc934-4db1-4b06-8965-5f4df9da1d14" data-root-id="p1582" style="display: contents;"></div> <ol> <li><strong>Run the Simulation:</strong> Execute the program using Bloqade’s emulator.</li> </ol> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">collections</span> <span class="kn">import</span> <span class="n">Counter</span>

<span class="n">blockade_radius</span> <span class="o">=</span> <span class="n">LOCATION_SCALE</span> <span class="o">*</span> <span class="mf">1.5</span> <span class="c1"># blockade radius is set to cover the diagonoal node (1: sqrt(2))
# Run the simulation (using Bloqade's built-in emulator)
# Specify the blockade radius matching the desired UDG constraint
</span><span class="n">emu_results</span> <span class="o">=</span> <span class="n">program</span><span class="p">.</span><span class="n">bloqade</span><span class="p">.</span><span class="nf">python</span><span class="p">().</span><span class="nf">run</span><span class="p">(</span>
    <span class="n">shots</span><span class="o">=</span><span class="mi">10000</span><span class="p">,</span>
    <span class="n">solver_name</span> <span class="o">=</span><span class="sh">"</span><span class="s">dop853</span><span class="sh">"</span><span class="p">,</span>
    <span class="n">blockade_radius</span><span class="o">=</span><span class="n">blockade_radius</span>
<span class="p">)</span>

<span class="c1"># Process the results (counts of final bitstrings)
</span><span class="n">report</span> <span class="o">=</span> <span class="n">emu_results</span><span class="p">.</span><span class="nf">report</span><span class="p">()</span>
<span class="n">counts</span> <span class="o">=</span> <span class="n">report</span><span class="p">.</span><span class="nf">counts</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span> <span class="c1"># Dictionary of {bitstring: count}. The [0] is the task index (can run multiple task in one run, with .batch_assign)
</span>
<span class="nf">print</span><span class="p">(</span><span class="n">counts</span><span class="p">)</span>

<span class="c1"># Sort by frequency
</span><span class="n">sorted_counts</span> <span class="o">=</span> <span class="p">{</span><span class="n">k</span><span class="p">:</span> <span class="n">v</span> <span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">v</span> <span class="ow">in</span> <span class="nf">sorted</span><span class="p">(</span><span class="n">counts</span><span class="p">.</span><span class="nf">items</span><span class="p">(),</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">item</span><span class="p">:</span> <span class="n">item</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">reverse</span><span class="o">=</span><span class="bp">True</span><span class="p">)}</span>
<span class="c1">#top2 = list(hist_dict.items())[:2]
#print(top2)
</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Bloqade Simulation Results (Top 5):</span><span class="sh">"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">bitstring</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="nf">list</span><span class="p">(</span><span class="n">sorted_counts</span><span class="p">.</span><span class="nf">items</span><span class="p">())[:</span><span class="mi">5</span><span class="p">]):</span>
    <span class="c1"># Note: Bloqade bitstring '0' often means Rydberg |r&gt; (in IS), '1' means ground |g&gt;
</span>    <span class="c1"># Verify this convention based on the expected MWIS solution
</span>    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s"> </span><span class="si">{</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s">. Bitstring: </span><span class="si">{</span><span class="n">bitstring</span><span class="si">}</span><span class="s"> (Count: </span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s">)</span><span class="sh">"</span><span class="p">)</span>

</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Bloqade Simulation Results (Top 5):
 1. Bitstring: 10010011111101011001110101 (Count: 648)
 2. Bitstring: 10110011111101011001110101 (Count: 299)
 3. Bitstring: 11010101111110011001110101 (Count: 291)
 4. Bitstring: 01111101010110111001110101 (Count: 278)
 5. Bitstring: 10010011111101011101101110 (Count: 275)
</code></pre></div></div> <p>We plot the</p> <ul> <li><code class="language-plaintext highlighter-rouge">shots</code> determines the number of simulation runs. * <code class="language-plaintext highlighter-rouge">blockade_radius</code> is crucial; it tells the simulator which pairs of atoms should blockade each other, implementing the UDG edges. * The result <code class="language-plaintext highlighter-rouge">counts</code> gives the frequency of observing each final state (bitstring). The most frequent bitstring is the candidate for the MWIS solution. <strong>Note:</strong> The mapping between Bloqade’s bitstring (‘0’/’1’) and the physical states ($\vert g\rangle$/$\vert r\rangle$) or MWIS inclusion (in/out) needs careful checking based on the Hamiltonian definition and expected outcome. Often ‘0’ represents the Rydberg state $\vert r\rangle$ (part of the IS).</li> </ul> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">visualize_bloqade_scipy_solution</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="n">top2</span><span class="p">,</span> <span class="n">qubo_result</span><span class="p">,</span> <span class="n">solution_vector</span><span class="p">,</span> <span class="n">output_file</span><span class="o">=</span><span class="sh">"</span><span class="s">img/compare_solution.png</span><span class="sh">"</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Visualize the Bloqade solution and scipy solution side by side.

    Args:
        locations: List of (x, y) tuples for node locations
        top2: Top 2 solutions from Bloqade
        qubo_result: QUBOResult from map_qubo
        solution_vector: Binary solution vector from MWIS solver
        output_file: Path to save the visualization
    </span><span class="sh">"""</span>
    <span class="c1"># Function to create a graph with nodes at fixed locations.
</span>    <span class="k">def</span> <span class="nf">create_graph</span><span class="p">(</span><span class="n">locs</span><span class="p">,</span> <span class="n">threshold</span><span class="p">):</span>
        <span class="n">G</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">Graph</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">idx</span><span class="p">,</span> <span class="n">pos</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">locs</span><span class="p">):</span>
            <span class="n">G</span><span class="p">.</span><span class="nf">add_node</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="n">pos</span><span class="p">)</span>

        <span class="c1"># Add edges based on distance threshold
</span>        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">locs</span><span class="p">)):</span>
            <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">locs</span><span class="p">)):</span>
                <span class="n">dist</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="nf">norm</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">locs</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">-</span> <span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">locs</span><span class="p">[</span><span class="n">j</span><span class="p">]))</span>
                <span class="k">if</span> <span class="n">dist</span> <span class="o">&lt;</span> <span class="n">threshold</span><span class="p">:</span>
                    <span class="n">G</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">G</span>

    <span class="c1"># Create two graphs (they share the same node positions)
</span>    <span class="n">G1</span> <span class="o">=</span> <span class="nf">create_graph</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="mf">7.5</span><span class="p">)</span>
    <span class="n">G2</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">Graph</span><span class="p">()</span>

    <span class="c1"># Retrieve node positions from one of the graphs.
</span>    <span class="n">pos</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">get_node_attributes</span><span class="p">(</span><span class="n">G1</span><span class="p">,</span> <span class="sh">'</span><span class="s">pos</span><span class="sh">'</span><span class="p">)</span>

    <span class="c1"># Determine node colors for each graph based on the corresponding bitstring.
</span>    <span class="n">bitstr1</span> <span class="o">=</span> <span class="n">top2</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>

    <span class="n">node_colors1</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">red</span><span class="sh">'</span> <span class="k">if</span> <span class="n">bit</span> <span class="o">==</span> <span class="sh">'</span><span class="s">0</span><span class="sh">'</span> <span class="k">else</span> <span class="sh">'</span><span class="s">lightgray</span><span class="sh">'</span> <span class="k">for</span> <span class="n">bit</span> <span class="ow">in</span> <span class="n">bitstr1</span><span class="p">]</span>
    <span class="n">node_size1</span> <span class="o">=</span> <span class="p">[</span><span class="mi">400</span> <span class="k">if</span> <span class="n">bit</span> <span class="o">==</span> <span class="sh">'</span><span class="s">0</span><span class="sh">'</span> <span class="k">else</span> <span class="mi">100</span> <span class="k">for</span> <span class="n">bit</span> <span class="ow">in</span> <span class="n">bitstr1</span><span class="p">]</span>

    <span class="c1"># Create subplots with two axes (side by side)
</span>    <span class="n">fig</span><span class="p">,</span> <span class="n">axes</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplots</span><span class="p">(</span><span class="n">ncols</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>

    <span class="c1"># Draw the first graph.
</span>    <span class="n">nx</span><span class="p">.</span><span class="nf">draw</span><span class="p">(</span><span class="n">G1</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">with_labels</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">node_color</span><span class="o">=</span><span class="n">node_colors1</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="n">node_size1</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Bloqade Solution</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Add nodes with positions from the grid graph
</span>    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">node</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">):</span>
        <span class="n">G2</span><span class="p">.</span><span class="nf">add_node</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="n">node</span><span class="p">.</span><span class="n">loc</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="n">node</span><span class="p">.</span><span class="n">weight</span><span class="p">)</span>

    <span class="c1"># Add edges based on unit disk constraint
</span>    <span class="n">radius</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">radius</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">)):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">)):</span>
            <span class="n">node1</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
            <span class="n">node2</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">j</span><span class="p">]</span>
            <span class="n">dist</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="nf">sum</span><span class="p">((</span><span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">node1</span><span class="p">.</span><span class="n">loc</span><span class="p">)</span> <span class="o">-</span> <span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">node2</span><span class="p">.</span><span class="n">loc</span><span class="p">))</span><span class="o">**</span><span class="mi">2</span><span class="p">))</span>
            <span class="k">if</span> <span class="n">dist</span> <span class="o">&lt;=</span> <span class="n">radius</span><span class="p">:</span>
                <span class="n">G2</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>

    <span class="c1"># Get positions
</span>    <span class="n">pos</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">get_node_attributes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="sh">'</span><span class="s">pos</span><span class="sh">'</span><span class="p">)</span>

    <span class="c1"># Selected nodes (part of the solution)
</span>    <span class="n">selected_nodes</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">))</span>
                      <span class="k">if</span> <span class="nf">round</span><span class="p">(</span><span class="n">solution_vector</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">==</span> <span class="mi">1</span><span class="p">]</span>

    <span class="c1"># Non-selected nodes
</span>    <span class="n">non_selected</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">))</span>
                    <span class="k">if</span> <span class="n">i</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">selected_nodes</span><span class="p">]</span>

    <span class="c1"># Pin nodes
</span>    <span class="n">pin_nodes</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">pins</span>

    <span class="c1"># Draw edges
</span>    <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_edges</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">width</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>

    <span class="c1"># Draw non-selected nodes
</span>    <span class="k">if</span> <span class="n">non_selected</span><span class="p">:</span>
        <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="n">nodelist</span><span class="o">=</span><span class="n">non_selected</span><span class="p">,</span>
                             <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">lightgray</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.6</span><span class="p">)</span>

    <span class="c1"># Draw selected nodes
</span>    <span class="k">if</span> <span class="n">selected_nodes</span><span class="p">:</span>
        <span class="c1"># Color selected nodes that are also pins
</span>        <span class="n">selected_pins</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">selected_nodes</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">pin_nodes</span><span class="p">]</span>
        <span class="n">selected_regular</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">selected_nodes</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">pin_nodes</span><span class="p">]</span>

        <span class="c1"># Draw selected regular nodes
</span>        <span class="k">if</span> <span class="n">selected_regular</span><span class="p">:</span>
            <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">nodelist</span><span class="o">=</span><span class="n">selected_regular</span><span class="p">,</span>
                                 <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">green</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">350</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.8</span><span class="p">)</span>

        <span class="c1"># Draw selected pin nodes
</span>        <span class="k">if</span> <span class="n">selected_pins</span><span class="p">:</span>
            <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">nodelist</span><span class="o">=</span><span class="n">selected_pins</span><span class="p">,</span>
                                 <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">red</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.8</span><span class="p">)</span>

    <span class="c1"># Draw non-selected pin nodes
</span>    <span class="n">non_selected_pins</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">pin_nodes</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">selected_nodes</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">non_selected_pins</span><span class="p">:</span>
        <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">nodelist</span><span class="o">=</span><span class="n">non_selected_pins</span><span class="p">,</span>
                             <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">orange</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">380</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.7</span><span class="p">)</span>

    <span class="c1"># Add pin labels
</span>    <span class="n">pin_labels</span> <span class="o">=</span> <span class="p">{</span><span class="n">pin</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">v</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">pin</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">pin_nodes</span><span class="p">)}</span>
    <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_labels</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">labels</span><span class="o">=</span><span class="n">pin_labels</span><span class="p">,</span> <span class="n">font_size</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>

    <span class="c1"># Draw the second graph.
</span>    <span class="c1">#nx.draw(G2, pos, ax=axes[1], with_labels=True, node_color=node_colors2, node_size=500)
</span>    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_axis_off</span><span class="p">()</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Scipy Solution</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1">#plt.title("MWIS Solution on Unit Disk Graph")
</span>    <span class="c1">#plt.axis('equal')
</span>
    <span class="c1">#plt.savefig(output_file)
</span>    <span class="c1">#plt.close()
</span>    <span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">visualize_bloqade_scipy_solution</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="nf">list</span><span class="p">(</span><span class="n">sorted_counts</span><span class="p">.</span><span class="nf">items</span><span class="p">())[:</span><span class="mi">2</span><span class="p">],</span> <span class="n">udg</span><span class="p">.</span><span class="n">qubo_result</span><span class="p">,</span> <span class="n">solution_vector</span><span class="p">)</span>
</code></pre></div></div> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_30_0.png" alt="png"/></p> <h3 id="implementation-with-pulser">Implementation with <code class="language-plaintext highlighter-rouge">pulser</code></h3> <p><code class="language-plaintext highlighter-rouge">pulser</code> is another Python library for programming and simulating neutral atom devices, focusing on pulse-level control. The <code class="language-plaintext highlighter-rouge">pulser_example.py</code> script demonstrates its usage.</p> <p><strong>Steps:</strong></p> <ol> <li><strong>Prepare Inputs:</strong> Similar to Bloqade, get scaled atom locations and normalized weights.</li> </ol> <p><strong>Note!</strong> we solve a smaller 2x2 Ising Problem, due to that the <code class="language-plaintext highlighter-rouge">Pulser</code> Emulator does not have a <code class="language-plaintext highlighter-rouge">subspace</code> method like <code class="language-plaintext highlighter-rouge">bloqade</code> does, therefore it has to simulate the whole system. A regular laptop can simulate ~ 15 nodes in 10 mins.</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">pulser</span> <span class="kn">import</span> <span class="n">Pulse</span><span class="p">,</span> <span class="n">Sequence</span><span class="p">,</span> <span class="n">Register</span>
<span class="kn">from</span> <span class="n">pulser_simulation</span> <span class="kn">import</span> <span class="n">QutipEmulator</span>
<span class="kn">from</span> <span class="n">pulser.channels.dmm</span> <span class="kn">import</span> <span class="n">DMM</span>
<span class="kn">from</span> <span class="n">pulser.devices</span> <span class="kn">import</span> <span class="n">AnalogDevice</span><span class="p">,</span> <span class="n">DigitalAnalogDevice</span>
<span class="kn">from</span> <span class="n">pulser.register</span> <span class="kn">import</span> <span class="n">Register</span>
<span class="kn">from</span> <span class="n">pulser.sampler</span> <span class="kn">import</span> <span class="n">sampler</span>
<span class="kn">from</span> <span class="n">pulser.sequence</span> <span class="kn">import</span> <span class="n">Sequence</span>
<span class="kn">from</span> <span class="n">pulser.pulse</span> <span class="kn">import</span> <span class="n">Pulse</span>
<span class="kn">from</span> <span class="n">pulser.waveforms</span> <span class="kn">import</span> <span class="n">ConstantWaveform</span><span class="p">,</span> <span class="n">RampWaveform</span><span class="p">,</span> <span class="n">CompositeWaveform</span><span class="p">,</span> <span class="n">InterpolatedWaveform</span>

<span class="kn">from</span> <span class="n">dataclasses</span> <span class="kn">import</span> <span class="n">replace</span>
</code></pre></div></div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">================================</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Demonstrating the mapping of an Ising model to a Unit Disk Graph</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">and solving it with Pulser Emulator.</span><span class="sh">"</span><span class="p">)</span>
<span class="c1"># Create a simple Ising model
# Quadratic terms (J)
</span><span class="n">quad</span> <span class="o">=</span> <span class="p">{</span>
    <span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">):</span> <span class="mf">1.0</span><span class="p">,</span>   <span class="c1"># Ferromagnetic coupling between qubits 0 and 1
</span><span class="p">}</span>

<span class="c1"># Linear terms (h)
</span><span class="n">linear</span> <span class="o">=</span> <span class="p">{</span>
    <span class="mi">0</span><span class="p">:</span> <span class="mf">0.1</span><span class="p">,</span>
    <span class="mi">1</span><span class="p">:</span> <span class="o">-</span><span class="mf">0.2</span><span class="p">,</span>
<span class="p">}</span>

<span class="c1"># Create the Ising model
</span><span class="n">ising_model</span> <span class="o">=</span> <span class="nc">IsingModel</span><span class="p">(</span><span class="n">quad</span><span class="o">=</span><span class="n">quad</span><span class="p">,</span> <span class="n">linear</span><span class="o">=</span><span class="n">linear</span><span class="p">,</span> <span class="n">constant</span><span class="o">=</span><span class="mf">0.0</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Created Ising model with:</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">ising_model</span><span class="p">.</span><span class="n">quad</span><span class="p">)</span><span class="si">}</span><span class="s"> quadratic terms</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">ising_model</span><span class="p">.</span><span class="n">linear</span><span class="p">)</span><span class="si">}</span><span class="s"> linear terms</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- </span><span class="si">{</span><span class="n">ising_model</span><span class="p">.</span><span class="nf">num_bits</span><span class="p">()</span><span class="si">}</span><span class="s"> qubits</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># Convert to a Unit Disk Graph
</span><span class="n">udg</span> <span class="o">=</span> <span class="n">ising_model</span><span class="p">.</span><span class="nf">to_unit_disk_graph</span><span class="p">(</span><span class="n">normalize</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>

<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Converted to Unit Disk Graph:</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">nodes</span><span class="p">)</span><span class="si">}</span><span class="s"> nodes in the grid graph</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">- </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">udg</span><span class="p">.</span><span class="n">pins</span><span class="p">)</span><span class="si">}</span><span class="s"> pins corresponding to original variables</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># Step 1: Solve the QUBO problem
</span><span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Step 1: Solve the QUBO problem with MWIS/scipy and brute force</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">---------------------------</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># Solve the Ising model using the UDG representation
</span><span class="n">result</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="nf">solve</span><span class="p">(</span><span class="n">use_brute_force</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>  <span class="c1"># For small problems, use brute force
</span><span class="n">solution_vector</span> <span class="o">=</span> <span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">solution_vector</span><span class="sh">'</span><span class="p">]</span>

<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Solution found using </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">solution_method</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="s"> approach!</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Original configuration: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">original_config</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Energy of the configuration: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>


<span class="c1"># Step 2: Compare solution methods
</span><span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Step 2: Compare solution methods</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">-------------------------------</span><span class="sh">"</span><span class="p">)</span>

<span class="k">if</span> <span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">result</span> <span class="ow">and</span> <span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">solution_method</span><span class="sh">'</span><span class="p">]</span> <span class="o">!=</span> <span class="sh">"</span><span class="s">brute_force</span><span class="sh">"</span><span class="p">:</span>
    <span class="n">bf_result</span> <span class="o">=</span> <span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span><span class="p">]</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Brute force minimum energy: </span><span class="si">{</span><span class="n">bf_result</span><span class="p">[</span><span class="sh">'</span><span class="s">min_energy</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Brute force minimum configuration: </span><span class="si">{</span><span class="n">bf_result</span><span class="p">[</span><span class="sh">'</span><span class="s">best_config</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">MWIS solution energy: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">MWIS solution configuration: </span><span class="si">{</span><span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">original_config</span><span class="sh">'</span><span class="p">]</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Show top 3 configurations from brute force
</span>    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Top configurations by energy:</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="n">energy</span><span class="p">)</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">bf_result</span><span class="p">[</span><span class="sh">'</span><span class="s">all_configs</span><span class="sh">'</span><span class="p">][:</span><span class="mi">3</span><span class="p">]):</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s">. Config </span><span class="si">{</span><span class="n">config</span><span class="si">}</span><span class="s">: Energy = </span><span class="si">{</span><span class="n">energy</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Convert to string for comparison since float equality can be tricky
</span>    <span class="k">if</span> <span class="nf">abs</span><span class="p">(</span><span class="n">bf_result</span><span class="p">[</span><span class="sh">'</span><span class="s">min_energy</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">])</span> <span class="o">&lt;</span> <span class="mf">1e-4</span> <span class="ow">and</span> <span class="nf">str</span><span class="p">(</span><span class="n">bf_result</span><span class="p">[</span><span class="sh">'</span><span class="s">best_config</span><span class="sh">'</span><span class="p">])</span> <span class="o">==</span> <span class="nf">str</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">original_config</span><span class="sh">'</span><span class="p">]):</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">✓ MWIS solution matches the brute force solution!</span><span class="sh">"</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">✗ MWIS solution differs from the brute force solution.</span><span class="sh">"</span><span class="p">)</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">  Energy difference: </span><span class="sh">"</span><span class="p">,</span> <span class="nf">abs</span><span class="p">(</span><span class="n">bf_result</span><span class="p">[</span><span class="sh">'</span><span class="s">min_energy</span><span class="sh">'</span><span class="p">]</span> <span class="o">-</span> <span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">energy</span><span class="sh">'</span><span class="p">]))</span>
<span class="k">elif</span> <span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">solution_method</span><span class="sh">'</span><span class="p">]</span> <span class="o">==</span> <span class="sh">"</span><span class="s">brute_force</span><span class="sh">"</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Using brute force solution directly (optimal solution guaranteed).</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Show top 3 configurations from brute force
</span>    <span class="k">if</span> <span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span> <span class="ow">in</span> <span class="n">result</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Top configurations by energy:</span><span class="sh">"</span><span class="p">)</span>
        <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">config</span><span class="p">,</span> <span class="n">energy</span><span class="p">)</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">result</span><span class="p">[</span><span class="sh">'</span><span class="s">brute_force_result</span><span class="sh">'</span><span class="p">][</span><span class="sh">'</span><span class="s">all_configs</span><span class="sh">'</span><span class="p">][:</span><span class="mi">3</span><span class="p">]):</span>
            <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="si">{</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s">. Config </span><span class="si">{</span><span class="n">config</span><span class="si">}</span><span class="s">: Energy = </span><span class="si">{</span><span class="n">energy</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Brute force comparison not available for this problem size.</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>================================
Demonstrating the mapping of an Ising model to a Unit Disk Graph
and solving it with Pulser Emulator.
Created Ising model with:
- 1 quadratic terms
- 2 linear terms
- 2 qubits
Overwriting delta from 1.5 to 1.5

Converted to Unit Disk Graph:
- 8 nodes in the grid graph
- 2 pins corresponding to original variables

Step 1: Solve the QUBO problem with MWIS/scipy and brute force
---------------------------
Solution found using brute_force approach!
Original configuration: [-1, 1]
Energy of the configuration: -2.3000000000000003

Step 2: Compare solution methods
-------------------------------
Using brute force solution directly (optimal solution guaranteed).

Top configurations by energy:
1. Config [-1, 1]: Energy = -2.3000000000000003
2. Config [1, -1]: Energy = -1.7
3. Config [1, 1]: Energy = 1.9000000000000001
</code></pre></div></div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">qubo_result</span> <span class="o">=</span> <span class="n">udg</span><span class="p">.</span><span class="n">qubo_result</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Mapping successful with </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">)</span><span class="si">}</span><span class="s"> nodes and </span><span class="si">{</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">pins</span><span class="p">)</span><span class="si">}</span><span class="s"> pins</span><span class="sh">"</span><span class="p">)</span>

<span class="n">locations</span> <span class="o">=</span> <span class="nf">qubo_grid_to_locations</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">)</span>
<span class="n">weights</span> <span class="o">=</span> <span class="nf">qubo_result_to_weights</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Node weights: </span><span class="si">{</span><span class="n">weights</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
<span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Node locations: </span><span class="si">{</span><span class="n">locations</span><span class="si">}</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Mapping successful with 8 nodes and 2 pins
Node weights: [1.3, 1.6, 5.0, 7.0, 7.0, 5.0, 1.4, 1.7]
Node locations: [(0.0, 10.0), (5.0, 0.0), (5.0, 5.0), (5.0, 10.0), (10.0, 5.0), (10.0, 10.0), (10.0, 15.0), (15.0, 5.0)]
</code></pre></div></div> <ol> <li><strong>Define Register and Device:</strong> Create a <code class="language-plaintext highlighter-rouge">Register</code> with atom locations and choose a <code class="language-plaintext highlighter-rouge">Device</code> model (which defines constraints like blockade radius implicitly or explicitly).</li> </ol> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Create the register
</span><span class="n">qubits</span> <span class="o">=</span> <span class="p">{</span><span class="sa">f</span><span class="sh">"</span><span class="s">q</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span><span class="p">:</span> <span class="n">coord</span> <span class="nf">for </span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">coord</span><span class="p">)</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">locations</span><span class="p">)}</span>

<span class="n">register</span> <span class="o">=</span> <span class="n">Register</span><span class="p">.</span><span class="nf">from_coordinates</span><span class="p">(</span>
    <span class="n">locations</span><span class="p">,</span> <span class="n">center</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">prefix</span><span class="o">=</span><span class="sh">"</span><span class="s">q</span><span class="sh">"</span>
<span class="p">)</span>
<span class="n">register</span><span class="p">.</span><span class="nf">draw</span><span class="p">(</span>
    <span class="n">blockade_radius</span><span class="o">=</span><span class="mf">7.5</span><span class="p">,</span>
    <span class="n">draw_graph</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">draw_half_radius</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_36_0.png" alt="png"/></p> <p>Next we need to define a local detuning map, such that $\Delta_i = w_i \times \Delta (t)$</p> <ul> <li><strong>Important:</strong> Implementing the <em>weighted</em> detuning $\delta_i(t) = w_i \times \delta_{global}(t)$ in Pulser typically requires more advanced features like local addressing channels or Digital Micromirror Devices (DMMs) if the target device supports them. The example above shows a simpler global pulse; consult Pulser documentation for implementing weighted detuning maps. The <code class="language-plaintext highlighter-rouge">pulser_example.py</code> uses DMMs, which is a more complex setup involving <code class="language-plaintext highlighter-rouge">DetuningMap</code>, <code class="language-plaintext highlighter-rouge">config_detuning_map</code>, and <code class="language-plaintext highlighter-rouge">add_dmm_detuning</code>.</li> </ul> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Normalize weights to be between 0 and 1 (required by Pulser)
</span><span class="n">lw</span> <span class="o">=</span> <span class="nf">len</span><span class="p">(</span><span class="n">weights</span><span class="p">)</span>
<span class="n">weights_norm</span> <span class="o">=</span>  <span class="p">[</span><span class="n">x</span><span class="o">/</span><span class="nf">max</span><span class="p">(</span><span class="n">weights</span><span class="p">)</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">weights</span><span class="p">]</span>
<span class="c1"># inverse of the weights (later for creating the detuning map)
</span><span class="n">w_inv_norm</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span> <span class="o">-</span> <span class="n">x</span> <span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">weights_norm</span><span class="p">]</span>

<span class="n">dmap1</span> <span class="o">=</span> <span class="n">register</span><span class="p">.</span><span class="nf">define_detuning_map</span><span class="p">(</span>
<span class="p">{</span> <span class="sa">f</span><span class="sh">"</span><span class="s">q</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span><span class="p">:</span> <span class="n">weights_norm</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">lw</span><span class="p">)}</span>  <span class="c1"># mapping between qubit ids and weights
</span><span class="p">)</span>

<span class="n">dmap2</span> <span class="o">=</span> <span class="n">register</span><span class="p">.</span><span class="nf">define_detuning_map</span><span class="p">(</span>
<span class="p">{</span> <span class="sa">f</span><span class="sh">"</span><span class="s">q</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span><span class="p">:</span> <span class="n">w_inv_norm</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">lw</span><span class="p">)}</span>  <span class="c1"># mapping between qubit ids and weights
</span><span class="p">)</span>

<span class="n">dmap1</span><span class="p">.</span><span class="nf">draw</span><span class="p">(</span><span class="n">labels</span><span class="o">=</span><span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">lw</span><span class="p">)])</span>
</code></pre></div></div> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_38_0.png" alt="png"/></p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Configure DetuningMap
</span><span class="n">dmm</span> <span class="o">=</span> <span class="nc">DMM</span><span class="p">(</span><span class="n">clock_period</span><span class="o">=</span><span class="mi">4</span><span class="p">,</span>
          <span class="n">min_duration</span><span class="o">=</span><span class="mi">16</span><span class="p">,</span>
          <span class="n">max_duration</span><span class="o">=</span><span class="mi">2</span><span class="o">**</span><span class="mi">26</span><span class="p">,</span>
          <span class="n">mod_bandwidth</span><span class="o">=</span><span class="mi">8</span><span class="p">,</span>
          <span class="n">bottom_detuning</span><span class="o">=-</span><span class="mi">2</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">pi</span> <span class="o">*</span> <span class="mi">20</span><span class="p">,</span>  <span class="c1"># detuning between 0 and -20 MHz
</span>          <span class="n">total_bottom_detuning</span><span class="o">=-</span><span class="mi">2</span> <span class="o">*</span> <span class="n">np</span><span class="p">.</span><span class="n">pi</span> <span class="o">*</span> <span class="mi">2000</span><span class="p">,</span>  <span class="c1"># total detuning
</span><span class="p">)</span>

<span class="n">mock_device</span> <span class="o">=</span> <span class="nf">replace</span><span class="p">(</span>
    <span class="n">AnalogDevice</span><span class="p">.</span><span class="nf">to_virtual</span><span class="p">(),</span>
    <span class="n">dmm_objects</span><span class="o">=</span><span class="p">(</span><span class="n">dmm</span><span class="p">,</span> <span class="nc">DMM</span><span class="p">()),</span>
    <span class="n">reusable_channels</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">)</span>
<span class="c1">#print(mock_device.dmm_channels)
</span></code></pre></div></div> <ol> <li><strong>Define Pulses and Sequence:</strong> Construct the pulse sequence using <code class="language-plaintext highlighter-rouge">pulser</code> objects. Implementing spatially varying detuning often requires <code class="language-plaintext highlighter-rouge">DetuningMap</code> and DMM channels if the device supports them, or applying pulses locally if using local addressing channels. For a simple adiabatic sweep similar to the Bloqade example using global pulses:</li> </ol> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">seq</span> <span class="o">=</span> <span class="nc">Sequence</span><span class="p">(</span><span class="n">register</span><span class="p">,</span> <span class="n">mock_device</span><span class="p">)</span>
<span class="n">seq</span><span class="p">.</span><span class="nf">config_detuning_map</span><span class="p">(</span><span class="n">dmap1</span><span class="p">,</span> <span class="sh">"</span><span class="s">dmm_0</span><span class="sh">"</span><span class="p">)</span>
<span class="n">seq</span><span class="p">.</span><span class="nf">config_detuning_map</span><span class="p">(</span><span class="n">dmap2</span><span class="p">,</span> <span class="sh">"</span><span class="s">dmm_1</span><span class="sh">"</span><span class="p">)</span>
<span class="c1">#print(seq.declared_channels)
</span>
<span class="n">T</span> <span class="o">=</span> <span class="mi">5000</span> <span class="c1"># ns
</span><span class="n">delta</span> <span class="o">=</span> <span class="mf">30.</span>
<span class="n">Omega</span> <span class="o">=</span> <span class="mi">5</span>
<span class="n">seq</span><span class="p">.</span><span class="nf">add_dmm_detuning</span><span class="p">(</span><span class="nc">RampWaveform</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="o">-</span><span class="n">delta</span><span class="p">,</span> <span class="mi">0</span><span class="p">),</span> <span class="sh">"</span><span class="s">dmm_0</span><span class="sh">"</span><span class="p">,</span> <span class="n">protocol</span><span class="o">=</span><span class="sh">"</span><span class="s">no-delay</span><span class="sh">"</span><span class="p">)</span>
<span class="n">seq</span><span class="p">.</span><span class="nf">add_dmm_detuning</span><span class="p">(</span><span class="nc">ConstantWaveform</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="o">-</span><span class="n">delta</span><span class="o">/</span><span class="mi">2</span><span class="p">),</span> <span class="sh">"</span><span class="s">dmm_1</span><span class="sh">"</span><span class="p">,</span> <span class="n">protocol</span><span class="o">=</span><span class="sh">"</span><span class="s">no-delay</span><span class="sh">"</span><span class="p">)</span>
<span class="n">seq</span><span class="p">.</span><span class="nf">declare_channel</span><span class="p">(</span><span class="sh">"</span><span class="s">ryd_glob</span><span class="sh">"</span><span class="p">,</span> <span class="sh">"</span><span class="s">rydberg_global</span><span class="sh">"</span><span class="p">)</span>

<span class="n">adiabatic_pulse</span> <span class="o">=</span> <span class="nc">Pulse</span><span class="p">(</span>
    <span class="nc">InterpolatedWaveform</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="p">[</span><span class="mf">1e-9</span><span class="p">,</span> <span class="n">Omega</span><span class="p">,</span> <span class="mf">1e-9</span><span class="p">]),</span>
    <span class="nc">ConstantWaveform</span><span class="p">(</span><span class="n">T</span><span class="p">,</span> <span class="n">delta</span><span class="o">/</span><span class="mi">2</span><span class="p">),</span>
    <span class="mi">0</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">seq</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">adiabatic_pulse</span><span class="p">,</span> <span class="sh">"</span><span class="s">ryd_glob</span><span class="sh">"</span><span class="p">,</span> <span class="n">protocol</span><span class="o">=</span><span class="sh">"</span><span class="s">no-delay</span><span class="sh">"</span><span class="p">)</span>
</code></pre></div></div> <p>Draw the system setting (Pulses) before simulation</p> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">seq</span><span class="p">.</span><span class="nf">draw</span><span class="p">(</span>
    <span class="n">mode</span><span class="o">=</span><span class="sh">"</span><span class="s">input+output</span><span class="sh">"</span><span class="p">,</span>  <span class="c1"># "input" only shows input signals, "input+output"
</span>    <span class="n">draw_qubit_det</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
    <span class="n">draw_qubit_amp</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">)</span>
</code></pre></div></div> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_43_0.png" alt="Alt text" width="900"/></p> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_43_1.png" alt="Alt text" width="900"/></p> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_43_2.png" alt="Alt text"/></p> <ol> <li><strong>Run the Simulation:</strong> Use Pulser’s emulator (<code class="language-plaintext highlighter-rouge">QutipEmulator</code>).</li> </ol> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="n">pulser_simulation</span> <span class="kn">import</span> <span class="n">QutipEmulator</span>

<span class="c1"># Simulate the sequence
# Blockade is handled by the emulator based on C6 coeff and atom distances
</span><span class="n">simul</span> <span class="o">=</span> <span class="n">QutipEmulator</span><span class="p">.</span><span class="nf">from_sequence</span><span class="p">(</span><span class="n">seq</span><span class="p">)</span>
<span class="n">results</span> <span class="o">=</span> <span class="n">simul</span><span class="p">.</span><span class="nf">run</span><span class="p">()</span>
<span class="n">final</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="nf">get_final_state</span><span class="p">()</span>
<span class="n">count_dict</span> <span class="o">=</span> <span class="n">results</span><span class="p">.</span><span class="nf">sample_final_state</span><span class="p">()</span>

<span class="c1"># Sort by frequency
</span><span class="n">sorted_counts_pulser</span> <span class="o">=</span> <span class="nf">dict</span><span class="p">(</span><span class="nf">sorted</span><span class="p">(</span><span class="n">count_dict</span><span class="p">.</span><span class="nf">items</span><span class="p">(),</span> <span class="n">key</span><span class="o">=</span><span class="k">lambda</span> <span class="n">item</span><span class="p">:</span> <span class="n">item</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">reverse</span><span class="o">=</span><span class="bp">True</span><span class="p">))</span>

<span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="se">\n</span><span class="s">Pulser Simulation Results (Top 5):</span><span class="sh">"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="p">(</span><span class="n">bitstring</span><span class="p">,</span> <span class="n">count</span><span class="p">)</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="nf">list</span><span class="p">(</span><span class="n">sorted_counts_pulser</span><span class="p">.</span><span class="nf">items</span><span class="p">())[:</span><span class="mi">5</span><span class="p">]):</span>
    <span class="c1"># Pulser bitstring '1' usually means Rydberg |r&gt; (in IS), '0' means ground |g&gt;
</span>    <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s"> </span><span class="si">{</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s">. Bitstring: </span><span class="si">{</span><span class="n">bitstring</span><span class="si">}</span><span class="s"> (Count: </span><span class="si">{</span><span class="n">count</span><span class="si">}</span><span class="s">)</span><span class="sh">"</span><span class="p">)</span>

<span class="n">most_frequent_bs_pulser</span> <span class="o">=</span> <span class="nf">list</span><span class="p">(</span><span class="n">sorted_counts_pulser</span><span class="p">.</span><span class="nf">keys</span><span class="p">())[</span><span class="mi">0</span><span class="p">]</span> <span class="k">if</span> <span class="n">sorted_counts_pulser</span> <span class="k">else</span> <span class="bp">None</span>
</code></pre></div></div> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Pulser Simulation Results (Top 5):
 1. Bitstring: 01010001 (Count: 344)
 2. Bitstring: 10001010 (Count: 132)
 3. Bitstring: 00011000 (Count: 91)
 4. Bitstring: 00001010 (Count: 74)
 5. Bitstring: 01010000 (Count: 74)
</code></pre></div></div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Below combines all the steps together, while setting the experiment parameters as function arguments.
</span>
<span class="sh">"""</span><span class="s">
def solve_ising_pulser(locations, weights, delta_max=30, Omega_max=5, t_max=5000):
    # Create a register with the given locations
    register = Register.from_coordinates(locations, center=True, prefix=</span><span class="sh">"</span><span class="s">q</span><span class="sh">"</span><span class="s">)
    # register.draw(blockade_radius=7.5, draw_graph=True, draw_half_radius=True) # Uncomment if want to visualize the register
    lw = len(weights)
    # Normalize weights to be between 0 and 1 (required by Pulser)
    weights_norm =  [x/max(weights) for x in weights]
    # inverse of the weights (later for creating the detuning map)
    w_inv_norm = [1 - x for x in weights_norm]

    dmap1 = register.define_detuning_map(
    { f</span><span class="sh">"</span><span class="s">q{i}</span><span class="sh">"</span><span class="s">: weights_norm[i] for i in range(lw)}  # mapping between qubit ids and weights
    )
    # dmap1.draw(labels=[i for i in range(lw)]) # Uncomment if want to visualize the detuning map
    dmap2 = register.define_detuning_map(
    { f</span><span class="sh">"</span><span class="s">q{i}</span><span class="sh">"</span><span class="s">: w_inv_norm[i] for i in range(lw)}  # mapping between qubit ids and weights
    )

    # Configure DetuningMap
    dmm = DMM(clock_period=4,
              min_duration=16,
              max_duration=2**26,
              mod_bandwidth=8,
              bottom_detuning=-2 * np.pi * 20,  # detuning between 0 and -20 MHz
              total_bottom_detuning=-2 * np.pi * 2000,  # total detuning
    )

    mock_device = replace(
        AnalogDevice.to_virtual(),
        dmm_objects=(dmm, DMM()),
        reusable_channels=True,
    )
    #print(mock_device.dmm_channels)

    seq = Sequence(register, mock_device)
    seq.config_detuning_map(dmap1, </span><span class="sh">"</span><span class="s">dmm_0</span><span class="sh">"</span><span class="s">)
    seq.config_detuning_map(dmap2, </span><span class="sh">"</span><span class="s">dmm_1</span><span class="sh">"</span><span class="s">)
    #print(seq.declared_channels)

    T = t_max
    delta = delta_max
    Omega = Omega_max
    seq.add_dmm_detuning(RampWaveform(T, -delta, 0), </span><span class="sh">"</span><span class="s">dmm_0</span><span class="sh">"</span><span class="s">, protocol=</span><span class="sh">"</span><span class="s">no-delay</span><span class="sh">"</span><span class="s">)
    seq.add_dmm_detuning(ConstantWaveform(T, -delta/2), </span><span class="sh">"</span><span class="s">dmm_1</span><span class="sh">"</span><span class="s">, protocol=</span><span class="sh">"</span><span class="s">no-delay</span><span class="sh">"</span><span class="s">)
    seq.declare_channel(</span><span class="sh">"</span><span class="s">ryd_glob</span><span class="sh">"</span><span class="s">, </span><span class="sh">"</span><span class="s">rydberg_global</span><span class="sh">"</span><span class="s">)

    adiabatic_pulse = Pulse(
        InterpolatedWaveform(T, [1e-9, Omega, 1e-9]),
        ConstantWaveform(T, delta/2),
        0,
    )
    seq.add(adiabatic_pulse, </span><span class="sh">"</span><span class="s">ryd_glob</span><span class="sh">"</span><span class="s">, protocol=</span><span class="sh">"</span><span class="s">no-delay</span><span class="sh">"</span><span class="s">)

    return seq
</span><span class="sh">"""</span>
</code></pre></div></div> <ul> <li>The emulator calculates the interactions based on the register geometry and the device’s $C_6$ coefficient, implicitly handling the blockade. <ul> <li><code class="language-plaintext highlighter-rouge">sample_final_state</code> provides the counts for different output bitstrings. Again, check the convention (‘0’/’1’ vs $\vert g\rangle$/$\vert r\rangle$). Pulser often uses ‘1’ for the Rydberg state $\vert r\rangle$.</li> </ul> </li> </ul> <h3 id="comparison-and-visualization">Comparison and Visualization</h3> <p>After obtaining results from both the classical MILP solver and the quantum simulators (Bloqade/Pulser), it’s essential to compare them.</p> <ul> <li><strong>Map Back:</strong> Ensure all results are mapped back to the original Ising spin configuration ($z_i \in {-1, +1}$) for a fair comparison. Remember the different bitstring conventions.</li> <li><strong>Energy Calculation:</strong> Calculate the energy of the configurations found by each method using the original Ising Hamiltonian (<code class="language-plaintext highlighter-rouge">ising_model.calc_energy(config)</code>).</li> <li><strong>Visualization:</strong> Create plots comparing the solutions, similar to <code class="language-plaintext highlighter-rouge">visualize_bloqade_scipy_solution</code> in the example scripts. This involves plotting the UDG graph and coloring the nodes based on whether they were selected in the MWIS found by the classical solver versus the most probable state(s) from the quantum simulation.</li> </ul> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">visualize_pulser_scipy_solution</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="n">top2</span><span class="p">,</span> <span class="n">qubo_result</span><span class="p">,</span> <span class="n">solution_vector</span><span class="p">,</span> <span class="n">output_file</span><span class="o">=</span><span class="sh">"</span><span class="s">img/compare_solution.png</span><span class="sh">"</span><span class="p">):</span>
    <span class="sh">"""</span><span class="s">
    Visualize the Bloqade solution and scipy solution side by side.

    Args:
        locations: List of (x, y) tuples for node locations
        top2: Top 2 solutions from Bloqade
        qubo_result: QUBOResult from map_qubo
        solution_vector: Binary solution vector from MWIS solver
        output_file: Path to save the visualization
    </span><span class="sh">"""</span>
    <span class="c1"># Function to create a graph with nodes at fixed locations.
</span>    <span class="k">def</span> <span class="nf">create_graph</span><span class="p">(</span><span class="n">locs</span><span class="p">,</span> <span class="n">threshold</span><span class="p">):</span>
        <span class="n">G</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">Graph</span><span class="p">()</span>
        <span class="k">for</span> <span class="n">idx</span><span class="p">,</span> <span class="n">pos</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">locs</span><span class="p">):</span>
            <span class="n">G</span><span class="p">.</span><span class="nf">add_node</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="n">pos</span><span class="p">)</span>

        <span class="c1"># Add edges based on distance threshold
</span>        <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">locs</span><span class="p">)):</span>
            <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">i</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">locs</span><span class="p">)):</span>
                <span class="n">dist</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linalg</span><span class="p">.</span><span class="nf">norm</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">locs</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">-</span> <span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">locs</span><span class="p">[</span><span class="n">j</span><span class="p">]))</span>
                <span class="k">if</span> <span class="n">dist</span> <span class="o">&lt;</span> <span class="n">threshold</span><span class="p">:</span>
                    <span class="n">G</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>

        <span class="k">return</span> <span class="n">G</span>

    <span class="c1"># Create two graphs (they share the same node positions)
</span>    <span class="n">G1</span> <span class="o">=</span> <span class="nf">create_graph</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="mf">7.5</span><span class="p">)</span>
    <span class="n">G2</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nc">Graph</span><span class="p">()</span>

    <span class="c1"># Retrieve node positions from one of the graphs.
</span>    <span class="n">pos</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">get_node_attributes</span><span class="p">(</span><span class="n">G1</span><span class="p">,</span> <span class="sh">'</span><span class="s">pos</span><span class="sh">'</span><span class="p">)</span>

    <span class="c1"># Determine node colors for each graph based on the corresponding bitstring.
</span>    <span class="n">bitstr1</span> <span class="o">=</span> <span class="n">top2</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>

    <span class="n">node_colors1</span> <span class="o">=</span> <span class="p">[</span><span class="sh">'</span><span class="s">red</span><span class="sh">'</span> <span class="k">if</span> <span class="n">bit</span> <span class="o">==</span> <span class="sh">'</span><span class="s">1</span><span class="sh">'</span> <span class="k">else</span> <span class="sh">'</span><span class="s">lightgray</span><span class="sh">'</span> <span class="k">for</span> <span class="n">bit</span> <span class="ow">in</span> <span class="n">bitstr1</span><span class="p">]</span>
    <span class="n">node_size1</span> <span class="o">=</span> <span class="p">[</span><span class="mi">400</span> <span class="k">if</span> <span class="n">bit</span> <span class="o">==</span> <span class="sh">'</span><span class="s">1</span><span class="sh">'</span> <span class="k">else</span> <span class="mi">100</span> <span class="k">for</span> <span class="n">bit</span> <span class="ow">in</span> <span class="n">bitstr1</span><span class="p">]</span>

    <span class="c1"># Create subplots with two axes (side by side)
</span>    <span class="n">fig</span><span class="p">,</span> <span class="n">axes</span> <span class="o">=</span> <span class="n">plt</span><span class="p">.</span><span class="nf">subplots</span><span class="p">(</span><span class="n">ncols</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">figsize</span><span class="o">=</span><span class="p">(</span><span class="mi">12</span><span class="p">,</span> <span class="mi">6</span><span class="p">))</span>

    <span class="c1"># Draw the first graph.
</span>    <span class="n">nx</span><span class="p">.</span><span class="nf">draw</span><span class="p">(</span><span class="n">G1</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">with_labels</span><span class="o">=</span><span class="bp">False</span><span class="p">,</span> <span class="n">node_color</span><span class="o">=</span><span class="n">node_colors1</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="n">node_size1</span><span class="p">)</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Pulser Solution</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1"># Add nodes with positions from the grid graph
</span>    <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">node</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">):</span>
        <span class="n">G2</span><span class="p">.</span><span class="nf">add_node</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">pos</span><span class="o">=</span><span class="n">node</span><span class="p">.</span><span class="n">loc</span><span class="p">,</span> <span class="n">weight</span><span class="o">=</span><span class="n">node</span><span class="p">.</span><span class="n">weight</span><span class="p">)</span>

    <span class="c1"># Add edges based on unit disk constraint
</span>    <span class="n">radius</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">radius</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">)):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">)):</span>
            <span class="n">node1</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
            <span class="n">node2</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">[</span><span class="n">j</span><span class="p">]</span>
            <span class="n">dist</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nf">sqrt</span><span class="p">(</span><span class="nf">sum</span><span class="p">((</span><span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">node1</span><span class="p">.</span><span class="n">loc</span><span class="p">)</span> <span class="o">-</span> <span class="n">np</span><span class="p">.</span><span class="nf">array</span><span class="p">(</span><span class="n">node2</span><span class="p">.</span><span class="n">loc</span><span class="p">))</span><span class="o">**</span><span class="mi">2</span><span class="p">))</span>
            <span class="k">if</span> <span class="n">dist</span> <span class="o">&lt;=</span> <span class="n">radius</span><span class="p">:</span>
                <span class="n">G2</span><span class="p">.</span><span class="nf">add_edge</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span>

    <span class="c1"># Get positions
</span>    <span class="n">pos</span> <span class="o">=</span> <span class="n">nx</span><span class="p">.</span><span class="nf">get_node_attributes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="sh">'</span><span class="s">pos</span><span class="sh">'</span><span class="p">)</span>

    <span class="c1"># Selected nodes (part of the solution)
</span>    <span class="n">selected_nodes</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">))</span>
                      <span class="k">if</span> <span class="nf">round</span><span class="p">(</span><span class="n">solution_vector</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="o">==</span> <span class="mi">1</span><span class="p">]</span>

    <span class="c1"># Non-selected nodes
</span>    <span class="n">non_selected</span> <span class="o">=</span> <span class="p">[</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="nf">len</span><span class="p">(</span><span class="n">qubo_result</span><span class="p">.</span><span class="n">grid_graph</span><span class="p">.</span><span class="n">nodes</span><span class="p">))</span>
                    <span class="k">if</span> <span class="n">i</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">selected_nodes</span><span class="p">]</span>

    <span class="c1"># Pin nodes
</span>    <span class="n">pin_nodes</span> <span class="o">=</span> <span class="n">qubo_result</span><span class="p">.</span><span class="n">pins</span>

    <span class="c1"># Draw edges
</span>    <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_edges</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">width</span><span class="o">=</span><span class="mf">0.5</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.5</span><span class="p">)</span>

    <span class="c1"># Draw non-selected nodes
</span>    <span class="k">if</span> <span class="n">non_selected</span><span class="p">:</span>
        <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span><span class="n">nodelist</span><span class="o">=</span><span class="n">non_selected</span><span class="p">,</span>
                             <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">lightgray</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.6</span><span class="p">)</span>

    <span class="c1"># Draw selected nodes
</span>    <span class="k">if</span> <span class="n">selected_nodes</span><span class="p">:</span>
        <span class="c1"># Color selected nodes that are also pins
</span>        <span class="n">selected_pins</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">selected_nodes</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">pin_nodes</span><span class="p">]</span>
        <span class="n">selected_regular</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">selected_nodes</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">pin_nodes</span><span class="p">]</span>

        <span class="c1"># Draw selected regular nodes
</span>        <span class="k">if</span> <span class="n">selected_regular</span><span class="p">:</span>
            <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">nodelist</span><span class="o">=</span><span class="n">selected_regular</span><span class="p">,</span>
                                 <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">green</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">350</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.8</span><span class="p">)</span>

        <span class="c1"># Draw selected pin nodes
</span>        <span class="k">if</span> <span class="n">selected_pins</span><span class="p">:</span>
            <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">nodelist</span><span class="o">=</span><span class="n">selected_pins</span><span class="p">,</span>
                                 <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">red</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">400</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.8</span><span class="p">)</span>

    <span class="c1"># Draw non-selected pin nodes
</span>    <span class="n">non_selected_pins</span> <span class="o">=</span> <span class="p">[</span><span class="n">n</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">pin_nodes</span> <span class="k">if</span> <span class="n">n</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">selected_nodes</span><span class="p">]</span>
    <span class="k">if</span> <span class="n">non_selected_pins</span><span class="p">:</span>
        <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_nodes</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">nodelist</span><span class="o">=</span><span class="n">non_selected_pins</span><span class="p">,</span>
                             <span class="n">node_color</span><span class="o">=</span><span class="sh">'</span><span class="s">orange</span><span class="sh">'</span><span class="p">,</span> <span class="n">node_size</span><span class="o">=</span><span class="mi">380</span><span class="p">,</span> <span class="n">alpha</span><span class="o">=</span><span class="mf">0.7</span><span class="p">)</span>

    <span class="c1"># Add pin labels
</span>    <span class="n">pin_labels</span> <span class="o">=</span> <span class="p">{</span><span class="n">pin</span><span class="p">:</span> <span class="sa">f</span><span class="sh">"</span><span class="s">v</span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="sh">"</span> <span class="k">for</span> <span class="n">i</span><span class="p">,</span> <span class="n">pin</span> <span class="ow">in</span> <span class="nf">enumerate</span><span class="p">(</span><span class="n">pin_nodes</span><span class="p">)}</span>
    <span class="n">nx</span><span class="p">.</span><span class="nf">draw_networkx_labels</span><span class="p">(</span><span class="n">G2</span><span class="p">,</span> <span class="n">pos</span><span class="p">,</span> <span class="n">ax</span><span class="o">=</span><span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">labels</span><span class="o">=</span><span class="n">pin_labels</span><span class="p">,</span> <span class="n">font_size</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>

    <span class="c1"># Draw the second graph.
</span>    <span class="c1">#nx.draw(G2, pos, ax=axes[1], with_labels=True, node_color=node_colors2, node_size=500)
</span>    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_axis_off</span><span class="p">()</span>
    <span class="n">axes</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="nf">set_title</span><span class="p">(</span><span class="sa">f</span><span class="sh">"</span><span class="s">Scipy Solution</span><span class="sh">"</span><span class="p">)</span>

    <span class="c1">#plt.title("MWIS Solution on Unit Disk Graph")
</span>    <span class="c1">#plt.axis('equal')
</span>
    <span class="c1">#plt.savefig(output_file)
</span>    <span class="c1">#plt.close()
</span>    <span class="n">plt</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
</code></pre></div></div> <div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">visualize_pulser_scipy_solution</span><span class="p">(</span><span class="n">locations</span><span class="p">,</span> <span class="nf">list</span><span class="p">(</span><span class="n">sorted_counts_pulser</span><span class="p">.</span><span class="nf">items</span><span class="p">())[:</span><span class="mi">2</span><span class="p">],</span> <span class="n">qubo_result</span><span class="p">,</span> <span class="n">solution_vector</span><span class="p">)</span>
</code></pre></div></div> <p><img src="/assets/img/ising_UDG/ising_MWIS_UDG_49_0.png" alt="png"/></p> <p>Discrepancies between the classical and quantum simulation results can arise from non-adiabatic evolution (simulation time <code class="language-plaintext highlighter-rouge">t_max</code> too short), decoherence effects (not modeled here), or differences in how weights/pulses are implemented in each framework.</p> <p>This section outlined the physics behind solving MWIS on neutral atom arrays and demonstrated how to simulate this process using <code class="language-plaintext highlighter-rouge">bloqade-analog</code> and <code class="language-plaintext highlighter-rouge">pulser</code>, starting from the UDG generated by <code class="language-plaintext highlighter-rouge">qamomile</code>.</p> <h2 id="summary-and-conclusion">Summary and Conclusion</h2> <p>Neutral atom quantum computers offer a powerful platform for tackling complex optimization problems. However, their native interactions, governed by the Rydberg blockade effect, naturally lend themselves to problems defined on Unit-Disk Graphs (UDGs), where connectivity is limited by distance. Many important problems, such as general QUBO or Ising models, feature arbitrary connectivity, posing a challenge for direct implementation.</p> <p>This tutorial explored the <strong>Unit-Disk Mapping (UDM)</strong> technique, specifically its formulation via the Maximum Weight Independent Set (MWIS) problem, as a method to bridge this gap. We demonstrated how:</p> <ol> <li>Arbitrarily connected QUBO/Ising problems can be systematically mapped onto an MWIS problem on a specially constructed UDG using geometric gadgets.</li> <li>The <code class="language-plaintext highlighter-rouge">qamomile</code> library facilitates this mapping process, converting an <code class="language-plaintext highlighter-rouge">IsingModel</code> definition into a <code class="language-plaintext highlighter-rouge">UnitDiskGraph</code> representation suitable for simulation or hardware execution.</li> <li>The resulting MWIS-UDG problem can be solved classically using standard methods like Mixed-Integer Linear Programming (MILP), as implemented via <code class="language-plaintext highlighter-rouge">scipy.optimize.milp</code>.</li> <li>Alternatively, the MWIS-UDG problem can be solved using adiabatic evolution protocols on neutral atom platforms, leveraging the Rydberg blockade to enforce the independent set constraint. We showed how to simulate this process using the <code class="language-plaintext highlighter-rouge">bloqade-analog</code> and <code class="language-plaintext highlighter-rouge">pulser</code> frameworks.</li> </ol> <p>The UDM technique, combined with the programmability of neutral atom arrays, opens up avenues for applying these quantum devices to a broader class of optimization problems beyond those with native geometric constraints. While the mapping introduces an overhead in the number of required qubits (nodes in the UDG), ongoing research explores optimization techniques to minimize this overhead for specific problem structures. As neutral atom hardware continues to scale and improve, techniques like UDM will be crucial for unlocking their potential for practical combinatorial optimization.</p> <h2 id="references">References</h2> <ul> <li> <p><strong>Unit-Disk Mapping Paper:</strong> Nguyen, M. T., Das, S., Weidinger, L., Staudacher, S., Katz, O., Häner, T., … &amp; Cong, I. (2023). Quantum Optimization with Arbitrary Connectivity Using Rydberg Atom Arrays. <em>PRX Quantum</em>, <em>4</em>(1), 010316. <a href="https://doi.org/10.1103/PRXQuantum.4.010316">DOI: 10.1103/PRXQuantum.4.010316</a></p> </li> <li> <p><strong>Bloqade-analog:</strong> <a href="https://queracomputing.github.io/bloqade-analog/">https://queracomputing.github.io/bloqade-analog/latest/</a></p> </li> <li> <p><strong>Pulser:</strong> <a href="https://github.com/pasqal-io/Pulser">https://github.com/pasqal-io/Pulser</a> Silver, L. S., Henry, L. P., Le Régent, A., Valentin, C., &amp; Henriet, L. (2022). Pulser: An open-source package for the design of pulse sequences in neutral-atom quantum devices. <em>Quantum</em>, <em>6</em>, 629. <a href="https://doi.org/10.22331/q-2022-01-24-629">DOI: 10.22331/q-2022-01-24-629</a> —</p> </li> </ul>]]></content><author><name>PoJen Wang</name></author><category term="quantum-computing"/><category term="jupyter"/><summary type="html"><![CDATA[Solving QUBO/Ising Problems via Unit-Disk Graphs on Neutral Atom Quantum Computers]]></summary></entry><entry><title type="html">Kmeans Clustering Algorithm with CUDA</title><link href="https://nez0b.github.io/blog/2024/Kmeans/" rel="alternate" type="text/html" title="Kmeans Clustering Algorithm with CUDA"/><published>2024-10-01T00:00:00+00:00</published><updated>2024-10-01T00:00:00+00:00</updated><id>https://nez0b.github.io/blog/2024/Kmeans</id><content type="html" xml:base="https://nez0b.github.io/blog/2024/Kmeans/"><![CDATA[<h2 id="introduction">Introduction</h2> <p>K-means <d-cite key="wiki"></d-cite>is a widely used unsupervised machine learning algorithm for clustering data points into K distinct groups based on their similarities. The algorithm aims to minimize the sum of squared distances between data points and their assigned cluster centroids. K-means has found applications in various fields, including image processing, customer segmentation, and data compression.</p> <p>In the general framework of the k-means algorithm, we are given a dataset $\mathbf{X} = (\mathbf{x_1}, \mathbf{x_2}, \dots, \mathbf{x_n})$, where each $\mathbf{x_i}$ represents a $d$-dimensional point in a vector space. The goal is to partition this dataset into $k$ distinct clusters $\mathbf{S} = {\mathbf{S_1}, \mathbf{S_2}, \dots, \mathbf{S_k}}$ such that points within each cluster are more similar to each other than to those in different clusters. This is achieved by solving the following optimization problem <d-footnote>However, solving this optimization problem exactly is NP-hard, which necessitates the use of approximation algorithms in practice. One common approximation method is Lloyd’s algorithm, developed by Stuart Lloyd in 1957. This iterative algorithm is what most people refer to when discussing k-means</d-footnote></p> <p>\(\underset{\mathbf{S}}{\text{arg min}} \sum_{i=1}^k \sum_{\mathbf{x} \in \mathbf{S_i}} ||\mathbf{x} - \mathbf{\mu_i}||^2,\) where $\mathbf{\mu_i}$ denotes the centroid of cluster $\mathbf{S_i}$.</p> <h3 id="k-means-algorithm-overview">K-means Algorithm Overview</h3> <p>The standard K-means algorithm follows these steps:</p> <ul> <li>Initialization: Randomly select K points as initial cluster centroids.</li> <li>Assignment: Assign each data point to the nearest centroid based on Euclidean distance.</li> <li>Update: Recalculate the centroids of each cluster by computing the mean of all points assigned to that cluster.</li> <li>Iteration: Repeat steps 2 and 3 until convergence or a maximum number of iterations is reached.</li> </ul> <h2 id="cuda-implementation">CUDA Implementation</h2> <h3 id="basic-cuda-implementation">Basic CUDA implementation</h3> <p>Our CUDA implementation of Kmeans algorithm follows from the the paradigm from the previous section. In the second step, “Assignment” part, we could leverage the thousands of CUDA cores to calculate the nearest center for each point simultaneously. The kernel function is outline below:</p> <figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">__global__</span> <span class="kt">void</span> 
<span class="nf">mapPointsToCenters</span><span class="p">(</span>
    <span class="n">point</span> <span class="o">*</span><span class="n">d_Points</span><span class="p">,</span>
    <span class="n">point</span> <span class="o">*</span><span class="n">d_Centers</span><span class="p">,</span>
    <span class="kt">int</span> <span class="o">*</span><span class="n">d_Labels</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">numPoints</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">numCenters</span><span class="p">)</span>
<span class="p">{</span>
    <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">blockDim</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
    <span class="k">if</span><span class="p">(</span><span class="n">idx</span> <span class="o">&gt;=</span> <span class="n">numPoints</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
    <span class="kt">float</span> <span class="n">min_dist</span> <span class="o">=</span> <span class="n">FLT_MAX</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">min_idx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span> <span class="n">i</span><span class="o">&lt;</span><span class="n">numCenters</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
        <span class="kt">float</span> <span class="n">dist</span> <span class="o">=</span> <span class="n">distance</span><span class="p">(</span><span class="n">d_Points</span><span class="p">[</span><span class="n">idx</span><span class="p">],</span> <span class="n">d_Centers</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
        <span class="k">if</span><span class="p">(</span><span class="n">dist</span> <span class="o">&lt;</span> <span class="n">min_dist</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">min_idx</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
            <span class="n">min_dist</span> <span class="o">=</span> <span class="n">dist</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">d_Labels</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">min_idx</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure> <p>Next, we reset the centroids to zero in preparation for recalculating them in the following step. Each point is then assigned to its nearest centroid. Afterward, we implement a kernel to sum all points sharing the same label and record the number of points assigned to each label in the variable <em>d_ClusterCounts</em>. Finally, we divide the cumulative sum of vectors for each label by <em>d_ClusterCounts</em> to compute the new centroids.</p> <figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">__global__</span> <span class="kt">void</span> 
<span class="nf">accumulateCenters</span><span class="p">(</span>
    <span class="n">point</span> <span class="o">*</span><span class="n">d_Points</span><span class="p">,</span>
    <span class="n">point</span> <span class="o">*</span><span class="n">d_Centers</span><span class="p">,</span>
    <span class="kt">int</span> <span class="o">*</span><span class="n">d_ClusterCounts</span><span class="p">,</span>
    <span class="kt">int</span> <span class="o">*</span><span class="n">d_Labels</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">numPoints</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">numCenters</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">blockDim</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
	<span class="k">if</span><span class="p">(</span><span class="n">idx</span> <span class="o">&gt;=</span> <span class="n">numPoints</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
	<span class="kt">int</span> <span class="n">clusterid</span> <span class="o">=</span> <span class="n">d_Labels</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span>
        <span class="kt">int</span> <span class="n">dim</span> <span class="o">=</span> <span class="n">d_Points</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">size</span><span class="p">;</span>
	<span class="k">for</span><span class="p">(</span><span class="kt">int</span> <span class="n">i</span><span class="o">=</span><span class="mi">0</span><span class="p">;</span><span class="n">i</span><span class="o">&lt;</span><span class="n">dim</span><span class="p">;</span><span class="n">i</span><span class="o">++</span><span class="p">)</span> 
		<span class="n">atomicAdd</span><span class="p">(</span><span class="o">&amp;</span><span class="n">d_Centers</span><span class="p">[</span><span class="n">clusterid</span><span class="p">].</span><span class="n">entries</span><span class="p">[</span><span class="n">i</span><span class="p">],</span> <span class="n">d_Points</span><span class="p">[</span><span class="n">idx</span><span class="p">].</span><span class="n">entries</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
	<span class="n">atomicAdd</span><span class="p">(</span><span class="o">&amp;</span><span class="n">d_ClusterCounts</span><span class="p">[</span><span class="n">clusterid</span><span class="p">],</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">__global__</span> <span class="kt">void</span> 
<span class="nf">updateCenters</span><span class="p">(</span>
    <span class="n">point</span> <span class="o">*</span><span class="n">d_Centers</span><span class="p">,</span>
    <span class="kt">int</span> <span class="o">*</span><span class="n">d_ClusterCounts</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">numCenters</span><span class="p">)</span>
<span class="p">{</span>
	<span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span><span class="o">*</span><span class="n">blockDim</span><span class="p">.</span><span class="n">x</span><span class="o">+</span><span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
	<span class="k">if</span><span class="p">(</span><span class="n">idx</span> <span class="o">&gt;=</span> <span class="n">numCenters</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
	<span class="n">d_Centers</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">/=</span> <span class="n">d_ClusterCounts</span><span class="p">[</span><span class="n">idx</span><span class="p">];</span>
<span class="p">}</span></code></pre></figure> <h3 id="cuda-shared-memory">CUDA shared memory</h3> <p>In the previous basic CUDA implementation, all the variables are stored and accessed from GPU’s global memory. We could improve our implementation by using GPU’s shared memory. CUDA’s shared memory is a special type of on-chip memory that allows threads within the same block to share data and communicate efficiently. It is much faster than global memory (which resides off-chip) but is limited in size (typically around 48 KB per block). Shared memory is accessible to all threads within a block, and it provides a mechanism for threads to collaborate by reading and writing to a common memory space, enabling data reuse and reducing the need for costly global memory access.</p> <p>In the second step, where we map each point to its nearest centroid, we could leverage the shared memory and stored the coordinate of the centroids in the shared memory so that it could be accessed much faster. Below is my implementation:</p> <figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">__global__</span> <span class="kt">void</span> <span class="nf">mapPointsToCenters_shmem</span><span class="p">(</span>
<span class="kt">int</span> <span class="n">DIM</span><span class="p">,</span>
    <span class="kt">double</span><span class="o">*</span> <span class="n">d_Points</span><span class="p">,</span>
    <span class="kt">double</span><span class="o">*</span> <span class="n">d_Centers</span><span class="p">,</span>
    <span class="kt">int</span> <span class="o">*</span><span class="n">d_Labels</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">numPoints</span><span class="p">,</span>
    <span class="kt">int</span> <span class="n">numCenters</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="kt">int</span> <span class="n">shmemSize</span> <span class="o">=</span> <span class="n">DIM</span> <span class="o">*</span> <span class="n">numCenters</span><span class="p">;</span>
    <span class="c1">//----------LOAD CENTROIDS TO SHARED MEMORY ----------------------------</span>
    <span class="k">extern</span> <span class="n">__shared__</span> <span class="kt">double</span> <span class="n">d_Centers_shmem</span><span class="p">[];</span>
    <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">blockIdx</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="n">blockDim</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">idx</span> <span class="o">&gt;=</span> <span class="n">numPoints</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>

    <span class="c1">// Load centers into shared memory for each block</span>
    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="n">threadIdx</span><span class="p">.</span><span class="n">x</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">shmemSize</span><span class="p">;</span> <span class="n">i</span> <span class="o">+=</span> <span class="n">blockDim</span><span class="p">.</span><span class="n">x</span><span class="p">)</span> <span class="p">{</span>
        <span class="n">d_Centers_shmem</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">d_Centers</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>  <span class="c1">// Copy from global memory to shared memory</span>
    <span class="p">}</span>
    <span class="c1">//----------LOAD CENTROIDS TO SHARED MEMORY ----------------------------</span>
    <span class="n">__syncthreads</span><span class="p">();</span> <span class="c1">// Ensure all centers are loaded before proceeding</span>

    <span class="c1">// Find nearest center</span>
    <span class="kt">double</span> <span class="n">min_dist</span> <span class="o">=</span> <span class="n">DBL_MAX</span><span class="p">;</span>
    <span class="kt">int</span> <span class="n">min_idx</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>

    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">numCenters</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
	<span class="kt">double</span> <span class="n">dist</span> <span class="o">=</span> <span class="mf">0.0</span><span class="p">;</span>

	<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">DIM</span><span class="p">;</span> <span class="n">j</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
		<span class="kt">double</span> <span class="n">diff</span> <span class="o">=</span> <span class="n">d_Points</span><span class="p">[</span><span class="n">idx</span> <span class="o">*</span> <span class="n">DIM</span> <span class="o">+</span> <span class="n">j</span><span class="p">]</span> <span class="o">-</span> <span class="n">d_Centers_shmem</span><span class="p">[</span><span class="n">i</span> <span class="o">*</span> <span class="n">DIM</span> <span class="o">+</span> <span class="n">j</span><span class="p">];</span>
            <span class="n">dist</span> <span class="o">+=</span> <span class="n">diff</span> <span class="o">*</span> <span class="n">diff</span><span class="p">;</span>
        <span class="p">}</span>
	<span class="n">dist</span> <span class="o">=</span> <span class="n">sqrt</span><span class="p">(</span><span class="n">dist</span><span class="p">);</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">dist</span> <span class="o">&lt;</span> <span class="n">min_dist</span><span class="p">)</span> <span class="p">{</span>
            <span class="n">min_dist</span> <span class="o">=</span> <span class="n">dist</span><span class="p">;</span>
            <span class="n">min_idx</span> <span class="o">=</span> <span class="n">i</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    <span class="n">d_Labels</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span> <span class="o">=</span> <span class="n">min_idx</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure> <h3 id="cuda-thrust">CUDA Thrust</h3> <p>The CUDA Thrust library is a high-level, C++-based parallel programming library designed to simplify GPU programming using NVIDIA’s CUDA architecture. It provides a set of STL (Standard Template Library)-like data structures and algorithms optimized for parallel execution on CUDA-enabled devices. Thrust abstracts the complexities of CUDA, allowing developers to write efficient parallel code with minimal knowledge of low-level CUDA programming. Below we highlight the key points.</p> <h4 id="calculate-distance-between-d-dimensional-vectors">Calculate distance between d-dimensional vectors</h4> <p>Given two d-dimension vectors, we could use the Thrust API to calcualte their euclidean distance as:</p> <figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="kt">double</span> <span class="nf">distance</span><span class="p">(</span><span class="k">const</span> <span class="n">thrust</span><span class="o">::</span><span class="n">device_vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;&amp;</span> <span class="n">centers</span><span class="p">,</span> 
                <span class="k">const</span> <span class="n">thrust</span><span class="o">::</span><span class="n">device_vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;&amp;</span> <span class="n">old_centers</span><span class="p">,</span> 
                <span class="kt">int</span> <span class="n">DIM</span><span class="p">,</span> 
                <span class="kt">int</span> <span class="n">numCenters</span><span class="p">,</span> 
                <span class="n">thrust</span><span class="o">::</span><span class="n">device_vector</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;&amp;</span> <span class="n">d_center_idx</span><span class="p">,</span>
                <span class="n">thrust</span><span class="o">::</span><span class="n">device_vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="o">&amp;</span><span class="n">squared_diffs</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">thrust</span><span class="o">::</span><span class="n">device_vector</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span> <span class="n">squared_diff_centers</span><span class="p">(</span><span class="n">numCenters</span><span class="p">);</span>
    
    <span class="n">thrust</span><span class="o">::</span><span class="n">transform</span><span class="p">(</span><span class="n">thrust</span><span class="o">::</span><span class="n">device</span><span class="p">,</span>
        <span class="n">centers</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">centers</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span>
        <span class="n">old_centers</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span>
        <span class="n">squared_diffs</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span>
        <span class="n">thrust</span><span class="o">::</span><span class="n">minus</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">()</span>
    <span class="p">);</span>
    <span class="n">thrust</span><span class="o">::</span><span class="n">transform</span><span class="p">(</span><span class="n">thrust</span><span class="o">::</span><span class="n">device</span><span class="p">,</span>
        <span class="n">squared_diffs</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">squared_diffs</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span>
        <span class="n">squared_diffs</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span>
        <span class="n">thrust</span><span class="o">::</span><span class="n">square</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">()</span>
    <span class="p">);</span>

    <span class="n">thrust</span><span class="o">::</span><span class="n">reduce_by_key</span><span class="p">(</span>
        <span class="n">d_center_idx</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">d_center_idx</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span>
        <span class="n">squared_diffs</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span>
        <span class="n">thrust</span><span class="o">::</span><span class="n">make_discard_iterator</span><span class="p">(),</span>
        <span class="n">squared_diff_centers</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span>
        <span class="n">thrust</span><span class="o">::</span><span class="n">equal_to</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(),</span>
        <span class="n">thrust</span><span class="o">::</span><span class="n">plus</span><span class="o">&lt;</span><span class="kt">double</span><span class="o">&gt;</span><span class="p">()</span>
    <span class="p">);</span>
<span class="p">}</span></code></pre></figure> <p>The <em>d_center_idx</em> give all the coordinates of a vector the same label so that they are be grouped together in the reduction step.</p> <h4 id="map-points-to-centers-step">Map points to centers step</h4> <p>To find the accumulate sum of points with the same label, for each point with a different centroid label, we give each coordinate of the vector a different, so that we have $\text{number of clusters} \times \text{dimension}$ labels. Suppose point $a_1,\; a_2$ are assigned to centroid 1 and 2 respectively, then we label each coordinate as follows:</p> \[\mathbf{a}_1 = \begin{pmatrix} a_{11} \\ a_{12} \\ \vdots \\ a_{1d} \end{pmatrix} \Rightarrow \begin{pmatrix} \text{label 1}\\ \text{label 2}\\ \vdots \\ \text{label d}\\ \end{pmatrix} ,\hspace{5pt} \mathbf{a}_2 = \begin{pmatrix} a_{21} \\ a_{22} \\ \vdots \\ a_{2d} \end{pmatrix} \Rightarrow \begin{pmatrix} \text{label d+1}\\ \text{label d+2}\\ \vdots \\ \text{label 2d}\\ \end{pmatrix} ,\hspace{5pt}\cdots\] <p>With this, we then use <code class="language-plaintext highlighter-rouge">thrust::stable_sort_by_key</code> to sort the input dataset array of size <code class="language-plaintext highlighter-rouge">(number of points*dimension)</code> by the above labels, then use the labels as key and use <code class="language-plaintext highlighter-rouge">thrust::reduce_by_key</code> to accumulate points with the same labels.</p> <h4 id="calculate-number-of-points-in-each-centroid">Calculate number of points in each centroid</h4> <p>After each point is assigned to its nearest centeroid, the number of points in each centroid can be found by</p> <figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">thrust</span><span class="o">::</span><span class="n">reduce_by_key</span><span class="p">(</span>
        <span class="n">d_labels</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">d_labels</span><span class="p">.</span><span class="n">end</span><span class="p">(),</span> 
        <span class="n">thrust</span><span class="o">::</span><span class="n">constant_iterator</span><span class="o">&lt;</span><span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="mi">1</span><span class="p">),</span> 
        <span class="n">thrust</span><span class="o">::</span><span class="n">make_discard_iterator</span><span class="p">(),</span> 
        <span class="n">d_ClusterCounts</span><span class="p">.</span><span class="n">begin</span><span class="p">()</span>
    <span class="p">);</span></code></pre></figure> <h2 id="comparison-between-different-implementations">Comparison between different implementations</h2> <p>We ran the kmeans algorithm on our CPU (serial), basic CUDA, CUDA shared memory and Thrust implementations. For each implementation, we timed and profiled the execution on three different input sets: (1) \texttt{random-n2048-d16-c16.txt} has 2048 points and each point is of dimension 16. (2) \texttt{random-n16384-d24-c16.txt} has 16384 points and each point is of dimension 24. (3) \texttt{random-n65536-d32-c16.txt} has 65536 points and each point is of dimension 32.</p> <p><a id="fig:device1"></a> <img src="/assets/img/kmeans/device1.png" alt="Alt text" width="700"/></p> <p>The experiments were conducted on a machine with an AMD Ryzen 5600G CPU (6 cores/12 threads), running Ubuntu 22.04. The GPU used was an Nvidia GTX 1080Ti with 11 GB memory, featuring 3584 CUDA cores, 28 streaming multiprocessors (SMs), and an L1 cache size of 48 KB per SM <d-cite key="gpu"></d-cite>. The CUDA driver version was 545.84, and the CUDA toolkit version was 12.3. Among the implementations, the shared-memory CUDA implementation was the fastest, as expected. This is because using shared memory made the mapPointsToCenters step more efficient. The Nvidia 1080Ti GPU has a maximum of 2048 threads per SM, and with 28 SMs, the maximum number of threads in flight is 57344. This should theoretically represent the maximum speed-up. However, due to variations in the parallelizability of different steps in the k-means algorithm, the actual maximum performance depends on the input size and dimension. From <a href="#fig:speedup">Figure.2 </a>, the maximum speed-up of approximately 5500 was achieved with the shared memory CUDA implementation on the n65536-d32 dataset, which is about one-tenth of the theoretical estimate. The slowest implementation was the Thrust version, which was slower than both the shared-memory and basic CUDA implementations across all input datasets. However, the performance gap between the Thrust and basic CUDA implementations narrowed as input size increased, and for the n65536-d32 dataset, their performance was nearly comparable. It is expected that for larger datasets, the Thrust implementation may outperform the basic CUDA implementation since the distance calculation in the basic version uses a for-loop, while the Thrust implementation is optimized for any dimensionality and is more efficient.</p> <p><a id="fig:speedup"></a> <img src="/assets/img/kmeans/speedup.png" alt="Alt text" width="700"/></p> <hr/>]]></content><author><name>PoJen Wang</name></author><category term="parallel-computing"/><category term="ML"/><summary type="html"><![CDATA[Kmeans Clustering Algorithm with CUDA and CUDA Thrust API]]></summary></entry><entry><title type="html">From Daubechies wavelets to qubits: an N₂ quantum-chemistry pipeline</title><link href="https://nez0b.github.io/blog/2024/wavelet-quantum-chemistry/" rel="alternate" type="text/html" title="From Daubechies wavelets to qubits: an N₂ quantum-chemistry pipeline"/><published>2024-08-07T00:00:00+00:00</published><updated>2024-08-07T00:00:00+00:00</updated><id>https://nez0b.github.io/blog/2024/wavelet-quantum-chemistry</id><content type="html" xml:base="https://nez0b.github.io/blog/2024/wavelet-quantum-chemistry/"><![CDATA[<p class="wavelet-lede"> Most quantum-chemistry workflows begin with atom-centered Gaussian orbitals. BigDFT offers a different starting point: compactly supported Daubechies wavelets on an adaptive real-space grid. This article follows that representation all the way from molecular orbitals to a qubit Hamiltonian and asks whether the resulting N₂ bond curve can be compared fairly with a conventional Gaussian-basis calculation. </p> <p>N₂ is a useful test case because it is small enough for exact active-space checks but demanding enough to expose basis resolution, frozen-core conventions, bond breaking, and solver error. The workflow extracts one- and two-electron integrals from wavelet orbitals, validates every representation boundary, and connects the same Hamiltonian to full configuration interaction (FCI), variational quantum eigensolver (VQE), quantum-selected configuration interaction (QSCI), and sample-based quantum diagonalization (SQD).</p> <p>The final workflow is compact enough to state in one line:</p> <div class="wavelet-pipeline"> <div><strong>BigDFT</strong>adaptive wavelet orbitals</div> <div><strong>Integral factory</strong>$h_{pq}$ and $(pq|rs)$</div> <div><strong>Active space</strong>freeze and select orbitals</div> <div><strong>Qubit map</strong>Jordan–Wigner Hamiltonian</div> <div><strong>Solvers</strong>FCI, VQE, QSCI, SQD</div> </div> <p>The complete implementation, datasets, analysis scripts, tests, and machine-readable results live in the <a href="https://github.com/nez0b/bigdft-drug-design/tree/agent/wavelet-qc-macos-revival"><code class="language-plaintext highlighter-rouge">agent/wavelet-qc-macos-revival</code> branch</a> of the GitHub codebase. Readers who want implementation or environment details should follow the README and documentation there.</p> <h2 id="the-central-question">The central question</h2> <p>The motivation is not that wavelets are automatically “better” than Gaussian orbitals. It is more specific.</p> <p>Quantum chemistry must make two truncations. First, a classical electronic-structure calculation represents continuous orbitals in a finite numerical basis. Second, a quantum algorithm keeps a finite set of spatial orbitals, usually turning $m$ spatial orbitals into $2m$ spin-orbital qubits. A representation that gives controlled real-space accuracy while keeping the chemically useful orbital space compact could therefore help both sides of the pipeline.</p> <p>BigDFT is attractive because it uses compactly supported Daubechies wavelets on an adaptive grid rather than a fixed list of atom-centered Gaussian functions <d-cite key="daubechies1988,genovese2008"></d-cite>. Prior work showed that wavelet molecular orbitals can be useful inputs to quantum computations and vibrational calculations <d-cite key="hong2022,chou2023"></d-cite>. The question here is whether that idea can support a reproducible end-to-end integral pipeline and a scientifically controlled molecular benchmark.</p> <p>The standard is therefore stronger than obtaining a plausible energy: the calculation needs a complete potential-energy curve, a properly matched reference, and independent checks at every representation boundary.</p> <h2 id="wavelets-in-one-picture">Wavelets in one picture</h2> <h3 id="multiresolution-instead-of-atom-centered-functions">Multiresolution instead of atom-centered functions</h3> <p>A molecular orbital is still an expansion,</p> <div class="wavelet-equation"> \[\phi_i(\mathbf r)=\sum_\alpha c_{i\alpha}\,\Phi_\alpha(\mathbf r),\] </div> <p>but the basis functions $\Phi_\alpha$ now live on a real-space grid. Scaling functions describe the smooth part of an orbital; wavelets add localized detail. Both have compact support, so a basis function is exactly zero outside a finite interval.</p> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/wavelet_qc/wavelet_basis_1d-480.webp 480w,/assets/img/wavelet_qc/wavelet_basis_1d-800.webp 800w,/assets/img/wavelet_qc/wavelet_basis_1d-1400.webp 1400w," sizes="(min-width: 930px) 930px, 95vw" type="image/webp"/> <img src="/assets/img/wavelet_qc/wavelet_basis_1d.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" alt="A Daubechies scaling function and wavelet with compact support, shown relative to the nitrogen bond length" data-zoomable="" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption">A one-dimensional view of the scaling function and wavelet used to illustrate the basis. Compact support and localized detail are the important ideas; this is not a molecular-orbital plot.</figcaption> </figure> <p>The three-dimensional basis is made from tensor products. At a coarse grid point there is one $\phi\phi\phi$ scaling function. In the fine region, the seven other $\phi/\psi$ combinations add detail. This is why the coefficient count in our cost analysis is naturally written as</p> <div class="wavelet-equation"> \[N_\text{basis}=N_\text{coarse}+7N_\text{fine}.\] </div> <p>The useful mental model is a microscope: retain a broad, inexpensive description of the vacuum and smooth orbital tails, then add resolution near the atoms where the functions vary rapidly.</p> <h3 id="the-three-practical-controls">The three practical controls</h3> <p>Wavelets do not remove convergence choices. They make those choices geometric and systematic:</p> <ul> <li><code class="language-plaintext highlighter-rouge">hgrid</code> is the real-space spacing. Smaller values resolve shorter-length-scale features and cost more.</li> <li><code class="language-plaintext highlighter-rouge">crmult</code> controls the radius of the coarse region around each atom.</li> <li><code class="language-plaintext highlighter-rouge">frmult</code> controls the smaller fine-resolution region near each nucleus.</li> </ul> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/wavelet_qc/wavelet_regions-480.webp 480w,/assets/img/wavelet_qc/wavelet_regions-800.webp 800w,/assets/img/wavelet_qc/wavelet_regions-1400.webp 1400w," sizes="(min-width: 930px) 930px, 95vw" type="image/webp"/> <img src="/assets/img/wavelet_qc/wavelet_regions.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" alt="Schematic coarse and fine wavelet grids surrounding two nitrogen atoms" data-zoomable="" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption">Schematic two-dimensional slices through the N₂ support regions. Blue points carry coarse scaling functions; red points also carry the seven fine wavelets. The circles are support masks, not electron-density contours.</figcaption> </figure> <p>This is an important correction to the loose phrase “basis-set free.” There is no cc-pVDZ-style catalog to choose, but there is still a finite basis. Its error is controlled by grid spacing and spatial support.</p> <h2 id="from-orbitals-to-qubits">From orbitals to qubits</h2> <p>Once BigDFT has converged the occupied and requested virtual orbitals, the downstream problem is familiar. For orthonormal orbitals, the electronic Hamiltonian is</p> <div class="wavelet-equation"> \[H=\sum_{pq} h_{pq}a_p^\dagger a_q +\frac{1}{2}\sum_{pqrs}(pq|rs)a_p^\dagger a_r^\dagger a_s a_q +E_\text{core}.\] </div> <p>The custom BigDFT postprocessor writes symmetry-unique <code class="language-plaintext highlighter-rouge">hpq</code> and <code class="language-plaintext highlighter-rouge">hpqrs</code> records. The maintained Python loader restores the matrix, electron-repulsion, and spin symmetries, reduces the problem to a chosen active space, and maps the fermionic operators to Pauli strings with Jordan–Wigner.</p> <h3 id="turning-four-index-integrals-into-poisson-solves">Turning four-index integrals into Poisson solves</h3> <p>The expensive object is</p> <div class="wavelet-equation"> \[(pq|rs)=\iint \phi_p(\mathbf r_1)\phi_q(\mathbf r_1) \frac{1}{|\mathbf r_1-\mathbf r_2|} \phi_r(\mathbf r_2)\phi_s(\mathbf r_2) \,d\mathbf r_1d\mathbf r_2.\] </div> <p>Rather than introduce unrelated quadrature machinery, the extractor reuses BigDFT’s real-space Poisson solver. For each orbital pair it forms $\rho_{pq}(\mathbf r)=\phi_p(\mathbf r)\phi_q(\mathbf r)$, solves</p> <div class="wavelet-equation"> \[\nabla^2V_{pq}(\mathbf r)=-4\pi\rho_{pq}(\mathbf r),\] </div> <p>and evaluates $\int \rho_{rs}(\mathbf r)V_{pq}(\mathbf r)d\mathbf r$. This is the central bridge from the wavelet representation to an ordinary second-quantized chemistry Hamiltonian.</p> <h3 id="a-validation-ladder">A validation ladder</h3> <p>A plausible energy is not enough. The rebuilt pipeline checks each handoff independently:</p> <ol> <li>The equilibrium fixture contains all 240 expected one-electron records and 7,260 symmetry-unique two-electron records for 15 spatial orbitals.</li> <li>Randomly selected permutations satisfy the real-integral symmetries.</li> <li>The occupied-orbital Hartree–Fock expression can be reconstructed from the emitted integrals. It is 0.437 Ha above the BigDFT PBE energy on the same orbitals, as expected from the different exchange-correlation treatment—not from a missing integral.</li> <li>For an MP2-guided CAS(4e,4o), exact diagonalization of the explicit eight-qubit Jordan–Wigner operator agrees with an independent PySCF FCI calculation to $6.75\times10^{-14}$ Ha.</li> <li>The eight-qubit Hamiltonian contains 185 Pauli terms. A small 64-parameter hardware-efficient VQE lands 4.05 mHa above exact: useful as a runnable demonstration, but outside the conventional 1.6 mHa “chemical accuracy” line.</li> </ol> <p>Only after those checks do I interpret the molecular curve.</p> <h2 id="making-the-n-comparison-fair">Making the N₂ comparison fair</h2> <h3 id="why-frozen-core-matters">Why frozen core matters</h3> <p>The most subtle part of the benchmark is not fitting the curve. It is deciding what can be compared.</p> <p>The BigDFT calculation uses an HGH-K/PBE pseudopotential and explicitly represents ten valence electrons. A normal all-electron N₂ calculation represents fourteen electrons. Comparing the two absolute totals—roughly $-20$ Ha and $-109$ Ha—would mostly compare different core conventions and energy zeros.</p> <p>I therefore used two PySCF references:</p> <ol> <li>A valence-matched GTH-PBE/<code class="language-plaintext highlighter-rouge">gth-dzvp</code> calculation with ten explicit electrons.</li> <li>An all-electron cc-pVDZ calculation with fourteen total electrons, but with the two doubly occupied N 1s-derived molecular orbitals frozen. That leaves the same ten correlated valence electrons in CAS(10e,10o).</li> </ol> <p>Freezing the core does not make the absolute all-electron energy equal to a pseudopotential energy. It makes the <em>correlated valence problem</em> comparable. Each potential curve is then shifted to its own minimum,</p> <div class="wavelet-equation"> \[\Delta E_m(R)=E_m(R)-\min_R E_m(R),\] </div> <p>so the comparison uses quantities that survive a change of energy zero: equilibrium distance, local curvature, harmonic frequency, and nearby curve shape.</p> <div class="wavelet-callout wavelet-caution"> <strong>What this comparison is not:</strong> GTH-PBE is not the exact same pseudopotential as BigDFT’s HGH-K/PBE, and the PySCF helper starts from RHF orbitals while BigDFT supplies PBE Kohn–Sham orbitals. The plot is a controlled cross-method benchmark, not a pure isolation of “wavelets versus Gaussians.” </div> <h3 id="the-completed-result">The completed result</h3> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/wavelet_qc/n2_dissociation_headline-480.webp 480w,/assets/img/wavelet_qc/n2_dissociation_headline-800.webp 800w,/assets/img/wavelet_qc/n2_dissociation_headline-1400.webp 1400w," sizes="(min-width: 930px) 930px, 95vw" type="image/webp"/> <img src="/assets/img/wavelet_qc/n2_dissociation_headline.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" alt="Minimum-shifted N2 dissociation curves for BigDFT wavelets, a valence pseudopotential Gaussian calculation, and an all-electron frozen-core calculation" data-zoomable="" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption">Three CAS(10e,10o) N₂ curves, each shifted to its own minimum. Vertical lines mark the fitted equilibrium distances; the table reports zero-independent observables.</figcaption> </figure> <table> <thead> <tr> <th>Method</th> <th style="text-align: right">Electrons represented</th> <th style="text-align: right">Correlated space</th> <th style="text-align: right">$r_e$ (Å)</th> <th style="text-align: right">$\omega_e$ (cm⁻¹)</th> </tr> </thead> <tbody> <tr> <td>BigDFT wavelet / HGH-PBE</td> <td style="text-align: right">10 valence</td> <td style="text-align: right">CAS(10e,10o)</td> <td style="text-align: right">1.0875</td> <td style="text-align: right">2548.5</td> </tr> <tr> <td>PySCF GTH-PBE / gth-dzvp</td> <td style="text-align: right">10 valence</td> <td style="text-align: right">CAS(10e,10o)</td> <td style="text-align: right">1.1066</td> <td style="text-align: right">2452.5</td> </tr> <tr> <td>PySCF all-electron / cc-pVDZ</td> <td style="text-align: right">14 total; 4 frozen core</td> <td style="text-align: right">CAS(10e,10o)</td> <td style="text-align: right">1.1050</td> <td style="text-align: right">2358.5</td> </tr> <tr> <td>Experiment</td> <td style="text-align: right">—</td> <td style="text-align: right">—</td> <td style="text-align: right">1.0977</td> <td style="text-align: right">2358.6</td> </tr> </tbody> </table> <p>The wavelet equilibrium distance is 0.0102 Å shorter than experiment, an error of about 0.93%. The fitted frequency is about 8% high. The bond length is the stronger result: the available scan is spaced by 0.2 Å near equilibrium and retains only five virtual orbitals, while a second derivative is especially sensitive to both choices. A denser near-minimum scan is the clearest scientific next step.</p> <p>The all-electron frozen-core frequency happens to match experiment closely, but this small benchmark does not support a general accuracy ranking. The important result is that the wavelet curve has a physically sensible minimum and can be compared without subtracting incompatible absolute energies.</p> <h2 id="what-resolution-costs">What resolution costs</h2> <p>The parameter scan is useful because it exposes both convergence and computational price.</p> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/wavelet_qc/scan_convergence-480.webp 480w,/assets/img/wavelet_qc/scan_convergence-800.webp 800w,/assets/img/wavelet_qc/scan_convergence-1400.webp 1400w," sizes="(min-width: 930px) 930px, 95vw" type="image/webp"/> <img src="/assets/img/wavelet_qc/scan_convergence.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" alt="Energy convergence as hgrid, crmult, and frmult are varied one at a time" data-zoomable="" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption">One-parameter-at-a-time convergence at the equilibrium geometry. The lower panels show differences from the finest value in each branch; the shaded region ends at 1.6 mHa.</figcaption> </figure> <p>Decreasing <code class="language-plaintext highlighter-rouge">hgrid</code> from 0.45 to 0.20 bohr changes the BigDFT energy from $-19.905076$ to $-19.910426$ Ha and the fixed CAS(10e,10o) energy from $-19.568101$ to $-19.572608$ Ha. The <code class="language-plaintext highlighter-rouge">hgrid=0.35</code> CAS result is already within 1.6 mHa of the finest tested point.</p> <p><code class="language-plaintext highlighter-rouge">frmult=6,7,8</code> is essentially flat here. Increasing <code class="language-plaintext highlighter-rouge">crmult</code> converges the Kohn–Sham energy, but the fixed-size CAS energy is not monotonic. That is not a violation of the variational principle: changing the box can change which diffuse virtual orbitals occupy the five retained virtual slots, so it is not the same subspace at every point.</p> <figure> <picture> <source class="responsive-img-srcset" srcset="/assets/img/wavelet_qc/scan_cost-480.webp 480w,/assets/img/wavelet_qc/scan_cost-800.webp 800w,/assets/img/wavelet_qc/scan_cost-1400.webp 1400w," sizes="(min-width: 930px) 930px, 95vw" type="image/webp"/> <img src="/assets/img/wavelet_qc/scan_cost.png" class="img-fluid rounded z-depth-1" width="100%" height="auto" alt="Measured wall time and peak memory as functions of the number of wavelet coefficients" data-zoomable="" loading="lazy" onerror="this.onerror=null; $('.responsive-img-srcset').remove();"/> </picture> <figcaption class="caption">Measured cost of one N₂ SCF-plus-integral point in the benchmark environment. The scatter includes different grid and support branches, so the dashed power laws are descriptive rather than universal scaling claims.</figcaption> </figure> <p>Across the parameter scan, the extractor grows from about 57 seconds and 0.5 GB RSS at <code class="language-plaintext highlighter-rouge">hgrid=0.45</code> to about 406 seconds and 2.9 GB at <code class="language-plaintext highlighter-rouge">hgrid=0.20</code>. Three complete curves at <code class="language-plaintext highlighter-rouge">hgrid=0.35</code>, 0.25, and 0.20 give fitted bond lengths of 1.0876, 1.0875, and 1.0873 Å. That stability is reassuring even though the absolute frequency remains resolution- and sampling-sensitive. These timings describe the recorded benchmark environment rather than a universal performance model.</p> <h2 id="adding-qsci-and-sqd">Adding QSCI and SQD</h2> <p>The next question is what happens after the wavelet Hamiltonian reaches a sample-based eigensolver.</p> <p>QSCI samples a quantum state in the occupation-number basis, retains the important Slater determinants, and diagonalizes the Hamiltonian in that selected classical subspace <d-cite key="kanno2026"></d-cite>. SQD adds particle-number postselection, batches, iterative configuration recovery, and distributed diagonalization; its appeal is to move most of the energy evaluation away from repeated Pauli measurements <d-cite key="robledomoreno2024"></d-cite>.</p> <p>For a controlled software test, I used the real wavelet CAS(6e,6o) Hamiltonian: 12 Jordan–Wigner qubits but only</p> <div class="wavelet-equation"> \[\binom{6}{3}\binom{6}{3}=400\] </div> <p>determinants in the fixed $(N_\alpha,N_\beta)=(3,3)$ sector. Samples were drawn from the squared coefficients of the exact PySCF FCI vector.</p> <table> <thead> <tr> <th style="text-align: right">Oracle shots</th> <th style="text-align: right">Unique determinants sampled</th> <th style="text-align: right">QSCI error (mHa)</th> <th style="text-align: right">SQD error (mHa)</th> </tr> </thead> <tbody> <tr> <td style="text-align: right">5,000</td> <td style="text-align: right">25</td> <td style="text-align: right">5.561</td> <td style="text-align: right">5.561</td> </tr> <tr> <td style="text-align: right">20,000</td> <td style="text-align: right">36</td> <td style="text-align: right">5.558</td> <td style="text-align: right">5.558</td> </tr> <tr> <td style="text-align: right">100,000</td> <td style="text-align: right">40</td> <td style="text-align: right">1.536</td> <td style="text-align: right">1.536</td> </tr> <tr> <td style="text-align: right">500,000</td> <td style="text-align: right">43</td> <td style="text-align: right">0.184</td> <td style="text-align: right">0.184</td> </tr> </tbody> </table> <p>The convergence has a simple interpretation: rare but energetically important determinants appear as sample coverage grows. QSCI and SQD agree exactly here because every sample already has the correct particle number and both routes diagonalize the same set of unique configurations. Recovery has nothing extra to repair in this clean experiment.</p> <div class="wavelet-callout wavelet-caution"> <strong>This is not QPU data.</strong> The exact FCI distribution is a classical sampler oracle chosen to validate subspace construction, bit ordering, energy constants, and diagonalization. It says nothing about state-preparation depth, device noise, mitigation, or quantum advantage. Those are deliberately separated from the software check. </div> <p>A separate QURI-QSCI diagnostic did not converge: its energy stayed 3.979 Ha from the CASCI reference and did not respond to the requested subspace size. It is recorded in the codebase as a negative diagnostic rather than presented as a successful result.</p> <h2 id="where-the-current-pipeline-stops">Where the current pipeline stops</h2> <p>The codebase also contains data from a 109Asp calculation with 132 spatial orbitals. That is exactly where scientific restraint matters.</p> <p>For real orbitals, the number of symmetry-unique two-electron records is</p> <div class="wavelet-equation"> \[M=\frac{n(n+1)}{2},\qquad N_\text{unique}=\frac{M(M+1)}{2}.\] </div> <p>At $n=132$, this is 38,531,031 records. The complete text export is 1.387 GB and is not vendored. A separate file stops at 100,000 records—only 0.26%—and comes from a different run, so the two cannot be concatenated. Expanding the 132 spatial orbitals into a dense 264-spin-orbital <code class="language-plaintext highlighter-rouge">float64</code> tensor would require about 38.9 GB before solver intermediates.</p> <p>The current workflow therefore records provenance and resource estimates only. It does <strong>not</strong> invent a 109Asp molecular energy from a truncated tensor. Sparse/out-of-core integrals, density fitting, localization, embedding, or a different active-space strategy are required before that system becomes a real downstream calculation.</p> <h2 id="what-this-result-does-and-does-not-say">What this result does and does not say</h2> <p>What I am comfortable claiming:</p> <ul> <li>The repository implements a complete wavelet-to-integral pipeline with documented inputs and outputs.</li> <li>The emitted integrals pass completeness, symmetry, mean-field, FCI, and qubit-mapping checks.</li> <li>The N₂ CAS(10e,10o) wavelet curve now has a completed, scientifically matched comparison; its fitted equilibrium distance is within about 1% of experiment.</li> <li>The maintained QSCI/SQD classical stages converge to FCI under a transparent exact-distribution sampler oracle.</li> </ul> <p>What I am <strong>not</strong> claiming:</p> <ul> <li>a pure wavelet-versus-Gaussian basis error measurement;</li> <li>a production-quality vibrational frequency from the coarse geometry grid;</li> <li>a successful 109Asp energy or drug-binding calculation;</li> <li>quantum advantage, quantum-hardware data, or chemical accuracy from the small VQE.</li> </ul> <p>The central lesson is that a useful end-to-end demonstration depends as much on matched comparisons and explicit limitations as it does on the solver result.</p> <h2 id="read-and-reproduce-the-code">Read and reproduce the code</h2> <p>Implementation and installation details are intentionally kept in the repository rather than duplicated in this article. The codebase includes a locked Python environment, numbered analysis scripts, tests, source JSON, generated figures, and detailed method notes. Start with the <a href="https://github.com/nez0b/bigdft-drug-design/tree/agent/wavelet-qc-macos-revival"><code class="language-plaintext highlighter-rouge">agent/wavelet-qc-macos-revival</code> branch</a>. Within that codebase, the main entry points are:</p> <ul> <li><code class="language-plaintext highlighter-rouge">README.md</code> for the overview and result table;</li> <li><code class="language-plaintext highlighter-rouge">INSTALL.md</code> for the environment and installation guide;</li> <li><code class="language-plaintext highlighter-rouge">docs/METHODOLOGY.md</code> for the comparison protocol;</li> <li><code class="language-plaintext highlighter-rouge">RESULTS.md</code> for numerical results and limitations;</li> <li><code class="language-plaintext highlighter-rouge">docs/SQD_QSCI.md</code> for the sample-based solver workflow;</li> <li><code class="language-plaintext highlighter-rouge">docs/PROVENANCE.md</code> for data and contributor provenance.</li> </ul> <p>The compact analyses start from committed integral fixtures, so readers can reproduce the figures and solver checks without first generating a new wavelet calculation. Read the repository documentation for implementation boundaries, data provenance, and contributor attribution.</p>]]></content><author><name>PoJen Wang</name></author><category term="scientific-computing"/><category term="quantum-chemistry"/><category term="wavelets"/><category term="quantum-computing"/><summary type="html"><![CDATA[How an adaptive wavelet representation becomes a validated active-space Hamiltonian, with a fair N₂ benchmark, VQE, QSCI, and SQD.]]></summary></entry><entry><title type="html">Quantum Transfer Learning</title><link href="https://nez0b.github.io/blog/2023/qnn-transfer/" rel="alternate" type="text/html" title="Quantum Transfer Learning"/><published>2023-11-20T00:00:00+00:00</published><updated>2023-11-20T00:00:00+00:00</updated><id>https://nez0b.github.io/blog/2023/qnn-transfer</id><content type="html" xml:base="https://nez0b.github.io/blog/2023/qnn-transfer/"><![CDATA[<h2 id="model--quantum-transfer-learning-with-quantum-pooling-layer">Model : Quantum transfer learning with quantum pooling layer</h2> <p>The idea of <strong>transfer leanring</strong> is to feed the data through pre-trained feature extraction networks first, and train only a small size feed forward netwrok after it to fine tune the moedel with respect to a specific data set.</p> <p>Since CIFAR100 image data are too large to directly encode in quantum circuits today, we here rely on a “imagenet” pre-trained ResNet18 as feature extraction layer. The image feature is reduced to 4 dimension through this network, and encoded into a 4 qubit circuit network.</p> <p><img src="/assets/img/qnn_transfer/transfer_learning_general.png" alt="alt text"/></p> <p>We then utilize the idea of quantum pooling layer[2] to further reduce the quantum curcuit to a single qubit. A single qubit is sufficient for binary classification by chooing the eigenstate with higher probability.</p> <h2><img src="/assets/img/qnn_transfer/transfer_learning_c2qconv.png" alt="alt text"/></h2> <h2 id="code">Code</h2> <div class="jupyter-notebook" style="position: relative; width: 100%; margin: 0 auto;"> <div class="jupyter-notebook-iframe-container"> <iframe src="/assets/jupyter/qnn_transfer_learning.ipynb.html" style="position: absolute; top: 0; left: 0; border-style: none;" width="100%" height="100%" onload="this.parentElement.style.paddingBottom = (this.contentWindow.document.documentElement.scrollHeight + 10) + 'px'"></iframe> </div> </div>]]></content><author><name>PoJen Wang</name></author><category term="quantum-computing"/><category term="QML"/><category term="jupyter"/><summary type="html"><![CDATA[Quantum transfer learning with quantum pooling layer]]></summary></entry></feed>