Skip to content

Commit

Permalink
MIS on neutral atom implementation (#120)
Browse files Browse the repository at this point in the history
Maximum independent set (MIS) problem implementation, including graph layouts and a mapping to neutral-atom-based devices.
  • Loading branch information
chris-van-den-oetelaar authored May 22, 2024
1 parent e797af9 commit 465ca25
Show file tree
Hide file tree
Showing 14 changed files with 3,819 additions and 2,889 deletions.
5,759 changes: 2,904 additions & 2,855 deletions .settings/module_db.json

Large diffs are not rendered by default.

64 changes: 32 additions & 32 deletions .settings/requirements_full.txt
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
seaborn==0.13.0
networkx==2.8.8
inquirer==3.1.2
packaging==23.1
pyyaml==6.0
typing-extensions==4.6.3
sphinx==6.2.1
sphinx-rtd-theme==1.2.0
numpy==1.23.5
dimod==0.12.5
amazon-braket-sdk==1.35.1
scipy==1.11.1
botocore==1.25.7
boto3==1.22.7
pennylane==0.28.0
pennylane-lightning==0.28.0
amazon-braket-pennylane-plugin==1.5.2
dwave-samplers==1.0.0
nnf==0.4.1
qubovert==1.2.5
python-sat==0.1.7.dev26
more-itertools==9.0.0
qiskit-optimization==0.5.0
pyqubo==1.4.0
dwave_networkx==0.8.13
qiskit==0.45.0
pandas==1.5.2
qiskit-ibmq-provider==0.19.2
cma==3.3.0
tensorboard==2.13.0
tensorboardX==2.6.2
qiskit_aer==0.11.2
seaborn==0.13.0
networkx==2.8.8
inquirer==3.1.2
packaging==23.1
pyyaml==6.0
typing-extensions==4.6.3
sphinx==6.2.1
sphinx-rtd-theme==1.2.0
numpy==1.23.5
dimod==0.12.5
amazon-braket-sdk==1.35.1
scipy==1.11.1
botocore==1.25.7
boto3==1.22.7
pennylane==0.28.0
pennylane-lightning==0.28.0
amazon-braket-pennylane-plugin==1.5.2
dwave-samplers==1.0.0
nnf==0.4.1
qubovert==1.2.5
python-sat==0.1.7.dev26
more-itertools==9.0.0
qiskit-optimization==0.5.0
pyqubo==1.4.0
dwave_networkx==0.8.13
qiskit==0.45.0
pandas==1.5.2
cma==3.3.0
tensorboard==2.13.0
tensorboardX==2.6.2
pulser==0.16.0
qiskit-aer==0.11.2
3 changes: 2 additions & 1 deletion src/Installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ def __init__(self):
{"name": "SAT", "class": "SAT", "module": "modules.applications.optimization.SAT.SAT"},
{"name": "TSP", "class": "TSP", "module": "modules.applications.optimization.TSP.TSP"},
{"name": "GenerativeModeling", "class": "GenerativeModeling",
"module": "modules.applications.QML.generative_modeling.GenerativeModeling"}
"module": "modules.applications.QML.generative_modeling.GenerativeModeling"},
{"name": "MIS", "class": "MIS", "module": "modules.applications.optimization.MIS.MIS"},
]

self.core_requirements = [
Expand Down
245 changes: 245 additions & 0 deletions src/modules/applications/optimization/MIS/MIS.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
# Copyright 2021 The QUARK Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import TypedDict
import pickle

import networkx

from modules.applications.Application import *
from modules.applications.optimization.Optimization import Optimization
from modules.applications.optimization.MIS.data.graph_layouts import \
generate_hexagonal_graph
from utils import start_time_measurement, end_time_measurement

# define R_rydberg
R_rydberg = 9.75


class MIS(Optimization):
"""
In planning problems, there will be tasks to be done, and some of them may be mutually exclusive.
We can translate this into a graph where the nodes are the tasks and the edges are the mutual exclusions.
The maximum independent set (MIS) problem is a combinatorial optimization problem that seeks to find the largest
subset of vertices in a graph such that no two vertices are adjacent.
"""

def __init__(self):
"""
Constructor method
"""
super().__init__("MIS")
self.submodule_options = ["NeutralAtom"]

@staticmethod
def get_requirements() -> list[dict]:
"""
Returns requirements of this module
:return: list of dict with requirements of this module
:rtype: list[dict]
"""
return [
]

def get_solution_quality_unit(self) -> str:
return "Set size"

def get_default_submodule(self, option: str) -> Core:

Check failure on line 59 in src/modules/applications/optimization/MIS/MIS.py

View workflow job for this annotation

GitHub Actions / Pylint

src/modules/applications/optimization/MIS/MIS.py#L59

Trailing whitespace (trailing-whitespace, C0303)
if option == "NeutralAtom":
from modules.applications.optimization.MIS.mappings.NeutralAtom import NeutralAtom # pylint: disable=C0415
return NeutralAtom()
else:
raise NotImplementedError(f"Mapping Option {option} not implemented")

def get_parameter_options(self) -> dict:
"""
Returns the configurable settings for this application
:return:
.. code-block:: python
return {
"size": {
"values": list(range(1, 18)),
"description": "How large should your graph be?"
},
"spacing": {
"values": [x/10 for x in range(1, 11)],
"description": "How much space do you want between your nodes,"
" relative to Rydberg distance?"
},
"filling_fraction": {
"values": [x/10 for x in range(1, 11)],
"description": "What should the filling fraction be?"
},
}
"""
return {
"size": {
"values": [1, 5, 10, 15],
"custom_input": True,
"allow_ranges": True,
"postproc": int,
"description": "How large should your graph be?"
},
"spacing": {
"values": [x/10 for x in range(3, 11, 2)],
"custom_input": True,
"allow_ranges": True,
"postproc": float,
"description": "How much space do you want between your nodes,"
" relative to Rydberg distance?"
},
"filling_fraction": {
"values": [x/10 for x in range(2, 11, 2)],
"custom_input": True,
"allow_ranges": True,
"postproc": float,
"description": "What should the filling fraction be?"
},
}

class Config(TypedDict):
"""
Attributes of a valid config
.. code-block:: python
size: int
spacing: float
filling_fraction: float
"""
size: int
spacing: float
filling_fraction: float

def generate_problem(self, config: Config) -> networkx.Graph:
"""
Generates a graph to solve the MIS for.
:param config: Config specifying the size and connectivity for the problem
:type config: Config
:return: networkx graph representing the problem
:rtype: networkx.Graph
"""

if config is None:
config = {"size": 3,
"spacing": 1,
"filling_fraction": 0.5}

# check if config has the necessary information
assert all(
x in config.keys()
for x in ['size', 'spacing', 'filling_fraction']
)

size = config.get('size')
spacing = config.get('spacing') * R_rydberg
filling_fraction = config.get('filling_fraction')

graph = generate_hexagonal_graph(
n_nodes=size,
spacing=spacing,
filling_fraction=filling_fraction,
)

logging.info("Created MIS problem with the generate hexagonal "
"graph method, with the following attributes:")
logging.info(f" - Graph size: {size}")
logging.info(f" - Spacing: {spacing}")
logging.info(f" - Filling fraction: {filling_fraction}")

self.application = graph
return graph.copy()

def process_solution(self, solution: list) -> (list, float):
"""
Returns list of visited nodes and the time it took to process the solution
:param solution: Unprocessed solution
:type solution: list
:return: Processed solution and the time it took to process it
:rtype: tuple(list, float)
"""
start_time = start_time_measurement()

return solution, end_time_measurement(start_time)

def validate(self, solution: list) -> (bool, float):
"""
Checks if the solution is an independent set
:param solution: List containing the nodes of the solution
:type solution: list
:return: Boolean whether the solution is valid and time it took to validate
:rtype: tuple(bool, float)
"""
start = start_time_measurement()
is_valid = True

Check failure on line 194 in src/modules/applications/optimization/MIS/MIS.py

View workflow job for this annotation

GitHub Actions / Pylint

src/modules/applications/optimization/MIS/MIS.py#L194

Trailing whitespace (trailing-whitespace, C0303)
nodes = list(self.application.nodes())
edges = list(self.application.edges())

# TODO: Check if the solution is maximal?

Check failure on line 198 in src/modules/applications/optimization/MIS/MIS.py

View workflow job for this annotation

GitHub Actions / Pylint

src/modules/applications/optimization/MIS/MIS.py#L198

Trailing whitespace (trailing-whitespace, C0303)

# Check if the solution is independent
is_independent = all((u, v) not in edges for u, v in edges if u in solution and v in solution)
if is_independent:
logging.info("The solution is independent")

Check failure on line 203 in src/modules/applications/optimization/MIS/MIS.py

View workflow job for this annotation

GitHub Actions / Pylint

src/modules/applications/optimization/MIS/MIS.py#L203

Trailing whitespace (trailing-whitespace, C0303)
else:
logging.warning("The solution is not independent")
is_valid = False

# Check if the solution is a set
solution_set = set(solution)
is_set = len(solution_set) == len(solution)
if is_set:
logging.info("The solution is a set")
else:
logging.warning("The solution is not a set")
is_valid = False

# Check if the solution is a subset of the original nodes
is_set = all(node in nodes for node in solution)
if is_set:
logging.info("The solution is a subset of the problem")
else:
logging.warning("The solution is not a subset of the problem")
is_valid = False

return is_valid, end_time_measurement(start)

def evaluate(self, solution: list) -> (int, float):
"""
Calculates the size of the solution
:param solution: List containing the nodes of the solution
:type solution: list
:return: Set size, time it took to calculate the set size
:rtype: tuple(int, float)
"""
start = start_time_measurement()
set_size = len(solution)

logging.info(f"Size of solution: {set_size}")

return set_size, end_time_measurement(start)

def save(self, path: str, iter_count: int) -> None:
with open(f"{path}/graph_iter_{iter_count}.gpickle", "wb") as file:
pickle.dump(self.application, file, pickle.HIGHEST_PROTOCOL)
15 changes: 15 additions & 0 deletions src/modules/applications/optimization/MIS/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2022 The QUARK Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for MIS"""
15 changes: 15 additions & 0 deletions src/modules/applications/optimization/MIS/data/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2022 The QUARK Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Module for MIS data"""
Loading

0 comments on commit 465ca25

Please sign in to comment.