Skip to main content
Glama

optimize_production_plan_tool

Optimize multi-period production planning to maximize profit or minimize costs by balancing product demand, resource capacity, and inventory constraints.

Instructions

Optimize multi-period production planning to maximize profit or minimize costs.

    Args:
        products: List of product dictionaries with costs and resource requirements
        resources: List of resource dictionaries with capacity constraints
        periods: Number of planning periods
        demand: List of demand requirements per product per period
        objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time")
        inventory_costs: Optional inventory holding costs per product
        setup_costs: Optional setup costs per product
        solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX")
        time_limit_seconds: Maximum solving time in seconds (default: 30.0)

    Returns:
        Optimization result with optimal production plan
    

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
productsYes
resourcesYes
periodsYes
demandYes
objectiveNomaximize_profit
inventory_costsNo
setup_costsNo
solver_nameNoCBC
time_limit_secondsNo

Implementation Reference

  • The handler function for 'optimize_production_plan_tool', decorated with @mcp.tool(). It constructs input data from parameters and calls the core solver solve_production_planning.
    @mcp.tool()
    def optimize_production_plan_tool(
        products: list[dict[str, Any]],
        resources: list[dict[str, Any]],
        periods: int,
        demand: list[dict[str, Any]],
        objective: str = "maximize_profit",
        inventory_costs: dict[str, float] | None = None,
        setup_costs: dict[str, float] | None = None,
        solver_name: str = "CBC",
        time_limit_seconds: float = 30.0,
    ) -> dict[str, Any]:
        """Optimize multi-period production planning to maximize profit or minimize costs.
    
        Args:
            products: List of product dictionaries with costs and resource requirements
            resources: List of resource dictionaries with capacity constraints
            periods: Number of planning periods
            demand: List of demand requirements per product per period
            objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time")
            inventory_costs: Optional inventory holding costs per product
            setup_costs: Optional setup costs per product
            solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX")
            time_limit_seconds: Maximum solving time in seconds (default: 30.0)
    
        Returns:
            Optimization result with optimal production plan
        """
        input_data = {
            "products": products,
            "resources": resources,
            "periods": periods,
            "demand": demand,
            "objective": objective,
            "inventory_costs": inventory_costs,
            "setup_costs": setup_costs,
            "solver_name": solver_name,
            "time_limit_seconds": time_limit_seconds,
        }
    
        result = solve_production_planning(input_data)
        result_dict: dict[str, Any] = result.model_dump()
        return result_dict
  • Core helper function that implements the mathematical optimization model using PuLP for production planning, handling multi-period, inventory, setup costs, etc.
    @with_resource_limits(timeout_seconds=120.0, estimated_memory_mb=150.0)
    def solve_production_planning(input_data: dict[str, Any]) -> OptimizationResult:
        """Solve Production Planning Problem using PuLP.
    
        Args:
            input_data: Production planning problem specification
    
        Returns:
            OptimizationResult with optimal production plan
        """
        start_time = time.time()
    
        try:
            # Parse and validate input
            planning_input = ProductionPlanningInput(**input_data)
            products = planning_input.products
            resources = planning_input.resources
            demand_constraints = planning_input.demand_constraints
            horizon = planning_input.planning_horizon
    
            # Create optimization problem
            if planning_input.objective == "maximize_profit":
                prob = pulp.LpProblem("Production_Planning", pulp.LpMaximize)
            else:
                prob = pulp.LpProblem("Production_Planning", pulp.LpMinimize)
    
            # Decision variables: production quantities for each product in each period
            production_vars: dict[int, dict[str, Any]] = {}
            setup_vars: dict[int, dict[str, Any]] = {}  # Binary variables for setup decisions
            inventory_vars: dict[int, dict[str, Any]] = {}  # Inventory levels
    
            for period in range(horizon):
                production_vars[period] = {}
                setup_vars[period] = {}
                inventory_vars[period] = {}
    
                for product in products:
                    # Production quantity
                    production_vars[period][product.name] = pulp.LpVariable(
                        f"production_{product.name}_period_{period}",
                        lowBound=0,
                        cat="Continuous",
                    )
    
                    # Setup decision (binary)
                    if product.setup_cost > 0:
                        setup_vars[period][product.name] = pulp.LpVariable(
                            f"setup_{product.name}_period_{period}", cat="Binary"
                        )
    
                    # Inventory level
                    inventory_vars[period][product.name] = pulp.LpVariable(
                        f"inventory_{product.name}_period_{period}",
                        lowBound=0,
                        cat="Continuous",
                    )
    
            # Resource capacity constraints
            for period in range(horizon):
                for resource_name, resource in resources.items():
                    resource_usage = pulp.lpSum(
                        production_vars[period][product.name] * product.resources.get(resource_name, 0)
                        for product in products
                    )
                    prob += (
                        resource_usage <= resource.available,
                        f"Resource_{resource_name}_Period_{period}",
                    )
    
            # Production constraints
            for period in range(horizon):
                for product in products:
                    prod_var = production_vars[period][product.name]
    
                    # Minimum production
                    if product.min_production > 0:
                        prob += (
                            prod_var >= product.min_production,
                            f"Min_Production_{product.name}_Period_{period}",
                        )
    
                    # Maximum production
                    if product.max_production is not None:
                        prob += (
                            prod_var <= product.max_production,
                            f"Max_Production_{product.name}_Period_{period}",
                        )
    
                    # Setup constraints
                    if product.setup_cost > 0:
                        setup_var = setup_vars[period][product.name]
                        # If producing, must setup
                        prob += (
                            prod_var <= (product.max_production or 1000000) * setup_var,
                            f"Setup_{product.name}_Period_{period}",
                        )
    
            # Demand constraints
            demand_by_product_period = {}
            for constraint in demand_constraints:
                period = constraint.period if constraint.period is not None else 0
                if period < horizon:
                    key = (constraint.product, period)
                    demand_by_product_period[key] = constraint
    
            # Inventory balance constraints
            for period in range(horizon):
                for product in products:
                    prod_var = production_vars[period][product.name]
                    inv_var = inventory_vars[period][product.name]
    
                    # Get demand for this product in this period
                    demand_constraint = demand_by_product_period.get((product.name, period))
                    demand = demand_constraint.min_demand if demand_constraint else 0
    
                    if period == 0:
                        # First period: production + initial_inventory = demand + ending_inventory
                        prob += (
                            prod_var == demand + inv_var,
                            f"Inventory_Balance_{product.name}_Period_{period}",
                        )
                    else:
                        # Other periods: production + previous_inventory = demand + ending_inventory
                        prev_inv_var = inventory_vars[period - 1][product.name]
                        prob += (
                            prod_var + prev_inv_var == demand + inv_var,
                            f"Inventory_Balance_{product.name}_Period_{period}",
                        )
    
                    # Maximum demand constraints
                    if demand_constraint and demand_constraint.max_demand is not None:
                        prob += (
                            prod_var + (inventory_vars[period - 1][product.name] if period > 0 else 0)
                            <= demand_constraint.max_demand + inv_var,
                            f"Max_Demand_{product.name}_Period_{period}",
                        )
    
            # Objective function
            if planning_input.objective == "maximize_profit":
                # Maximize profit = revenue - production costs - setup costs - inventory costs
                total_profit = 0.0
    
                for period in range(horizon):
                    # Production profit
                    period_profit = pulp.lpSum(
                        production_vars[period][product.name] * product.profit for product in products
                    )
                    total_profit += period_profit
    
                    # Setup costs
                    if any(product.setup_cost > 0 for product in products):
                        setup_costs = pulp.lpSum(
                            setup_vars[period][product.name] * product.setup_cost
                            for product in products
                            if product.setup_cost > 0
                        )
                        total_profit -= setup_costs
    
                    # Inventory costs
                    if planning_input.inventory_cost > 0:
                        inventory_costs = pulp.lpSum(
                            inventory_vars[period][product.name] * planning_input.inventory_cost
                            for product in products
                        )
                        total_profit -= inventory_costs
    
                prob += total_profit, "Total_Profit"
    
            elif planning_input.objective == "minimize_cost":
                # Minimize total production and setup costs
                total_cost = 0.0
    
                for period in range(horizon):
                    # Production costs (negative profit)
                    production_costs = pulp.lpSum(
                        production_vars[period][product.name] * (-product.profit)
                        for product in products
                    )
                    total_cost += production_costs
    
                    # Setup costs
                    if any(product.setup_cost > 0 for product in products):
                        setup_costs = pulp.lpSum(
                            setup_vars[period][product.name] * product.setup_cost
                            for product in products
                            if product.setup_cost > 0
                        )
                        total_cost += setup_costs
    
                prob += total_cost, "Total_Cost"
    
            elif planning_input.objective == "minimize_time":
                # Minimize total production time
                total_time = pulp.lpSum(
                    production_vars[period][product.name] * product.production_time
                    for period in range(horizon)
                    for product in products
                )
                prob += total_time, "Total_Time"
    
            # Solve
            prob.solve(pulp.PULP_CBC_CMD(msg=0))
    
            # Process results
            status = pulp.LpStatus[prob.status]
            execution_time = time.time() - start_time
    
            if prob.status == pulp.LpStatusOptimal:
                # Extract solution
                production_plan: list[dict[str, Any]] = []
                total_profit = 0.0
                total_cost = 0.0
                total_time = 0.0
                resource_utilization: dict[str, list[float]] = {}
    
                for period in range(horizon):
                    period_plan: dict[str, Any] = {
                        "period": period,
                        "products": [],
                        "resource_usage": {},
                        "period_profit": 0.0,
                        "period_cost": 0.0,
                    }
    
                    for product in products:
                        production_qty = production_vars[period][product.name].varValue or 0
                        inventory_qty = inventory_vars[period][product.name].varValue or 0
                        setup_decision = (
                            setup_vars[period][product.name].varValue if product.setup_cost > 0 else 0
                        )
    
                        product_profit = production_qty * product.profit
                        product_cost = production_qty * (-product.profit) if product.profit < 0 else 0
                        product_time = production_qty * product.production_time
    
                        period_plan["products"].append(
                            {
                                "name": product.name,
                                "production_quantity": production_qty,
                                "inventory_level": inventory_qty,
                                "setup_required": bool(setup_decision),
                                "profit": product_profit,
                                "production_time": product_time,
                            }
                        )
    
                        period_plan["period_profit"] = float(period_plan["period_profit"]) + float(
                            product_profit
                        )
                        period_plan["period_cost"] = float(period_plan["period_cost"]) + float(
                            product_cost
                        )
    
                        total_profit += float(product_profit)
                        total_cost += float(product_cost)
                        total_time += float(product_time)
    
                    # Calculate resource usage for this period
                    for resource_name, resource in resources.items():
                        usage = sum(
                            production_vars[period][product.name].varValue
                            * product.resources.get(resource_name, 0)
                            for product in products
                        )
                        period_plan["resource_usage"][resource_name] = {
                            "used": usage,
                            "available": resource.available,
                            "utilization": usage / resource.available if resource.available > 0 else 0,
                        }
    
                        if resource_name not in resource_utilization:
                            resource_utilization[resource_name] = []
                        resource_list = resource_utilization[resource_name]
                        resource_list.append(float(usage))
    
                    production_plan.append(period_plan)
    
                # Calculate summary statistics
                resource_utilization_summary: dict[str, dict[str, float]] = {}
                for resource_name, usage_list in resource_utilization.items():
                    resource = resources[resource_name]
                    resource_utilization_summary[resource_name] = {
                        "total_usage": sum(usage_list),
                        "average_utilization": sum(u / resource.available for u in usage_list)
                        / len(usage_list)
                        if resource.available > 0
                        else 0,
                        "peak_utilization": max(u / resource.available for u in usage_list)
                        if resource.available > 0
                        else 0,
                    }
    
                summary = {
                    "total_profit": total_profit,
                    "total_cost": total_cost,
                    "total_production_time": total_time,
                    "planning_horizon": horizon,
                    "resource_utilization_summary": resource_utilization_summary,
                }
    
                return OptimizationResult(
                    status=OptimizationStatus.OPTIMAL,
                    objective_value=pulp.value(prob.objective),
                    variables={"production_plan": production_plan, "summary": summary},
                    execution_time=execution_time,
                    solver_info={
                        "solver_name": "PuLP CBC",
                        "objective": planning_input.objective,
                        "num_products": len(products),
                        "num_resources": len(resources),
                        "planning_horizon": horizon,
                    },
                )
    
            elif prob.status == pulp.LpStatusInfeasible:
                return OptimizationResult(
                    status=OptimizationStatus.INFEASIBLE,
                    error_message="Production planning problem is infeasible. Check resource constraints and demand requirements.",
                    execution_time=execution_time,
                )
    
            elif prob.status == pulp.LpStatusUnbounded:
                return OptimizationResult(
                    status=OptimizationStatus.UNBOUNDED,
                    error_message="Production planning problem is unbounded.",
                    execution_time=execution_time,
                )
    
            else:
                return OptimizationResult(
                    status=OptimizationStatus.ERROR,
                    error_message=f"Solver failed with status: {status}",
                    execution_time=execution_time,
                )
    
        except Exception as e:
            return OptimizationResult(
                status=OptimizationStatus.ERROR,
                error_message=f"Production planning error: {str(e)}",
                execution_time=time.time() - start_time,
            )
  • The registration function that defines and registers the tool using the MCP decorator.
    def register_production_tools(mcp: FastMCP[Any]) -> None:
        """Register production planning optimization tools with MCP server."""
    
        @mcp.tool()
        def optimize_production_plan_tool(
            products: list[dict[str, Any]],
            resources: list[dict[str, Any]],
            periods: int,
            demand: list[dict[str, Any]],
            objective: str = "maximize_profit",
            inventory_costs: dict[str, float] | None = None,
            setup_costs: dict[str, float] | None = None,
            solver_name: str = "CBC",
            time_limit_seconds: float = 30.0,
        ) -> dict[str, Any]:
            """Optimize multi-period production planning to maximize profit or minimize costs.
    
            Args:
                products: List of product dictionaries with costs and resource requirements
                resources: List of resource dictionaries with capacity constraints
                periods: Number of planning periods
                demand: List of demand requirements per product per period
                objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time")
                inventory_costs: Optional inventory holding costs per product
                setup_costs: Optional setup costs per product
                solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX")
                time_limit_seconds: Maximum solving time in seconds (default: 30.0)
    
            Returns:
                Optimization result with optimal production plan
            """
            input_data = {
                "products": products,
                "resources": resources,
                "periods": periods,
                "demand": demand,
                "objective": objective,
                "inventory_costs": inventory_costs,
                "setup_costs": setup_costs,
                "solver_name": solver_name,
                "time_limit_seconds": time_limit_seconds,
            }
    
            result = solve_production_planning(input_data)
            result_dict: dict[str, Any] = result.model_dump()
            return result_dict
  • The call site where register_production_tools is invoked during MCP server creation to register the production tools.
    register_production_tools(mcp)
  • Pydantic schema used for validating the input data to the solver function.
    class ProductionPlanningInput(BaseModel):
        """Input schema for Production Planning."""
    
        products: list[Product]
        resources: dict[str, Resource]
        demand_constraints: list[DemandConstraint] = Field(default_factory=list)
        planning_horizon: int = Field(default=1, ge=1)
        objective: str = Field(
            default="maximize_profit",
            pattern="^(maximize_profit|minimize_cost|minimize_time)$",
        )
        allow_backorders: bool = Field(default=False)
        inventory_cost: float = Field(default=0.0, ge=0)
    
        @field_validator("products")
        @classmethod
        def validate_products(cls, v: list[Product]) -> list[Product]:
            if not v:
                raise ValueError("Must have at least one product")
            return v
    
        @field_validator("resources")
        @classmethod
        def validate_resources(cls, v: dict[str, Resource]) -> dict[str, Resource]:
            if not v:
                raise ValueError("Must have at least one resource")
            return v
    
        @field_validator("demand_constraints")
        @classmethod
        def validate_demand_constraints(cls, v: list[DemandConstraint]) -> list[DemandConstraint]:
            if not v:
                raise ValueError("Must have at least one demand constraint")
            return v

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