#!/usr/bin/env python3
import numpy as np
import random

def backpack_annealing(max_capacity, values, volumes, Tstart=1000, coolingRate=0.95, Niterations=10000):
    """
    Solve backpack problem using simulated annealing.
    Uses minimization paradigm by negating the maximum value.
    """
    n = len(values)
    
    # Initial random solution
    current_solution = np.random.randint(0, 2, n)
    current_value = calculate_value(current_solution, values, volumes, max_capacity)
    
    # Best solution found
    best_solution = current_solution.copy()
    best_value = current_value
    
    # Temperature
    T = Tstart
    
    for iteration in range(Niterations):
        # Generate neighbor solution by flipping one bit
        neighbor_solution = current_solution.copy()
        flip_index = random.randint(0, n-1)
        neighbor_solution[flip_index] = 1 - neighbor_solution[flip_index]
        
        # Calculate neighbor value
        neighbor_value = calculate_value(neighbor_solution, values, volumes, max_capacity)
        
        # Accept or reject the neighbor
        delta = neighbor_value - current_value
        
        if delta < 0 or random.random() < np.exp(-delta / T):
            current_solution = neighbor_solution
            current_value = neighbor_value
            
            # Update best solution if improved
            if current_value < best_value:  # Since we're minimizing, lower is better
                best_solution = current_solution.copy()
                best_value = current_value
        
        # Cool down temperature
        T *= coolingRate
        
        # Prevent temperature from becoming too small
        if T < 1e-10:
            T = 1e-10
    
    # Convert back to maximize value (negate the minimized value)
    final_value = -best_value
    return final_value, best_solution

def calculate_value(solution, values, volumes, max_capacity):
    """
    Calculate the value of a solution with penalty for capacity violations.
    Returns negative value for maximization (minimization paradigm).
    """
    total_volume = np.sum(volumes * solution)
    total_value = np.sum(values * solution)
    
    # If capacity exceeded, add penalty proportional to oversize
    if total_volume > max_capacity:
        penalty = (total_volume - max_capacity) * 1000  # Penalty factor
        return -(total_value - penalty)  # Negative because we minimize
    else:
        return -total_value  # Negative because we minimize

def print_solution(values, volumes, max_capacity, max_value, selected_items):
    print(f"Backpack Capacity: {max_capacity}")
    print(f"Maximum Value: {max_value}")
    print(f"Selected Items: {selected_items}")
    print(f"Total volume used: {np.sum(volumes * selected_items)}")
    print(f"Values: {values}")
    print(f"Volumes: {volumes}")
    print(f"Total number of objects: {len(values)}")

if __name__ == "__main__":
    print("====== Example 1 ===========")
    values1 = np.array([12, 10, 20, 15], dtype=int)
    volumes1 = np.array([2, 1, 3, 2], dtype=int)
    max_capacity1 = 5
    
    max_val1, selected1 = backpack_annealing(max_capacity1, values1, volumes1)
    print_solution(values1, volumes1, max_capacity1, max_val1, selected1)
    
    print("====== Example 2 ===========")
    N = 20
    values2 = (100 * np.random.uniform(size=(N))).astype(int)
    volumes2 = (80 * np.random.uniform(size=(N))).astype(int)
    max_capacity2 = 50
    
    import time
    t0 = time.time()
    max_val2, selected2 = backpack_annealing(max_capacity2, values2, volumes2)
    print_solution(values2, volumes2, max_capacity2, max_val2, selected2)
    print(f"Execution time: {time.time() - t0}")
