基于Pyomo的扩展运输问题建模问询:阶梯函数型单位入库成本
Alright, let's walk through building this extended transportation problem with Pyomo's abstract model—especially tackling that tricky step-function inbound cost. I've broken this down into actionable steps that align with your requirements:
First, let's clarify the key pieces we need to model:
- Decision Variables: The quantity of goods transported between each pair of warehouses (let's call this
x[i,j]for shipments from warehouseitoj). - Cost Types:
- Line haul cost: Per-unit cost for moving goods directly between warehouses.
- Outbound cost: Per-unit cost for shipping goods out of a warehouse (regardless of destination).
- Inbound cost: Per-unit cost that changes based on the total quantity received at a warehouse (the step-function cost from your CSV).
- Objective: Minimize the sum of all three cost types.
Abstract models in Pyomo are great here because they let you separate the problem structure from your specific data (like warehouse lists or cost values).
2.1 Define sets and parameters
First, we'll define the sets (groups of items) and parameters (fixed values) for our model:
from pyomo.environ import * import pandas as pd # Initialize the abstract model model = AbstractModel() # --- Sets --- # List of all warehouses model.Warehouses = Set() # Valid transportation routes (pairs of warehouses i -> j) model.Routes = Set(within=model.Warehouses * model.Warehouses) # --- Fixed cost parameters --- # Per-unit line haul cost for each route model.linehaul_cost = Param(model.Routes, within=NonNegativeReals) # Per-unit outbound cost for each warehouse model.outbound_cost = Param(model.Warehouses, within=NonNegativeReals) # --- Step-function inbound cost parameters (loaded from CSV) --- # Load your CSV file (columns: Quantity_Low, Quantity_High, Unit_Cost) inbound_df = pd.read_csv('inbound_cost_steps.csv') # Create a list of step indices inbound_steps = list(range(len(inbound_df))) # Define quantity breakpoints (add infinity as the upper bound for the last step) inbound_qty_breakpoints = list(inbound_df['Quantity_Low']) + [float('inf')] # Map each step to its unit cost inbound_unit_costs = dict(zip(inbound_steps, inbound_df['Unit_Cost'])) # Add these to the model as parameters model.Inbound_Steps = Set(initialize=inbound_steps) model.inbound_qty_breakpoints = Param(initialize=inbound_qty_breakpoints) model.inbound_unit_costs = Param(model.Inbound_Steps, initialize=inbound_unit_costs)
2.2 Define decision variables
Next, we'll define our main decision variable (transportation quantities) plus helper variables to handle the step inbound cost:
# Main variable: quantity transported from i to j model.x = Var(model.Routes, within=NonNegativeReals) # Helper variable: total inbound quantity for each warehouse j model.inbound_total = Var(model.Warehouses, within=NonNegativeReals) # Constraint to calculate inbound total as sum of all shipments to j def calc_inbound_total(model, j): return model.inbound_total[j] == sum(model.x[i,j] for i in model.Warehouses if (i,j) in model.Routes) model.Inbound_Total_Constraint = Constraint(model.Warehouses, rule=calc_inbound_total) # Helper variable: total inbound cost for each warehouse j model.inbound_cost_total = Var(model.Warehouses, within=NonNegativeReals)
2.3 Implement the step-function inbound cost
This is the most critical part. Pyomo's Piecewise component makes it easy to model step-function costs without manually writing a bunch of binary variables. We'll use it to link the total inbound quantity to the total inbound cost:
def inbound_cost_rule(model, j): return Piecewise( model.inbound_cost_total[j], # Dependent variable (total cost) model.inbound_total[j], # Independent variable (total quantity) pw_pts=model.inbound_qty_breakpoints, # Quantity breakpoints from CSV f_rule=model.inbound_unit_costs, # Unit cost for each step pw_constr_type='EQ', # Enforce equality between cost and step function pw_repn='CC' # Continuous convex representation (ideal for step costs) ) model.Inbound_Cost_Piecewise = Constraint(model.Warehouses, rule=inbound_cost_rule)
2.4 Define the objective function
Now we'll write the objective to minimize total cost, which is the sum of line haul, outbound, and inbound costs:
def total_cost_rule(model): # Calculate total line haul cost linehaul_total = sum(model.linehaul_cost[i,j] * model.x[i,j] for (i,j) in model.Routes) # Calculate total outbound cost (sum of all shipments out of each warehouse * per-unit cost) outbound_total = sum( model.outbound_cost[j] * sum(model.x[j,k] for k in model.Warehouses if (j,k) in model.Routes) for j in model.Warehouses ) # Calculate total inbound cost (sum of helper variable values) inbound_total = sum(model.inbound_cost_total[j] for j in model.Warehouses) return linehaul_total + outbound_total + inbound_total # Set the objective to minimize total cost model.Total_Cost = Objective(rule=total_cost_rule, sense=minimize)
2.5 Add flow constraints (optional but common)
Depending on your specific problem, you might need flow balance constraints. For example, if this is a transshipment problem (no inventory stored at warehouses), add:
def flow_balance_rule(model, j): # Total inbound = total outbound for each warehouse return sum(model.x[i,j] for i in model.Warehouses if (i,j) in model.Routes) == sum(model.x[j,k] for k in model.Warehouses if (j,k) in model.Routes) model.Flow_Balance = Constraint(model.Warehouses, rule=flow_balance_rule)
If you have supply/demand constraints (e.g., some warehouses have fixed supply, others fixed demand), adjust this accordingly.
Abstract models require a data source (like a .dat file or Python dictionary) to create a concrete instance. Here's an example .dat file (transport_data.dat):
set Warehouses := W1 W2 W3; set Routes := (W1,W2) (W1,W3) (W2,W1) (W2,W3) (W3,W1) (W3,W2); param linehaul_cost := (W1,W2) 2.5 (W1,W3) 3.0 (W2,W1) 2.5 (W2,W3) 1.8 (W3,W1) 3.0 (W3,W2) 1.8; param outbound_cost := W1 1.2 W2 1.0 W3 1.5;
Then load the data and solve:
# Create a concrete instance from the abstract model and data file instance = model.create_instance('transport_data.dat') # Choose a solver (GLPK is open-source; use Gurobi/CPLEX for larger models) solver = SolverFactory('glpk') result = solver.solve(instance) # Print results print(f"Total Minimum Cost: ${instance.Total_Cost():.2f}") print("\nTransportation Quantities:") for (i,j) in instance.Routes: print(f"From {i} to {j}: {instance.x[i,j]():.2f} units") print("\nInbound Details per Warehouse:") for j in instance.Warehouses: print(f"{j}: Inbound = {instance.inbound_total[j]():.2f} units | Inbound Cost = ${instance.inbound_cost_total[j]():.2f}")
- Per-warehouse step costs: If each warehouse has its own step-function inbound cost, modify the
inbound_unit_costsparameter to be indexed by bothWarehousesandInbound_Steps, then load CSV data per warehouse. - Step interval handling: Ensure your CSV's quantity intervals align with the
pw_ptssetup. For example, if your first step is 0-100 units, set the first breakpoint to 0, next to 100, etc. - Solver choice: For large-scale models, commercial solvers like Gurobi or CPLEX will perform much better than open-source options like GLPK.
内容的提问来源于stack exchange,提问作者Kreator

