2022-06-14 01:35:10 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# IMPORTS
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
from src.thirdparty.types import *;
|
|
|
|
from src.thirdparty.maths import *;
|
|
|
|
|
|
|
|
from models.generated.config import *;
|
2022-06-14 10:09:21 +02:00
|
|
|
from src.core.utils import *;
|
2022-06-14 01:35:10 +02:00
|
|
|
from src.models.rucksack import *;
|
|
|
|
from src.models.stacks import *;
|
|
|
|
from src.algorithms.rucksack.display import *;
|
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# EXPORTS
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
__all__ = [
|
|
|
|
'rucksack_greedy_algorithm',
|
|
|
|
'rucksack_branch_and_bound_algorithm',
|
|
|
|
];
|
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# METHOD greedy algorithm
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
def rucksack_greedy_algorithm(
|
2022-06-14 14:40:02 +02:00
|
|
|
max_cost: float,
|
|
|
|
costs: np.ndarray,
|
2022-06-14 01:35:10 +02:00
|
|
|
values: np.ndarray,
|
|
|
|
items: np.ndarray,
|
|
|
|
fractional: bool,
|
|
|
|
verbose: bool,
|
|
|
|
) -> Solution:
|
|
|
|
'''
|
|
|
|
Durch den Greedy-Algorithm wird der optimale Wert eines Rucksacks
|
|
|
|
unter Rücksicht der Kapizitätsschranke eingeschätzt.
|
|
|
|
|
|
|
|
NOTE: Wenn man `fractional = True` verwendet, liefert der Algorithmus
|
|
|
|
eine obere Schranke des maximalen Wertes beim Originalproblem.
|
|
|
|
'''
|
|
|
|
# sortiere daten:
|
2022-06-14 14:40:02 +02:00
|
|
|
order = get_sort_order(costs=costs, values=values);
|
2022-06-14 09:03:29 +02:00
|
|
|
|
|
|
|
# verbose output hier behandeln (irrelevant für Algorithmus):
|
|
|
|
if verbose:
|
2022-06-14 14:40:02 +02:00
|
|
|
repr = display_order(order=order, costs=costs, values=values, items=items, one_based=True);
|
2022-06-14 09:03:29 +02:00
|
|
|
print('');
|
|
|
|
print('\x1b[1mRucksack Problem - Greedy\x1b[0m');
|
|
|
|
print('');
|
|
|
|
print(repr);
|
|
|
|
print('');
|
2022-06-14 01:35:10 +02:00
|
|
|
|
|
|
|
# führe greedy aus:
|
2022-06-14 14:40:02 +02:00
|
|
|
n = len(costs);
|
|
|
|
cost_total = 0;
|
2022-06-14 01:35:10 +02:00
|
|
|
vector = [ 0 for _ in range(n) ];
|
2022-06-14 14:40:02 +02:00
|
|
|
for i in order:
|
2022-06-14 01:35:10 +02:00
|
|
|
# füge Item i hinzu, solange das Gesamtgewicht noch <= Schranke
|
2022-06-14 14:40:02 +02:00
|
|
|
if cost_total + costs[i] <= max_cost:
|
|
|
|
cost_total += costs[i];
|
2022-06-14 01:35:10 +02:00
|
|
|
vector[i] = 1;
|
2022-06-14 14:40:02 +02:00
|
|
|
# falls Bruchteile erlaubt sind, füge einen Bruchteil des i. Items hinzu und abbrechen
|
|
|
|
elif fractional:
|
|
|
|
vector[i] = (max_cost - cost_total)/costs[i];
|
2022-06-14 01:35:10 +02:00
|
|
|
break;
|
2022-06-14 14:40:02 +02:00
|
|
|
# ansonsten weiter machen:
|
|
|
|
else:
|
|
|
|
continue;
|
2022-06-14 01:35:10 +02:00
|
|
|
|
2022-06-14 01:53:48 +02:00
|
|
|
# Aspekte der Lösung speichern:
|
2022-06-14 10:09:21 +02:00
|
|
|
rucksack = [i for i, v in enumerate(vector) if v > 0]; # Indexes von Items im Rucksack
|
2022-06-14 01:53:48 +02:00
|
|
|
soln = Solution(
|
|
|
|
vector = vector,
|
|
|
|
items = items[rucksack].tolist(),
|
2022-06-14 14:40:02 +02:00
|
|
|
costs = costs[rucksack].tolist(),
|
2022-06-14 01:53:48 +02:00
|
|
|
values = values[rucksack].tolist(),
|
|
|
|
);
|
|
|
|
|
2022-06-14 01:35:10 +02:00
|
|
|
# verbose output hier behandeln (irrelevant für Algorithmus):
|
|
|
|
if verbose:
|
2022-06-14 14:40:02 +02:00
|
|
|
repr_rucksack = display_rucksack(items=items[rucksack], costs=costs[rucksack], values=values[rucksack]);
|
2022-06-14 10:09:21 +02:00
|
|
|
print('\x1b[1mEingeschätzte Lösung\x1b[0m');
|
2022-06-14 01:35:10 +02:00
|
|
|
print('');
|
2022-06-14 14:40:02 +02:00
|
|
|
print(f'Mask: {soln.vector}');
|
|
|
|
print('Rucksack:')
|
|
|
|
print(repr_rucksack);
|
2022-06-14 01:35:10 +02:00
|
|
|
print('');
|
|
|
|
|
|
|
|
# Lösung ausgeben
|
2022-06-14 01:53:48 +02:00
|
|
|
return soln;
|
2022-06-14 01:35:10 +02:00
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# METHOD branch and bound algorithm
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
|
|
|
def rucksack_branch_and_bound_algorithm(
|
2022-06-14 14:40:02 +02:00
|
|
|
max_cost: float,
|
|
|
|
costs: np.ndarray,
|
2022-06-14 01:35:10 +02:00
|
|
|
values: np.ndarray,
|
|
|
|
items: np.ndarray,
|
|
|
|
verbose: bool,
|
|
|
|
) -> Solution:
|
|
|
|
'''
|
|
|
|
Durch Branch & Bound wird der optimale Wert eines Rucksacks
|
|
|
|
unter Rücksicht der Kapizitätsschranke exakt und effizienter bestimmt.
|
|
|
|
'''
|
|
|
|
|
2022-06-14 14:40:02 +02:00
|
|
|
order = get_sort_order(costs=costs, values=values);
|
2022-06-14 01:35:10 +02:00
|
|
|
|
2022-06-14 09:03:29 +02:00
|
|
|
# verbose output hier behandeln (irrelevant für Algorithmus):
|
|
|
|
if verbose:
|
2022-06-14 14:40:02 +02:00
|
|
|
repr = display_order(order=order, costs=costs, values=values, items=items, one_based=True);
|
2022-06-14 09:03:29 +02:00
|
|
|
print('');
|
|
|
|
print('\x1b[1mRucksack Problem - Branch & Bound\x1b[0m');
|
|
|
|
print('');
|
|
|
|
print(repr);
|
|
|
|
print('');
|
|
|
|
|
|
|
|
logged_steps = [];
|
2022-06-14 14:40:02 +02:00
|
|
|
vector = empty_mask(n=len(costs));
|
2022-06-14 01:35:10 +02:00
|
|
|
lb_estimate = np.inf;
|
|
|
|
S = Stack();
|
|
|
|
S.push(vector);
|
|
|
|
while not S.empty():
|
2022-06-14 14:40:02 +02:00
|
|
|
lb, u, can_add_all, can_add_none = estimate_lower_bound(mask=S.top(), max_cost=max_cost, costs=costs, values=values, items=items);
|
2022-06-14 01:35:10 +02:00
|
|
|
if verbose:
|
2022-06-14 12:19:35 +02:00
|
|
|
logged_steps.append((lb_estimate, lb, str(S), u, can_add_all, can_add_none));
|
2022-06-14 01:35:10 +02:00
|
|
|
# Update nur nötig, wenn die (eingeschätzte) untere Schranke von A das bisherige Minimum verbessert:
|
|
|
|
A: Mask = S.pop();
|
|
|
|
if lb < lb_estimate:
|
2022-06-14 11:24:32 +02:00
|
|
|
# Bound, wenn sich A nicht weiter aufteilen lässt od. man A wie eine einelementige Option behandeln kann:
|
|
|
|
if not A.splittable() or can_add_all or can_add_none:
|
|
|
|
lb_estimate = lb;
|
|
|
|
if can_add_all:
|
|
|
|
vector = A.pad_ones();
|
|
|
|
elif can_add_none:
|
|
|
|
vector = A.pad_zeros();
|
|
|
|
else:
|
|
|
|
vector = A;
|
2022-06-14 12:19:35 +02:00
|
|
|
# Branch sonst
|
2022-06-14 11:24:32 +02:00
|
|
|
else:
|
2022-06-14 01:35:10 +02:00
|
|
|
B, C = A.split();
|
|
|
|
S.push(B);
|
|
|
|
# Nur dann C auf Stack legen, wenn mind. eine Möglichkeit in C die Kapazitätsschranke erfüllt:
|
2022-06-14 14:40:02 +02:00
|
|
|
if sum(costs[C.indexes_one]) <= max_cost:
|
2022-06-14 01:35:10 +02:00
|
|
|
S.push(C);
|
|
|
|
|
2022-06-14 01:53:48 +02:00
|
|
|
# Aspekte der Lösung speichern
|
|
|
|
rucksack = vector.indexes_one; # Indexes von Items im Rucksack
|
|
|
|
soln = Solution(
|
|
|
|
vector = vector.decision,
|
|
|
|
items = items[rucksack].tolist(),
|
|
|
|
values = values[rucksack].tolist(),
|
2022-06-14 14:40:02 +02:00
|
|
|
costs = costs[rucksack].tolist(),
|
2022-06-14 01:53:48 +02:00
|
|
|
);
|
|
|
|
|
2022-06-14 01:35:10 +02:00
|
|
|
# verbose output hier behandeln (irrelevant für Algorithmus):
|
|
|
|
if verbose:
|
2022-06-14 14:40:02 +02:00
|
|
|
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]);
|
2022-06-14 10:09:21 +02:00
|
|
|
print('\x1b[1mLösung\x1b[0m');
|
2022-06-14 01:35:10 +02:00
|
|
|
print('');
|
|
|
|
print(repr);
|
|
|
|
print('');
|
2022-06-14 10:09:21 +02:00
|
|
|
print(f'Mask: {soln.vector}');
|
2022-06-14 14:40:02 +02:00
|
|
|
print('Rucksack:');
|
|
|
|
print(repr_rucksack);
|
2022-06-14 01:53:48 +02:00
|
|
|
print('');
|
|
|
|
|
2022-06-14 01:35:10 +02:00
|
|
|
# Lösung ausgeben
|
2022-06-14 01:53:48 +02:00
|
|
|
return soln;
|
2022-06-14 01:35:10 +02:00
|
|
|
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
# AUXILIARY METHOD resort
|
|
|
|
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
|
|
2022-06-14 14:40:02 +02:00
|
|
|
def get_sort_order(costs: np.ndarray, values: np.ndarray) -> List[int]:
|
2022-06-14 01:35:10 +02:00
|
|
|
'''
|
2022-06-14 14:40:02 +02:00
|
|
|
Sortiert Daten absteigend nach values/costs.
|
2022-06-14 01:35:10 +02:00
|
|
|
'''
|
2022-06-14 14:40:02 +02:00
|
|
|
n = len(costs);
|
2022-06-14 01:35:10 +02:00
|
|
|
indexes = list(range(n));
|
2022-06-14 14:40:02 +02:00
|
|
|
order = sorted(indexes, key=lambda i: -values[i]/costs[i]);
|
2022-06-14 01:35:10 +02:00
|
|
|
return order;
|
|
|
|
|
|
|
|
def estimate_lower_bound(
|
|
|
|
mask: Mask,
|
2022-06-14 14:40:02 +02:00
|
|
|
max_cost: float,
|
|
|
|
costs: np.ndarray,
|
2022-06-14 01:35:10 +02:00
|
|
|
values: np.ndarray,
|
|
|
|
items: np.ndarray,
|
2022-06-14 11:24:32 +02:00
|
|
|
) -> Tuple[float, List[float], bool]:
|
2022-06-14 01:35:10 +02:00
|
|
|
'''
|
|
|
|
Wenn partielle Information über den Rucksack festgelegt ist,
|
|
|
|
kann man bei dem unbekannten Teil das Rucksack-Problem
|
|
|
|
mit Greedy-Algorithmus »lösen«,
|
|
|
|
um schnell eine gute Einschätzung zu bestimmen.
|
|
|
|
|
|
|
|
NOTE: Diese Funktion wird `g(vector)` im Skript bezeichnet.
|
|
|
|
'''
|
|
|
|
indexes_one = mask.indexes_one;
|
|
|
|
indexes_unset = mask.indexes_unset;
|
|
|
|
n = len(mask);
|
|
|
|
vector = np.zeros(shape=(n,), dtype=float);
|
2022-06-14 12:19:35 +02:00
|
|
|
|
|
|
|
# Berechnungen bei Items mit bekanntem Status in Rucksack:
|
|
|
|
value_rucksack = sum(values[indexes_one]);
|
2022-06-14 14:40:02 +02:00
|
|
|
cost_rucksack = sum(costs[indexes_one]);
|
2022-06-14 01:35:10 +02:00
|
|
|
vector[indexes_one] = 1;
|
|
|
|
|
2022-06-14 12:19:35 +02:00
|
|
|
# Für Rest des Rucksacks (Items mit unbekanntem Status):
|
2022-06-14 14:40:02 +02:00
|
|
|
cost_rest = max_cost - cost_rucksack;
|
2022-06-14 12:19:35 +02:00
|
|
|
can_add_all = False;
|
|
|
|
can_add_none = False;
|
|
|
|
# Prüfe, ob man als Lösung alles/nichts hinzufügen kann:
|
2022-06-14 14:40:02 +02:00
|
|
|
if len(indexes_unset) > 0 and sum(costs[indexes_unset]) <= cost_rest:
|
2022-06-14 12:19:35 +02:00
|
|
|
can_add_all = True;
|
|
|
|
vector[indexes_unset] = 1;
|
|
|
|
value_rest = sum(values[indexes_unset]);
|
2022-06-14 14:40:02 +02:00
|
|
|
elif len(indexes_unset) > 0 and min(costs[indexes_unset]) > cost_rest:
|
2022-06-14 12:19:35 +02:00
|
|
|
can_add_none = True;
|
|
|
|
vector[indexes_unset] = 0;
|
|
|
|
value_rest = 0;
|
|
|
|
# Sonst mit Greedy-Algorithmus lösen:
|
|
|
|
# NOTE: Lösung ist eine Überschätzung des max-Wertes.
|
|
|
|
else:
|
|
|
|
soln_rest = rucksack_greedy_algorithm(
|
2022-06-14 14:40:02 +02:00
|
|
|
max_cost = cost_rest, # <- Kapazität = Restgewicht
|
|
|
|
costs = costs[indexes_unset],
|
2022-06-14 12:19:35 +02:00
|
|
|
values = values[indexes_unset],
|
|
|
|
items = items[indexes_unset],
|
|
|
|
fractional = True,
|
|
|
|
verbose = False,
|
|
|
|
);
|
|
|
|
value_rest = soln_rest.total_value;
|
|
|
|
vector[indexes_unset] = soln_rest.vector;
|
2022-06-14 11:24:32 +02:00
|
|
|
|
2022-06-14 12:19:35 +02:00
|
|
|
# Einschätzung des max-Wertes:
|
2022-06-14 01:35:10 +02:00
|
|
|
value_max_est = value_rucksack + value_rest;
|
2022-06-14 12:19:35 +02:00
|
|
|
|
|
|
|
# Ausgabe mit -1 multiplizieren (weil maximiert wird):
|
2022-06-14 11:24:32 +02:00
|
|
|
return -value_max_est, vector.tolist(), can_add_all, can_add_none;
|