Path: blob/main/Lessons/Lesson 03 - LP 3/Lesson_03.ipynb
871 views
Lesson 03: Linear Programming Applications
To familiarize you with some linear programming applications as well as to get additional practice at abstract modeling, we will present three different applications in this lesson:
transportation problems - moving goods from "suppliers" to "demanders"
assignment problems - assigning tasks to workers
scheduling problems - creating a schedule of minimal length from multiple events
Transportation Problems
Transportation problems are characterized by moving goods or services from a set of "suppliers" to a set of "demanders". The worker-days problem we encountered in Lessons 1 and 2 can be thought of as a transportation problem. the "suppliers" are the workers, the "demanders" are the days of the week on which workers must be scheduled, and the objects that are supplied are the hours of labor supplied by each worker. The Self Assessment problem about the Medequip Company near the end of Lesson 2 is also a transportation problem where diagnostic equipment is supplied to customers who "demand" it.
Prototypical Transportation Example from Textbook
You should read the complete details of this problem beginning on page 319 of the textbook. In short, we want to transport truckloads of canned peas from canneries to warehouses while minimizing the total shipping cost. The supply (Output), demand (Allocation), and shipping cost per truckload are shown in this table:
We'll use this example to demonstrate the main concepts in transportation problems.
Features of Transportation Problems
Integer Solutions Property
For transportation and assignment problems if the amounts supplied and demanded are integer valued, then the solutions will always be integers.
This is super important because it allows us to use the Simplex method or other LP solvers to achieve integer valued solutions even if we allow real numbers for the decision variables. If we restrict the decision variables to be integer valued, then the solution procedure is much more computationally intensive. We'll discuss this in a later lesson about Integer Programming.
Mathematical Formulation
Mathematically we can frame the problem as below (identical to page 324). Just below this cell is a video that discusses this formulation.
Let be the set of canneries and let be the set of warehouses.
Decision Variables: let be the number of units shipped from cannery to warehouse
Constants:
is the shipping cost per unit between cannery and warehouse
is the number of units demanded by warehouse
is the number of units supplied by cannery
Objective Function: minimize Cost
Constraints:
Supply:
Demand:
Nonnegativity: for each
Pyomo Solution Walkthrough
Here is a video that walks through the Pyomo solution for the prototype example. The code is below the video.
Balanced vs Unbalanced Transportation Problems
In the video below we present two approaches for dealing with unbalanced transportation problems in which the total supply exceeds the total demand.
Is balancing necessary? Generally the only reason to balance a transportation problem is to make use of a special purpose solver for the linear program that is faster than the regular approach (the simplex method), but this isn't necessary except for very large problems. This specialized algorithm can be read about in the textbook.
Unbalanced transportation problems can always be formulated as:
Objective Function: minimize Cost
Constraints:
Supply:
Demand:
Nonnegativity: for each
As long as the objective is to minimize cost there will be no unnecessary products shipped.
Self Assessment: Unbalanced Transportation Problem
This is the same as the supply and demand self-assessment problem from Lesson 2 except that the demand has been reduced at one of the medical centers to produce an unbalanced problem.
The Medequip Company produces precision medical diagnostic equipment at two factories. Three medical centers have placed orders for this month’s production output. The table below shows what the cost would be for shipping each unit from each factory to each of these customers. Also shown are the number of units that will be produced at each factory and the number of units ordered by each customer.
Customer 1 | Customer 2 | Customer 3 | Output | |
---|---|---|---|---|
Factory 1 | \$600 | \$800 | \$700 | 400 units |
Factory 2 | \$400 | \$900 | \$600 | 500 units |
Order size | 300 units | 200 units | 300 units |
Find the minimum total shipping cost two different ways:
Introduce a dummy (extra) distribution center with demand of 100 to balance supply and demand and set all costs to zero for shipping to the dummy center. You can use the standard balanced code for this approach.
Adjust the supply constraint so that the total amount shipped at each mill is less than or equal to () the supply amount at the distribution center.
Incorporating Route Capacities
There may be limits on how much can be shipped along some or all of the routes (edges of the graph). Suppose our capacities are limited as per the following table.
In this case we have limited the capacity on most routes to 60 truckloads, but one route has effectively unlimited capacity, and one is infeasible with zero capacity. For the unlimited route we set the capacity to be very large so that the amount shipped is not restricted. The code below illustrates how to incorporate these route capacities.
Dealing with Infeasible Routes
It often happens in transportation problems that shipping is not available on along certain routes due to problems or prohibitive costs. In the previous example the route between cannery 1 and warehouse 4 was infeasible which was handled by setting the maximum capacity on that route to zero.
There are at least three approaches to dealing with infeasible routes:
Enforce infeasible routes by setting the maximum capacity to be zero on those routes like in the previous example. To make a route have unlimited capacity simply set the maximum capacity to be a larger value than any amount that will be transported along that route. This approach was demonstrated in the example just above.
If there is a mix of only infeasible and unlimited routes then it is possible to "trick" the model into making some routes infeasible by setting the cost to be a very large number for the routes that should be infeasible. This is sometimes called the "big M" method where the cost on infeasible routes is set to a very large number M. We'll show that approach in the next example. We describe this method because it's in the textbook, but in practice it's probably best to use one of the other approaches as both are more robust.
Assume that the amounts shipped on infeasible routes are zero and eliminate decision variables for those routes. This approach may be necessary for very large problems with many infeasible routes (a sparse problem) to reduce the number of decision variables in the solution. Details of this approach are beyond the scope of our course.
Self-Assessment: Big M Method
Consider the example above where three of the routes are rendered infeasible as shown in the table below:
Start with the code above that is labeled "basic transportation code". Set the costs along the infeasible routes to be and solve the linear program. Does your solution have zero amounts along the infeasible routes? The minimum cost should now be higher than before. Why does that make sense?
Transporting Multiple Products
The homework includes a problem in which you transport multiple products from suppliers to customers. This brief section gives you a couple of hints about how to set that up.
To move toward making a more realistic problem we consider a transportation problem in which multiple products are transported from suppliers to customers. To accomplish this, our decision variables will need to be indexed by three sets: product, supplier, and customer. Like this:
model.transp[ product, supplier, customer ]
You can think of this as a three-dimensional array which in turn can be thought of as a stack of two-dimensional arrays:
Image from c-programmingbooks.blogspot.com
The 0th 2D array corresponds to the first product while the rows and columns of that array correspond to suppliers and customers respectively. According to the picture, 4 units of product 1 are shipped from supplier number 1 to customer number 2. To find the cost of shipping the products we have to sum the cost per unit times the number of units over all the elements in the three-dimensional array, like this:
sum(cost[p,s,c] * model.transp[p,s,c] for p in products for s in suppliers for c in customers)
A supply constraint means that total amount of each product shipped from each supplier must match the supply available.
In the picture above this corresponds to summing each row of the stacked 2D arrays to make sure it matches the supplied amount.
If limited capacity is available for shipping from each supplier to each customer we have to add up the total amount of all products to be sure it isn't too large:
Suppose we are solving an inventory problem for a large retail chain in which the suppliers are warehouses or distribution centers and the customers are the individual retail stores or outlets. Each warehouse only serves a subset of the stores to minimize shipping costs. We won't need decision variables for those routes that are infeasible, so we'll use Technique 3 to reduce the number of decision variables. You will explore a multi-product problem in the homework.
Assignment Problems
We like to think of an assignment problem as a transportation problem in which we are transporting objects to destinations. Assigning workers to jobs can be transporting workers to jobs where each worker has a supply of 1 and each job has a demand of 1. If the number of workers doesn't match the number of jobs then we can either:
Add dummy workers or dummy jobs with zero cost to make the number of workers and jobs the same. The advantage to a balanced problem is that a faster specialized algorithm can be used than if the problem is unbalanced.
For the larger set of jobs or workers change the supply or demand constraint to use "" instead of "".
The second approach works with any linear program solver, but doesn't match the efficiency of the special algorithm approach. However, the increased inefficiency isn't necessary except for very large assignment problems.
Prototypical Assignment Problem
This problem is described completely on page 348 of the textbook. We're including it here as an example to show how to solve it using Pyomo. In short, we're assigning 3 machines to 4 locations. Because this is imbalanced, a 4th dummy machine is added. Each machine has an hourly cost that depends on the location. Machine 2 cannot be used in location 2 and there is no cost associated with assigning the dummy machine to any location since this just means that no machine is installed at that location. The mathematical formulation is identical to that of the transportation problem using a supply and demand of 1 at each machine and location. The cost table is shown here for convenience:
The will be a very large value ("big M") to prevent an assignment of machine 2 to location 2.
Here is a Pyomo solution that uses a dummy machine to match the sizes of the two sets:
Self-Assessment: Unbalanced assignment problem without dummies
Solve the prototypical assignment problem above without introducing any dummy machines or locations. You'll need to slightly adjust one or more constraints.
Scheduling Problems
A common application for constraint programming is to generate feasible schedules of workers to shifts, sports league play schedules, processing jobs to machines, etc. We will use a linear programming approach to solve some scheduling problems, but another commonly used tool is constraint programming which is discussed briefly in textbook section 12.9.
A Housebuilding Example
We have a housebuilding problem in which there are 10 tasks of fixed size, each of which needs to be assigned a starting time in such a way as to minimize the total time. Moreover, there are precedence constraints wherein some tasks must be completed before others are started. The table below summarizes the durations and precedence constraints:
task | duration | must be done before |
---|---|---|
masonry | 35 | carpentry, plumbing, ceiling |
carpentry | 15 | roofing |
plumbing | 40 | facade, garden |
ceiling | 15 | painting |
roofing | 5 | windows, facade, garden |
painting | 10 | moving |
windows | 5 | moving |
facade | 10 | moving |
garden | 5 | moving |
moving | 5 |
In this example it's pretty clear that the final task will be "moving", but in general we may not know which task is the final task. To let the code determine the final task we add an artificial task called "final" that takes zero duration and we arrange the precedence constraints so that every task occurs before the final task. Like this:
task | duration | must be done before |
---|---|---|
masonry | 35 | carpentry, plumbing, ceiling, final |
carpentry | 15 | roofing, final |
plumbing | 40 | facade, garden, final |
ceiling | 15 | painting, final |
roofing | 5 | windows, facade, garden, final |
painting | 10 | moving, final |
windows | 5 | moving, final |
facade | 10 | moving, final |
garden | 5 | moving, final |
moving | 5 | final |
final | 0 |
To set up a linear program we introduce variables that represent the start time of each task: Since the final task has zero duration, will be the same as the total time to complete all of the tasks. To minimize the total time we simply minimize the value of and the objective function is simply
To arrange the constraints consider the finish time for each task. For example, masonry takes 35 days so its finish time is . We also know that masonry must be completed before carpentry so that the finish time for masonry is less than or equal to the start time for carpentry: The other precedence constraints are similar.
Here is what that looks like in Pyomo:
To study and understand this model it might be helpful to print out the constraints:
Note that these constraints have been rearranged by Pyomo. The first says:
We can ignore the lower bound and rearrange this to get:
Note that the remaining constraints can be rearranged similarly.