Skip to content
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

Adding the Max-k-Cut application #299

Closed
wants to merge 29 commits into from
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
3bb2d53
Adding the Max-k-cut application
clausia Sep 23, 2021
57208a7
validate if it is possible to use the matplotlib library
clausia Dec 14, 2021
4db521a
minor fix (for library validations)
clausia Dec 14, 2021
11d6f96
Merge branch 'main' into max-k-cut-app
clausia Dec 16, 2021
5fa9126
Merge branch 'main' into max-k-cut-app
manoelmarques Dec 16, 2021
6cb7536
Merge branch 'main' into max-k-cut-app
manoelmarques Dec 16, 2021
9fb9458
Merge branch 'main' into max-k-cut-app
manoelmarques Dec 16, 2021
d457732
Merge branch 'main' into max-k-cut-app
clausia Dec 23, 2021
6408781
Merge branch 'main' into max-k-cut-app
clausia Jan 6, 2022
afdabab
Merge branch 'main' into max-k-cut-app
fedonman Jan 9, 2022
2ccae04
Get colors parameter out of init, using algorithm_globals
clausia Jan 10, 2022
63ba6b1
update copyright year
fedonman Jan 10, 2022
c42e061
Merge branch 'main' into max-k-cut-app
clausia Jan 14, 2022
82c67bd
Merge branch 'main' into max-k-cut-app
clausia Jan 20, 2022
6e64655
Merge branch 'main' into max-k-cut-app
clausia Jan 23, 2022
6bec1f9
Merge branch 'main' into max-k-cut-app
manoelmarques Jan 26, 2022
4aa90a8
Merge branch 'main' into max-k-cut-app
clausia Feb 7, 2022
c8a8107
Merge branch 'main' into max-k-cut-app
clausia Feb 9, 2022
a74c686
Merge branch 'main' into max-k-cut-app
clausia Feb 11, 2022
323ec1e
Merge branch 'main' into max-k-cut-app
clausia Feb 28, 2022
980df66
Merge branch 'main' into max-k-cut-app
clausia Mar 1, 2022
e696d23
Merge branch 'main' into max-k-cut-app
clausia Mar 14, 2022
647028f
Merge branch 'main' into max-k-cut-app
clausia Mar 28, 2022
c869735
Merge branch 'main' into max-k-cut-app
clausia Apr 3, 2022
5cf0eee
Merge branch 'main' into max-k-cut-app
clausia Apr 4, 2022
2997c45
Merge branch 'main' into max-k-cut-app
clausia Apr 10, 2022
91f390c
Merge branch 'main' into max-k-cut-app
clausia Apr 19, 2022
2d1e302
Merge branch 'main' into max-k-cut-app
clausia Apr 23, 2022
d19b1d1
Merge branch 'main' into max-k-cut-app
clausia May 3, 2022
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
4 changes: 4 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ makefile
matplotlib
maxcut
maxfun
maxkcut
maxiter
mdl
minimizer
Expand Down Expand Up @@ -149,6 +150,7 @@ py
pxd
qaoa
qasm
qce
qiskit
qiskit's
qn
Expand All @@ -163,6 +165,7 @@ qubits
qubo
readme
representable
rgba
rhobeg
rhoend
rhs
Expand Down Expand Up @@ -194,6 +197,7 @@ subspaces
sys
subproblem
summands
tabi
tavernelli
terra
th
Expand Down
4 changes: 3 additions & 1 deletion docs/tutorials/09_application_classes.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@
" - Given a graph, a depot node, and the number of vehicles (routes), find a set of routes such that each node is covered exactly once except the depot and the total distance of the routes is minimized.\n",
"11. Vertex cover problem\n",
" - Given an undirected graph, find a subset of nodes with the minimum size such that each edge has at least one endpoint in the subsets.\n",
"12. Max-k-Cut problem\n",
" - Given an undirected graph, find a partition of nodes into at most k subsets such that the total weight of the edges between the k subsets is maximized.\n",
"\n",
"The application classes for graph problems (`GraphOptimizationApplication`) provide a functionality to draw graphs of an instance and a result.\n",
"Note that you need to install `matplotlib` beforehand to utilize the functionality."
Expand Down Expand Up @@ -1518,4 +1520,4 @@
},
"nbformat": 4,
"nbformat_minor": 2
}
}
3 changes: 3 additions & 0 deletions qiskit_optimization/applications/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
GraphPartition
Knapsack
Maxcut
Maxkcut
NumberPartition
SetPacking
SKModel
Expand All @@ -56,6 +57,7 @@
from .graph_partition import GraphPartition
from .knapsack import Knapsack
from .max_cut import Maxcut
from .max_k_cut import Maxkcut
from .number_partition import NumberPartition
from .optimization_application import OptimizationApplication
from .set_packing import SetPacking
Expand All @@ -72,6 +74,7 @@
"GraphOptimizationApplication",
"Knapsack",
"Maxcut",
"Maxkcut",
"NumberPartition",
"OptimizationApplication",
"SetPacking",
Expand Down
198 changes: 198 additions & 0 deletions qiskit_optimization/applications/max_k_cut.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.


"""An application class for the Max-k-cut."""

from typing import List, Dict, Tuple, Optional, Union
import networkx as nx
import numpy as np
from docplex.mp.model import Model

from qiskit_optimization.algorithms import OptimizationResult
from qiskit_optimization.problems.quadratic_program import QuadraticProgram
from qiskit_optimization.translators import from_docplex_mp
from .graph_optimization_application import GraphOptimizationApplication

try:
from matplotlib.pyplot import cm
from matplotlib.colors import to_rgba

_HAS_MATPLOTLIB = True
except ImportError:
import random as rd

_HAS_MATPLOTLIB = False


class Maxkcut(GraphOptimizationApplication):
"""Optimization application for the "max-k-cut" [1] problem based on a NetworkX graph.

References:
[1]: Z. Tabi et al.,
"Quantum Optimization for the Graph Coloring Problem with Space-Efficient Embedding"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This paper addresses a graph coloring problem and does not mention max-k-cut problem.
Could you refer an appropriate paper or page?

Choose a reason for hiding this comment

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

Hi @t-imamichi, graph colouring problem is a decision variant of max-k-cut. Graph coloring problem is about checking if there "exists colouring of the nodes which make every edge connecting nodes with a different colour." Contrary, Max-K-Cut is about finding the colouring for which the number of such edges is maximized. You could say that graph colouring to max-K-Cut shows similar relation as SAT to MAX-SAT. For this reason, and the fact max cut is already used in the package we decided to choose max-k-cut name

Copy link
Collaborator

@t-imamichi t-imamichi Feb 9, 2022

Choose a reason for hiding this comment

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

As far as the paper mentions only coloring problem, I don't think it's an appropriate citation of max-k-cut. It would confuse users. Why not rename the class "k-coloring problem" as the paper addresses?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Moreover, there are many papers directly addressing max-k-cut problem. Why do you need to refer that paper (Tabi et al.) for max-k-cut class?

Copy link

@adamglos92 adamglos92 Feb 9, 2022

Choose a reason for hiding this comment

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

The notion of feasibility changes. For Max-Cut every solution is feasible, for graph colouring only those with all edges connecting different nodes are feasible. This is obviously correct but inconsistent with max-cut used in the qiskit-optimization.
Since this is for optimization purposes mostly we found Max-K-Cut more accurate. But we can also move it to k-graph-coloring
We are not aware of older papers that propose the given formulation. If there is one, we're open to replacing the reference.

Copy link
Collaborator

@t-imamichi t-imamichi Feb 9, 2022

Choose a reason for hiding this comment

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

You mentioned

if there "exists colouring of the nodes which make every edge connecting nodes with a different colour."

But, coloring does not take care of edge weights. So, I don't think the optimal solution of coloring does not corresponds to the optimal solution to max-k-cut problem.

You must make a mixed integer programming model of max-k-cut. Here is the definition of k-cut value (though this page addresses minimization)
https://en.wikipedia.org/wiki/Minimum_k-cut

Copy link
Collaborator

Choose a reason for hiding this comment

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

You may perhaps try to use an solution of coloring problem as a heuristic solution of max-k-cut problem?
If so, I ask you to make a proper mixed integer programming model of max-k-cut problem.
The model of application classes should be exact. Otherwise, even if we apply CPLEX or Gurobi to the model, we may not be able to obtain the optimal solution.

2020 IEEE International Conference on Quantum Computing and Engineering (QCE),
2020, pp. 56-62, doi: 10.1109/QCE49297.2020.00018.,
https://ieeexplore.ieee.org/document/9259934
"""

def __init__(
self,
graph: Union[nx.Graph, np.ndarray, List],
k: int,
colors: Optional[Union[List[str], List[List[int]]]] = None,
) -> None:
"""
Args:
graph: A graph representing a problem. It can be specified directly as a
`NetworkX <https://networkx.org/>`_ graph,
or as an array or list format suitable to build out a NetworkX graph.
k: The number of colors
colors: List of strings or list of colors in rgba lists to be assigned to each
resulting subset, there must be as many colors as the number k
"""
super().__init__(graph=graph)
self._subsets_num = k
self._colors = colors if colors and len(colors) >= k else None
Copy link
Member

Choose a reason for hiding this comment

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

For colors I see that more are allowed to be defined and presumably ignored; however if colors were given but less than k then they are discarded. In looking further, from what can see, these are just used if/when the user the draws the result.

I am wondering whether it might be better to move this off the constructor and just say have a method call something like set_draw_colors that takes the same thing and stores it for the rendering. That makes this feel less like part of the problem definition - at least to me. I would have said pass them on draw but then this is implementing logic defined in the base class and it does not seem to support customizing the rendering in this way.

In terms of the colors I think it should define what the string and int formats are - if its simply matplot color format then I think that can be stated perhaps with a link to matplot colot doc that has the info plus perhaps an example in the code to help people.

Also I wonder since this is a set of colors should they be passed in as a set so that it contains unique items - otherwise a user can specify the same color again. In regards of the behavior or more or less colors given than k maybe you prefer the less severe mechanism of discarding all or part of them depending on size. I think at least the code should emit a warning to alert the user - ideally they should be passing in k colors right not some other sized set. If this is a separate method just to set draw colors then maybe allowing only None (to reset to internal default) or a k sized set and raising a ValueError if the size if not equal to k might be more acceptable?

Copy link
Author

Choose a reason for hiding this comment

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

Thank you for your comments, they have already been addressed:

  • The importance of including the definition of colors in the constructor is that this implementation is based on the "Graph Coloring Problem", but I understand your concern, we can have the solution of the max-k-cut problem without the need to draw the resulting graph. To change (or add) the colors you have the methods set/get (colors, called like that for the same reason, that we are thinking about the "Graph Coloring Problem").
    So, I have removed the colors parameter from the constructor, (before it was optional), I have already done this update.

  • As for the documentation, certainly including an example is much better for people who use this class, that update is ready, please review.

  • Regarding that the user can specify a repeated color, it seems to me that we should allow this type of flexibility, that this decision is up to the user, finally if it was not his/her intention, she/he will be able to see the result when he requests the drawing of the graph (the user will see fewer colors than 'k').

  • Now it is being validated that the size of the color list is equal to the k parameter, and if it is not, the ValueError is thrown indicating that the colors have not been assigned because they are not the correct amount.

Copy link
Author

Choose a reason for hiding this comment

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

@woodsp-ibm, what do you think of the changes?


def to_quadratic_program(self) -> QuadraticProgram:
"""Convert a Max-k-cut problem instance into a
:class:`~qiskit_optimization.problems.QuadraticProgram`

Returns:
The :class:`~qiskit_optimization.problems.QuadraticProgram` created
from the Max-k-cut problem instance.
"""
for w, v in self._graph.edges:
self._graph.edges[w, v].setdefault("weight", 1)

mdl = Model(name="Max-k-cut")
n = self._graph.number_of_nodes()
k = self._subsets_num
x = {(v, i): mdl.binary_var(name=f"x_{v}_{i}") for v in range(n) for i in range(k)}
first_penalty = mdl.sum_squares((1 - mdl.sum(x[v, i] for i in range(k)) for v in range(n)))
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you make a model with a standard formulation of the problem? Please do not penalize constraints by yourself.
In the application classes, we make a model with a standard formulation. The conversion to QUBO will be done within algorithm classes.

second_penalty = mdl.sum(
mdl.sum(self._graph.edges[v, w]["weight"] * x[v, i] * x[w, i] for i in range(k))
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 this takes sum of edges whose end nodes are same color (i). Is it a correct k-cut value?
I think k-cut value is the sum of edges whose end nodes have different colors.
Anyways, could you refer an appropriate paper or page and make a model (obj func + constraints).

for v, w in self._graph.edges
)
objective = first_penalty + second_penalty
mdl.minimize(objective)

op = from_docplex_mp(mdl)
return op

def interpret(self, result: Union[OptimizationResult, np.ndarray]) -> List[List[int]]:
"""Interpret a result as k lists of node indices

Args:
result : The calculated result of the problem

Returns:
k lists of node indices correspond to k node sets for the Max-k-cut
"""
x = self._result_to_x(result)
n = self._graph.number_of_nodes()
cut = [[] for i in range(self._subsets_num)] # type: List[List[int]]

n_selected = x.reshape((n, self._subsets_num))
for i in range(n):
node_in_subset = np.where(n_selected[i] == 1)[0] # one-hot encoding
if len(node_in_subset) != 0:
cut[node_in_subset[0]].append(i)

return cut

def _draw_result(
self,
result: Union[OptimizationResult, np.ndarray],
pos: Optional[Dict[int, np.ndarray]] = None,
) -> None:
"""Draw the result with colors

Args:
result : The calculated result for the problem
pos: The positions of nodes
"""
x = self._result_to_x(result)
nx.draw(self._graph, node_color=self._node_color(x), pos=pos, with_labels=True)

def _node_color(
self, x: np.ndarray
) -> Union[List[Tuple[float, float, float, float]], List[str]]:
# Return a list of colors for draw.

n = self._graph.number_of_nodes()

# k colors chosen (randomly or from cm.rainbow), or from given color list
Copy link
Member

Choose a reason for hiding this comment

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

As far as doing things randomly there exists an random generator in algorithm_globals in Terra that we created to have a common generator that algorithms and applications would use so that a user could create reproducible results by seeding it. Using that, instead of whats done here, would allow a user to seed it and have the same colored drawing each time if they prefer - of course they could pass in their own colors too, but I think using the common generator is preferable.

Copy link
Author

Choose a reason for hiding this comment

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

Now algorithm_globals is used

colors = (
(
cm.rainbow(np.linspace(0, 1, self._subsets_num))
if _HAS_MATPLOTLIB
else [
"#" + "".join([rd.choice("0123456789ABCDEF") for i in range(6)])
for j in range(self._subsets_num)
]
)
if self._colors is None
else self._colors
)
gray = to_rgba("lightgray") if _HAS_MATPLOTLIB else "lightgray"
node_colors = [gray for _ in range(n)]

n_selected = x.reshape((n, self._subsets_num))
for i in range(n):
node_in_subset = np.where(n_selected[i] == 1) # one-hot encoding
if len(node_in_subset[0]) != 0:
node_colors[i] = (
to_rgba(colors[node_in_subset[0][0]])
if _HAS_MATPLOTLIB
else colors[node_in_subset[0][0]]
)

return node_colors

@property
def k(self) -> int:
"""Getter of k

Returns:
The number of colors
"""
return self._subsets_num

@k.setter
def k(self, k: int) -> None:
"""Setter of k

Args:
k: The number of colors
"""
self._subsets_num = k
self._colors = self._colors if self._colors and len(self._colors) >= k else None

@property
def colors(self) -> Union[List[str], List[List[int]]]:
"""Getter of colors list

Returns:
The k size color list
"""
return self._colors

@colors.setter
def colors(self, colors: Union[List[str], List[List[int]]]) -> None:
"""Setter of colors list

Args:
colors: The k size color list
"""
self._colors = colors if colors and len(colors) >= self._subsets_num else None
14 changes: 14 additions & 0 deletions releasenotes/notes/add-max-k-cut-app-7e451a5993171175.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
features:
- |
Adding the Max-k-Cut application :class:`qiskit_optimization.applications.Maxkcut`.

Problem: Given an undirected graph, find a partition of nodes into at most k subsets such
that the total weight of the edges between the k subsets is maximized.

To solve this problem, the space-efficient quantum optimization representation (or encoding)
for the graph coloring problem proposed in [1] is used.

[1]: Z. Tabi et al., "Quantum Optimization for the Graph Coloring Problem with Space-Efficient
Embedding," 2020 IEEE International Conference on Quantum Computing and Engineering (QCE),
2020, pp. 56-62, doi: 10.1109/QCE49297.2020.00018., https://ieeexplore.ieee.org/document/9259934
Loading