master > master: code py - darstellung hervorhebung + moves

- Hervorhebung von Summanden, die vom Greedyalgorithmus betroffen sind
- Moves Spalte hinzugefügt
- Summen + padding + moves jetzt abgedunkelt
This commit is contained in:
RD 2022-06-15 07:23:07 +02:00
parent 3a937e71d3
commit aa4aaf2fe6
4 changed files with 103 additions and 31 deletions

View File

@ -122,24 +122,31 @@ def rucksack_branch_and_bound_algorithm(
print('');
logged_steps = [];
step: Step;
mask = empty_mask(n=len(costs));
lb_estimate = np.inf;
bound = np.inf;
S = Stack();
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);
mask = A;
# für logging:
if verbose:
step.move = EnumBranchAndBoundMove.BOUND;
# Branch sonst
else:
B, C = A.split();
@ -147,6 +154,11 @@ 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 = mask.indexes_one; # Indexes von Items im Rucksack

View File

@ -87,30 +87,36 @@ 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:
def display_branch_and_bound(values: np.ndarray, steps: List[Step]) -> str:
# füge Summen-Ausdrücke für Greedy-Alg hinzu:
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 step.choice in used_choices:
expr = f'{step.bound_subtree:g}';
else:
used_choices.append(choice);
expr = display_sum(choice=choice, values=values, as_maximum=False, order=order);
rows.append((f'{lb_estimate:+g}', expr, S, ('' if pad == MaskValue.UNSET else pad.value)));
used_choices.append(step.choice);
expr = display_sum(choice=step.choice, values=values, as_maximum=False, order=step.order, indexes=step.indexes);
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: 'bound', 1: 'g(TOP(S))', 2: 'S', 3: 'pad?'}) \
.reset_index(drop=True);
table = pd.DataFrame(rows).reset_index(drop=True);
# benutze pandas-Dataframe + tabulate, um schöner darzustellen:
repr = tabulate(
table,
headers=['bound', 'g(TOP(S))', 'S — stack', 'pad?'],
headers=['bound', 'g(TOP(S))', 'S — stack', '\x1b[2mpad?\x1b[0m', '\x1b[2mmove\x1b[0m'],
showindex=False,
colalign=('left', 'left', 'right', 'center'),
colalign=('left', 'left', 'right', 'center', 'left'),
tablefmt='rst'
);
return repr;
@ -123,17 +129,21 @@ 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)];
def render(x: Tuple[bool, Fraction, float]):
b, u, value = x;
expr = f'\x1b[91m{value:g}\x1b[0m' if b else f'\x1b[2m{value:g}\x1b[0m';
return expr if u == 1 else f'\x1b[4;2m{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
]);
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';

View File

@ -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',
];

View File

@ -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);