# Copyright (c) 2023 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 os
from typing import Any, Optional
import clingo
from discrete_optimization.coloring.problem import ColoringSolution
from discrete_optimization.coloring.solvers.starting_solution import (
WithStartingSolutionColoringSolver,
)
from discrete_optimization.generic_tools.asp_tools import AspClingoSolver
cur_folder = os.path.abspath(os.path.dirname(__file__))
logger = logging.getLogger(__name__)
[docs]
class AspColoringSolver(AspClingoSolver, WithStartingSolutionColoringSolver):
"""Solver based on Answer Set Programming formulation and clingo solver."""
hyperparameters = WithStartingSolutionColoringSolver.hyperparameters
[docs]
def retrieve_solution(self, model: clingo.Model) -> ColoringSolution:
symbols = model.symbols(atoms=True)
colors = [
(s.arguments[0].number, s.arguments[1].name)
for s in symbols
if s.name == "color"
]
colors_name = list(set([s[1] for s in colors]))
colors_to_index = self.compute_clever_colors_map(colors_name)
colors_vect = [0] * self.problem.number_of_nodes
for num, color in colors:
colors_vect[num - 1] = colors_to_index[color]
return ColoringSolution(problem=self.problem, colors=colors_vect)
[docs]
def init_model(self, **kwargs: Any) -> None:
if self.problem.use_subset:
self.init_model_with_subset(**kwargs)
else:
self.init_model_without_subset(**kwargs)
[docs]
def init_model_without_subset(self, **kwargs: Any) -> None:
basic_model = """
1 {color(X,C) : col(C)} 1 :- node(X).
:- edge(X,Y), color(X,C), color(Y,C).
color(X, C) :- fixed_color(X, C).
#show color/2.
ncolors(C) :- C = #count{Color: color(_,Color)}.
#minimize {C: ncolors(C)}.
"""
max_models = kwargs.get("max_models", 1)
nb_colors = kwargs.get("nb_colors", None)
if nb_colors is None:
solution = self.get_starting_solution(
params_objective_function=self.params_objective_function, **kwargs
)
nb_colors = solution.nb_color
self.ctl = clingo.Control(
["--warn=no-atom-undefined", f"--models={max_models}", "--opt-mode=optN"]
)
self.ctl.add("base", [], basic_model)
string_data_input = self.build_string_data_input(nb_colors=nb_colors)
self.ctl.add("base", [], string_data_input)
[docs]
def init_model_with_subset(self, **kwargs: Any) -> None:
basic_model = """
1 {color(X,C) : col(C)} 1 :- node(X).
:- edge(X,Y), color(X,C), color(Y,C).
ncolors(C) :- C = #count{Color : color(N, Color), subset_node(N)}.
color(X, C) :- fixed_color(X, C).
#show color/2.
#minimize {C: ncolors(C)}.
"""
# # TODO make this work : :- color(X, C), subset_node(X), col(C), C > MaxValue.
max_models = kwargs.get("max_models", 1)
nb_colors = kwargs.get("nb_colors", None)
nb_colors_subset = nb_colors
if nb_colors is None:
solution = self.get_starting_solution(
params_objective_function=self.params_objective_function, **kwargs
)
nb_colors = self.problem.count_colors_all_index(solution.colors)
nb_colors_subset = self.problem.count_colors(solution.colors)
self.ctl = clingo.Control(
["--warn=no-atom-undefined", f"--models={max_models}", "--opt-mode=optN"]
)
self.ctl.add("base", [], basic_model)
string_data_input = self.build_string_data_input(
nb_colors=nb_colors, nb_colors_subset=nb_colors_subset
)
self.ctl.add("base", [], string_data_input)
[docs]
def compute_clever_colors_map(self, colors_name: list[str]):
colors_to_protect = set()
colors_to_index = {}
if self.problem.has_constraints_coloring:
colors_to_protect = set(
[
f"c_{x}"
for x in self.problem.constraints_coloring.color_constraint.values()
]
)
colors_to_index = {
f"c_{x}": x
for x in self.problem.constraints_coloring.color_constraint.values()
}
color_name = [
colors_name[j]
for j in range(len(colors_name))
if colors_name[j] not in colors_to_protect
]
value_name = [
j
for j in range(len(colors_name))
if j not in [v for v in colors_to_index.values()]
]
for c, val in zip(color_name, value_name):
colors_to_index[c] = val
return colors_to_index