A firm makes seven products $1,\ldots,7$ on the following machines: 4 grinders, 2 vertical drills, 3 horizontal drills, 1 borer, and 1 planer.
Each product yields a certain contribution to the profit (defined as selling price minus cost of raw materials expressed in Euro/unit). These quantities (in Euro/unit) together with the production times (hours/unit) required on each process are given below.
 product     1      2      3      4      5      6     7
 profit      10     6      8      4      11     9     3
 grinding    0.5    0.7    0      0      0.3    0.2   0.5
 vdrill      0.1    0.2    0      0.3    0      0.6   0
 hdrill      0.2    0      0.8    0      0      0     0.6
 boring      0.05   0.03   0      0.07   0.1    0     0.08
 planning    0      0      0.01   0      0.05   0     0.05
In the first month (January) and the five subsequent months certain machines will be down for maintenance. These machines will be:
 January   1 grinder
 February  2 hdrill
 March     1 borer
 April     1 vdrill
 May       1 grinder
 May       1 vdrill
 June      1 planer
 June      1 hdrill
There are marketing limitations on each product in each month. That is, in each month the amount sold for each product cannot exceed these values:
 product    1     2      3     4     5      6     7
 January    500   1000   300   300   800    200   100
 February   600   500    200   0     400    300   150
 March      300   600    0     0     500    400   100
 April      200   300    400   500   200    0     100
 May        0     100    500   100   1000   300   0
 June       500   500    100   300   1100   500   60
It is possible to store products in a warehouse. The capacity of the storage is 100 units per product type per month. The cost is 0.5 Euro per unit of product per months. There are no stocks in the first month but it is desired to have a stock of 50 of each product type at the end of June.
The factory works 6 days a week with two shifts of 8 hours each day. (It can be assumed that each month consists of 24 working days.)
The factory wants to determine a production plan, that is, the quantity to produce, sell and store in each month for each product, that maximizes the total profit.
Model the factory planning problem for the month of January as an LP problem.
Model the multi-period (from January to June) factory planning problem as an LP problem. Use mathematical notation and indicate in general terms how many variables and how many constraints your model has.
Implement the single-period model (Task 1) in a SpreadSheet. Implement the multi-period model (Task 2) in Python and Gurobi. Solve the problem on the data given.
Here are the data of the problem:
from collections import OrderedDict
class Data:
    def __init__(self):
        self.products = [1, 2, 3, 4, 5, 6, 7];
        self.machines = ["grinder","vdrill","hdrill","borer","planer"]
        self.months = ["january","february","march","april","may","june"]
        
        self.profits = [10, 6, 8, 4, 11, 9, 3]
        
        tmp = {
        'grinder'     :[0.5,    0.7,    0,      0,      0.3,    0.2,   0.5],  
        'vdrill'      :[0.1 ,   0.2 ,   0   ,   0.3 ,   0   ,   0.6,   0],    
        'hdrill'      :[0.2 ,   0   ,   0.8 ,   0   ,   0   ,   0  ,   0.6],  
        'borer'      :[0.05,   0.03,   0   ,   0.07,   0.1 ,   0  ,   0.08],
        'planer'    :[0   ,   0   ,   0.01,   0   ,   0.05,   0  ,   0.05]
        }
        self.coeff=OrderedDict()
        for m in self.machines:
            for (j,p) in enumerate(self.products):
                self.coeff[m,p] = tmp[m][j] 
        
        self.capacity = {"grinder": 4,"vdrill": 2,"hdrill": 3, "borer": 1, "planer": 1}
        
        tmp = {
        "grinder": [("january", 1), ("may", 1)],
        "hdrill": [("february", 2),("june", 1)], 
        "borer":  [("march", 1)],
        "vdrill": [("april", 1),("may", 1)],
        "planer": [("june", 1)] 
        }
        
        self.maintainance = OrderedDict()
        for m in self.machines:
            for t in self.months:
                self.maintainance[m,t] = 0
            if m in tmp:
                for s in tmp[m]:
                    self.maintainance[m,s[0]]=s[1]
        
        tmp = {
        "january":    [500   ,1000   ,300   ,300   ,800    ,200   ,100],
        "february":   [600   ,500    ,200   ,0     ,400    ,300   ,150],
        "march":      [300   ,600    ,0     ,0     ,500    ,400   ,100],
        "april":      [200   ,300    ,400   ,500   ,200    ,0     ,100],   
        "may":        [0     ,100    ,500   ,100   ,1000   ,300   ,0  ],
        "june":       [500   ,500    ,100   ,300   ,1100   ,500   ,60 ]
        }
        
        self.market_limits = OrderedDict()
        for m in self.months:
            for (j,p) in enumerate(self.products):
                self.market_limits[m,p] = tmp[m][j] 
    
    def printData(self):
        print("Months:", self.months)
        print("Products:", self.products)
        print("Machines:", self.machines)
        print("Coefficients: ",self.coeff)
        print("Market_limits:", self.market_limits)
        print("Maitainance:", self.maintainance)
We import the Gurobi Python Module and other Python libraries. With Gurobi 9.1.1, a pip installation of the product will automatically include a size-limited (2000 variables, 2000 linear constraints, and 200 quadratic constraints) license that should work in a Docker container, which is used by Google Colab.
%pip install -i https://pypi.gurobi.com gurobipy
import gurobipy as gp
Here, instead of stipulating when each machine is down for maintenance, it is desired to find the best month for each machine to be down.
Each machine must be down for maintenance in one month of the six apart from the grinding machines, only two of which need to be down in any six months.
Extend the model that correctly addressed tasks 2 and 3 to allow it to make these extra decisions.
How many variables did you need to add? What is the domain of these variables?
Has the matrix of the problem a similar structure to the one of the point above?
Is the solution from Task 3 a valid solution to this problem? What information can it bear in this new case?
Implement and solve the model in Python and Gurobi. After how many nodes in the branch and bound tree is the optimal solution found? And after how many is it proven optimal?
How much worth is the extra flexibility of choosing when to place downtimes?
The column ``LP iter'' indicates the number of simplex iterations for solving the LP problem. Why is this number increasing through the search?
Can you devise another model and compare them computationally? Which is best? Why?