Skip to content

add the Unit-Disk Mapping Module #125

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
May 9, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,774 changes: 1,774 additions & 0 deletions docs/tutorial/usage/ising_MWIS_UDG.ipynb

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion qamomile/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from qamomile.core import circuit as circuit_module
from qamomile.core.bitssample import * # noqa
from qamomile.core.converters import qaoa as qaoa
from qamomile.core.ising_qubo import IsingModel, UnitDiskGraph

circuit = circuit_module

__all__ = ["qaoa", "circuit", "BitsSample", "BitsSampleSet"]
__all__ = [
"qaoa", "circuit", "BitsSample", "BitsSampleSet",
"IsingModel", "UnitDiskGraph"
]
138 changes: 136 additions & 2 deletions qamomile/core/ising_qubo.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import dataclasses
import typing as typ

import copy
import numpy as np
from ..udm import map_qubo, solve_qubo, qubo_result_to_networkx, QUBOResult
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think udm is independent subpackage, so it should not use in core.
It is better to move this to inside of udm

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. Now the entire udm related files are outside of the core. I created a Ising_UnitDiskGraph in the udm module instead.



@dataclasses.dataclass
Expand Down Expand Up @@ -116,6 +117,139 @@ def normalize_by_rms(self):
self.linear[key] /= normalization_factor
for key in self.quad:
self.quad[key] /= normalization_factor

def to_unit_disk_graph(self, normalize: bool = True) -> 'UnitDiskGraph':
"""Convert the Ising model to a unit disk graph representation.

Args:
normalize: Whether to normalize the coefficients before conversion

Returns:
UnitDiskGraph object representing the Ising model
"""
if normalize:
# Create a copy of the model to avoid modifying the original
model = IsingModel(
quad=copy.deepcopy(self.quad),
linear=copy.deepcopy(self.linear),
constant=self.constant,
index_map=copy.deepcopy(self.index_map) if self.index_map else None
)
model.normalize_by_abs_max()
else:
model = self

return UnitDiskGraph(model)


@dataclasses.dataclass
class UnitDiskGraph:
"""
A representation of an Ising model as a unit disk graph suitable for neutral atom quantum computers.

This class wraps the UDM module to map QUBO/Ising problems to unit disk graphs.
"""
ising_model: IsingModel
_qubo_result: typ.Optional[QUBOResult] = None
_networkx_graph: typ.Optional[object] = None
_delta: typ.Optional[float] = None

def __post_init__(self):
"""Initialize the unit disk graph mapping after construction."""
self._create_mapping()

def _create_mapping(self):
"""Create the unit disk graph mapping from the Ising model."""
# Convert the Ising model to J and h matrices/vectors
n = self.ising_model.num_bits()

# Initialize J matrix and h vector
J = np.zeros((n, n))
h = np.zeros(n)

# Fill in the J matrix from quad terms
for (i, j), value in self.ising_model.quad.items():
J[i, j] = value
J[j, i] = value # Make symmetric

# Fill in the h vector from linear terms
for i, value in self.ising_model.linear.items():
h[i] = value

# Calculate delta parameter for weight scaling
self._delta = 1.5 * max(np.max(np.abs(h)), np.max(np.abs(J)))

# Map the QUBO problem to a unit disk graph
self._qubo_result = map_qubo(J, h, self._delta)

# Convert to a NetworkX graph for visualization and analysis
self._networkx_graph = qubo_result_to_networkx(self._qubo_result)

def solve(self, use_brute_force: bool = False, binary_variables: bool = False) -> dict:
"""
Solve the Ising model using the unit disk graph mapping.

Args:
use_brute_force: Whether to use brute force enumeration for small problems
binary_variables: Whether to use {0,1} variables (True) or {-1,1} variables (False)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think in Qamomile workflow, library only deals with Ising model (spin variables), so you need to care about {0,1} case.
Or if binary {0,1} is better than spin {-1,1} when converting, you can only deal with better formulation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I removed the {0,1} encoding option. It was for other type of problem.


Returns:
Dictionary containing solution information including:
- original_config: Configuration for the original Ising variables
- energy: Energy of the solution
- solution_method: Method used to find the solution ("brute_force" or "mwis")
"""
# Convert to J and h matrices/vectors
n = self.ising_model.num_bits()

J = np.zeros((n, n))
h = np.zeros(n)

for (i, j), value in self.ising_model.quad.items():
J[i, j] = value
J[j, i] = value

for i, value in self.ising_model.linear.items():
h[i] = value
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there big reasons to create matrix version of Ising model?
If so, I think it is better to use this instead of your own implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok. I use IsingMatrix Class instead. Create the numpy matrix is easier for using scipy solver.


# Solve the QUBO problem
result = solve_qubo(
J, h,
binary_variables=binary_variables,
use_brute_force=use_brute_force,
max_brute_force_size=20 # Adjust this value based on performance needs
)

return result

@property
def qubo_result(self) -> QUBOResult:
"""Get the QUBOResult object from the UDM module."""
return self._qubo_result

@property
def networkx_graph(self) -> object:
"""Get the NetworkX graph representation."""
return self._networkx_graph

@property
def pins(self) -> list:
"""Get the list of pins (indices of nodes corresponding to original variables)."""
if self._qubo_result:
return self._qubo_result.pins
return []

@property
def nodes(self) -> list:
"""Get the list of nodes in the unit disk graph."""
if self._qubo_result and hasattr(self._qubo_result.grid_graph, 'nodes'):
return self._qubo_result.grid_graph.nodes
return []

@property
def delta(self) -> float:
"""Get the delta parameter used for scaling the weights."""
return self._delta


def calc_qubo_energy(qubo: dict[tuple[int, int], float], state: list[int]) -> float:
Expand Down Expand Up @@ -201,4 +335,4 @@ def qubo_to_ising(
ising_J = _J
ising_h = _h
return IsingModel(ising_J, ising_h, constant, index_map)
return IsingModel(ising_J, ising_h, constant)
return IsingModel(ising_J, ising_h, constant)
22 changes: 22 additions & 0 deletions qamomile/udm/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
Unit Disk Mapping (UDM) module for Qamomile.

This module implements algorithms for mapping various optimization problems (like QUBO and Ising models)
to unit disk grid graphs, which can be naturally encoded in neutral-atom quantum computers.
"""

from .dragondrop import (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module name dragondrop doesn't clearly show its purpose or functionality. If it's related to solving QUBO problems or MWIS on graphs, a name like graph_solver, qubo_tools, or mwis_utils might be more appropriate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed from dragondrop do mwis_solver

map_qubo,
map_simple_wmis,
solve_qubo,
solve_mwis_scipy,
qubo_result_to_networkx,
QUBOResult,
WMISResult
)

__all__ = [
"map_qubo", "map_simple_wmis", "solve_qubo",
"solve_mwis_scipy", "qubo_result_to_networkx",
"QUBOResult", "WMISResult",
]
Loading