Optimization of a weekly production plan with Python and Gurobi — Part 3 | by Soulard Baptiste | Towards Data Science

We studied in the previous articles how to spread the workload between several production lines to meet the demand while reducing labour, inventory, and shortage costs.
This model had some limitations and might not be used in a natural environment. Indeed, most of the time, the demand is a quantity to ship according to a list of customer orders. Also, the cycle time of each material is different.
We will see here how to optimize the production schedule in this case.
The idea of final assembly scheduling is to refine the daily demand by scheduling the production orders at a daily level. Our objective is to reduce the costs by smoothing the production load to reduce labour costs while producing just in time to reduce inventory and shortage costs.
We are in a make-to-order scheme with three production lines in parallel. The factory is organised so that one-piece flow is always respected and all the tasks to produce a material are realised on the same line. Between 7 and 12 hours per day, each production line can be initiated at an extra cost charged as overtime work.
We need to schedule the production orders to meet the requirement expressed by our list of customer orders.
In addition to what we did in the previous articles, we will add the concept of cycle time for each product family that our factory is producing. This cycle time is used as an input through a matrix that shows the demonstrated capacity for an 8 hour shift. As the equipment varies from a line to another, the capability depends not only on the product family but also on the assembly line, as you can see below.
How to propose a final assembly schedule that minimizes the cost based on these inputs?
The algorithm presented in this article is based on the one presented in parts 1 and 2. In this article, we will only insist on the concepts newly introduced.
Below is a summary of our problem that will be solved using Python and Gurobi solver.
Inputs- Customer requirements- Capacity matrix- Labor cost per hour- Inventory cost per material per day- Shortage cost per material per dayVariables- x_qty[date, order, assembly line]: quantity of materials produced per day for each order on a given assembly line- x_time[date, order, assembly line]: working time per day for each order on a given assembly line. Defined according to the cycle time for each model.Constraints- Produce the quantity required by our customers- From 7 to 8 standard working hours per day- From 0 to 4 overtime working hours per dayObjective function to minimizeSum of all costs considered- Labor cost- Inventory cost- Shortage cost
‣ Extracting the data
This time, to propose a model that better fits the need in a professional environment, the inputs are coming from an Excel file that could be extracted from our ERP. Our algorithm will read the customer’s requirement and generate the optimized production schedule in another Excel file using the same template.
We will use the pandas’ library to fit this information and transform the data to use it in our optimization algorithm.
The second Excel file used as an input is the demonstrated capacity of each line for each product. Ideally, all the assembly lines should have the same process and, thus, the same ability. However, it is not the case in our small assembly factory.
The demonstrated capacity is not based on the theoretical routing times, and the losses due to a lack of efficiency are already considered. Here is the 8 hours capacity per line:
Let’s now get the data from Excel and load it into a pandas data frame:
The data presented previously and stored in two Excel files are not stored into the variable’s customer_orders and capacity. Moreover, to create our algorithm, we need to convert the capacity into the cycle time, i.e., the time required to produce one item. This information is stored in the variable cycle_time.
After extracting these data from the local file, we need to set the labour, inventory, and shortage costs. This is done in the same way as for the previous articles and will not be shown here.
Contrary to the previous articles, the calendar on which we operate is built based on the customer orders file. If you need it, you can find the code on my Git Hub.
Now that we have access to all the information to treat, we will create the dictionaries to solve our model with the solver Gurobi.
‣ Creating the dictionaries
First, we need to create a dictionary containing information related to cycle times. We want to know the time required to produce one unit of each order on each assembly line.
As we do not know the cycle time for each order yet, we need to link the two data frames created in the first part of this article, i.e. customer orders and capacity data frames. We will also take this opportunity to format the date.
We now have all the information necessary to build our dictionaries in one data frame.
For the record, dictionaries are used to store data indexed by keys. As we need to know the time required to produce one unit of each order on each assembly line, the key used to index cycle times dictionary is the tuple (order, assembly line).
To create the dictionary, we will look for the cycle time information into the customer_orders data frame and iterate each order and each assembly line.
The output will be:
cycle_times = {('A','Line_1'): 0.025, ('A','Line_2'): 0.0228, ... ,('L','Line_2'): 0.0228,('L','Line_3'): 0.025}
Similarly, we create, the dictionary containing the daily requirements i.e. the customer’s need.
daily_requirements = {('2020/07/13','A'): 600, ('2020/07/13','B'): 0, ... ,('2020/07/19','K'): 0, ('2020/07/19','L'): 200}
At this point, we have managed to extract and refined the data we will use for our optimization problem. Let us now initiate to define our variables.
Here is the main innovation compared to what we have done so far in the first two articles.
In the previous articles, we were working in two dimensions, and our variable was the time spent working on each assembly line each day. This time we add one dimension, the order concerned by this variable.
We have one main optimization variable x_qty and one slave variable x_time defined using the cycle_times dictionary. A tuple in three dimensions represents them.
x_qty[date, order, assembly line]: quantity of items produced per day for each order on a given assembly line
x_time[date, order, assembly line]: working time per day for each order on a given assembly line
Apart from these new variables, we will use the variables defined in the previous articles. Here is a reminder of these variables:
‣ Time variables
These variables are used to set the constraints on the working hours depending on the regulations in place in our factory.
 • reg_hours[date, assembly line]: regular working time on each assembly line • OT_hours[date, assembly line]: overtime working hours on each assembly line • line_opening[date, assembly line]: binary variable telling if an assembly line is opened each day • total_hours[date, assembly line]: total working hours on each assembly line. It is the sum of regular and overtime working hours on the days the line is opened.
‣ Quantity variables
These variables are used to calculate inventory and shortage costs.
 • early_prod[date, order]: quantity of items produced early for each order • late_prod[date, order]: quantity of items produced late for each order
As seen in the introduction, we have some constraints on the daily working time which is a hindrance to meet our customer’s requirement.
The constraints on the limitation of working hours have been set in the definition of each variable by setting the lower and upper boundaries to match with the regulation.
To meet our customer’s demand, we want to produce the exact quantity ordered. To do so, we will set the following constraint:
Our objective is to propose a cost-efficient schedule considering the labour cost, inventory cost, and shortage cost. We will add all these variables into our objective function and then minimize this function.
After minimising the objective function, our optimisation algorithm returns the following production schedule:
You can see in the graph below how the customer’s requirements have been spread between our production lines. We can notice that the production of each order is realised on the day it is required to minimise our inventory.
This is not the case for order L as it represents only a few hours of production, and it is forbidden to open a line for less than 7 hours. The order L is produced on July 18th, and it will build an inventory of 200 pieces that will be shipped on July 19th.
We can check that the limitations on the daily working time are respected. These limitations are defined accordingly to the local regulations.
In our case, the regulations in place define a minimum working time of 7 hours and a maximum of 12 hours per day, 8 hours being the ideal working time for a load of 100%. These extremums are represented by the grey lines on the graph below.
The daily working time on each line always falls between 7 and 12 hours, and the regulations are respected.
Let us now display the inventory and shortages to expect with this schedule proposal.
As seen in the production schedule, order L impacts our inventory level by adding 200 pieces of model 7 to store for one day. There is no late production, the customer’s requirements are met without any shortage.
What if the customer’s requirement is too high to be handled on the first day?
Let us discover how our scheduling optimization algorithm handles a significant customer order at the beginning of the timeframe.
The planning below shows the production schedule proposed by our model if the volume of the first customer order is higher. In this example, the quantity of order A is 2000 while it was 600 pieces in our first test. This quantity can not be handled one day, even if our three lines are running fully.
We can see that our model manages this type of request by proposing to produce these materials on the next day. It is more visible in the shortage report below, where we can see that we could not make 515 pieces. They will be produced on the 13th of July.
Of course, this should not happen as it will negatively impact customer satisfaction.
Through these three articles, we learned step by step how to formalize an optimization problem and how to solve it using Python and Gurobi solver.
This methodology has been applied to a Make To Order factory that needs to schedule its production to reduce the costs, including labour, inventory, and shortages.
All the constraints and all the costs have not been considered; some areas of improvement could be:
Considering changeover time between the different model
Modelling a more complex factory with various tasks to be performed on separate lines in a specific order
towardsdatascience.com
towardsdatascience.com
The repository of this project on my GitHub : https://github.com/soulabat/Production-plan-optimization/tree/master/Planning_optimization_part1
Explanation of how SAP is using linear optimization in its modules for planning optimization: http://www.gurobi.com/pdfs/user-events/2017-frankfurt/SAP.pdf
Gurobi documentation: https://www.gurobi.com/documentation/8.0/examples/workforce5_py.html
Feel free to contact me if you need further information or if you want to exchange views on this subject. You can reach me on LinkedIn.