ads2_2022/code/python/src/algorithms/rucksack/algorithms.py

263 lines
9.3 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# IMPORTS
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
from src.thirdparty.types import *;
from src.thirdparty.maths import *;
from models.generated.config import *;
from src.core.utils import *;
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(
capacity: float,
weights: np.ndarray,
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:
order = resort_by_value_per_weight(weights=weights, values=values, items=items);
uorder = iperm(order);
# verbose output hier behandeln (irrelevant für Algorithmus):
if verbose:
repr = display_order(order=order, weights=weights[uorder], values=values[uorder], items=items[uorder], one_based=True);
print('');
print('\x1b[1mRucksack Problem - Greedy\x1b[0m');
print('');
print(repr);
print('');
# führe greedy aus:
n = len(weights);
weight_total = 0;
vector = [ 0 for _ in range(n) ];
for i in range(n):
# füge Item i hinzu, solange das Gesamtgewicht noch <= Schranke
if weight_total + weights[i] <= capacity:
weight_total += weights[i];
vector[i] = 1;
# sonst abbrechen. Falls Bruchteile erlaubt, füge einen Bruchteil des i. Items hinzu
else:
if fractional:
vector[i] = (capacity - weight_total)/weights[i];
break;
# Aspekte der Lösung speichern:
rucksack = [i for i, v in enumerate(vector) if v > 0]; # Indexes von Items im Rucksack
soln = Solution(
vector = vector,
items = items[rucksack].tolist(),
weights = weights[rucksack].tolist(),
values = values[rucksack].tolist(),
);
# verbose output hier behandeln (irrelevant für Algorithmus):
if verbose:
# Umsortierung rückgängig machen:
vector = [ soln.vector[i] for i in order ];
rucksack = [ uorder[r] for r in rucksack ];
permute_data(weights=weights, values=values, items=items, perm=uorder);
# Ausdrücke bestimmen:
expr_value = display_sum(vector=vector, values=values);
expr_weight = display_sum(vector=vector, values=weights);
print('\x1b[1mEingeschätzte Lösung\x1b[0m');
print('');
if fractional:
print(f'Mask: {soln.vector}');
print(f' ---> {vector} (unter urspr. Sortierung)');
print(f'Rucksack: {", ".join(items[rucksack])}.');
print(f'max. Value ≈ {expr_value}');
print(f'∑ Weights = {expr_weight}');
print('');
# Lösung ausgeben
return soln;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# METHOD branch and bound algorithm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def rucksack_branch_and_bound_algorithm(
capacity: float,
weights: np.ndarray,
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.
'''
order = resort_by_value_per_weight(weights=weights, values=values, items=items);
uorder = iperm(order);
# verbose output hier behandeln (irrelevant für Algorithmus):
if verbose:
repr = display_order(order=order, weights=weights[uorder], values=values[uorder], items=items[uorder], one_based=True);
print('');
print('\x1b[1mRucksack Problem - Branch & Bound\x1b[0m');
print('');
print(repr);
print('');
logged_steps = [];
vector = empty_mask(n=len(weights));
lb_estimate = np.inf;
S = Stack();
S.push(vector);
while not S.empty():
lb, u = estimate_lower_bound(mask=S.top(), capacity=capacity, weights=weights, values=values, items=items);
if verbose:
logged_steps.append((lb_estimate, lb, u, str(S)));
# Update nur nötig, wenn die (eingeschätzte) untere Schranke von A das bisherige Minimum verbessert:
A: Mask = S.pop();
if lb < lb_estimate:
# Branch:
if A.splittable():
B, C = A.split();
# NOTE: per Wahl erfüllt mind. eine Möglichkeit in B die Kapazitätsschranke.
S.push(B);
# Nur dann C auf Stack legen, wenn mind. eine Möglichkeit in C die Kapazitätsschranke erfüllt:
if sum(weights[C.indexes_one]) <= capacity:
S.push(C);
# Bound, wenn die Maske den Rucksack komplett bestimmt:
else:
lb_estimate = lb;
vector = A;
# 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(),
weights = weights[rucksack].tolist(),
);
# verbose output hier behandeln (irrelevant für Algorithmus):
if verbose:
# NOTE: Information in Tabelle gemäß permutierten Daten:
repr = display_branch_and_bound(values=values, steps=logged_steps);
# Umsortierung rückgängig machen:
vector = [ soln.vector[i] for i in order ];
rucksack = [ uorder[r] for r in rucksack ];
permute_data(weights=weights, values=values, items=items, perm=uorder);
# Ausdrücke bestimmen:
expr_value = display_sum(vector=vector, values=values);
expr_weight = display_sum(vector=vector, values=weights);
print('\x1b[1mLösung\x1b[0m');
print('');
print(repr);
print('');
print(f'Mask: {soln.vector}');
print(f' ---> {vector} (unter urspr. Sortierung)');
print(f'Rucksack: {", ".join(items[rucksack])}.');
print(f'max. Value ≈ {expr_value}');
print(f'∑ Weights = {expr_weight}');
print('');
# Lösung ausgeben
return soln;
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# AUXILIARY METHOD resort
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
def resort_by_value_per_weight(
weights: np.ndarray,
values: np.ndarray,
items: np.ndarray,
) -> List[int]:
'''
Sortiert Daten absteigend nach values/weights.
'''
n = len(weights);
indexes = list(range(n));
order = sorted(indexes, key=lambda i: -values[i]/weights[i]);
permute_data(weights=weights, values=values, items=items, perm=order);
return order;
def permute_data(
weights: np.ndarray,
values: np.ndarray,
items: np.ndarray,
perm: List[int],
):
weights[:] = weights[perm];
values[:] = values[perm];
items[:] = items[perm];
return;
def estimate_lower_bound(
mask: Mask,
capacity: float,
weights: np.ndarray,
values: np.ndarray,
items: np.ndarray,
) -> Tuple[float, List[float]]:
'''
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.
'''
# Berechnungen bei Items mit bekanntem Status in Rucksack:
indexes_one = mask.indexes_one;
weight_rucksack = sum(weights[indexes_one]);
value_rucksack = sum(values[indexes_one]);
# Löse mit Greedy-Algorithmus auf Items mit unbekanntem Status.
# NOTE: Lösung ist eine Überschätzung des max-Wertes.
indexes_unset = mask.indexes_unset;
soln_rest = rucksack_greedy_algorithm(
capacity = capacity - weight_rucksack, # <- Kapazität = Restgewicht
weights = weights[indexes_unset],
values = values[indexes_unset],
items = items[indexes_unset],
fractional = True,
verbose = False,
);
value_rest = soln_rest.total_value;
# Lösung des [0,1]-Vektors speichern:
n = len(mask);
vector = np.zeros(shape=(n,), dtype=float);
vector[indexes_one] = 1;
vector[indexes_unset] = soln_rest.vector;
# Einschätzung des max-Wertes (Ausgabe mit -1 multiplizieren):
value_max_est = value_rucksack + value_rest;
return -value_max_est, vector.tolist();