Skip to content

Commit

Permalink
Fix issues with stricter myp for knapsack
Browse files Browse the repository at this point in the history
  • Loading branch information
nhuet authored and g-poveda committed Jan 23, 2023
1 parent 3c905f6 commit 844976f
Show file tree
Hide file tree
Showing 15 changed files with 277 additions and 196 deletions.
91 changes: 55 additions & 36 deletions discrete_optimization/knapsack/knapsack_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from copy import deepcopy
from dataclasses import dataclass
from typing import Dict, List, Optional, Sequence, Type, cast
from typing import Any, Dict, List, Optional, Sequence, Type, Union, cast

import numpy as np

Expand All @@ -29,7 +29,7 @@ class Item:
value: float
weight: float

def __str__(self):
def __str__(self) -> str:
return (
"ind: "
+ str(self.index)
Expand All @@ -53,39 +53,41 @@ def __init__(
self.weight = weight
self.list_taken = list_taken

def copy(self):
def copy(self) -> "KnapsackSolution":
return KnapsackSolution(
problem=self.problem,
value=self.value,
weight=self.weight,
list_taken=list(self.list_taken),
)

def lazy_copy(self):
def lazy_copy(self) -> "KnapsackSolution":
return KnapsackSolution(
problem=self.problem,
value=self.value,
weight=self.weight,
list_taken=self.list_taken,
)

def change_problem(self, new_problem: Problem):
def change_problem(self, new_problem: Problem) -> None:
if not isinstance(new_problem, KnapsackModel):
raise ValueError("new_problem must a KnapsackModel for a KnapsackSolution.")
self.problem = new_problem
self.list_taken = list(self.list_taken)

def __str__(self):
def __str__(self) -> str:
s = "Value=" + str(self.value) + "\n"
s += "Weight=" + str(self.weight) + "\n"
s += "Taken : " + str(self.list_taken)
return s

def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

def __eq__(self, other):
return self.list_taken == other.list_taken
def __eq__(self, other: object) -> bool:
return (
isinstance(other, KnapsackSolution) and self.list_taken == other.list_taken
)


class KnapsackModel(Problem):
Expand Down Expand Up @@ -178,7 +180,7 @@ def satisfy(self, knapsack_solution: KnapsackSolution) -> bool: # type: ignore
self.evaluate(knapsack_solution)
return knapsack_solution.weight <= self.max_capacity # type: ignore # avoid is None check for efficiency

def __str__(self):
def __str__(self) -> str:
s = (
"Knapsack model with "
+ str(self.nb_items)
Expand All @@ -200,7 +202,7 @@ def get_solution_type(self) -> Type[Solution]:

class KnapsackModel_Mobj(KnapsackModel):
@staticmethod
def from_knapsack(knapsack_model: KnapsackModel):
def from_knapsack(knapsack_model: KnapsackModel) -> "KnapsackModel_Mobj":
return KnapsackModel_Mobj(
list_items=knapsack_model.list_items,
max_capacity=knapsack_model.max_capacity,
Expand Down Expand Up @@ -245,51 +247,58 @@ def evaluate_mobj(self, solution: KnapsackSolution) -> TupleFitness: # type: ig


class KnapsackSolutionMultidimensional(Solution):
value: float
weights: List[float]
list_taken: List[bool]

def __init__(self, problem, list_taken, value=None, weights=None):
def __init__(
self,
problem: Union[
"MultidimensionalKnapsack", "MultiScenarioMultidimensionalKnapsack"
],
list_taken: List[int],
value: Optional[float] = None,
weights: Optional[List[float]] = None,
):
self.problem = problem
self.value = value
self.weights = weights
self.list_taken = list_taken

def copy(self):
def copy(self) -> "KnapsackSolutionMultidimensional":
return KnapsackSolutionMultidimensional(
problem=self.problem,
value=self.value,
weights=self.weights,
list_taken=list(self.list_taken),
)

def lazy_copy(self):
def lazy_copy(self) -> "KnapsackSolutionMultidimensional":
return KnapsackSolutionMultidimensional(
problem=self.problem,
value=self.value,
weights=self.weights,
list_taken=self.list_taken,
)

def change_problem(self, new_problem):
self.__init__(
problem=new_problem,
value=self.value,
weights=self.weights,
list_taken=list(self.list_taken),
)
def change_problem(self, new_problem: Problem) -> None:
if not isinstance(new_problem, MultidimensionalKnapsack):
raise ValueError(
"new_problem must a MultidimensionalKnapsack for a KnapsackSolutionMultidimensional."
)
self.problem = new_problem
self.list_taken = list(self.list_taken)

def __str__(self):
def __str__(self) -> str:
s = "Value=" + str(self.value) + "\n"
s += "Weights=" + str(self.weights) + "\n"
s += "Taken : " + str(self.list_taken)
return s

def __hash__(self):
def __hash__(self) -> int:
return hash(str(self))

def __eq__(self, other):
return self.list_taken == other.list_taken
def __eq__(self, other: object) -> bool:
return (
isinstance(other, KnapsackSolutionMultidimensional)
and self.list_taken == other.list_taken
)


@dataclass(frozen=True)
Expand All @@ -298,7 +307,7 @@ class ItemMultidimensional:
value: float
weights: List[float]

def __str__(self):
def __str__(self) -> str:
return (
"ind: "
+ str(self.index)
Expand Down Expand Up @@ -351,14 +360,16 @@ def get_objective_register(self) -> ObjectiveRegister:
dict_objective_to_doc=dict_objective,
)

def evaluate_from_encoding(self, int_vector, encoding_name) -> Dict[str, float]:
def evaluate_from_encoding(
self, int_vector: List[int], encoding_name: str
) -> Dict[str, float]:
if encoding_name == "list_taken":
kp_sol = KnapsackSolutionMultidimensional(
problem=self, list_taken=int_vector
)
elif encoding_name == "custom":
kwargs = {encoding_name: int_vector, "problem": self}
kp_sol = KnapsackSolutionMultidimensional(**kwargs)
kwargs: Dict[str, Any] = {encoding_name: int_vector}
kp_sol = KnapsackSolutionMultidimensional(problem=self, **kwargs)
else:
raise NotImplementedError("encoding_name must be 'list_taken' or 'custom'")
objectives = self.evaluate(kp_sol)
Expand Down Expand Up @@ -397,6 +408,10 @@ def evaluate_value(
def evaluate_weight_violation(
self, knapsack_solution: KnapsackSolutionMultidimensional
) -> float:
if knapsack_solution.weights is None:
raise RuntimeError(
"knapsack_solution.weights should not be None when calling evaluate_weight_violation."
)
return sum(
[
max(0.0, knapsack_solution.weights[j] - self.max_capacities[j])
Expand All @@ -405,14 +420,18 @@ def evaluate_weight_violation(
)

def satisfy(self, knapsack_solution: KnapsackSolutionMultidimensional) -> bool: # type: ignore # avoid isinstance checks for efficiency
if knapsack_solution.value is None:
if knapsack_solution.value is None or knapsack_solution.weights is None:
self.evaluate(knapsack_solution)
if knapsack_solution.value is None or knapsack_solution.weights is None:
raise RuntimeError(
"knapsack_solution.value and knapsack_solution.weights should not be None now."
)
return all(
knapsack_solution.weights[j] <= self.max_capacities[j]
for j in range(len(self.max_capacities))
)

def __str__(self):
def __str__(self) -> str:
s = (
"Knapsack model with "
+ str(self.nb_items)
Expand All @@ -433,7 +452,7 @@ def get_dummy_solution(self) -> KnapsackSolutionMultidimensional:
def get_solution_type(self) -> Type[Solution]:
return KnapsackSolutionMultidimensional

def copy(self):
def copy(self) -> "MultidimensionalKnapsack":
return MultidimensionalKnapsack(
list_items=[deepcopy(x) for x in self.list_items],
max_capacities=list(self.max_capacities),
Expand Down
10 changes: 6 additions & 4 deletions discrete_optimization/knapsack/knapsack_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
# LICENSE file in the root directory of this source tree.

import os
from typing import Optional
from typing import List, Optional

from discrete_optimization.datasets import get_data_home
from discrete_optimization.knapsack.knapsack_model import Item, KnapsackModel


def get_data_available(
data_folder: Optional[str] = None, data_home: Optional[str] = None
):
) -> List[str]:
"""Get datasets available for knapsack.
Params:
Expand All @@ -35,7 +35,9 @@ def get_data_available(
return datasets


def parse_input_data(input_data, force_recompute_values: bool = False) -> KnapsackModel:
def parse_input_data(
input_data: str, force_recompute_values: bool = False
) -> KnapsackModel:
"""
Parse a string of the following form :
item_count max_capacity
Expand All @@ -59,7 +61,7 @@ def parse_input_data(input_data, force_recompute_values: bool = False) -> Knapsa
)


def parse_file(file_path, force_recompute_values=False) -> KnapsackModel:
def parse_file(file_path: str, force_recompute_values: bool = False) -> KnapsackModel:
with open(file_path, "r", encoding="utf-8") as input_data_file:
input_data = input_data_file.read()
knapsack_model = parse_input_data(
Expand Down
19 changes: 14 additions & 5 deletions discrete_optimization/knapsack/knapsack_solvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

from typing import Any, Dict, List, Tuple, Type

from discrete_optimization.generic_tools.do_problem import Problem
from discrete_optimization.generic_tools.lp_tools import ParametersMilp
from discrete_optimization.generic_tools.result_storage.result_storage import (
ResultStorage,
)
from discrete_optimization.knapsack.knapsack_model import KnapsackModel
from discrete_optimization.knapsack.solvers.cp_solvers import (
CPKnapsackMZN,
Expand All @@ -13,6 +17,7 @@
)
from discrete_optimization.knapsack.solvers.dyn_prog_knapsack import KnapsackDynProg
from discrete_optimization.knapsack.solvers.greedy_solvers import GreedyBest
from discrete_optimization.knapsack.solvers.knapsack_solver import SolverKnapsack
from discrete_optimization.knapsack.solvers.lp_solvers import (
KnapsackORTools,
LPKnapsack,
Expand All @@ -21,7 +26,7 @@
MilpSolverName,
)

solvers: Dict[str, List[Tuple[Type, Dict[str, Any]]]] = {
solvers: Dict[str, List[Tuple[Type[SolverKnapsack], Dict[str, Any]]]] = {
"lp": [
(KnapsackORTools, {}),
(LPKnapsackCBC, {}),
Expand Down Expand Up @@ -57,26 +62,30 @@
for solver, param in solvers[key]:
solvers_map[solver] = (key, param)

solvers_compatibility = {}
solvers_compatibility: Dict[Type[SolverKnapsack], List[Type[Problem]]] = {}
for x in solvers:
for y in solvers[x]:
solvers_compatibility[y[0]] = [KnapsackModel]


def look_for_solver(domain):
def look_for_solver(domain: KnapsackModel) -> List[Type[SolverKnapsack]]:
class_domain = domain.__class__
return look_for_solver_class(class_domain)


def look_for_solver_class(class_domain):
def look_for_solver_class(
class_domain: Type[KnapsackModel],
) -> List[Type[SolverKnapsack]]:
available = []
for solver in solvers_compatibility:
if class_domain in solvers_compatibility[solver]:
available += [solver]
return available


def solve(method, knapsack_model: KnapsackModel, **args):
def solve(
method: Type[SolverKnapsack], knapsack_model: KnapsackModel, **args: Any
) -> ResultStorage:
solver = method(knapsack_model)
solver.init_model(**args)
return solver.solve(**args)
4 changes: 2 additions & 2 deletions discrete_optimization/knapsack/mutation/mutation_knapsack.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def __init__(self, knapsack_model: KnapsackModel, attribute: Optional[str] = Non
self.attribute = attribute

def switch_on(
self, variable: KnapsackSolution, come_from_outside=False
self, variable: KnapsackSolution, come_from_outside: bool = False
) -> Tuple[KnapsackSolution, LocalMove, Dict[str, float]]:
if variable.weight is None or variable.value is None:
raise RuntimeError(
Expand Down Expand Up @@ -182,7 +182,7 @@ def switch_on(
return self.switch_off(variable, True)

def switch_off(
self, variable: KnapsackSolution, come_from_outside=False
self, variable: KnapsackSolution, come_from_outside: bool = False
) -> Tuple[KnapsackSolution, LocalMove, Dict[str, float]]:
if variable.weight is None or variable.value is None:
raise RuntimeError(
Expand Down
Loading

0 comments on commit 844976f

Please sign in to comment.