Skip to content

Commit

Permalink
adding tsp model and updated examples
Browse files Browse the repository at this point in the history
  • Loading branch information
g-poveda committed Nov 27, 2024
1 parent 7e88204 commit cfe0e63
Show file tree
Hide file tree
Showing 9 changed files with 481 additions and 14 deletions.
3 changes: 3 additions & 0 deletions discrete_optimization/coloring/solvers/toulbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def init_model(self, **kwargs: Any) -> None:
if nb_colors is None:
# Run greedy solver to get an upper bound of the bound.
sol = self.get_starting_solution(**kwargs)
self.sol = sol
nb_colors = int(self.problem.evaluate(sol)["nb_colors"])
nb_colors_all = self.problem.count_colors_all_index(sol.colors)
nb_colors_on_subset = self.problem.count_colors(sol.colors)
Expand Down Expand Up @@ -198,6 +199,8 @@ def init_model(self, **kwargs: Any) -> None:
model.AddFunction([f"x_{index1}", f"x_{index2}"], costs_i1_i2)
index += 1
self.model = model
if hasattr(self, "sol") and kwargs["greedy_start"]:
self.set_warm_start(self.sol)

def default_costs_matrix(self, nb_colors_all: int, nb_colors_on_subset: int):
costs = [
Expand Down
10 changes: 8 additions & 2 deletions discrete_optimization/generic_tools/toulbar_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class ToulbarSolver(SolverDO):
)
]

@abstractmethod
def init_model(self, **kwargs: Any) -> None:
...

@abstractmethod
def retrieve_solution(
self, solution_from_toulbar2: tuple[list, float, int]
Expand All @@ -49,7 +53,7 @@ def solve(
) -> ResultStorage:
if self.model is None:
self.init_model(**kwargs)
solution = self.model.Solve(showSolutions=1, timeLimit=time_limit)
solution = self.model.Solve(showSolutions=1, timeLimit=int(time_limit))
logger.info(
f"Solution value = {solution[1]}, bound={self.model.GetDDualBound()}"
)
Expand All @@ -71,7 +75,9 @@ def init_model(self, **kwargs: Any) -> None:

def solve(self, time_limit: Optional[int] = 20, **kwargs: Any) -> ResultStorage:
try:
solution = self.model.SolveNext(showSolutions=1, timeLimit=time_limit)
solution = self.model.SolveNext(
showSolutions=1, timeLimit=int(time_limit)
)
logger.info(f"=== Solution === \n {solution}")
logger.info(
f"Best solution = {solution[1]}, Bound = {self.model.GetDDualBound()}"
Expand Down
7 changes: 5 additions & 2 deletions discrete_optimization/rcpsp/solvers/toulbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ def init_model(self, **kwargs: Any) -> None:
f"Please go with ToulbarMultimodeRcpspSolver instead."
)
raise exc
model = pytoulbar2.CFN(ubinit=self.problem.horizon)
if "vns" in kwargs:
model = pytoulbar2.CFN(ubinit=self.problem.horizon, vns=kwargs["vns"])
else:
model = pytoulbar2.CFN(ubinit=self.problem.horizon)
n_jobs = self.problem.n_jobs
horizon = self.problem.horizon
index_var = 0
Expand Down Expand Up @@ -355,7 +358,7 @@ def __init__(self, problem: RcpspProblem, fraction_task: float = 0.5):
problem=self.problem, graph=self.graph, nb_cut_part=3
)
neighbors_2 = NeighborRandom(
problem=self.problem, graph=self.graph, fraction_subproblem=0.4
problem=self.problem, graph=self.graph, fraction_subproblem=0.3
)
neighbors_3 = NeighborBuilderTimeWindow(
problem=self.problem, graph=self.graph, time_window_length=20
Expand Down
133 changes: 133 additions & 0 deletions discrete_optimization/tsp/solvers/toulbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Copyright (c) 2024 AIRBUS and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.
import logging
import random
from typing import Any, Iterable

from discrete_optimization.generic_tools.do_solver import SolverDO, WarmstartMixin
from discrete_optimization.generic_tools.hyperparameters.hyperparameter import (
CategoricalHyperparameter,
)
from discrete_optimization.generic_tools.lns_tools import ConstraintHandler
from discrete_optimization.generic_tools.result_storage.result_storage import (
ResultStorage,
)
from discrete_optimization.generic_tools.toulbar_tools import ToulbarSolver
from discrete_optimization.tsp.mutation import find_intersection
from discrete_optimization.tsp.problem import TspProblem, TspSolution
from discrete_optimization.tsp.solvers.tsp_solver import TspProblem, TspSolver
from discrete_optimization.tsp.utils import build_matrice_distance

logger = logging.getLogger(__name__)
try:
import pytoulbar2

toulbar_available = True
except ImportError as e:
toulbar_available = False


class ToulbarTspSolver(ToulbarSolver, TspSolver, WarmstartMixin):
hyperparameters = ToulbarSolver.hyperparameters + [
CategoricalHyperparameter(
name="encoding_all_diff",
choices=["binary", "salldiff", "salldiffdp", "salldiffkp", "walldiff"],
default="salldiffkp",
)
]

def set_warm_start(self, solution: TspSolution) -> None:
indexes = self.problem.original_indices_to_permutation_indices_dict
for i in range(len(solution.permutation)):
self.model.CFN.wcsp.setBestValue(i, indexes[solution.permutation[i]])

def __init__(self, problem: TspProblem, **kwargs: Any):
super().__init__(problem, **kwargs)
self.distance_matrix = build_matrice_distance(
self.problem.node_count,
method=self.problem.evaluate_function_indexes,
)
self.distance_matrix[self.problem.end_index, self.problem.start_index] = 0

def init_model(self, **kwargs: Any) -> None:
kwargs = self.complete_with_default_hyperparameters(kwargs)
model = pytoulbar2.CFN(vns=kwargs.get("vns", None))
nb_nodes_variable = len(self.problem.original_indices_to_permutation_indices)
indexes = self.problem.original_indices_to_permutation_indices_dict
rev_indexes = {indexes[i]: i for i in indexes}
for i in range(nb_nodes_variable):
model.AddVariable(f"perm_{i}", values=range(nb_nodes_variable))
logger.debug("Starting set all diff")
model.AddAllDifferent(
scope=range(nb_nodes_variable),
encoding=kwargs["encoding_all_diff"],
incremental=False,
)
logger.debug("set all diff done")
model.AddFunction(
[f"perm_0"],
[
self.distance_matrix[self.problem.start_index, rev_indexes[i]]
for i in range(nb_nodes_variable)
],
)

model.AddFunction(
[f"perm_{nb_nodes_variable-1}"],
[
self.distance_matrix[rev_indexes[i], self.problem.end_index]
for i in range(nb_nodes_variable)
],
)
for i in range(nb_nodes_variable - 1):
model.AddFunction(
[f"perm_{i}", f"perm_{i+1}"],
[
self.distance_matrix[rev_indexes[ii], rev_indexes[jj]]
for ii in range(nb_nodes_variable)
for jj in range(nb_nodes_variable)
],
)
self.model = model
self.rev_indexes = rev_indexes
logger.debug("Init done")

def retrieve_solution(
self, solution_from_toulbar2: tuple[list, float, int]
) -> TspSolution:
return TspSolution(
problem=self.problem,
start_index=self.problem.start_index,
end_index=self.problem.end_index,
permutation=[self.rev_indexes[x] for x in solution_from_toulbar2[0]],
)


class TspConstraintHandlerToulbar(ConstraintHandler):
def __init__(self, fraction_nodes: float = 0.5):
self.fraction_nodes = fraction_nodes

def adding_constraint_from_results_store(
self, solver: ToulbarTspSolver, result_storage: ResultStorage, **kwargs: Any
) -> Iterable[Any]:
sol: TspSolution = result_storage.get_best_solution_fit()[0]

list_ = find_intersection(
variable=sol, points=solver.problem.list_points, nb_tests=1000
)
nb_nodes = len(sol.permutation)
subset = random.sample(range(nb_nodes), k=int(self.fraction_nodes * nb_nodes))
if len(list_) > 0:
subset = [i for i in subset if i not in [list_[0][0], list_[0][1]]]
solver.model.CFN.timer(100)
indexes = solver.problem.original_indices_to_permutation_indices_dict
text = ",".join(f"{i}={indexes[sol.permutation[i]]}" for i in subset)
text = "," + text
solver.model.Parse(text)
solver.set_warm_start(sol)

def remove_constraints_from_previous_iteration(
self, solver: SolverDO, previous_constraints: Iterable[Any], **kwargs: Any
) -> None:
pass
63 changes: 62 additions & 1 deletion examples/coloring/run_toulbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,18 @@
ToulbarColoringSolverForLns,
)
from discrete_optimization.generic_tools.callbacks.early_stoppers import TimerStopper
from discrete_optimization.generic_tools.lns_tools import (
BaseLns,
InitialSolutionFromSolver,
TrivialInitialSolution,
)
from discrete_optimization.generic_tools.result_storage.result_storage import (
from_solutions_to_result_storage,
)
from discrete_optimization.generic_tools.toulbar_tools import to_lns_toulbar

logging.basicConfig(level=logging.INFO)


def run_toulbar_coloring():
logging.basicConfig(level=logging.INFO)
Expand Down Expand Up @@ -192,5 +202,56 @@ def run_toulbar_with_constraints():
plt.show()


def run_optuna_study():
from discrete_optimization.generic_tools.optuna.utils import (
generic_optuna_experiment_monoproblem,
)

files_available = get_data_available()
file = [f for f in get_data_available() if "gc_250_9" in f][0]
color_problem = parse_file(file)
solvers_to_test = [ToulbarColoringSolver, BaseLns]
copy = ToulbarColoringSolver.hyperparameters
ToulbarColoringSolver.hyperparameters = (
ToulbarColoringSolver.copy_and_update_hyperparameters(
["vns", "value_sequence_chain", "greedy_start"],
**{
"vns": {"choices": [None, -4]},
"value_sequence_chain": {"choices": [False]},
"greedy_start": {"choices": [True]},
},
)
)
ToulbarColoringSolver.hyperparameters += [
c for c in copy if c.name not in ["vns", "value_sequence_chain", "greedy_start"]
]
generic_optuna_experiment_monoproblem(
problem=color_problem,
study_basename="study-toulbar",
# storage_path="./optuna-journal-toulbar.log",
solvers_to_test=solvers_to_test,
overwrite_study=True,
kwargs_fixed_by_solver={
ToulbarColoringSolver: {"time_limit": 50, "greedy_start": True},
BaseLns: {
"callbacks": [TimerStopper(total_seconds=50)],
"constraint_handler": ColoringConstraintHandlerToulbar(
fraction_node=0.83
),
"post_process_solution": None,
"initial_solution_provider": InitialSolutionFromSolver(
solver=GreedyColoringSolver(problem=color_problem),
strategy=NxGreedyColoringMethod.best,
),
"nb_iteration_lns": 1000,
"time_limit_subsolver": 5,
},
},
suggest_optuna_kwargs_by_name_by_solver={
BaseLns: {"subsolver": {"choices": [to_lns_toulbar(ToulbarColoringSolver)]}}
},
)


if __name__ == "__main__":
run_toulbar_with_do_lns()
run_optuna_study()
47 changes: 47 additions & 0 deletions examples/knapsack/run_toulbar.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Copyright (c) 2024 AIRBUS and its affiliates.
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

import logging

from discrete_optimization.generic_tools.callbacks.early_stoppers import (
NbIterationStopper,
)
from discrete_optimization.knapsack.parser import get_data_available, parse_file
from discrete_optimization.knapsack.solvers.toulbar import ToulbarKnapsackSolver


def run_toulbar():
logging.basicConfig(level=logging.INFO)
file = [f for f in get_data_available() if "ks_10000_0" in f][0]
knapsack_problem = parse_file(file)
solver = ToulbarKnapsackSolver(problem=knapsack_problem)
solver.init_model(vns=-4)
res = solver.solve(time_limit=100)
sol = res.get_best_solution()
print(knapsack_problem.satisfy(sol))
print(knapsack_problem.max_capacity)
print(sol, "\n", sol)


def run_toulbar_ws():
logging.basicConfig(level=logging.INFO)
file = [f for f in get_data_available() if "ks_500_0" in f][0]
knapsack_problem = parse_file(file)
from discrete_optimization.knapsack.solvers.greedy import GreedyBestKnapsackSolver

solver = GreedyBestKnapsackSolver(problem=knapsack_problem)
sol, fit = solver.solve().get_best_solution_fit()
print(fit, " current sol")
solver = ToulbarKnapsackSolver(problem=knapsack_problem)
solver.init_model(vns=-4)
solver.set_warm_start(solution=sol)
res = solver.solve(time_limit=100)
sol = res.get_best_solution()
print(knapsack_problem.satisfy(sol))
print(knapsack_problem.max_capacity)
print(sol, "\n", sol)


if __name__ == "__main__":
run_toulbar_ws()
Loading

0 comments on commit cfe0e63

Please sign in to comment.