Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
DataScienceUWL
GitHub Repository: DataScienceUWL/DS775
Path: blob/main/Lessons/Lesson 03 - LP 3/Lesson_03.ipynb
871 views
Kernel: Python 3 (system-wide)
# EXECUTE FIRST # computational imports from pyomo.environ import * import pandas as pd # for playing videos, customize height and width if desired def play_video(vid_name, w=640, h=360): from IPython.display import display, IFrame from IPython.core.display import HTML vid_path = "https://media.uwex.edu/content/ds/ds775_r19/" + vid_name + "/index.html" print(vid_path) hlink = '<a href = ' + vid_path + ' target = """_blank""">Open video in new tab</a>' display(IFrame(vid_path, width=w, height=h)) display(HTML(hlink))

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

# execute to play video play_video("ds775_lesson3_transportation-overview")

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 CC be the set of canneries and let WW be the set of warehouses.

Decision Variables: let xc,wx_{c,w} be the number of units shipped from cannery cCc \in C to warehouse wWw \in W

Constants:

  • qc,wq_{c,w} is the shipping cost per unit between cannery cCc \in C and warehouse wWw \in W

  • dwd_w is the number of units demanded by warehouse wWw \in W

  • scs_c is the number of units supplied by cannery cCc \in C

Objective Function: minimize Cost =cCwWqc,wxc,w= \displaystyle \sum_{c \in C} \sum_{w \in W} q_{c,w} x_{c,w}

Constraints:

  • Supply: wWxc,w=sc, for each cC \displaystyle \sum_{w \in W} x_{c,w} = s_c, \mbox{ for each } c \in C

  • Demand: cCxc,w=dw, for each wW \displaystyle \sum_{c \in C} x_{c,w} = d_w, \mbox{ for each } w \in W

  • Nonnegativity: xc,w0x_{c,w} \geq 0 for each cC,wWc \in C, w \in W

# execute for video play_video("ds775_lesson3_understanding-the-sums")

Pyomo Solution Walkthrough

Here is a video that walks through the Pyomo solution for the prototype example. The code is below the video.

# execute for video play_video("ds775_lesson3_transportation-pyomo-walkthrough")
# basic transportation code from pyomo.environ import* ### PROBLEM DATA ### # load data canneries = ['can1', 'can2','can3'] warehouses = ['ware1','ware2','ware3','ware4'] supply = [75, 125, 100] demand = [80, 65, 70, 85] unit_ship_cost = [[464, 513, 654, 867], [352, 416, 690, 791], [995, 682, 388, 685] ] # parse dictionaries supply_dict = dict( zip( canneries, supply) ) demand_dict = dict( zip( warehouses, demand)) unit_ship_cost_dict = { c: {w: unit_ship_cost[i][j] for j,w in enumerate(warehouses) } for i,c in enumerate(canneries)} ### MODEL CONSTRUCTION ### # declaration model = ConcreteModel() # decision variables model.transp = Var(canneries, warehouses, domain=NonNegativeReals) # objective model.total_cost = Objective(expr=sum(unit_ship_cost_dict[c][w] * model.transp[c, w] for c in canneries for w in warehouses), sense=minimize) # constraints model.supply_ct = ConstraintList() for c in canneries: model.supply_ct.add( sum(model.transp[c, w] for w in warehouses) == supply_dict[c]) model.demand_ct = ConstraintList() for w in warehouses: model.demand_ct.add( sum(model.transp[c, w] for c in canneries) == demand_dict[w]) ### SOLUTION ### solver = SolverFactory('glpk') solver.solve(model) ### OUTPUT ### print(f"Minimum Total Cost = ${model.total_cost():,.2f}") # put amounts in dataframe for nicer display import pandas as pd dvars = pd.DataFrame([[model.transp[c, w]() for w in warehouses] for c in canneries], index=canneries, columns=warehouses) print("Number of truckloads to ship from each cannery to each warehouse:") dvars
Minimum Total Cost = $152,535.00 Number of truckloads to ship from each cannery to each warehouse:

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.

# execute for video play_video("ds775_lesson3_balanced-vs-unbalanced")

Unbalanced transportation problems can always be formulated as:

Objective Function: minimize Cost =cCwWqc,wxc,w= \displaystyle \sum_{c \in C} \sum_{w \in W} q_{c,w} x_{c,w}

Constraints:

  • Supply: wWxc,wsc, for each cC \displaystyle \sum_{w \in W} x_{c,w} \leq s_c, \mbox{ for each } c \in C

  • Demand: cCxc,wdw, for each wW \displaystyle \sum_{c \in C} x_{c,w}\geq d_w, \mbox{ for each } w \in W

  • Nonnegativity: xc,w0x_{c,w} \geq 0 for each cC,wWc \in C, w \in W

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:

  1. 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.

  2. Adjust the supply constraint so that the total amount shipped at each mill is less than or equal to (\leq) 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.

# transportation including capacity constraints from pyomo.environ import* ### PROBLEM DATA ### # load data canneries = ['can1', 'can2','can3'] warehouses = ['ware1','ware2','ware3','ware4'] supply = [75, 125, 100] demand = [80, 65, 70, 85] unit_ship_cost = [[464, 513, 654, 867], [352, 416, 690, 791], [995, 682, 388, 685] ] bigM = 300 capacities = [[60,60,60,0],[60,60,60,60],[60,60,bigM,60]] # parse dictionaries supply_dict = dict( zip( canneries, supply) ) demand_dict = dict( zip( warehouses, demand)) unit_ship_cost_dict = { c: {w: unit_ship_cost[i][j] for j,w in enumerate(warehouses) } for i,c in enumerate(canneries)} capacities_dict = { c: {w: capacities[i][j] for j,w in enumerate(warehouses) } for i,c in enumerate(canneries)} ### MODEL CONSTRUCTION ### # declaration model = ConcreteModel() # decision variables model.transp = Var(canneries, warehouses, domain=NonNegativeReals) # objective model.total_cost = Objective(expr=sum(unit_ship_cost_dict[c][w] * model.transp[c, w] for c in canneries for w in warehouses), sense=minimize) # constraints model.supply_ct = ConstraintList() for c in canneries: model.supply_ct.add( sum(model.transp[c, w] for w in warehouses) == supply_dict[c]) model.demand_ct = ConstraintList() for w in warehouses: model.demand_ct.add( sum(model.transp[c, w] for c in canneries) == demand_dict[w]) model.capacity_ct = ConstraintList() for c in canneries: for w in warehouses: model.capacity_ct.add( model.transp[c,w] <= capacities_dict[c][w] ) ### SOLUTION ### solver = SolverFactory('glpk') solver.solve(model) ### OUTPUT ### print(f"Minimum Total Cost = ${model.total_cost():,.2f}") # put amounts in dataframe for nicer display import pandas as pd dvars = pd.DataFrame([[model.transp[c, w]() for w in warehouses] for c in canneries], index=canneries, columns=warehouses) print("Number of truckloads to ship from each cannery to each warehouse:") dvars
Minimum Total Cost = $153,990.00 Number of truckloads to ship from each cannery to each warehouse:

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:

  1. 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.

  2. 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.

  3. 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 M=10000M = 10000 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?

# self-assessment big M method solution from pyomo.environ import* ### PROBLEM DATA ### # load data canneries = ['can1', 'can2','can3'] warehouses = ['ware1','ware2','ware3','ware4'] supply = [75, 125, 100] demand = [80, 65, 70, 85] bigM = 10000 unit_ship_cost = [[464, bigM, 654, 867], [bigM, 416, 690, 791], [995, 682, bigM, 685] ] # parse dictionaries supply_dict = dict( zip( canneries, supply) ) demand_dict = dict( zip( warehouses, demand)) unit_ship_cost_dict = { c: {w: unit_ship_cost[i][j] for j,w in enumerate(warehouses) } for i,c in enumerate(canneries)} ### MODEL CONSTRUCTION ### # declaration model = ConcreteModel() # decision variables model.transp = Var(canneries, warehouses, domain=NonNegativeReals) # objective model.total_cost = Objective(expr=sum(unit_ship_cost_dict[c][w] * model.transp[c, w] for c in canneries for w in warehouses), sense=minimize) # constraints model.supply_ct = ConstraintList() for c in canneries: model.supply_ct.add( sum(model.transp[c, w] for w in warehouses) == supply_dict[c]) model.demand_ct = ConstraintList() for w in warehouses: model.demand_ct.add( sum(model.transp[c, w] for c in canneries) == demand_dict[w]) ### SOLUTION ### solver = SolverFactory('glpk') solver.solve(model) ### OUTPUT ### print(f"Minimum Total Cost = ${model.total_cost():,.2f}") # put amounts in dataframe for nicer display import pandas as pd dvars = pd.DataFrame([[model.transp[c, w]() for w in warehouses] for c in canneries], index=canneries, columns=warehouses) print("Number of truckloads to ship from each cannery to each warehouse:") dvars
Minimum Total Cost = $176,000.00 Number of truckloads to ship from each cannery to each warehouse:

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.

for p in products: for s in suppliers: sum( model.transp[p,s,c] for c in customers) == supply[p,s]

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:

for s in supplier: for c in customer: sum( model.transp[p,s,c] for p in products ) <= capacity )

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:

  1. 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.

  2. For the larger set of jobs or workers change the supply or demand constraint to use "\leq" 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:

LaTeX\LaTeX

The MM 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:

machines = ['mac1', 'mac2', 'mac3', 'macD'] supply = dict(zip(machines, [1, 1, 1, 1])) locations = ['loc1', 'loc2', 'loc3', 'loc4'] demand = dict(zip(locations, [1, 1, 1, 1])) bigM = 1000 cost_list = [[13,16,12,11],[15,bigM,13,20],[5,7,10,6],[0,0,0,0]] cost = { machines[m]: {locations[l]: cost_list[m][l] for l in range(len(locations))} for m in range(len(machines)) } # throw an error if total supply and demand do not match assert (sum(supply.values()) == sum(demand.values())) model = ConcreteModel() model.assign= Var(machines, locations, domain=NonNegativeReals) model.total_cost = Objective(expr=sum(cost[m][l] * model.assign[m, l] for m in machines for l in locations), sense=minimize) model.supply_ct = ConstraintList() for m in machines: model.supply_ct.add( sum(model.assign[m, l] for l in locations) == supply[m]) model.demand_ct = ConstraintList() for l in locations: model.demand_ct.add( sum(model.assign[m, l ] for m in machines) == demand[l]) # solve and display solver = SolverFactory('glpk') solver.solve(model) # display solution print(f"Minimum Cost per hour = ${model.total_cost():,.2f}") # put amounts in dataframe for nicer display dvars = pd.DataFrame([[model.assign[m, l]() for l in locations] for m in machines], index = machines, columns=locations) print("Machine assignments to locations:") dvars
Minimum Cost per hour = $29.00 Machine assignments to locations:

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:

taskdurationmust be done before
masonry35carpentry, plumbing, ceiling
carpentry15roofing
plumbing40facade, garden
ceiling15painting
roofing5windows, facade, garden
painting10moving
windows5moving
facade10moving
garden5moving
moving5

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:

taskdurationmust be done before
masonry35carpentry, plumbing, ceiling, final
carpentry15roofing, final
plumbing40facade, garden, final
ceiling15painting, final
roofing5windows, facade, garden, final
painting10moving, final
windows5moving, final
facade10moving, final
garden5moving, final
moving5final
final0

To set up a linear program we introduce variables that represent the start time of each task: xmasonry,xcarpentry,,xfinal.x_{\mbox{masonry}}, x_{\mbox{carpentry}}, \ldots, x_{\mbox{final}}. Since the final task has zero duration, xfinalx_{\mbox{final}} 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 xfinalx_{\mbox{final}} and the objective function is simply Z=xfinal. Z = x_{\mbox{final}}.

To arrange the constraints consider the finish time for each task. For example, masonry takes 35 days so its finish time is xmasonry+35x_{\mbox{masonry}} + 35. 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: xmasonry+35xcarpentry.x_{\mbox{masonry}} + 35 \leq x_{\mbox{carpentry}}. The other precedence constraints are similar.

Here is what that looks like in Pyomo:

from pyomo.environ import * import pandas as pd ### PROBLEM DATA ### tasks = ['masonry','carpentry','plumbing','ceiling','roofing','painting','windows','facade','garden','moving','final'] durations = [35, 15, 40, 15, 5, 10, 5, 10 ,5 , 5, 0] num_tasks = len(tasks) task_duration_dict = dict( zip( tasks, durations ) ) # for each task we have a list of tasks that must go after ... task:['these','tasks','after'] # this is typed out for clarity precedence_dict = { 'masonry': ['carpentry', 'plumbing', 'ceiling','final'], 'carpentry': ['roofing','final'], 'plumbing': ['facade', 'garden','final'], 'ceiling': ['painting','final'], 'roofing': ['windows', 'facade', 'garden','final'], 'painting': ['moving','final'], 'windows': ['moving','final'], 'facade': ['moving','final'], 'garden': ['moving','final'], 'moving': ['final'] } M = ConcreteModel(name = "Schedule") M.start = Var(tasks, domain = NonNegativeReals) M.length = Objective( expr = M.start['final'], sense = minimize ) M.precedence_ct = ConstraintList() for before in precedence_dict.keys(): for after in precedence_dict[before]: M.precedence_ct.add( M.start[before] + task_duration_dict[before] <= M.start[after]) ### Solution ### solver = SolverFactory('glpk') solver.solve(M) ### Display ### print(f"Total Time = {M.length():,.0f}\n") for t in tasks: print(f"Start {t} at {M.start[t]():.0f} and end at {M.start[t]()+task_duration_dict[t]:.0f}")
Total Time = 90 Start masonry at 0 and end at 35 Start carpentry at 35 and end at 50 Start plumbing at 35 and end at 75 Start ceiling at 35 and end at 50 Start roofing at 50 and end at 55 Start painting at 50 and end at 60 Start windows at 55 and end at 60 Start facade at 75 and end at 85 Start garden at 75 and end at 80 Start moving at 85 and end at 90 Start final at 90 and end at 90

To study and understand this model it might be helpful to print out the constraints:

M.precedence_ct.pprint()
precedence_ct : Size=24, Index=precedence_ct_index, Active=True Key : Lower : Body : Upper : Active 1 : -Inf : start[masonry] + 35 - start[carpentry] : 0.0 : True 2 : -Inf : start[masonry] + 35 - start[plumbing] : 0.0 : True 3 : -Inf : start[masonry] + 35 - start[ceiling] : 0.0 : True 4 : -Inf : start[masonry] + 35 - start[final] : 0.0 : True 5 : -Inf : start[carpentry] + 15 - start[roofing] : 0.0 : True 6 : -Inf : start[carpentry] + 15 - start[final] : 0.0 : True 7 : -Inf : start[plumbing] + 40 - start[facade] : 0.0 : True 8 : -Inf : start[plumbing] + 40 - start[garden] : 0.0 : True 9 : -Inf : start[plumbing] + 40 - start[final] : 0.0 : True 10 : -Inf : start[ceiling] + 15 - start[painting] : 0.0 : True 11 : -Inf : start[ceiling] + 15 - start[final] : 0.0 : True 12 : -Inf : start[roofing] + 5 - start[windows] : 0.0 : True 13 : -Inf : start[roofing] + 5 - start[facade] : 0.0 : True 14 : -Inf : start[roofing] + 5 - start[garden] : 0.0 : True 15 : -Inf : start[roofing] + 5 - start[final] : 0.0 : True 16 : -Inf : start[painting] + 10 - start[moving] : 0.0 : True 17 : -Inf : start[painting] + 10 - start[final] : 0.0 : True 18 : -Inf : start[windows] + 5 - start[moving] : 0.0 : True 19 : -Inf : start[windows] + 5 - start[final] : 0.0 : True 20 : -Inf : start[facade] + 10 - start[moving] : 0.0 : True 21 : -Inf : start[facade] + 10 - start[final] : 0.0 : True 22 : -Inf : start[garden] + 5 - start[moving] : 0.0 : True 23 : -Inf : start[garden] + 5 - start[final] : 0.0 : True 24 : -Inf : start[moving] + 5 - start[final] : 0.0 : True

Note that these constraints have been rearranged by Pyomo. The first says: <xmasonry+35xcarpentry0- \infty < x_{\mbox{masonry}} + 35 - x_{\mbox{carpentry}} \leq 0

We can ignore the lower bound and rearrange this to get: xmasonry+35xcarpentry.x_{\mbox{masonry}} + 35 \leq x_{\mbox{carpentry}}.

Note that the remaining constraints can be rearranged similarly.