14 July 2022 (1,460 words)
In this article we continue the Python Production mix series, using the Pyomo library. Specifically, we build Model 2, which improves Model 1 by extracting the hard coded coefficients and putting them into Python data structures.
Separately the data from the model is an important step in developing a practical design that can be used for operational models.
Articles in this series
Articles in the Python Production mix series:
- Python optimization Rosetta Stone
- Production mix - Model 1, Pyomo concrete
- Production mix - Model 2, Pyomo separate data
- Production mix - Model 3, Pyomo external data
- Production mix - Model 4, Pyomo json file
- Production mix - Model 5, Pyomo using def
- Production mix - Model 6, Pyomo abstract
- Production mix - Model 7, PuLP
- Production mix - Model 8, OR-Tools
- Production mix - Model 9, Gekko
- Production mix - Model 10, CVXPY
- Production mix - Model 11, SciPy
- Production mix - Conclusions
Download the model
The Python code for this model is available in the following files:
The "Jupyter notebook" file contains a formatted combination of Python code and markdown code – this file should be opened and run in Jupyter Lab. We describe setting up our Python environment, including Jupyter Lab and various Python libraries, in the article Set up a Python modelling environment.
The "Python code" file is a plain text file containing only the Python code of this model. Download this file if you have a non-Jupyter environment for running Python programs.
The model files are also available on GitHub.
Formulation for Model 2
For this model, by separating the data from the model, we're using the general (rather than specific) formulation, as shown in Figure 1.
Model 2 Python code
The first task is to import the libraries that are needed for our program. As shown in Figure 2, in addition to the Pyomo library, we import the pandas library. We'll use pandas when writing the model's output.
As shown in Figure 3, we continue to use a Pyomo concrete model.
Unlike Model 1, here we separate the data from the model. Therefore, we need to declare and populate some data structures.
We start with simple constants, called
SalesLimit, which will be the right-hand side values of our constraints.
Next, we declare and populate a Python Dictionary called
Coefficients to contain the coefficients for each variable. Each row of the dictionary represents a product, enabling us to easily add more products. Within each row, we have the
Sales coefficients that we'll use in the constraints, along with the
Margin coefficient that we'll use in the objective function. If we added a new constraint, then we would add the associated variable coefficients as a new column in this dictionary.
Finally, we create a dictionary keys object called
Products, which contains the dictionary's top-level keys – i.e.
Orbs. We'll use these keys to select the coefficients for each product.
Define the model
With the preliminaries done, we can now define the model. We do this, as shown in Figure 4, by adding variables, constraints, and an objective function to the
- Variables. In Model 1, we had a variable for each product. Here we define a set of variables, indexed by
Productscontains two keys, two variables are created. We also initialize each of the variables, just to show how it is done, though it doesn't matter in this model.
- Constraints. Rather than specifying individual product variables, for the left-hand side of each constraint we sum over all
Products. For example, when the index
phas the value
Discs, the term
12.50(see Figure 3). The right-hand side of each constraint is the value we specified in the declarations section above.
- Objective function. The objective function is defined in a similar way.
Check the model definition
It is important to check that our model definition works correctly. Python has a "pretty print" module,
pprint, for outputting data structures in a formatted way. We can call
pprint on our Pyomo
Model object by simply running the line
Model.pprint(). The output is shown in Figure 5.
As a quick check, the important parts in the
pprint output are:
- We have two variables,
- The objective function is:
80.0*Production[Discs] + 200.0*Production[Orbs].
- The Material constraint is:
18.0*Production[Discs] + 30.0*Production[Orbs], with a right-hand side of
500.0. The other constraints are similar.
Each of these outputs is as expected.
Now we can solve the model, as shown in Figure 6.
This code is the same as Model 1.
As for Model 1, we output the model's name, the solve status, and the objective function value – as shown in Figure 7.
But instead of writing the variables individually, here we use a pandas DataFrame to collate the variable values. Note that we access the variable values using
p in an index of the
Having collated the results in a DataFrame, we simply
display the DataFrame, which writes a nicely formatted table of results.
The output is shown in Figure 8. Apart from formatting, the solution is the same as for Model 1.
Evaluation of this model
Model 2 is an improvement compared with Model 1. Pyomo enables us to create and use data structures that are a step towards a more general model design.
pprint() help us validate that our model works correctly. Such validation will become more important as our model design becomes more sophisticated.
There is still more that we can do to improve the model, which we'll continuing exploring in the next article of this series.
Although we've separated the data from the model, the data is still contained in the same file. In the next article, we'll put the data in an external file. We'll also improve the way we handle the solution process.
This article continues our exploration of optimization modelling in Python. We've improved Model 1, with a clear separation of the data from the model.
In the next article, we'll continue improving the design.
If you would like to know more about this model, or you want help with your own models, then please contact us.