add an option to do regular bronze here, which is 8-12% tin and 88-92% copper.:
from typing import Dict, List, Tuple
import pulp

class Chunk:
    def __init__(self, metal: str, size: str, mb: int, available: int):
        self.metal = metal
        self.size = size
        self.mb = mb
        self.available = available

    def __repr__(self):
        return f"{self.available}x {self.metal} {self.size} chunk(s) ({self.mb} mb each)"

def calculate_ratios(x_vars: Dict[str, pulp.LpVariable], chunks: Dict[str, Chunk], total_mb_var: pulp.LpVariable):
    """Calculate metal ratios based on variables"""
    total_zinc_mb = pulp.lpSum([chunks[chunk].mb * x_vars[chunk] for chunk in chunks if chunks[chunk].metal == 'zinc'])
    total_copper_mb = pulp.lpSum([chunks[chunk].mb * x_vars[chunk] for chunk in chunks if chunks[chunk].metal == 'copper'])
    total_bismuth_mb = pulp.lpSum([chunks[chunk].mb * x_vars[chunk] for chunk in chunks if chunks[chunk].metal == 'bismuth'])
    
    # Total MB
    total_mb = pulp.lpSum([chunks[chunk].mb * x_vars[chunk] for chunk in chunks])
    
    # Assign to total_mb_var for use in constraints
    total_mb_var = total_mb
    
    return total_zinc_mb, total_copper_mb, total_bismuth_mb, total_mb

def find_optimal_bronze_combination_ilp(chunks: Dict[str, Chunk]) -> Tuple[List[Tuple[str, int]], int]:
    """Find the optimal combination for maximum bronze production using ILP"""
    # Initialize the problem
    prob = pulp.LpProblem("Bronze_Production_Optimization", pulp.LpMaximize)

    # Decision variables: number of each chunk to select (0 or 1 per slot, up to available)
    x = {chunk: pulp.LpVariable(f"x_{chunk}", lowBound=0, upBound=min(chunks[chunk].available,9), cat='Integer') for chunk in chunks if chunks[chunk].available > 0}

    # Total number of slots used should be <=9
    prob += pulp.lpSum([x[chunk] for chunk in x]) <= 9, "Total_Slots_Used"

    # Metal MB sums
    total_zinc_mb = pulp.lpSum([chunks[chunk].mb * x[chunk] for chunk in x if chunks[chunk].metal == 'zinc'])
    total_copper_mb = pulp.lpSum([chunks[chunk].mb * x[chunk] for chunk in x if chunks[chunk].metal == 'copper'])
    total_bismuth_mb = pulp.lpSum([chunks[chunk].mb * x[chunk] for chunk in x if chunks[chunk].metal == 'bismuth'])
    
    total_mb = pulp.lpSum([chunks[chunk].mb * x[chunk] for chunk in x])

    # Avoid division by zero by ensuring total_mb >=1
    prob += total_mb >=1, "Total_MB_Min"

    # Metal ratio constraints
    # 20 <= (zinc_mb / total_mb) *100 <=30
    prob += total_zinc_mb * 100 >= 20 * total_mb, "Zinc_Ratio_Lower"
    prob += total_zinc_mb * 100 <= 30 * total_mb, "Zinc_Ratio_Upper"

    # 50 <= (copper_mb / total_mb) *100 <=65
    prob += total_copper_mb * 100 >= 50 * total_mb, "Copper_Ratio_Lower"
    prob += total_copper_mb * 100 <= 65 * total_mb, "Copper_Ratio_Upper"

    # 10 <= (bismuth_mb / total_mb) *100 <=20
    prob += total_bismuth_mb * 100 >= 10 * total_mb, "Bismuth_Ratio_Lower"
    prob += total_bismuth_mb * 100 <= 20 * total_mb, "Bismuth_Ratio_Upper"

    # Objective: maximize total_mb
    prob += total_mb, "Total_MB"

    # Solve the problem
    solver = pulp.PULP_CBC_CMD(msg=True)
    result = prob.solve(solver)

    if pulp.LpStatus[result] != 'Optimal':
        return [], 0  # No valid combination found

    # Extract the solution
    best_combination = []
    max_mb = int(pulp.value(total_mb))
    for chunk in x:
        count = int(pulp.value(x[chunk]))
        if count > 0:
            best_combination.append((chunk, count))

    return best_combination, max_mb

def print_solution(combination: List[Tuple[str, int]], total_mb: int, chunks: Dict[str, Chunk]):
    """Print the solution in a readable format"""
    if not combination:
        print("No valid combination found!")
        return

    print("\nOptimal Bronze Production Solution:")
    print("-" * 50)
    total_zinc_mb = sum(chunks[chunk].mb * count for chunk, count in combination if chunks[chunk].metal == 'zinc')
    total_copper_mb = sum(chunks[chunk].mb * count for chunk, count in combination if chunks[chunk].metal == 'copper')
    total_bismuth_mb = sum(chunks[chunk].mb * count for chunk, count in combination if chunks[chunk].metal == 'bismuth')

    for chunk_name, count in combination:
        chunk = chunks[chunk_name]
        print(f"{count}x {chunk.metal.capitalize()} {chunk.size} chunk(s) ({chunk.mb} mb each)")

    print("-" * 50)
    print(f"Total MB: {total_mb}")
    # Calculate ratios
    zinc_ratio = (total_zinc_mb / total_mb) * 100 if total_mb else 0
    copper_ratio = (total_copper_mb / total_mb) * 100 if total_mb else 0
    bismuth_ratio = (total_bismuth_mb / total_mb) * 100 if total_mb else 0
    print(f"Metal Ratios:")
    print(f"Zinc: {zinc_ratio:.1f}%")
    print(f"Copper: {copper_ratio:.1f}%")
    print(f"Bismuth: {bismuth_ratio:.1f}%\n")

if __name__ == "__main__":
    # Available chunks
    chunks = {
        'zinc_nugget': Chunk('zinc', 'nugget', 16, 0),
        'zinc_crushed': Chunk('zinc', 'crushed', 72, 0),
        'sphalerite_dust': Chunk('zinc', 'dust', 129, 54),

        'copper_crushed': Chunk('copper', 'crushed', 80, 0),
        'copper_big': Chunk('copper', 'big', 42, 4),
        'copper_medium': Chunk('copper', 'medium', 31, 5),
        'copper_small': Chunk('copper', 'small', 21, 0),
        'copper_ingot': Chunk('copper', 'ingot', 144, 0),
        'native_copper_small': Chunk('copper', 'native_small', 24, 0),
        'native_copper_dust': Chunk('copper', 'native_dust', 144, 64+49),

        'bismuth_big': Chunk('bismuth', 'big', 48, 0),
        'bismuth_medium': Chunk('bismuth', 'medium', 36, 8),
        'bismuth_small': Chunk('bismuth', 'small', 24, 11),
    }

    # Find and print the optimal solution
    best_combination, max_mb = find_optimal_bronze_combination_ilp(chunks)
    print_solution(best_combination, max_mb, chunks)