Compare commits

..

No commits in common. "92f038d4dd67eb41283afc738edd9ff4ab05f498" and "3b8f80cff92a1797ea1f3cd49e3fb285718b1ccb" have entirely different histories.

6 changed files with 70 additions and 97 deletions

View File

@ -72,7 +72,7 @@
once: false once: false
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Beispiele für Vorlesung + Seminarwoche 11 (VL + Blatt 10) # Beispiele für Seminarwoche 11 (Blatt 10)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- &rucksack_1 - &rucksack_1
@ -89,20 +89,6 @@
allow-fractional: true allow-fractional: true
- <<: *rucksack_1 - <<: *rucksack_1
algorithm: BRANCH-AND-BOUND algorithm: BRANCH-AND-BOUND
- name: RUCKSACK
algorithm: BRANCH-AND-BOUND
max-cost: 460
items: [
'Lakritze',
'Esspapier',
'Gummibärchen',
'Schokolade',
'Apfelringe',
]
costs:
[220, 80, 140, 90, 100]
values:
[100, 10, 70, 80, 100]
- name: RUCKSACK - name: RUCKSACK
algorithm: BRANCH-AND-BOUND algorithm: BRANCH-AND-BOUND
max-cost: 90 max-cost: 90

View File

@ -57,15 +57,15 @@ def rucksack_greedy_algorithm(
# führe greedy aus: # führe greedy aus:
n = len(costs); n = len(costs);
cost_total = 0; cost_total = 0;
vector = [ Fraction(0) for _ in range(n) ]; vector = [ 0 for _ in range(n) ];
for i in order: for i in order:
# füge Item i hinzu, solange das Gesamtgewicht noch <= Schranke # füge Item i hinzu, solange das Gesamtgewicht noch <= Schranke
if cost_total + costs[i] <= max_cost: if cost_total + costs[i] <= max_cost:
cost_total += costs[i]; cost_total += costs[i];
vector[i] = Fraction(1); vector[i] = 1;
# falls Bruchteile erlaubt sind, füge einen Bruchteil des i. Items hinzu und abbrechen # falls Bruchteile erlaubt sind, füge einen Bruchteil des i. Items hinzu und abbrechen
elif fractional: elif fractional:
vector[i] = Fraction(Fraction(max_cost - cost_total)/Fraction(costs[i]), _normalize=False); vector[i] = (max_cost - cost_total)/costs[i];
break; break;
# ansonsten weiter machen: # ansonsten weiter machen:
else: else:
@ -74,8 +74,7 @@ def rucksack_greedy_algorithm(
# Aspekte der Lösung speichern: # 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(vector) if v > 0]; # Indexes von Items im Rucksack
soln = Solution( soln = Solution(
order = order, vector = vector,
choice = vector,
items = items[rucksack].tolist(), items = items[rucksack].tolist(),
costs = costs[rucksack].tolist(), costs = costs[rucksack].tolist(),
values = values[rucksack].tolist(), values = values[rucksack].tolist(),
@ -86,7 +85,7 @@ def rucksack_greedy_algorithm(
repr_rucksack = display_rucksack(items=items[rucksack], costs=costs[rucksack], values=values[rucksack]); repr_rucksack = display_rucksack(items=items[rucksack], costs=costs[rucksack], values=values[rucksack]);
print('\x1b[1mEingeschätzte Lösung\x1b[0m'); print('\x1b[1mEingeschätzte Lösung\x1b[0m');
print(''); print('');
print(f'Mask: [{", ".join(map(str, soln.choice))}]'); print(f'Mask: {soln.vector}');
print('Rucksack:') print('Rucksack:')
print(repr_rucksack); print(repr_rucksack);
print(''); print('');
@ -127,18 +126,20 @@ def rucksack_branch_and_bound_algorithm(
S = Stack(); S = Stack();
S.push(vector); S.push(vector);
while not S.empty(): while not S.empty():
lb, choice, order_, pad = estimate_lower_bound(mask=S.top(), max_cost=max_cost, costs=costs, values=values, items=items); lb, u, can_add_all, can_add_none = estimate_lower_bound(mask=S.top(), max_cost=max_cost, costs=costs, values=values, items=items);
if verbose: if verbose:
logged_steps.append((lb_estimate, lb, str(S), choice, order_, pad)); logged_steps.append((lb_estimate, lb, str(S), u, can_add_all, can_add_none));
# Update nur nötig, wenn die (eingeschätzte) untere Schranke von A das bisherige Minimum verbessert: # Update nur nötig, wenn die (eingeschätzte) untere Schranke von A das bisherige Minimum verbessert:
A: Mask = S.pop(); A: Mask = S.pop();
if lb < lb_estimate: if lb < lb_estimate:
# Bound, wenn sich A nicht weiter aufteilen lässt od. man A wie eine einelementige Option behandeln kann: # Bound, wenn sich A nicht weiter aufteilen lässt od. man A wie eine einelementige Option behandeln kann:
if not A.splittable() or pad != MaskValue.UNSET: if not A.splittable() or can_add_all or can_add_none:
lb_estimate = lb; lb_estimate = lb;
# falls A als einelementige Menge betrachtet werden kann, ersetze unbekannte Werte: if can_add_all:
if pad != MaskValue.UNSET: vector = A.pad_ones();
A = A.pad(pad); elif can_add_none:
vector = A.pad_zeros();
else:
vector = A; vector = A;
# Branch sonst # Branch sonst
else: else:
@ -151,8 +152,7 @@ def rucksack_branch_and_bound_algorithm(
# Aspekte der Lösung speichern # Aspekte der Lösung speichern
rucksack = vector.indexes_one; # Indexes von Items im Rucksack rucksack = vector.indexes_one; # Indexes von Items im Rucksack
soln = Solution( soln = Solution(
order = order, vector = vector.decision,
choice = vector.choice,
items = items[rucksack].tolist(), items = items[rucksack].tolist(),
values = values[rucksack].tolist(), values = values[rucksack].tolist(),
costs = costs[rucksack].tolist(), costs = costs[rucksack].tolist(),
@ -160,13 +160,13 @@ def rucksack_branch_and_bound_algorithm(
# verbose output hier behandeln (irrelevant für Algorithmus): # verbose output hier behandeln (irrelevant für Algorithmus):
if verbose: if verbose:
repr = display_branch_and_bound(values=values, steps=logged_steps); repr = display_branch_and_bound(values=values, steps=logged_steps, order=order);
repr_rucksack = display_rucksack(items=items[rucksack], costs=costs[rucksack], values=values[rucksack]); repr_rucksack = display_rucksack(items=items[rucksack], costs=costs[rucksack], values=values[rucksack]);
print('\x1b[1mLösung\x1b[0m'); print('\x1b[1mLösung\x1b[0m');
print(''); print('');
print(repr); print(repr);
print(''); print('');
print(f'Mask: [{", ".join(map(str, soln.choice))}]'); print(f'Mask: {soln.vector}');
print('Rucksack:'); print('Rucksack:');
print(repr_rucksack); print(repr_rucksack);
print(''); print('');
@ -184,8 +184,7 @@ def get_sort_order(costs: np.ndarray, values: np.ndarray) -> List[int]:
''' '''
n = len(costs); n = len(costs);
indexes = list(range(n)); indexes = list(range(n));
margin = [ value/cost for cost, value in zip(costs, values) ]; order = sorted(indexes, key=lambda i: -values[i]/costs[i]);
order = sorted(indexes, key=lambda i: -margin[i]);
return order; return order;
def estimate_lower_bound( def estimate_lower_bound(
@ -194,7 +193,7 @@ def estimate_lower_bound(
costs: np.ndarray, costs: np.ndarray,
values: np.ndarray, values: np.ndarray,
items: np.ndarray, items: np.ndarray,
) -> Tuple[float, List[Fraction], List[int], MaskValue]: ) -> Tuple[float, List[float], bool]:
''' '''
Wenn partielle Information über den Rucksack festgelegt ist, Wenn partielle Information über den Rucksack festgelegt ist,
kann man bei dem unbekannten Teil das Rucksack-Problem kann man bei dem unbekannten Teil das Rucksack-Problem
@ -206,25 +205,25 @@ def estimate_lower_bound(
indexes_one = mask.indexes_one; indexes_one = mask.indexes_one;
indexes_unset = mask.indexes_unset; indexes_unset = mask.indexes_unset;
n = len(mask); n = len(mask);
choice = np.zeros(shape=(n,), dtype=Fraction); vector = np.zeros(shape=(n,), dtype=float);
order = np.asarray(range(n));
# Berechnungen bei Items mit bekanntem Status in Rucksack: # Berechnungen bei Items mit bekanntem Status in Rucksack:
value_rucksack = sum(values[indexes_one]); value_rucksack = sum(values[indexes_one]);
cost_rucksack = sum(costs[indexes_one]); cost_rucksack = sum(costs[indexes_one]);
choice[indexes_one] = Fraction(1); vector[indexes_one] = 1;
# Für Rest des Rucksacks (Items mit unbekanntem Status): # Für Rest des Rucksacks (Items mit unbekanntem Status):
cost_rest = max_cost - cost_rucksack; cost_rest = max_cost - cost_rucksack;
pad = MaskValue.UNSET; can_add_all = False;
can_add_none = False;
# Prüfe, ob man als Lösung alles/nichts hinzufügen kann: # Prüfe, ob man als Lösung alles/nichts hinzufügen kann:
if len(indexes_unset) > 0 and sum(costs[indexes_unset]) <= cost_rest: if len(indexes_unset) > 0 and sum(costs[indexes_unset]) <= cost_rest:
pad = MaskValue.ONE; can_add_all = True;
choice[indexes_unset] = Fraction(1); vector[indexes_unset] = 1;
value_rest = sum(values[indexes_unset]); value_rest = sum(values[indexes_unset]);
elif len(indexes_unset) > 0 and min(costs[indexes_unset]) > cost_rest: elif len(indexes_unset) > 0 and min(costs[indexes_unset]) > cost_rest:
pad = MaskValue.ZERO; can_add_none = True;
choice[indexes_unset] = Fraction(0); vector[indexes_unset] = 0;
value_rest = 0; value_rest = 0;
# Sonst mit Greedy-Algorithmus lösen: # Sonst mit Greedy-Algorithmus lösen:
# NOTE: Lösung ist eine Überschätzung des max-Wertes. # NOTE: Lösung ist eine Überschätzung des max-Wertes.
@ -237,13 +236,11 @@ def estimate_lower_bound(
fractional = True, fractional = True,
verbose = False, verbose = False,
); );
choice[indexes_unset] = soln_rest.choice;
value_rest = soln_rest.total_value; value_rest = soln_rest.total_value;
# Berechne Permutation für Teilrucksack vector[indexes_unset] = soln_rest.vector;
permute_part(order, indexes=indexes_unset, order=soln_rest.order, in_place=True);
# Einschätzung des max-Wertes: # Einschätzung des max-Wertes:
value_max_est = value_rucksack + value_rest; value_max_est = value_rucksack + value_rest;
# Ausgabe mit -1 multiplizieren (weil maximiert wird): # Ausgabe mit -1 multiplizieren (weil maximiert wird):
return -value_max_est, choice.tolist(), order.tolist(), pad; return -value_max_est, vector.tolist(), can_add_all, can_add_none;

View File

@ -10,7 +10,6 @@ from src.thirdparty.maths import *;
from src.thirdparty.types import *; from src.thirdparty.types import *;
from src.models.stacks import *; from src.models.stacks import *;
from src.models.rucksack import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# EXPORTS # EXPORTS
@ -39,7 +38,7 @@ def display_order(
'order': order, 'order': order,
'values': values, 'values': values,
'costs': costs, 'costs': costs,
'margin': [str(Fraction(Fraction(value), Fraction(cost))) for cost, value in zip(costs, values)], 'u': (values/costs),
}) \ }) \
.reset_index(drop=True); .reset_index(drop=True);
if one_based: if one_based:
@ -63,11 +62,10 @@ def display_rucksack(
costs: np.ndarray, costs: np.ndarray,
values: np.ndarray, values: np.ndarray,
) -> str: ) -> str:
render = lambda r: f'{r:g}';
table = pd.DataFrame({ table = pd.DataFrame({
'items': items.tolist() + ['----', ''], 'items': items.tolist() + ['----', ''],
'costs': list(map(render, costs)) + ['', f'\x1b[92;1m{sum(costs):g}\x1b[0m'], 'costs': costs.tolist() + ['', f'\x1b[92;1m{sum(costs)}\x1b[0m'],
'values': list(map(render, values)) + ['', f'\x1b[92;1m{sum(values):g}\x1b[0m'], 'values': values.tolist() + ['', f'\x1b[92;1m{sum(values)}\x1b[0m'],
}); });
repr = tabulate( repr = tabulate(
table, table,
@ -84,28 +82,30 @@ def display_rucksack(
def display_branch_and_bound( def display_branch_and_bound(
values: np.ndarray, values: np.ndarray,
steps: List[Tuple[float, float, Stack, List[Fraction], List[int], MaskValue]], steps: List[Tuple[float, float, Stack, List[float], bool, bool]],
order: Optional[List[int]] = None,
) -> str: ) -> str:
# füge Summen-Ausdrücke für Greedy-Alg hinzu: # füge Summen-Ausdrücke für Greedy-Alg hinzu:
rows = []; rows = [];
used_choices = []; used_vectors = [];
for lb_estimate, lb, S, choice, order, pad in steps: for lb_estimate, lb, S, u, can_add_all, can_add_none in steps:
if choice in used_choices: pad = '1' if can_add_all else ('0' if can_add_none else '');
if u in used_vectors:
expr = f'{lb:g}'; expr = f'{lb:g}';
else: else:
used_choices.append(choice); used_vectors.append(u)
expr = display_sum(choice=choice, values=values, as_maximum=False, order=order); expr = display_sum(vector=u, values=values, as_maximum=False, order=order);
rows.append((f'{lb_estimate:+g}', expr, S, ('' if pad == MaskValue.UNSET else pad.value))); rows.append((f'{lb_estimate:g}', expr, pad, S));
table = pd.DataFrame(rows) \ table = pd.DataFrame(rows) \
.rename(columns={0: 'bound', 1: 'g(TOP(S))', 2: 'S', 3: 'pad?'}) \ .rename(columns={0: 'b', 1: 'g(TOP(S))', 2: 'pad?', 3: 'S'}) \
.reset_index(drop=True); .reset_index(drop=True);
# benutze pandas-Dataframe + tabulate, um schöner darzustellen: # benutze pandas-Dataframe + tabulate, um schöner darzustellen:
repr = tabulate( repr = tabulate(
table, table,
headers=['bound', 'g(TOP(S))', 'S — stack', 'pad?'], headers=['b', 'g(TOP(S))', 'pad?', 'S'],
showindex=False, showindex=False,
colalign=('left', 'left', 'right', 'center'), colalign=('left', 'left', 'center', 'right'),
tablefmt='rst' tablefmt='rst'
); );
return repr; return repr;
@ -115,17 +115,17 @@ def display_branch_and_bound(
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def display_sum( def display_sum(
choice: List[Fraction], vector: List[float],
values: np.ndarray, values: np.ndarray,
order: Optional[List[int]] = None, order: Optional[List[int]] = None,
as_maximum: bool = True, as_maximum: bool = True,
) -> str: ) -> str:
parts = [ (u, x) for u, x in zip(choice, values)]; parts = [ (u, x) for u, x in zip(vector, values)];
if not (order is None): if not (order is None):
parts = [ parts[j] for j in order ]; parts = [ parts[j] for j in order ];
value = sum([ u*x for u, x in parts]); value = sum([ u*x for u, x in parts]);
expr = '+'.join([ expr = '+'.join([
f'{x:g}' if u == 1 else f'{u}·{x:g}' f'{x:g}' if u == 1 else f'{Fraction(str(u))}·{x:g}'
for u, x in parts if u > 0 for u, x in parts if u > 0
]); ]);
if as_maximum: if as_maximum:

View File

@ -6,7 +6,6 @@
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.code import *; from src.thirdparty.code import *;
from src.thirdparty.maths import *;
from src.thirdparty.types import *; from src.thirdparty.types import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -15,7 +14,6 @@ from src.thirdparty.types import *;
__all__ = [ __all__ = [
'iperm', 'iperm',
'permute_part',
]; ];
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -26,22 +24,7 @@ def iperm(order: List[int]) -> List[int]:
''' '''
Computes the inverse of a permutation. Computes the inverse of a permutation.
''' '''
n = len(order);
perm = list(enumerate(order)); perm = list(enumerate(order));
uorder = list(map(lambda x: x[0], sorted(perm, key=lambda x: x[1]))); uorder = list(map(lambda x: x[0], sorted(perm, key=lambda x: x[1])));
return uorder; return uorder;
def permute_part(
x: np.ndarray,
indexes: List[int],
order: List[int],
in_place: bool = True,
) -> np.ndarray:
'''
Permutes a part of a list by a relative permutation for that part of the list.
'''
if not in_place:
x = x[:];
part = x[indexes];
part[:] = part[order];
x[indexes] = part;
return x;

View File

@ -7,7 +7,6 @@
from __future__ import annotations; from __future__ import annotations;
from src.thirdparty.maths import *;
from src.thirdparty.types import *; from src.thirdparty.types import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -48,9 +47,8 @@ class Mask():
return ''.join([ str(m.value) for m in self.values ]); return ''.join([ str(m.value) for m in self.values ]);
@property @property
def choice(self) -> List[Fraction]: def decision(self) -> List[int]:
assert all(x != MaskValue.UNSET for x in self.values); return [ x.value for x in self.values ];
return [ Fraction(x.value) for x in self.values ];
@property @property
def indexes_set(self) -> List[int]: def indexes_set(self) -> List[int]:
@ -78,11 +76,17 @@ class Mask():
vector2[self.index] = MaskValue.ONE; vector2[self.index] = MaskValue.ONE;
return Mask(vector1), Mask(vector2); return Mask(vector1), Mask(vector2);
def pad(self, x: MaskValue) -> Mask: def pad_zeros(self) -> Mask:
''' '''
Pads unset values with a give by given value. Completes mask by filling in unset values with zeros
''' '''
return Mask([ x if u == MaskValue.UNSET else u for u in self.values ]); return Mask([ MaskValue.ZERO if u == MaskValue.UNSET else u for u in self.values ]);
def pad_ones(self) -> Mask:
'''
Completes mask by filling in unset values with zeros
'''
return Mask([ MaskValue.ONE if u == MaskValue.UNSET else u for u in self.values ]);
@property @property
def support(self) -> List[int]: def support(self) -> List[int]:

View File

@ -5,9 +5,9 @@
# IMPORTS # IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from __future__ import annotations; from __future__ import annotations
from dataclasses import asdict;
from src.thirdparty.maths import *;
from src.thirdparty.types import *; from src.thirdparty.types import *;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -24,8 +24,7 @@ __all__ = [
@dataclass @dataclass
class SolutionRaw(): class SolutionRaw():
order: List[int] = field(); vector: List[float] = field();
choice: List[Fraction] = field();
items: List[str] = field(); items: List[str] = field();
values: List[float] = field(repr=False); values: List[float] = field(repr=False);
costs: List[float] = field(repr=False); costs: List[float] = field(repr=False);
@ -33,12 +32,16 @@ class SolutionRaw():
class Solution(SolutionRaw): class Solution(SolutionRaw):
@property @property
def support(self) -> List[float]: def support(self) -> List[float]:
return [ i for i, v in enumerate(self.choice) if v > 0 ]; return [ i for i, v in enumerate(self.vector) if v > 0 ];
@property
def vector_support(self) -> List[float]:
return [ v for v in self.vector if v > 0 ];
@property @property
def total_weight(self) -> float: def total_weight(self) -> float:
return sum([ self.choice[i]*x for (i, x) in zip(self.support, self.costs) ]); return sum([ self.vector[i]*x for (i, x) in zip(self.support, self.costs) ]);
@property @property
def total_value(self) -> float: def total_value(self) -> float:
return sum([ self.choice[i]*x for (i, x) in zip(self.support, self.values) ]); return sum([ self.vector[i]*x for (i, x) in zip(self.support, self.values) ]);