Skip to content

Commit

Permalink
Merge pull request #2715 from Robbybp/incidence-graph
Browse files Browse the repository at this point in the history
[contrib] Refactor `incidence_analysis` to cache a graph instead of a matrix
  • Loading branch information
blnicho authored Feb 13, 2023
2 parents 24d5259 + 6f1995f commit 48cafd2
Show file tree
Hide file tree
Showing 10 changed files with 864 additions and 290 deletions.
11 changes: 7 additions & 4 deletions pyomo/contrib/incidence_analysis/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
from .triangularize import block_triangularize
from .matching import maximum_matching
from .interface import IncidenceGraphInterface
from .interface import (
IncidenceGraphInterface,
get_bipartite_incidence_graph,
)
from .util import (
generate_strongly_connected_components,
solve_strongly_connected_components,
)
generate_strongly_connected_components,
solve_strongly_connected_components,
)
31 changes: 16 additions & 15 deletions pyomo/contrib/incidence_analysis/common/dulmage_mendelsohn.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@


def _get_projected_digraph(bg, matching, top_nodes):
"""
"""
""" """
digraph = DiGraph()
digraph.add_nodes_from(top_nodes)
for n in top_nodes:
Expand All @@ -45,8 +44,7 @@ def _get_projected_digraph(bg, matching, top_nodes):


def _get_reachable_from(digraph, sources):
"""
"""
""" """
_filter = set()
reachable = []
for node in sources:
Expand Down Expand Up @@ -96,14 +94,17 @@ def dulmage_mendelsohn(bg, top_nodes=None, matching=None):
t_other = [t for t in top_nodes if t not in _filter]
b_other = [b for b in bot_nodes if b not in _filter]

return ((
t_unmatched,
t_reachable,
t_matched_with_reachable,
t_other,
), (
b_unmatched,
b_reachable,
b_matched_with_reachable,
b_other,
))
return (
(
t_unmatched,
t_reachable,
t_matched_with_reachable,
t_other,
),
(
b_unmatched,
b_reachable,
b_matched_with_reachable,
b_other,
),
)
8 changes: 3 additions & 5 deletions pyomo/contrib/incidence_analysis/connected.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@


def get_independent_submatrices(matrix):
"""
"""
""" """
nxc = nx.algorithms.components
nxb = nx.algorithms.bipartite
from_biadjacency_matrix = nxb.matrix.from_biadjacency_matrix
Expand All @@ -27,11 +26,10 @@ def get_independent_submatrices(matrix):
# nodes have values in [N, N+M-1].
# We could also check the "bipartite" attribute of each node...
row_blocks = [
sorted([node for node in comp if node < N])
for comp in connected_components
sorted([node for node in comp if node < N]) for comp in connected_components
]
col_blocks = [
sorted([node-N for node in comp if node >= N])
sorted([node - N for node in comp if node >= N])
for comp in connected_components
]
return row_blocks, col_blocks
40 changes: 21 additions & 19 deletions pyomo/contrib/incidence_analysis/dulmage_mendelsohn.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@
from pyomo.common.dependencies import networkx as nx
from pyomo.contrib.incidence_analysis.common.dulmage_mendelsohn import (
dulmage_mendelsohn as dm_nx,
)
)

"""
This module imports the general Dulmage-Mendelsohn-on-a-graph function
from "common" and implements an interface for coo_matrix-like objects.
"""

RowPartition = namedtuple(
"RowPartition",
["unmatched", "overconstrained", "underconstrained", "square"],
)
"RowPartition",
["unmatched", "overconstrained", "underconstrained", "square"],
)
ColPartition = namedtuple(
"ColPartition",
["unmatched", "underconstrained", "overconstrained", "square"],
)
"ColPartition",
["unmatched", "underconstrained", "overconstrained", "square"],
)


def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None):
"""
Expand All @@ -45,9 +47,9 @@ def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None):
graph = matrix_or_graph
if top_nodes is None:
raise ValueError(
"top_nodes must be specified if a graph is provided,"
"\notherwise the result is ambiguous."
)
"top_nodes must be specified if a graph is provided,"
"\notherwise the result is ambiguous."
)
partition = dm_nx(graph, top_nodes=top_nodes, matching=matching)
# RowPartition and ColPartition do not make sense for a general graph.
# However, here we assume that this graph comes from a Pyomo model,
Expand All @@ -74,17 +76,17 @@ def dulmage_mendelsohn(matrix_or_graph, top_nodes=None, matching=None):
# Matrix rows have bipartite=0, columns have bipartite=1
bg = from_biadjacency_matrix(matrix)
row_partition, col_partition = dm_nx(
bg,
top_nodes=list(range(M)),
matching=matching,
)
bg,
top_nodes=list(range(M)),
matching=matching,
)

partition = (
row_partition,
tuple([n - M for n in subset] for subset in col_partition)
# Column nodes have values in [M, M+N-1]. Apply the offset
# to get values corresponding to indices in user's matrix.
)
row_partition,
tuple([n - M for n in subset] for subset in col_partition)
# Column nodes have values in [M, M+N-1]. Apply the offset
# to get values corresponding to indices in user's matrix.
)

partition = (RowPartition(*partition[0]), ColPartition(*partition[1]))
return partition
Loading

0 comments on commit 48cafd2

Please sign in to comment.