Skip to content

Commit

Permalink
Remove dependency in python-mip
Browse files Browse the repository at this point in the history
All necessart solvers should have been translated into their
ortools.mathopt counterpart.
We can get rid of this dependency which was unstable on some
platforms.
  • Loading branch information
nhuet committed Oct 4, 2024
1 parent e82cd73 commit 472d887
Show file tree
Hide file tree
Showing 46 changed files with 69 additions and 4,951 deletions.
15 changes: 1 addition & 14 deletions discrete_optimization/coloring/coloring_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
ColoringCPSatSolver,
ModelingCPSat,
)
from discrete_optimization.coloring.solvers.coloring_lp_solvers import (
ColoringLP,
ColoringLP_MIP,
MilpSolverName,
)
from discrete_optimization.coloring.solvers.coloring_lp_solvers import ColoringLP
from discrete_optimization.coloring.solvers.coloring_solver import SolverColoring

try:
Expand Down Expand Up @@ -56,15 +52,6 @@
"parameters_milp": ParametersMilp.default(),
},
),
(
ColoringLP_MIP,
{
"milp_solver_name": MilpSolverName.CBC,
"greedy_start": True,
"parameters_milp": ParametersMilp.default(),
"use_cliques": False,
},
),
],
"cp": [
(
Expand Down
84 changes: 0 additions & 84 deletions discrete_optimization/coloring/solvers/coloring_lp_lns_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
)
from discrete_optimization.coloring.solvers.coloring_lp_solvers import (
ColoringLP,
ColoringLP_MIP,
ColoringLPMathOpt,
)
from discrete_optimization.coloring.solvers.greedy_coloring import GreedyColoring
Expand All @@ -30,15 +29,8 @@
GurobiConstraintHandler,
InitialSolution,
OrtoolsMathOptConstraintHandler,
PymipConstraintHandler,
)
from discrete_optimization.generic_tools.lns_tools import ConstraintHandler
from discrete_optimization.generic_tools.lp_tools import (
GurobiMilpSolver,
MilpSolver,
MilpSolverName,
PymipMilpSolver,
)
from discrete_optimization.generic_tools.result_storage.result_storage import (
ResultStorage,
)
Expand Down Expand Up @@ -204,79 +196,3 @@ def adding_constraint_from_results_store(
)
)
return constraints


class ConstraintHandlerFixColorsPyMip(PymipConstraintHandler):
"""Constraint builder used in LNS+ LP (using pymip library) for coloring problem.
This constraint handler is pretty basic, it fixes a fraction_to_fix proportion of nodes color.
Attributes:
problem (ColoringProblem): input coloring problem
fraction_to_fix (float): float between 0 and 1, representing the proportion of nodes to constrain.
"""

def __init__(self, problem: ColoringProblem, fraction_to_fix: float = 0.9):
self.problem = problem
self.fraction_to_fix = fraction_to_fix

def adding_constraint_from_results_store(
self, solver: PymipMilpSolver, result_storage: ResultStorage, **kwargs: Any
) -> Iterable[Any]:
if not isinstance(solver, ColoringLP_MIP):
raise ValueError("milp_solver must a ColoringLP for this constraint.")
if solver.model is None:
solver.init_model()
if solver.model is None:
raise RuntimeError(
"milp_solver.model must be not None after calling milp_solver.init_model()"
)
subpart_color = set(
random.sample(
solver.nodes_name,
int(self.fraction_to_fix * solver.number_of_nodes),
)
)

dict_color_fixed = {}
dict_color_start = {}
current_solution = result_storage.get_best_solution()
if current_solution is None:
raise ValueError(
"result_storage.get_best_solution() " "should not be None."
)
if not isinstance(current_solution, ColoringSolution):
raise ValueError(
"result_storage.get_best_solution() " "should be a ColoringSolution."
)
if current_solution.colors is None:
raise ValueError(
"result_storage.get_best_solution().colors " "should not be None."
)
max_color = max(current_solution.colors)
for n in solver.nodes_name:
dict_color_start[n] = current_solution.colors[solver.index_nodes_name[n]]
if n in subpart_color and dict_color_start[n] <= max_color - 1:
dict_color_fixed[n] = dict_color_start[n]
colors_var = solver.variable_decision["colors_var"]
lns_constraint = []
start = []
for key in colors_var:
n, c = key
if c == dict_color_start[n]:
start += [(colors_var[n, c], 1)]
else:
start += [(colors_var[n, c], 0)]
if n in dict_color_fixed:
if c == dict_color_fixed[n]:
lns_constraint.append(
solver.model.add_constr(colors_var[key] == 1, name=str((n, c)))
)
else:
lns_constraint.append(
solver.model.add_constr(colors_var[key] == 0, name=str((n, c)))
)
solver.model.start = start
if solver.milp_solver_name == MilpSolverName.GRB:
solver.model.solver.update()
return lns_constraint
170 changes: 0 additions & 170 deletions discrete_optimization/coloring/solvers/coloring_lp_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@
from collections.abc import Callable, Hashable
from typing import Any, Optional, TypedDict, Union

import mip
import networkx as nx
from mip import BINARY, INTEGER, xsum
from ortools.math_opt.python import mathopt

from discrete_optimization.coloring.coloring_model import (
Expand All @@ -32,9 +30,7 @@
ConstraintType,
GurobiMilpSolver,
MilpSolver,
MilpSolverName,
OrtoolsMathOptMilpSolver,
PymipMilpSolver,
VariableType,
)

Expand Down Expand Up @@ -338,172 +334,6 @@ def convert_to_variable_values(
return _BaseColoringLP.convert_to_variable_values(self, solution)


class ColoringLP_MIP(PymipMilpSolver, _BaseColoringLP):
"""Coloring LP solver based on pymip library.
Note:
Gurobi and CBC are available as backend solvers.
Attributes:
problem (ColoringProblem): coloring problem instance to solve
params_objective_function (ParamsObjectiveFunction): objective function parameters
(however this is just used for the ResultStorage creation, not in the optimisation)
milp_solver_name (MilpSolverName): backend solver to use (either CBC ou GRB)
"""

hyperparameters = _BaseColoringLP.hyperparameters

problem: ColoringProblem

def __init__(
self,
problem: ColoringProblem,
params_objective_function: Optional[ParamsObjectiveFunction] = None,
milp_solver_name: MilpSolverName = MilpSolverName.CBC,
**kwargs: Any,
):
_BaseColoringLP.__init__(
self,
problem=problem,
params_objective_function=params_objective_function,
**kwargs,
)
self.set_milp_solver_name(milp_solver_name=milp_solver_name)

def init_model(self, **kwargs: Any) -> None:
kwargs = self.complete_with_default_hyperparameters(kwargs)
greedy_start = kwargs["greedy_start"]
use_cliques = kwargs["use_cliques"]
if greedy_start:
logger.info("Computing greedy solution")
greedy_solver = GreedyColoring(
self.problem,
params_objective_function=self.params_objective_function,
)
sol = greedy_solver.solve(
strategy=NXGreedyColoringMethod.best
).get_best_solution()
if sol is None:
raise RuntimeError(
"greedy_solver.solve(strategy=NXGreedyColoringMethod.best).get_best_solution() "
"should not be None."
)
if not isinstance(sol, ColoringSolution):
raise RuntimeError(
"greedy_solver.solve(strategy=NXGreedyColoringMethod.best).get_best_solution() "
"should be a ColoringSolution."
)
self.start_solution = sol
else:
logger.info("Get dummy solution")
self.start_solution = self.problem.get_dummy_solution()
nb_colors = self.start_solution.nb_color
nb_colors_subset = nb_colors
if self.problem.use_subset:
nb_colors_subset = self.problem.count_colors(self.start_solution.colors)
nb_colors = self.problem.count_colors_all_index(self.start_solution.colors)

if nb_colors is None:
raise RuntimeError("self.start_solution.nb_color should not be None.")
color_model = mip.Model(
"color", sense=mip.MINIMIZE, solver_name=self.solver_name
)
colors_var = {}
range_node = self.nodes_name
range_color = range(nb_colors)
range_color_subset = range(nb_colors_subset)
range_color_per_node = {}
for node in self.nodes_name:
rng = self.get_range_color(
node_name=node,
range_color_subset=range_color_subset,
range_color_all=range_color,
)
for color in rng:
colors_var[node, color] = color_model.add_var(
var_type=BINARY, obj=0, name="x_" + str((node, color))
)
range_color_per_node[node] = set(rng)
one_color_constraints = {}
for n in range_node:
one_color_constraints[n] = color_model.add_constr(
xsum([colors_var[n, c] for c in range_color_per_node[n]]) == 1
)
cliques = []
g = self.graph.to_networkx()
if use_cliques:
for c in nx.algorithms.clique.find_cliques(g):
cliques += [c]
cliques = sorted(cliques, key=lambda x: len(x), reverse=True)
else:
cliques = [[e[0], e[1]] for e in g.edges()]
cliques_constraint: dict[Union[int, tuple[int, int]], Any] = {}
index_c = 0
opt = color_model.add_var(var_type=INTEGER, lb=0, ub=nb_colors, obj=1)
if use_cliques:
for c in cliques[:100]:
cliques_constraint[index_c] = color_model.add_constr(
xsum(
[
(color_i + 1) * colors_var[node, color_i]
for node in c
for color_i in range_color_per_node[node]
]
)
>= sum([i + 1 for i in range(len(c))])
)
cliques_constraint[(index_c, 1)] = color_model.add_constr(
xsum(
[
colors_var[node, color_i]
for node in c
for color_i in range_color_per_node[node]
]
)
<= opt
)
index_c += 1
edges = g.edges()
constraints_neighbors = {}
for e in edges:
for c in range_color_per_node[e[0]]:
if c in range_color_per_node[e[1]]:
constraints_neighbors[(e[0], e[1], c)] = color_model.add_constr(
colors_var[e[0], c] + colors_var[e[1], c] <= 1
)
for n in range_node:
color_model.add_constr(
xsum(
[
(color_i + 1) * colors_var[n, color_i]
for color_i in range_color_per_node[n]
]
)
<= opt
)
self.model = color_model
self.variable_decision = {"colors_var": colors_var}
self.constraints_dict = {
"one_color_constraints": one_color_constraints,
"constraints_neighbors": constraints_neighbors,
}
self.description_variable_description = {
"colors_var": {
"shape": (self.number_of_nodes, nb_colors),
"type": bool,
"descr": "for each node and each color," " a binary indicator",
}
}
self.description_constraint["one_color_constraints"] = {
"descr": "one and only one color " "should be assignated to a node"
}
self.description_constraint["constraints_neighbors"] = {
"descr": "no neighbors can have same color"
}


class ColoringLPMathOpt(OrtoolsMathOptMilpSolver, _BaseColoringLP):
"""Coloring LP solver based on pymip library.
Expand Down
12 changes: 0 additions & 12 deletions discrete_optimization/facility/facility_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
from discrete_optimization.facility.solvers.facility_lp_solver import (
LP_Facility_Solver,
LP_Facility_Solver_CBC,
LP_Facility_Solver_PyMip,
MilpSolverName,
ParametersMilp,
)
from discrete_optimization.facility.solvers.facility_solver import SolverFacility
Expand Down Expand Up @@ -50,16 +48,6 @@
"n_cheapest": 10,
},
),
(
LP_Facility_Solver_PyMip,
{
"parameters_milp": ParametersMilp.default(),
"use_matrix_indicator_heuristic": True,
"milp_solver_name": MilpSolverName.CBC,
"n_shortest": 10,
"n_cheapest": 10,
},
),
],
"cp": [
(
Expand Down
Loading

0 comments on commit 472d887

Please sign in to comment.