Compare commits
No commits in common. '1c6912cc514b874d46b3e237117118f0b14d1e20' and 'a306f0df26744e25d228c2ada5c065378a385926' have entirely different histories.
1c6912cc51
...
a306f0df26
@ -1,111 +0,0 @@
|
|||||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
|
||||||
# Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from typing import List, Union, Optional, Any
|
|
||||||
|
|
||||||
import gurobipy as gp
|
|
||||||
import networkx as nx
|
|
||||||
import numpy as np
|
|
||||||
from gurobipy import quicksum
|
|
||||||
from networkx import Graph
|
|
||||||
from scipy.stats.distributions import rv_frozen
|
|
||||||
|
|
||||||
from miplearn.io import read_pkl_gz
|
|
||||||
from miplearn.problems import _gurobipy_set_params
|
|
||||||
from miplearn.solvers.gurobi import GurobiModel
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MaxCutData:
|
|
||||||
graph: Graph
|
|
||||||
weights: np.ndarray
|
|
||||||
|
|
||||||
|
|
||||||
class MaxCutGenerator:
|
|
||||||
"""
|
|
||||||
Random instance generator for the Maximum Cut Problem.
|
|
||||||
|
|
||||||
The generator operates in two modes. When `fix_graph=True`, a single random
|
|
||||||
Erdős-Rényi graph $G_{n,p}$ is generated during initialization, with parameters $n$
|
|
||||||
and $p$ drawn from their respective probability distributions. For each instance,
|
|
||||||
only edge weights are randomly sampled from the set {1, -1}, while the graph
|
|
||||||
structure remains fixed.
|
|
||||||
|
|
||||||
When `fix_graph=False`, both the graph structure and edge weights are randomly
|
|
||||||
generated for each instance.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
n: rv_frozen,
|
|
||||||
p: rv_frozen,
|
|
||||||
fix_graph: bool,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Initialize the problem generator.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
n: rv_discrete
|
|
||||||
Probability distribution for the number of nodes.
|
|
||||||
p: rv_continuous
|
|
||||||
Probability distribution for the graph density.
|
|
||||||
fix_graph: bool
|
|
||||||
Controls graph generation for instances. If false, a new random graph is
|
|
||||||
generated for each instance. If true, the same graph is reused across instances.
|
|
||||||
"""
|
|
||||||
assert isinstance(n, rv_frozen), "n should be a SciPy probability distribution"
|
|
||||||
assert isinstance(p, rv_frozen), "p should be a SciPy probability distribution"
|
|
||||||
self.n = n
|
|
||||||
self.p = p
|
|
||||||
self.fix_graph = fix_graph
|
|
||||||
self.graph = None
|
|
||||||
if fix_graph:
|
|
||||||
self.graph = self._generate_graph()
|
|
||||||
|
|
||||||
def generate(self, n_samples: int) -> List[MaxCutData]:
|
|
||||||
def _sample() -> MaxCutData:
|
|
||||||
if self.graph is not None:
|
|
||||||
graph = self.graph
|
|
||||||
else:
|
|
||||||
graph = self._generate_graph()
|
|
||||||
m = graph.number_of_edges()
|
|
||||||
weights = np.random.randint(2, size=(m,)) * 2 - 1
|
|
||||||
return MaxCutData(graph, weights)
|
|
||||||
|
|
||||||
return [_sample() for _ in range(n_samples)]
|
|
||||||
|
|
||||||
def _generate_graph(self) -> Graph:
|
|
||||||
return nx.generators.random_graphs.binomial_graph(self.n.rvs(), self.p.rvs())
|
|
||||||
|
|
||||||
def build_maxcut_model_gurobipy(
|
|
||||||
data: Union[str, MaxCutData],
|
|
||||||
params: Optional[dict[str, Any]] = None,
|
|
||||||
) -> GurobiModel:
|
|
||||||
# Initialize model
|
|
||||||
model = gp.Model()
|
|
||||||
_gurobipy_set_params(model, params)
|
|
||||||
|
|
||||||
# Read data
|
|
||||||
data = _maxcut_read(data)
|
|
||||||
nodes = list(data.graph.nodes())
|
|
||||||
edges = list(data.graph.edges())
|
|
||||||
|
|
||||||
# Add decision variables
|
|
||||||
x = model.addVars(nodes, vtype=gp.GRB.BINARY, name="x")
|
|
||||||
|
|
||||||
# Add the objective function
|
|
||||||
model.setObjective(quicksum(
|
|
||||||
- data.weights[i] * x[e[0]] * (1 - x[e[1]]) for (i, e) in enumerate(edges)
|
|
||||||
))
|
|
||||||
|
|
||||||
model.update()
|
|
||||||
return GurobiModel(model)
|
|
||||||
|
|
||||||
def _maxcut_read(data: Union[str, MaxCutData]) -> MaxCutData:
|
|
||||||
if isinstance(data, str):
|
|
||||||
data = read_pkl_gz(data)
|
|
||||||
assert isinstance(data, MaxCutData)
|
|
||||||
return data
|
|
@ -1,57 +0,0 @@
|
|||||||
# MIPLearn: Extensible Framework for Learning-Enhanced Mixed-Integer Optimization
|
|
||||||
# Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
|
||||||
# Released under the modified BSD license. See COPYING.md for more details.
|
|
||||||
import random
|
|
||||||
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from miplearn.problems.maxcut import MaxCutGenerator, build_maxcut_model_gurobipy
|
|
||||||
from scipy.stats import randint, uniform
|
|
||||||
|
|
||||||
def _set_seed():
|
|
||||||
random.seed(42)
|
|
||||||
np.random.seed(42)
|
|
||||||
|
|
||||||
def test_maxcut_generator_not_fixed() -> None:
|
|
||||||
_set_seed()
|
|
||||||
gen = MaxCutGenerator(
|
|
||||||
n=randint(low=5, high=6),
|
|
||||||
p=uniform(loc=0.5, scale=0.0),
|
|
||||||
fix_graph=False,
|
|
||||||
)
|
|
||||||
data = gen.generate(3)
|
|
||||||
assert len(data) == 3
|
|
||||||
assert list(data[0].graph.nodes()) == [0, 1, 2, 3, 4]
|
|
||||||
assert list(data[0].graph.edges()) == [(0, 2), (0, 3), (0, 4), (2, 3), (2, 4), (3, 4)]
|
|
||||||
assert data[0].weights.tolist() == [-1, 1, -1, -1, -1, 1]
|
|
||||||
assert list(data[1].graph.nodes()) == [0, 1, 2, 3, 4]
|
|
||||||
assert list(data[1].graph.edges()) == [(0, 1), (0, 3), (0, 4), (1, 4), (3, 4)]
|
|
||||||
assert data[1].weights.tolist() == [-1, -1, -1, 1, -1]
|
|
||||||
|
|
||||||
def test_maxcut_generator_fixed() -> None:
|
|
||||||
random.seed(42)
|
|
||||||
np.random.seed(42)
|
|
||||||
gen = MaxCutGenerator(
|
|
||||||
n=randint(low=5, high=6),
|
|
||||||
p=uniform(loc=0.5, scale=0.0),
|
|
||||||
fix_graph=True,
|
|
||||||
)
|
|
||||||
data = gen.generate(3)
|
|
||||||
assert len(data) == 3
|
|
||||||
assert list(data[0].graph.nodes()) == [0, 1, 2, 3, 4]
|
|
||||||
assert list(data[0].graph.edges()) == [(0, 2), (0, 3), (0, 4), (2, 3), (2, 4), (3, 4)]
|
|
||||||
assert data[0].weights.tolist() == [-1, 1, -1, -1, -1, 1]
|
|
||||||
assert list(data[1].graph.nodes()) == [0, 1, 2, 3, 4]
|
|
||||||
assert list(data[1].graph.edges()) == [(0, 2), (0, 3), (0, 4), (2, 3), (2, 4), (3, 4)]
|
|
||||||
assert data[1].weights.tolist() == [-1, -1, -1, 1, -1, -1]
|
|
||||||
|
|
||||||
def test_maxcut_model():
|
|
||||||
_set_seed()
|
|
||||||
data = MaxCutGenerator(
|
|
||||||
n=randint(low=20, high=21),
|
|
||||||
p=uniform(loc=0.5, scale=0.0),
|
|
||||||
fix_graph=True,
|
|
||||||
).generate(1)[0]
|
|
||||||
model = build_maxcut_model_gurobipy(data)
|
|
||||||
model.optimize()
|
|
||||||
assert model.inner.ObjVal == -26
|
|
Loading…
Reference in new issue