Skip to content

Commit

Permalink
Standardize MWIS mod interface (#136)
Browse files Browse the repository at this point in the history
* Rename mwis result class
* Decorate user-facing functions to improve API documentation
  • Loading branch information
simonbowly authored Jun 7, 2024
1 parent d19956d commit 26e8f14
Show file tree
Hide file tree
Showing 2 changed files with 20 additions and 21 deletions.
2 changes: 1 addition & 1 deletion docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ API Reference
:members: min_cut, MinCutResult

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

.. automodule:: gurobi_optimods.opf
:members: solve_opf, compute_violations, solution_plot, violation_plot, read_case_matpower
Expand Down
39 changes: 19 additions & 20 deletions src/gurobi_optimods/mwis.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@


@dataclass
class Result:
class MWISResult:
"""
Data class representing the maximum weighted independent set
(clique) and its weight
Expand All @@ -38,7 +38,8 @@ class Result:
f: float


def maximum_weighted_independent_set(graph, weights, **kwargs):
@optimod()
def maximum_weighted_independent_set(graph, weights, *, create_env):
"""Find a set of mutually non-adjacent vertices with maximum weighted sum.
Parameters
Expand All @@ -57,22 +58,21 @@ def maximum_weighted_independent_set(graph, weights, **kwargs):
Returns
-------
Result
MWISResult
A data class representing the maximum weighted independent set array
and its weight.
"""
if sp.issparse(graph):
return _maximum_weighted_independent_set_scipy(graph, weights, **kwargs)
return _maximum_weighted_independent_set_scipy(graph, weights, create_env)
elif isinstance(graph, pd.DataFrame):
return _maximum_weighted_independent_set_pandas(graph, weights, **kwargs)
return _maximum_weighted_independent_set_pandas(graph, weights, create_env)
elif nx is not None and isinstance(graph, nx.Graph):
return _maximum_weighted_independent_set_networkx(graph, weights, **kwargs)
return _maximum_weighted_independent_set_networkx(graph, weights, create_env)
else:
raise ValueError(f"Unknown graph type: {type(graph)}")


@optimod()
def _maximum_weighted_independent_set_scipy(adjacency_matrix, weights, *, create_env):
def _maximum_weighted_independent_set_scipy(adjacency_matrix, weights, create_env):
"""This implementation uses the gurobipy matrix friendly APIs which are well
suited for the input data in scipy data structures."""
with create_env() as env, gp.Model("mwis", env=env) as model:
Expand Down Expand Up @@ -100,11 +100,10 @@ def _maximum_weighted_independent_set_scipy(adjacency_matrix, weights, *, create
)
model.optimize()
(mwis,) = np.where(x.X >= 0.5)
return Result(mwis, sum(weights[mwis]))
return MWISResult(mwis, sum(weights[mwis]))


@optimod()
def _maximum_weighted_independent_set_pandas(frame, weights, *, create_env):
def _maximum_weighted_independent_set_pandas(frame, weights, create_env):
"""This implementation uses the gurobipy-pandas APIs which are well
suited for the input data in pandas dataframes structures."""
with create_env() as env, gp.Model("mwis", env=env) as model:
Expand All @@ -125,11 +124,10 @@ def _maximum_weighted_independent_set_pandas(frame, weights, *, create_env):
)
model.optimize()
(mwis,) = np.where(x.gppd.X >= 0.5)
return Result(mwis, weights["weights"].iloc[mwis].sum())
return MWISResult(mwis, weights["weights"].iloc[mwis].sum())


@optimod()
def _maximum_weighted_independent_set_networkx(graph, weights, *, create_env):
def _maximum_weighted_independent_set_networkx(graph, weights, create_env):
"""This implementation uses the gurobipy term-based APIs which are well
suited for the input data in networkx data structures."""
with create_env() as env, gp.Model("mwis", env=env) as model:
Expand All @@ -148,10 +146,11 @@ def _maximum_weighted_independent_set_networkx(graph, weights, *, create_env):
)
model.optimize()
(mwis,) = np.where(np.array(model.getAttr("X", model.getVars())) >= 0.5)
return Result(mwis, sum(weights[mwis]))
return MWISResult(mwis, sum(weights[mwis]))


def maximum_weighted_clique(graph, weights, **kwargs):
@optimod()
def maximum_weighted_clique(graph, weights, *, create_env):
"""Find a set of fully connected vertices with maximum weighted sum.
Parameters
Expand All @@ -170,15 +169,15 @@ def maximum_weighted_clique(graph, weights, **kwargs):
Returns
-------
Result
MWISResult
A data class representing the maximum weighted clique array
and its weight.
"""
if sp.issparse(graph):
num_vertices, _ = graph.shape
complement_matrix = sp.triu(np.ones((num_vertices, num_vertices)), k=1) - graph
return _maximum_weighted_independent_set_scipy(
complement_matrix, weights, **kwargs
complement_matrix, weights, create_env
)
elif isinstance(graph, pd.DataFrame):
num_vertices = len(weights)
Expand All @@ -190,11 +189,11 @@ def maximum_weighted_clique(graph, weights, **kwargs):
)
complement_frame = pd.DataFrame(data, columns=["node1", "node2"])
return _maximum_weighted_independent_set_pandas(
complement_frame, weights, **kwargs
complement_frame, weights, create_env
)
elif nx is not None and isinstance(graph, nx.Graph):
return _maximum_weighted_independent_set_networkx(
nx.complement(graph), weights, **kwargs
nx.complement(graph), weights, create_env
)
else:
raise ValueError(f"Unknown graph type: {type(graph)}")

0 comments on commit 26e8f14

Please sign in to comment.