diff --git a/code/python/src/algorithms/rucksack/algorithms.py b/code/python/src/algorithms/rucksack/algorithms.py index 4408064..5074925 100644 --- a/code/python/src/algorithms/rucksack/algorithms.py +++ b/code/python/src/algorithms/rucksack/algorithms.py @@ -57,25 +57,25 @@ def rucksack_greedy_algorithm( # führe greedy aus: n = len(costs); cost_total = 0; - vector = [ Fraction(0) for _ in range(n) ]; + choice = [ Fraction(0) for _ in range(n) ]; for i in order: # füge Item i hinzu, solange das Gesamtgewicht noch <= Schranke if cost_total + costs[i] <= max_cost: cost_total += costs[i]; - vector[i] = Fraction(1); + choice[i] = Fraction(1); # falls Bruchteile erlaubt sind, füge einen Bruchteil des i. Items hinzu und abbrechen elif fractional: - vector[i] = Fraction(Fraction(max_cost - cost_total)/Fraction(costs[i]), _normalize=False); + choice[i] = Fraction(Fraction(max_cost - cost_total)/Fraction(costs[i]), _normalize=False); break; # ansonsten weiter machen: else: continue; # Aspekte der Lösung speichern: - rucksack = [i for i, v in enumerate(vector) if v > 0]; # Indexes von Items im Rucksack + rucksack = [i for i, v in enumerate(choice) if v > 0]; # Indexes von Items im Rucksack soln = Solution( order = order, - choice = vector, + choice = choice, items = items[rucksack].tolist(), costs = costs[rucksack].tolist(), values = values[rucksack].tolist(), @@ -83,7 +83,7 @@ def rucksack_greedy_algorithm( # verbose output hier behandeln (irrelevant für Algorithmus): if verbose: - repr_rucksack = display_rucksack(items=items[rucksack], costs=costs[rucksack], values=values[rucksack]); + repr_rucksack = display_rucksack(items=items, costs=costs, values=values, choice=choice); print('\x1b[1mEingeschätzte Lösung\x1b[0m'); print(''); print(f'Mask: [{", ".join(map(str, soln.choice))}]'); @@ -122,24 +122,31 @@ def rucksack_branch_and_bound_algorithm( print(''); logged_steps = []; - vector = empty_mask(n=len(costs)); - lb_estimate = np.inf; + step: Step; + mask = empty_mask(n=len(costs)); + bound = np.inf; S = Stack(); - S.push(vector); + S.push(mask); while not S.empty(): - lb, choice, order_, pad = estimate_lower_bound(mask=S.top(), max_cost=max_cost, costs=costs, values=values, items=items); + # top-Element auslesen und Bound berechnen: + A: Mask = S.top(); + bound_subtree, choice, order_, pad = estimate_lower_bound(mask=A, max_cost=max_cost, costs=costs, values=values, items=items); + # für logging: if verbose: - logged_steps.append((lb_estimate, lb, str(S), choice, order_, pad)); + step = Step(bound=bound, bound_subtree=bound_subtree, stack_str=str(S), choice=choice, order=order_, indexes=A.indexes_unset, pad=pad); + S.pop(); # Update nur nötig, wenn die (eingeschätzte) untere Schranke von A das bisherige Minimum verbessert: - A: Mask = S.pop(); - if lb < lb_estimate: - # Bound, wenn sich A nicht weiter aufteilen lässt od. man A wie eine einelementige Option behandeln kann: + if bound_subtree < bound: + # Bound aktualisieren, wenn sich A nicht weiter aufteilen od. wenn sich A wie eine einelementige Option behandeln läst: if not A.splittable() or pad != MaskValue.UNSET: - lb_estimate = lb; + bound = bound_subtree; # falls A als einelementige Menge betrachtet werden kann, ersetze unbekannte Werte: if pad != MaskValue.UNSET: A = A.pad(pad); - vector = A; + mask = A; + # für logging: + if verbose: + step.move = EnumBranchAndBoundMove.BOUND; # Branch sonst else: B, C = A.split(); @@ -147,12 +154,17 @@ def rucksack_branch_and_bound_algorithm( # Nur dann C auf Stack legen, wenn mind. eine Möglichkeit in C die Kapazitätsschranke erfüllt: if sum(costs[C.indexes_one]) <= max_cost: S.push(C); + # für logging: + if verbose: + step.move = EnumBranchAndBoundMove.BRANCH; + if verbose: + logged_steps.append(step); # Aspekte der Lösung speichern - rucksack = vector.indexes_one; # Indexes von Items im Rucksack + rucksack = mask.indexes_one; # Indexes von Items im Rucksack soln = Solution( order = order, - choice = vector.choice, + choice = mask.choice, items = items[rucksack].tolist(), values = values[rucksack].tolist(), costs = costs[rucksack].tolist(), @@ -161,11 +173,11 @@ def rucksack_branch_and_bound_algorithm( # verbose output hier behandeln (irrelevant für Algorithmus): if verbose: repr = display_branch_and_bound(values=values, steps=logged_steps); - repr_rucksack = display_rucksack(items=items[rucksack], costs=costs[rucksack], values=values[rucksack]); - print('\x1b[1mLösung\x1b[0m'); - print(''); + repr_rucksack = display_rucksack(items=items, costs=costs, values=values, choice=mask.choice); print(repr); print(''); + print('\x1b[1mLösung\x1b[0m'); + print(''); print(f'Mask: [{", ".join(map(str, soln.choice))}]'); print('Rucksack:'); print(repr_rucksack); @@ -201,7 +213,7 @@ def estimate_lower_bound( mit Greedy-Algorithmus »lösen«, um schnell eine gute Einschätzung zu bestimmen. - NOTE: Diese Funktion wird `g(vector)` im Skript bezeichnet. + NOTE: Diese Funktion wird `g(mask)` im Skript bezeichnet. ''' indexes_one = mask.indexes_one; indexes_unset = mask.indexes_unset; diff --git a/code/python/src/algorithms/rucksack/display.py b/code/python/src/algorithms/rucksack/display.py index 62a942f..7549893 100644 --- a/code/python/src/algorithms/rucksack/display.py +++ b/code/python/src/algorithms/rucksack/display.py @@ -9,6 +9,8 @@ from src.thirdparty.code import *; from src.thirdparty.maths import *; from src.thirdparty.types import *; +from src.setup import config; +from models.generated.config import *; from src.models.stacks import *; from src.models.rucksack import *; @@ -62,18 +64,31 @@ def display_rucksack( items: np.ndarray, costs: np.ndarray, values: np.ndarray, + choice: List[Fraction], ) -> str: + show_options = config.OPTIONS.rucksack.show; render = lambda r: f'{r:g}'; + choice = np.asarray(choice); + rucksack = np.where(choice > 0); + if not(EnumRucksackShow.all_weights in show_options): + items = items[rucksack]; + costs = costs[rucksack]; + values = values[rucksack]; + choice = choice[rucksack]; table = pd.DataFrame({ 'items': items.tolist() + ['----', '∑'], - 'costs': list(map(render, costs)) + ['', f'\x1b[92;1m{sum(costs):g}\x1b[0m'], - 'values': list(map(render, values)) + ['', f'\x1b[92;1m{sum(values):g}\x1b[0m'], + 'nr': list(map(str, choice)) + + ['----', f'\x1b[92;1m{float(sum(choice)):g}\x1b[0m'], + 'costs': list(map(render, costs)) + + ['----', f'\x1b[92;1m{sum(choice*costs):g}\x1b[0m'], + 'values': list(map(render, values)) + + ['----', f'\x1b[92;1m{sum(choice*values):g}\x1b[0m'], }); repr = tabulate( table, - headers=['item', 'cost', 'value'], + headers=['item', 'nr', 'cost', 'value'], showindex=False, - colalign=('left', 'center', 'center'), + colalign=('left', 'center', 'center', 'center'), tablefmt='rst' ); return repr; @@ -82,30 +97,39 @@ def display_rucksack( # METHOD display result of branch and bound # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -def display_branch_and_bound( - values: np.ndarray, - steps: List[Tuple[float, float, Stack, List[Fraction], List[int], MaskValue]], -) -> str: - # füge Summen-Ausdrücke für Greedy-Alg hinzu: +def display_branch_and_bound(values: np.ndarray, steps: List[Step]) -> str: + show_options = config.OPTIONS.rucksack.show; + show_all_sums = (EnumRucksackShow.all_sums in show_options); + rows = []; used_choices = []; - for lb_estimate, lb, S, choice, order, pad in steps: - if choice in used_choices: - expr = f'{lb:g}'; + index_soln = max([-1] + [ i for i, step in enumerate(steps) if step.move == EnumBranchAndBoundMove.BOUND ]); + for i, step in enumerate(steps): + if show_all_sums or step.choice not in used_choices: + # Füge Summen-Ausdrücke für Greedy-Alg hinzu: + used_choices.append(step.choice); + expr = display_sum(choice=step.choice, values=values, as_maximum=False, order=step.order, indexes=step.indexes); else: - used_choices.append(choice); - expr = display_sum(choice=choice, values=values, as_maximum=False, order=order); - rows.append((f'{lb_estimate:g}', expr, ('' if pad == MaskValue.UNSET else pad.value), S)); + expr = f'{step.bound_subtree:g}'; + pad_str = ('' if step.pad == MaskValue.UNSET else step.pad.value); + move_str = ('' if step.move == EnumBranchAndBoundMove.NONE else step.move.value); + if i == index_soln: + move_str = f'{move_str} *'; + rows.append({ + 'bound': f'{step.bound:+g}', + 'bound_subtree': expr, + 'stack': step.stack_str, + 'pad': f'\x1b[2m{pad_str}\x1b[0m', + 'move': f'\x1b[2m{move_str}\x1b[0m', + }); - table = pd.DataFrame(rows) \ - .rename(columns={0: 'b', 1: 'g(TOP(S))', 2: 'pad?', 3: 'S'}) \ - .reset_index(drop=True); + table = pd.DataFrame(rows).reset_index(drop=True); # benutze pandas-Dataframe + tabulate, um schöner darzustellen: repr = tabulate( table, - headers=['b', 'g(TOP(S))', 'pad?', 'S'], + headers=['bound', 'g(TOP(S))', 'S — stack', '\x1b[2mpad?\x1b[0m', '\x1b[2mmove\x1b[0m'], showindex=False, - colalign=('left', 'left', 'center', 'right'), + colalign=('left', 'left', 'right', 'center', 'left'), tablefmt='rst' ); return repr; @@ -118,17 +142,31 @@ def display_sum( choice: List[Fraction], values: np.ndarray, order: Optional[List[int]] = None, + indexes: List[int] = [], as_maximum: bool = True, ) -> str: - parts = [ (u, x) for u, x in zip(choice, values)]; + show_options = config.OPTIONS.rucksack.show; + show_all_weights = (EnumRucksackShow.all_weights in show_options); + + def render(x: Tuple[bool, Fraction, float]): + b, u, value = x; + if u == 0: + expr = f'\x1b[94;2m{value:g}\x1b[0m' if b else f'\x1b[2m{value:g}\x1b[0m'; + else: + expr = f'\x1b[94m{value:g}\x1b[0m' if b else f'\x1b[0m{value:g}\x1b[0m'; + if not show_all_weights and u == 1: + return expr; + return f'\x1b[2;4m{u}\x1b[0m\x1b[2m·\x1b[0m{expr}'; + + parts = [ (i in indexes, u, x) for i, (u, x) in enumerate(zip(choice, values)) ]; if not (order is None): parts = [ parts[j] for j in order ]; - value = sum([ u*x for u, x in parts]); - expr = '+'.join([ - f'{x:g}' if u == 1 else f'{u}·{x:g}' - for u, x in parts if u > 0 - ]); + if not show_all_weights: + parts = list(filter(lambda x: x[1] > 0, parts)); + + value = sum([ u*x for _, u, x in parts ]); + expr = '\x1b[2m+\x1b[0m'.join(map(render, parts)); + if as_maximum: - return f'{value:g} = {expr}'; - else: - return f'-{value:g} = -({expr})'; + return f'{value:g} \x1b[2m=\x1b[0m {expr}'; + return f'-{value:g} \x1b[2m= -(\x1b[0m{expr}\x1b[2m)\x1b[0m'; diff --git a/code/python/src/models/rucksack/__init__.py b/code/python/src/models/rucksack/__init__.py index f5395c7..0e4bdac 100644 --- a/code/python/src/models/rucksack/__init__.py +++ b/code/python/src/models/rucksack/__init__.py @@ -7,6 +7,7 @@ from src.models.rucksack.mask import *; from src.models.rucksack.solution import *; +from src.models.rucksack.logging import *; # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # EXPORTS @@ -17,4 +18,6 @@ __all__ = [ 'MaskValue', 'Mask', 'Solution', + 'EnumBranchAndBoundMove', + 'Step', ]; diff --git a/code/python/src/models/rucksack/logging.py b/code/python/src/models/rucksack/logging.py new file mode 100644 index 0000000..187500d --- /dev/null +++ b/code/python/src/models/rucksack/logging.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# IMPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +from __future__ import annotations; + +from src.thirdparty.maths import *; +from src.thirdparty.types import *; + +from src.models.rucksack.mask import *; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# EXPORTS +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +__all__ = [ + 'EnumBranchAndBoundMove', + 'Step', +]; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# CLASS Move +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +class EnumBranchAndBoundMove(Enum): + NONE = -1; + BOUND = 'bound'; + BRANCH = 'branch'; + +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# CLASS Step +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +@dataclass +class Step(): + bound: float = field(); + bound_subtree: float = field(); + stack_str: str = field(); + choice: List[Fraction] = field(); + order: List[int] = field(); + # the indexes upon which the greedy algorithm is carried out: + indexes: List[int] = field(); + pad: MaskValue = field(); + move: EnumBranchAndBoundMove = field(default=EnumBranchAndBoundMove.NONE);