Supply Chains

The questions below are due on Thursday July 15, 2021; 11:00:00 AM.
 
You are not logged in.

Please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.mit.edu) to authenticate, and then you will be redirected back to this page.

Recall the Warehouse Problem from week 4, where we maintained a warehouse's inventory based on a series of transactions. We will now construct a large program that will take in customer orders and determine which ones to fulfill based on profit and available inventory. We will write our program using a series of smaller functions because that will allow us to better to test each step, and the framework for this program can be downloaded HERE.

In this folder there is also a test.py file. Running this file will run tests on all the functions. At the start, none of them will pass, but if for example you correctly complete the function compute_delivery_profit, then the tests for that function, such as test_compute_delivery_profit_1 will pass. These are the same tests that will be run on your code once you submit it.

Parsing Orders
We will be reading in the orders from csv files. A customer order will be represented by a row of four values in a csv file: an x coordinate, a y coordinate, the commodity being ordered, and the amount. For example: `10, 7, "chains", 15` would correspond to an order of 15 chains to coordinate `(10, 7)`. Your first task is to fill in the helper function `parse_orders` so that it takes in a filename and returns a list of order tuples of the form `((x coordinate, y coordinate), item, amount)`.

For example, if the input is orders_tiny.csv, the returned value should be the list [((10, 7), "chains", 15), ((1.8, -9), "anvils", 32)].

After completing this function, you should be passing Tests 1-3.

Week 3 section on reading in csv files may be a Relevant Reading.

Order Cost
We only have one warehouse from which to send deliveries, but we will have multiple modes of delivery. For example, truck deliveries are low cost per mile, but must travel along roads; whereas drones can fly in straight lines but are more expensive. Therefore, we want to have a function to calculate the cost of delivery so we can choose the best one.

Fortunately we will provide functions that take in two location tuples and provide the delivery cost. For example if we wanted to know the truck delivery cost from point (1,0) to point (5,6), we could call:

print(truck_delivery_cost((1,0),  (5,6)))

We'll want to compute potentially a lot of delivery costs reusing the same warehouse location and delivery mode, but with different customer locations. One way is to write a function that takes in those three inputs:

compute_delivery_cost(warehouse_location, delivery_cost_function, customer1_location)

compute_delivery_cost(warehouse_location, delivery_cost_function, customer2_location)

but since some of the inputs aren't changing, we can instead create a function that returns a function as mentioned in the readings.

The goal is to fill in the function make_cost_computer so that it takes in a warehouse location and a delivery mode and then returns a function. The returned function should take in a customer location and then return the delivery cost. In the end we should be able to use this function in this manner:

truck_compute_cost = make_cost_computer(warehouse_location, truck_cost_function)

truck_cost_function(customer1_location)

truck_cost_function(customer2_location)

As a concrete example, we could use the function as such:

def manhattan_cost(point1, point2):
    (x1,y1),(x2,y2) = point1, point2
    return abs(x1-x2) + abs(y1-y2)

truck_compute_cost = make_cost_computer((1,2), manhattan_cost)

print(truck_compute_cost((4,6))) # should print 7 as that is the manhattan distance from (1,2)

After completing these functions, you should be able to pass tests 4-7.

Compute Profit
We also care about how much profit a order can reap. For this we will write a function `make_profit_computer` that takes in a `warehouse_location`, a `delivery_cost_function`, and a `prices` dictionary mapping item strings (such as "anvils") to a numeric cost. This should return a function that takes in an `order_tuple` as we defined in section 1, and returns the numeric profit. Profit will be equal to the gross revenue (product of the amount of the commodity times the price) minus the delivery cost.

For example:

def manhattan_cost(point1, point2):
    (x1,y1),(x2,y2) = point1, point2
    return abs(x1-x2) + abs(y1-y2)

prices = {"anvils": 10}
truck_compute_profit = make_profit_computer((1,2), manhattan_cost, prices)

print(truck_compute_profit(((4,6), "anvils", 4)) # should print 33 as that is 4*10 sale price minus the manhattan cost of 7

You may assume that any product name that appears in an order will be in the prices dictionary.

After completing this function, you should be able to pass tests 8-10.

Sorting Orders
In python we can take advantage of the powerful built-in function, `sort`. We can take a list and call `sort` on it as such:
l = [9,3,18]
l.sort()
print(l) # [3, 9, 18] - now sorted in ascending order

By default it will modify the list to be in ascending order, but we can also provide a custom sorting function to sort using the keyword arguement key. The function should take in an element of the list, and return a value by which the element should be sorted. For example, if we want the list to be sorted in descending order, we could pass in a function to negate the value of the elements so they are sorted in reverse.

l = [9,3,18]
def negate(x):
    return -x

l.sort(key = negate)
print(l) # [18, 9, 3] - now sorted in descending order

For the next part of the program, we want a function sort_orders that can takes in a list of orders and a profit computing function returned by the make_profit_computer and modifies the orders to be sorted from largest profit to lowest. The function should return None. One way to do this is by making a helper function that takes in an order and returns a value by which to sort the orders (in this case the profit). Then we can use that function as the key argument of the sort function.

For example, if our profit computing function is defined to return just the amount of the order, then a call to sort_orders should behave as follows:

orders = [((10, 7), "chains", 15), ((1.8, -9), "anvils", 32), ((3, 7.5), "chains", 13)]
profit_computer = lambda x: x[2]

result = sort_orders(orders, profit_computer)
'''
result should be None, but now orders should be modified to:
[((1.8, -9), "anvils", 32), ((10, 7), "chains", 15), ((3, 7.5), "chains", 13)]
which is sorted in order according to the amounts of each order, with the largest amount first.

After completing this function, you should be able to pass tests 11-14.

Selecting Orders
Now it's time for our overall, final function! We will make a function to take in five arguments: an `orders_filename` string, an `inventory` dictionary, a `prices` dictionary, a `warehouse_location`, and a `delivery_cost_function`. This function should return a list of order tuples that should be fulfilled (the order of the output is not important).

To determine the orders to fulfill, we will prioritize the most profitable orders and fulfill them as long as there is sufficient inventory stock. One approach is to first sort the orders by profit and then check in order if there is enough inventory. If there is, we should add the order to our output list and update the inventory to reflect the current available amount of that commodity.

Note, we should not fulfill any orders with a negative profit. Also, if an item is not in the inventory, then we should treat that situation as having 0 of that item.

def manhattan_cost(point1, point2):
    (x1,y1),(x2,y2) = point1, point2
    return abs(x1-x2) + abs(y1-y2)

prices = {"anvils": 1.2, "chains": 1.2}
inventory = {"anvils": 20, "chains": 15, "pipes":5}

answer = select_orders("orders_small.csv", inventory, prices, (1,2), manhattan_cost)
'''
answer should be [((1,3),"chains",10)] since the profit for that order is 10*1.2-1 = 11, while the order ((10,7),"chains",20) has profit 1.2*20 - 14 = 10, and there's not enough inventory for both. Finally, the order ((1.8,-9),"anvils",32) cannot be fulfilled due to inventory constraints.
'''

 No file selected

Please upload your code again here. This question will be manually graded for good code style. Remember to use succinct, descriptive variable names and comments to explain confusing lines.
 No file selected

Next Exercise: Survey

Back to exercises