Skip to main content
Glama
knapsack.py11.4 kB
"""Knapsack problem tools for MCP server.""" import logging import time from typing import Any from fastmcp import FastMCP try: from ortools.algorithms.python import knapsack_solver ORTOOLS_AVAILABLE = True except ImportError as e: logging.warning(f"OR-Tools not available for knapsack solver: {e}") knapsack_solver = None ORTOOLS_AVAILABLE = False from mcp_optimizer.config import settings from mcp_optimizer.utils.resource_monitor import with_resource_limits logger = logging.getLogger(__name__) # Define function that can be imported directly @with_resource_limits(timeout_seconds=60.0, estimated_memory_mb=100.0) def solve_knapsack_problem( items: list[dict[str, Any]], capacity: float, volume_capacity: float | None = None, knapsack_type: str = "0-1", max_items_per_type: int | None = None, ) -> dict[str, Any]: """Solve knapsack optimization problems using OR-Tools.""" if not ORTOOLS_AVAILABLE: return { "status": "error", "total_value": None, "selected_items": [], "execution_time": 0.0, "error_message": "OR-Tools is not available. Please install it with 'pip install ortools'", } try: # Validate input if not items: return { "status": "error", "total_value": None, "selected_items": [], "execution_time": 0.0, "error_message": "No items provided", } if capacity <= 0: return { "status": "error", "total_value": None, "selected_items": [], "execution_time": 0.0, "error_message": "Capacity must be positive", } # Validate item format for i, item in enumerate(items): if not isinstance(item, dict): return { "status": "error", "total_value": None, "selected_items": [], "execution_time": 0.0, "error_message": f"Item {i} must be a dictionary", } required_fields = ["name", "value", "weight"] for field in required_fields: if field not in item: return { "status": "error", "total_value": None, "selected_items": [], "execution_time": 0.0, "error_message": f"Item {i} missing required field: {field}", } if item["value"] < 0 or item["weight"] < 0: return { "status": "error", "total_value": None, "selected_items": [], "execution_time": 0.0, "error_message": f"Item {i} value and weight must be non-negative", } start_time = time.time() # Choose appropriate solver based on constraints has_volume_constraints = volume_capacity and any("volume" in item for item in items) if has_volume_constraints: # Use multidimensional solver for volume constraints solver = knapsack_solver.KnapsackSolver( knapsack_solver.KNAPSACK_MULTIDIMENSION_BRANCH_AND_BOUND_SOLVER, "KnapsackSolver", ) else: # Use dynamic programming for single dimension solver = knapsack_solver.KnapsackSolver( knapsack_solver.KNAPSACK_DYNAMIC_PROGRAMMING_SOLVER, "KnapsackSolver" ) # Prepare data values = [] weights = [] volumes = [] item_names = [] for item in items: quantity = item.get("quantity", 1) if knapsack_type == "unbounded": # For unbounded, add multiple copies up to capacity max_copies = int(capacity / item["weight"]) + 1 for _ in range(max_copies): values.append(int(item["value"] * 1000)) # Scale for integer solver weights.append(int(item["weight"] * 1000)) if volume_capacity and "volume" in item: volumes.append(int(item["volume"] * 1000)) item_names.append(item["name"]) elif knapsack_type == "bounded": # For bounded, add specified quantity max_quantity = max_items_per_type or quantity for _ in range(max_quantity): values.append(int(item["value"] * 1000)) weights.append(int(item["weight"] * 1000)) if volume_capacity and "volume" in item: volumes.append(int(item["volume"] * 1000)) item_names.append(item["name"]) else: # 0-1 knapsack values.append(int(item["value"] * 1000)) weights.append(int(item["weight"] * 1000)) if volume_capacity and "volume" in item: volumes.append(int(item["volume"] * 1000)) item_names.append(item["name"]) # Set up constraints capacities = [int(capacity * 1000)] if volume_capacity and volumes: capacities.append(int(volume_capacity * 1000)) weight_matrix = [weights, volumes] else: weight_matrix = [weights] solver.init(values, weight_matrix, capacities) # Set time limit solver.set_time_limit(int(settings.max_solve_time)) # Solve computed_value = solver.solve() execution_time = time.time() - start_time if computed_value > 0: # Extract solution selected_items = [] total_value = 0.0 item_counts: dict[str, int] = {} for i in range(len(values)): if solver.best_solution_contains(i): item_name = item_names[i] original_item = next(item for item in items if item["name"] == item_name) if item_name in item_counts: item_counts[item_name] += 1 else: item_counts[item_name] = 1 # Create selected items list for item_name, count in item_counts.items(): original_item = next(item for item in items if item["name"] == item_name) selected_items.append( { "name": item_name, "quantity": count, "value": original_item["value"], "weight": original_item["weight"], "volume": original_item.get("volume"), "total_value": original_item["value"] * count, "total_weight": original_item["weight"] * count, "total_volume": original_item.get("volume", 0) * count if original_item.get("volume") else None, } ) total_value += original_item["value"] * count result = { "status": "optimal", "total_value": total_value, "selected_items": selected_items, "execution_time": execution_time, "solver_info": { "solver_name": "OR-Tools KnapsackSolver", "algorithm": "Dynamic Programming" if not has_volume_constraints else "Branch and Bound", "items_count": len(items), "capacity": capacity, "volume_capacity": volume_capacity, }, } else: result = { "status": "infeasible", "total_value": 0.0, "selected_items": [], "execution_time": execution_time, "solver_info": { "solver_name": "OR-Tools KnapsackSolver", "algorithm": "Dynamic Programming" if not has_volume_constraints else "Branch and Bound", "items_count": len(items), "capacity": capacity, "volume_capacity": volume_capacity, }, } logger.info(f"Knapsack problem solved with status: {result['status']}") return result except Exception as e: logger.error(f"Error in solve_knapsack_problem: {e}") return { "status": "error", "total_value": None, "selected_items": [], "execution_time": 0.0, "error_message": f"Failed to solve knapsack problem: {str(e)}", } def register_knapsack_tools(mcp: FastMCP[Any]) -> None: """Register knapsack problem tools with the MCP server.""" @mcp.tool() def solve_knapsack_problem_tool( items: list[dict[str, Any]], capacity: float, volume_capacity: float | None = None, knapsack_type: str = "0-1", max_items_per_type: int | None = None, ) -> dict[str, Any]: """Solve knapsack optimization problems using OR-Tools. This tool solves knapsack problems where items need to be selected to maximize value while staying within capacity constraints. Use cases: - Cargo loading: Optimize loading of trucks, ships, or planes by weight and volume - Portfolio selection: Choose optimal set of investments within budget constraints - Resource allocation: Select projects or activities with limited budget or resources - Advertising planning: Choose optimal mix of advertising channels within budget - Menu planning: Select dishes for a restaurant menu considering costs and popularity - Inventory optimization: Decide which products to stock in limited warehouse space Args: items: List of items, each with 'name', 'value', 'weight', and optionally 'volume', 'quantity' capacity: Weight capacity constraint volume_capacity: Volume capacity constraint (optional) knapsack_type: Type of knapsack problem ('0-1', 'bounded', 'unbounded') max_items_per_type: Maximum items per type for bounded knapsack Returns: Knapsack result with total value and selected items Example: # Select items to maximize value within weight limit solve_knapsack_problem( items=[ {"name": "Item1", "value": 10, "weight": 5, "volume": 2}, {"name": "Item2", "value": 15, "weight": 8, "volume": 3}, {"name": "Item3", "value": 8, "weight": 3, "volume": 1} ], capacity=10, volume_capacity=5 ) """ result = solve_knapsack_problem( items, capacity, volume_capacity, knapsack_type, max_items_per_type ) result_dict: dict[str, Any] = result return result_dict logger.info("Registered knapsack tools")

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dmitryanchikov/mcp-optimizer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server