Skip to content

Commit

Permalink
Adding Clique to MWIS (#125)
Browse files Browse the repository at this point in the history
* adds max clique implementation using the complement graph
* refactor to cover scipy,pandas,networkx
* add more tests

---------

Co-authored-by: Simon Bowly <[email protected]>
  • Loading branch information
maliheha and simonbowly authored Jan 30, 2024
1 parent c02d528 commit 3a28b20
Show file tree
Hide file tree
Showing 8 changed files with 870 additions and 105 deletions.
2 changes: 1 addition & 1 deletion docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ API Reference
:members: min_cut, MinCutResult

.. automodule:: gurobi_optimods.mwis
:members: maximum_weighted_independent_set
:members: maximum_weighted_independent_set, maximum_weighted_clique, Result

.. automodule:: gurobi_optimods.opf
:members: solve_opf, compute_violations, solution_plot, violation_plot, read_case_matpower
Expand Down
2 changes: 2 additions & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
# -- Bibfiles
bibtex_bibfiles = [
"refs/graphs.bib",
"refs/mwis.bib",
"refs/opf.bib",
"refs/portfolio.bib",
"refs/qubo.bib",
Expand All @@ -80,6 +81,7 @@
"Graph": "networkx.Graph",
"LinAlgError": "numpy.linalg.LinAlgError",
"spmatrix": "scipy.sparse.spmatrix",
"sparray": "scipy.sparse.sparray",
}
numpydoc_xref_ignore = {"optional", "or", "of"}

Expand Down
2 changes: 1 addition & 1 deletion docs/source/gallery.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ The OptiMods Gallery
:text-align: center
:img-top: mods/figures/min-cost-flow-result.png

.. grid-item-card:: Maximum Weighted Independent Set
.. grid-item-card:: Maximum Weighted Independent Set/Clique
:link: mods/mwis
:link-type: doc
:text-align: center
Expand Down
Binary file modified docs/source/mods/figures/mwis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
232 changes: 176 additions & 56 deletions docs/source/mods/mwis.rst
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
Maximum Weighted Independent Set
================================
The maximum independent set problem is one of the fundamental problems
in combinatorial optimization with ubiquitous applications and connections to
other problems. Maximum clique, minimum vertex cover, and maximum matching are
a few examples of problems that can be reduced to a maximum independent set problem.

In this Mod, we consider the more general problem of the maximum weighted
independent set (MWIS), which has applications in various fields such as computer
vision, pattern recognition, molecular structure matching, social network analysis,
and genome data mapping. To better understand how a theoretical graph theory
Maximum Weighted Independent Set/Clique
=========================================
The maximum independent set problem and its complement, the maximum
clique are among fundamental problems in combinatorial optimization with ubiquitous
applications and connections to other problems (:footcite:t:`bomze1999maximum,wu2015review`).

In this Mod, we consider the more general problems of the maximum weighted
independent set and maximum weighted clique which have applications
in various fields such as computer vision, pattern recognition,
molecular structure matching, social network analysis, and genome data mapping.
To better understand how a theoretical graph theory
problem can be used to address a real-world challenge, let us review one
application area in detail.
application area for each problem in detail.

To measure the structural similarity between two molecules, the
molecules are first represented as labeled graphs where the vertices and the edges
correspond to the atoms of the molecule and its chemical bonds, respectively. To
find the largest substructure (subgraph) that appears in both molecular
**Maximum weighted independent set**: To measure the structural similarity between
two molecules, the molecules are first represented as labeled graphs where the
vertices and the edges correspond to the atoms of the molecule and its chemical bonds,
respectively. To find the largest substructure (subgraph) that appears in both molecular
graphs, it suffices to find the maximum weighted independent set of a third graph,
known as conflict graph. The vertices and the edges of the conflict
graph represent possible mappings and conflicts
between two molecules, respectively.
known as conflict graph. The vertices and the edges of the conflict graph represent
possible mappings and conflicts between two molecules, respectively.

**Maximum weighted clique**: Consider a social network where the people and the
connections between them are respectively represented as vertices and the edges
of a graph. Different social and psychological behavior of people are also taken
into account as vertex weights. To find a group of people who all know each other
and have the maximum social/psychological harmony, it suffices to identify the maximum
weighted clique of the social graph.

Problem Specification
---------------------

**Maximum weighted independent set**:
Consider an undirected graph :math:`G` with :math:`n` vertices and :math:`m`
edges where each vertex is associated with a positive weight :math:`w`. Find a
maximum weighted independent set, i.e., select a set of vertices in graph
Expand All @@ -39,6 +45,9 @@ vertex :math:`i \in V` has a positive weight :math:`w_i`. Find a subset :math:`S
* among all such independent sets, the set :math:`S` has the maximum total
vertex weight.

*Note*: In case all vertices have equal weights, the cardinality of
set :math:`S` represents the stability number of graph :math:`G`.

.. dropdown:: Background: Optimization Model

This Mod is implemented by formulating a Binary Integer Programming (BIP)
Expand All @@ -60,65 +69,176 @@ vertex :math:`i \in V` has a positive weight :math:`w_i`. Find a subset :math:`S
& x_i \in \{0, 1\} & \forall i \in V
\end{align}
The input data for this Mod includes a scipy sparse matrix in CSR (Compressed
SparseRow) format
that captures the adjacency matrix of the graph :math:`G` (upper triangle only), plus a
numpy array that captures the weights of the vertices.
**Maximum weighted clique**: Given an undirected graph :math:`G = (V, E, w)`, finding
the maximum weighted clique of graph :math:`G` is equivalent to finding the
maximum weighted independent set of its complement graph
:math:`G^{\prime} = (V, E^{\prime}, w)` where

* for every edge :math:`(i, j)` in :math:`E`, there is no edge in :math:`E^{\prime}`, and
* for every edge :math:`(i, j)` not in :math:`E`, there is an edge in :math:`E^{\prime}`.

Code
----
*Note*: In case all vertices have equal weights, the cardinality of
the maximum clique set represents the clique number of graph :math:`G`.

The example below finds a maximum weighted independent set for
a graph with 8 vertices and 12 edges known as the cube graph.
Interface
---------

.. testcode:: mwis
The functions ``maximum_weighted_independent_set`` and ``maximum_weighted_clique``
support scipy sparse matrix/array, networkx graph, and pandas dataframes as
possible input graph. The input graph captures the adjacency matrix of
the graph :math:`G` (upper triangle with zero diagonals only). The user
should also provide the vertex weights as a numpy array or a pandas dataframe
depending on the input graph data structure.

import scipy.sparse as sp
import networkx as nx
import numpy as np
from gurobi_optimods.mwis import maximum_weighted_independent_set
The example below finds the maximum weighted independent set and
the maximum weighted clique for a graph with 8 vertices and 12 edges
known as the cube graph.

.. tabs::

.. group-tab:: scipy.sparse
The input graph and the vertex weights are provided as a scipy
sparse array and a numpy array, respectively.

.. testcode:: mwis_sp

import scipy.sparse as sp
import networkx as nx
import numpy as np
from gurobi_optimods.mwis import maximum_weighted_independent_set, maximum_weighted_clique

# Graph adjacency matrix (upper triangular) as a sparse array.
g = nx.cubical_graph()
graph_adjacency = sp.triu(nx.to_scipy_sparse_array(g))
# Vertex weights
weights = np.array([2**i for i in range(8)])

# Compute maximum weighted independent set.
mwis = maximum_weighted_independent_set(graph_adjacency, weights)

# Compute maximum weighted clique.
mwc = maximum_weighted_clique(graph_adjacency, weights)

.. testoutput:: mwis_sp
:hide:

...
Best objective 1.650000000000e+02, best bound 1.650000000000e+02, gap 0.0000%
...
Best objective 1.920000000000e+02, best bound 1.920000000000e+02, gap 0.0000%

.. group-tab:: networkx
The input graph and the vertex weights are provided as
a networkx graph and a numpy array, respectively.

.. testcode:: mwis_nx

import networkx as nx
import numpy as np
from gurobi_optimods.mwis import maximum_weighted_independent_set, maximum_weighted_clique

# A networkx Graph
graph = nx.cubical_graph()
# Vertex weights
weights = np.array([2**i for i in range(8)])

# Compute maximum weighted independent set.
mwis = maximum_weighted_independent_set(graph, weights)

# Compute maximum weighted clique.
mwc = maximum_weighted_clique(graph, weights)

# Graph adjacency matrix (upper triangular) as a sparse matrix.
g = nx.cubical_graph()
adjacency_matrix = sp.triu(nx.to_scipy_sparse_array(g))
# Vertex weights
weights = np.array([2**i for i in range(8)])
.. testoutput:: mwis_nx
:hide:

# Compute maximum weighted independent set.
mwis = maximum_weighted_independent_set(adjacency_matrix, weights)
...
Best objective 1.650000000000e+02, best bound 1.650000000000e+02, gap 0.0000%
...
Best objective 1.920000000000e+02, best bound 1.920000000000e+02, gap 0.0000%

.. testoutput:: mwis
:hide:

...
Best objective 1.650000000000e+02, best bound 1.650000000000e+02, gap 0.0000%
.. group-tab:: pandas
The input graph is a pandas dataframe with two columns named as
"node1" and "node2" capturing the vertex pairs of an edge. The vertex
weights is also a pandas dataframe with a column named as "weights"
describing the weight of each vertex.

.. testcode:: mwis_pd

import networkx as nx
import pandas as pd
import numpy as np
from gurobi_optimods.mwis import maximum_weighted_independent_set, maximum_weighted_clique

# A networkx Graph
g = nx.cubical_graph()
frame = pd.DataFrame(g.edges, columns=["node1", "node2"])
# Vertex weights
weights = pd.DataFrame(np.array([2**i for i in range(8)]), columns=["weights"])

# Compute maximum weighted independent set.
mwis = maximum_weighted_independent_set(frame, weights)

# Compute maximum weighted clique.
mwc = maximum_weighted_clique(frame, weights)

.. testoutput:: mwis_pd
:hide:

...
Best objective 1.650000000000e+02, best bound 1.650000000000e+02, gap 0.0000%
...
Best objective 1.920000000000e+02, best bound 1.920000000000e+02, gap 0.0000%


Solution
--------

The solution is a numpy array containing the vertices in set :math:`S`.
Independent of the input types, the solution is always a data class including
the numpy array of the vertices in the independent set or clique as well as
its weight.

.. doctest:: mwis
:options: +NORMALIZE_WHITESPACE
.. code-block:: Python
>>> mwis
Result(x=array([0, 2, 5, 7]), f=165)
>>> mwis.x
array([0, 2, 5, 7])
>>> maximum_vertex_weight = sum(weights[mwis])
>>> maximum_vertex_weight
>>> mwis.f
165
>>> mwc
Result(x=array([6, 7]), f=192)
>>> mwc.x
array([6, 7])
>>> mwc.f
192
.. doctest:: mwis
:options: +NORMALIZE_WHITESPACE
>>> import networkx as nx
>>> import matplotlib.pyplot as plt
>>> layout = nx.spring_layout(g, seed=0)
>>> color_map = ["red" if node in mwis else "lightgrey" for node in g.nodes()]
>>> nx.draw(g, pos=layout, node_color=color_map, node_size=600, with_labels=True)
.. code-block:: Python
The vertices in the independent set are highlighted in red.
import networkx as nx
import matplotlib.pyplot as plt
fig, (ax1, ax2) = plt.subplots(1, 2)
layout = nx.spring_layout(g, seed=0)
# Plot the maximum weighted independent set
color_map = ["red" if node in mwis.x else "lightgrey" for node in g.nodes()]
nx.draw(g, pos=layout, ax= ax1, node_color=color_map, node_size=600, with_labels=True)
# Plot the maximum weighted clique
color_map = ["blue" if node in mwc.x else "lightgrey" for node in g.nodes()]
nx.draw(g, pos=layout, ax = ax2, node_color=color_map, node_size=600, with_labels=True)
fig.tight_layout()
plt.show()
The vertices in the independent set and in the clique are highlighted in red and
blue, respectively.

.. image:: figures/mwis.png
:width: 600

.. footbibliography::
19 changes: 19 additions & 0 deletions docs/source/refs/mwis.bib
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@article{bomze1999maximum,
title={The maximum clique problem},
author={Bomze, Immanuel M and Budinich, Marco and Pardalos, Panos M and Pelillo, Marcello},
journal={Handbook of Combinatorial Optimization: Supplement Volume A},
pages={1--74},
year={1999},
publisher={Springer}
}

@article{wu2015review,
title={A review on algorithms for maximum clique problems},
author={Wu, Qinghua and Hao, Jin-Kao},
journal={European Journal of Operational Research},
volume={242},
number={3},
pages={693--709},
year={2015},
publisher={Elsevier}
}
Loading

0 comments on commit 3a28b20

Please sign in to comment.