14 July 2022
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 models
The model is 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
Import dependencies
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.
Declarations
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 Hours
, kg
, and 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 People
, Materials
, and 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. Discs
and 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 Model
object:
- Variables. In Model 1, we had a variable for each product. Here we define a set of variables, indexed by
Products
. SinceProducts
contains 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 indexp
has the valueDiscs
, the termCoefficients[p]['People']
has value12.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,
Production[Discs]
andProduction[Orbs]
. - 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 of500.0
. The other constraints are similar.
Each of these outputs is as expected.
Solve model
Now we can solve the model, as shown in Figure 6.
This code is the same as Model 1.
Write output
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 pyo.value(Model.Production[p])
, where p
in an index of the Products
keys.
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.
Tools like 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.
Next steps
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.
Conclusion
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.