Pyomo style guide#
The purpose of this guide is to promote more readable and maintainable Pyomo models. This guide supplements standard Python style conventions, such as PEP 8, with specific recommendations for Pyomo. It is not intended as an alternative to the core Pyomo documentation.
Comments and suggestions are welcome!
Workflows#
This guide addresses a typical Pyomo workflow shown in this diagram:

This guide focuses on the workflow stage labeled “Python Model Builder” where the optimization model consists of Python code that accepts problem specific data to produce a Pyomo model object. Well-written Python code will use the Pyomo package to reflect the structure of the mathematical model on which it is based, and accept commonly used data sources to produce solvable model instances.
Coding Conventions#
Use pyo as the preferred alias for Pyomo#
Name space conflicts in Python are a common and avoidable source of runtime errors. Pyomo, for example, overloads functions commonly encountered in mathematical applications, such as sin, log, exp. As a consequence, this sequence of imports
# don't do this!
from pyomo.environ import *
from numpy import as *
will overwrite Pyomo’s math functions and lead to unexpected results and errors. For this reason, importing Pyomo directly into the Python name space is strongly discouraged.
Name space conflicts with commonly used packages are avoided by importing libraries with alias. Regrettably, the available documentation has included multiple aliases for Pyomo, among them are pe, pmo, and aml. Here we propose the consistent use of pyo as an alias for pyomo.environ.
# do this
import pyomo.environ as pyo
If a more concise notation is absolutely required for presentations or classroom use, for those specific cases import only the necessary Pyomo objects. For example
# for presentations or teaching, consider this
from pyomo.environ import ConcreteModel, Var, Objective, maximize, SolverFactory
Use pyo.ConcreteModel instead of pyo.AbstractModel#
Optimization applications required a close coupling of mathematics and problem data. Data sources are needed to determine values for the parameters appearing the mathematical description of the problem. For algebraic modeling languages like Pyomo, this raises a “chicken or the egg” dilemma: Which comes first, the algebraic model represented in the modeling language, or the data that specify values for the model parameters?
Pyomo provides for both possibilities. A Pyomo pyo.AbstractModel object can be created before the data is known. Pyomo includes methods to load parameter values after the data becomes available. This establishes a two phase procedure where the second phase employs Pyomo methods to access data and create a specific model instance.
In contrast, a Pyomo pyo.ConcreteModel object is created from known data. This results in single phase of model building where components are fully constructed and initialized when they are first attached to the pyo.ConcreteModel object.
Here we encourage the consistent use of pyo.ConcreteModel to build Pyomo models. The main reason is that Python offers tools for wrangling data from a wide range of sources. The tools range from standard Python data structures like tuples, lists, and dictionaries, to feature-rich packages such as Pandas and datatable. These tools can used to construct and initialize parameters in a ConcreteModel as it is constructed. Using pyo.ConcreteModel removes the need to create and store intermediate forms of the algebraic model or, for that mater, to pre-process the model data for later use in an AbstractModel.
This is the preferred syntax for creating a Pyomo model
# do this to create a model instance
m = pyo.ConcreteModel('model description')
Use pyo.Set and pyo.RangeSet to index model components#
The ability to index components, such as variables and constraints, is essential to scaling a model for larger applications. In general, components attached to a Pyomo ConcreteModel can be indexed by ordered Python iterables such as lists, dictionary keys, tuples, and ordered sets. In principle, this enables a deep integration of Python data structures with Pyomo models.
In practice, however, the use of pyo.Set and pyo.RangeSet to create index sets is encouraged for the following reasons:
Pyomo
SetandRangeSetprovides a consistent and uniform a uniform interface between data pre-processing and model creation.Pyomo Set provides flexible options for filtering, validating, and manipulating sets.
Internally, Pyomo uses Sets and RangeSets to trace model dependencies to provide better error reporting and sensitivity calculations.
Pyomo creates an associated internal Pyomo Set each time a Python iterable is used to create indexed model objects. Creating of multiple indexed objects with the same iterable produces redundant sets.
To illustrate, consider a Python dictionary
bounds = {"a": 12, "b": 23, "c": 14}
this
# do this
m.B = pyo.Set(initialize=list(bounds.keys()))
m.x = pyo.Var(m.B)
is preferred to this.
# don't do this
m = pyo.ConcreteModel()
m.x = pyo.Var(bounds.keys())
pyo.Set() and pyo.RangeSet() incorporate a significant collection of methods to operate on sets operations. As general guidance, operations on sets should are preferred over complex looping and conditional logic on set components.
Parameters#
Pyomo parameters are instances of the pyo.Param() class. Parameters hold problem data, may be indexed, and are attached to specific model or block. When combined with sets, parameters and set objects can completely define and modularize the interface application data and the optimization model.
As a simple example, consider a parameter \(a_{i,j}\) for \(i\in I\), \(j\in J\), appearing in a mathematical model, where \(a_{i,j}\) refers to an element in a Pandas DataFrame named df. Using Pyomo’s decorator notation, this parameter can be initialized by
@m.Param(m.I, m.J)
def a(m, i, j):
return df.loc[i, j]
By default, Pyomo parameters are immutable which assures their values will be consistent throughout the model construction and transformations. Parameters determining the size of index sets, or fixed upper and lower bounds on decision variables, are examples where using an immutable Pyomo parameter is good practice.
Mutable parameters are created with a keyword option mutable=True in the decorator. Mutable parameters are useful for optimization models that will be solved multiple times as a function of a few parameters. The use of mutable parameters should be limited and intentional.
Variables#
Decision variables are attached to a specific model or block, may be indexed, and may be restricted to particular sets or range of values.
Use domain rather than within#
pyo.Var() accepts a keyword option within or domain to specify a class of decision variables. To reduce the cognitive burden for a new user or when reading Pyomo models, consistent use of domain is preferred because of its common use in mathematics to represent the set of all values for which a variable is defined.
Use bounds when known and fixed#
A Pyomo model can place bounds on decision variables with either the bounds keyword in the argument to pyo.Var, or as explicit constraints in the model.
When upper or lower bounds for a variable are known and fixed, best practice is to use of bounds when creating the variable. This practice can reduce the number of explicit constraints in the model and simplify coding and model display.
Constraints and Objective#
Prefer pyo.Constraint to pyo.ConstraintList#
The pyo.ConstraintList() class is useful for creating a collection of constraints for which there is no simple indexing, such as implementing algorithms that employ constraint generation. pyo.Constraint(), however, should always be preferred when the constraints can be indexed over known sets.
Use decorators to improve readability#
Indexed Pyomo constraints are constructed by a rule. When constraints are specified with pyo.Constraint(), rules are normally named by adding _rule as a suffix to the name of the associated constraint. For example, assuming model m and the associated sets, parameters, and variables have been previously defined,
def new_constraint_rule(m, s):
return m.x[s] <= m.ub[s]
m.new_constraint = pyo.Constraint(m.S, rule=new_constraint_rule)
A recent addition to Pyomo is the use of Python decorators to create some modeling objects, including parameters, constraints, objectives, disjunctions, and blocks. Using decorators, the above example is written as
@m.Constraint(m.S)
def new_constraint_rule(m, s):
return m.x[s] <= m.ub[s]
The use of decorators improves readability and maintainability. Decorators eliminate the need for the rule keyword, requires the name of the constraint in one rather than three places.
The decorator syntax is straightforward for objectives and simple constraints. For Python users unfamiliar with decorators, decorators can be described as a way to ‘tag’ functions that are to be incorporated into the Pyomo model. Indices and keywords are used modify the extend to the bahavior of the decorator.
@model.Constraint()
def demand_constraint(model):
return model.x + model.y <= 40
@model.Objective(sense=pyo.maximize)
def profit(model):
return 3*model.x + 4*model.y
Indices are also included in the decorator for indexed objects.
@model.Constraint(model.SOURCES)
def capacity_constraint(model, src):
return sum(model.ship[src, dst] for dst in model.DESTINATIONS) <= model.capacity[src]
@model.Constraint(model.DESTINATIONS)
def demand_constraint(model, dst):
return sum(model.ship[src, dst] for dst in model.SOURCES) <= model.demand[dst]
Naming conventions#
The choice of constraint and variables names is important for readable Pyomo models. Good practice is to use descriptive lower case names with words separated by underscores consistent with PEP 8 recommendations.
Pyomo models commonly use alternative conventions to enhance readability by visually distinguishing components of a model.
Prefer short model and block names#
Model and block names should be consistent with PEP 8 naming standards (i.e., all lowercase with words separated by underscore). Short model names are preferred for readability and to avoid excessively long lines. A single lower case m is acceptable in instances of a model with a single block.
Complex models may require more descriptive names for readability.
Set and RangeSet names may be all caps#
Consistent with common mathematical conventions in optimization modeling, use of upper-case names to denote Pyomo sets is an acceptable deviation from PEP style guidelines. Corresponding lower case name can then be used to denote elements of the set. For example, the objective
may be implemented as
import pyomo.environ as pyo
m = pyo.ConcreteModel()
m.MACHINES = pyo.Set(initialize=["A", "B", "C"])
m.finish_time = pyo.Var(m.MACHINES, domain=pyo.NonNegativeReals)
@m.Objective(sense=pyo.minimize)
def total_time(m):
return sum(m.finish_time[machine] for machine in m.MACHINES)
Parameter names may be capitalized#
Parameter names, especially mutable parameters intended for use in parametric studies, may use capitalized words (i.e., “CamelCase”).
Use descriptive Constraint and Variable names#
Objectives, constraints, variables, disjuncts, and disjunctions should use descriptive names following PEP 8 guidelines with lower case words separated by underscore (i.e, “snake_case”).
As an exception for small tutorial examples where mathematical formulation accompanies the model, the corresponding Pyomo model may use the same variable and parameter name. For example, a mathematical model written as
may be encoded as
import pyomo.environ as pyo
# create model instance
m = pyo.ConcreteModel()
# decision variables
m.x = pyo.Var(domain=pyo.NonNegativeReals)
m.y = pyo.Var(domain=pyo.NonNegativeReals)
# objective
m.f = pyo.Objective(expr = 40*m.x + 30*m.y, sense=pyo.maximize)
# declare constraints
m.a = pyo.Constraint(expr = 2*m.x + m.y <= 10)
m.b = pyo.Constraint(expr = m.x + 2*m.y <= 15)
m.pprint()
This practice is generally discouraged, because the resulting models are not easily read without reference to the accompanying mathematical notes. Pyomo includes a .doc attribute that can be used to document relationships between the Pyomo model and any reference materials.
import pyomo.environ as pyo
# create model instance
m = pyo.ConcreteModel()
# decision variables
m.production_x = pyo.Var(domain=pyo.NonNegativeReals, doc="x")
m.production_y = pyo.Var(domain=pyo.NonNegativeReals, doc="y")
# objective
m.profit = pyo.Objective(expr = 40*m.production_x + 30*m.production_y, sense=pyo.maximize)
m.profit.doc = "f"
# declare constraints
m.labor_a = pyo.Constraint(expr = 2*m.production_x + m.production_y <= 10, doc="A")
m.labor_b = pyo.Constraint(expr = m.production_x + 2*m.production_y <= 15, doc="B")
m.pprint()
Data styles and conventions#
Reading, manipulating, and writing data sets often consumes a considerable amount of time and coding in routine projects. Standardizing on a basic set of principles for organizing data can streamline coding and model development. Below we promote the use of Tidy Data for managing data sets associated with Pyomo models.
Use Tidy Data#
Tidy data is a semantic model for of organizing data sets. The core principle of Tidy data is that each data set is organized by rows and columns where each entry is a single value. Each column contains all data associated with single variable. Each row contains all values for a single observation.
scenario |
demand |
Price |
|---|---|---|
high |
200 |
20 |
medium |
100 |
18 |
low |
50 |
15 |
Tidy data may be read in multiple ways. Pandas DataFrame objects are well suited to Tidy Data and recommended for reading, writing, visualizing, and displaying Tidy Data.
When using doubly nested Python dictionaries, the primary keys should provide unique identifiers for each observation. Each observation is a dictionary. The secondary keys label the variables in the observation, each entry consisting of a single value.
scenarios = {
"high": {"demand": 200, "price": 20},
"medium": {"demand": 100, "price": 18},
"low": {"demand": 50, "price": 15}
}
Alternative structures may include nested lists, lists of dictionaries, or numpy arrays. In each case a single data will be referenced as data[obs][var] where obs identifies a particular observation or slice of observations, and var identifies a variable.
Multi-dimensional or multi-indexed data#
Pyomo models frequently require \(n\)-dimensional data, or data with \(n\) indices. Following the principles of Tidy Data, variable values should appear in a single column, with additional columns to uniquely index each value.
For example, the following table displays data showing the distance from a set of warehouses to a set of customers.
Customer 1 |
Customer 2 |
Customer 3 |
|
|---|---|---|---|
Warehouse A |
254 |
173 |
330 |
Warehouse B |
340 |
128 |
220 |
Warehouse C |
430 |
250 |
225 |
Warehouse D |
135 |
180 |
375 |
The distance variable is distributed among multiple columns. Reorganizing the data using Tidy Data principles results in a table with the all values for the distance variable in a single column.
Warehouse |
Customer |
Distance |
|---|---|---|
Warehouse A |
Customer 1 |
254 |
Warehouse B |
Customer 1 |
340 |
Warehouse C |
Customer 1 |
430 |
Warehouse D |
Customer 1 |
135 |
Warehouse A |
Customer 2 |
173 |
Warehouse B |
Customer 2 |
128 |
Warehouse C |
Customer 2 |
250 |
Warehouse D |
Customer 2 |
180 |
Warehouse A |
Customer 3 |
330 |
Warehouse B |
Customer 3 |
220 |
Warehouse C |
Customer 3 |
225 |
Warehouse D |
Customer 3 |
375 |
When working with multi-dimensional data, or other complex data structures, special care should be taken to factor “data wrangling” from model building.
Use Pandas for display and visualization#
The Pandas library provides an extensive array of functions for the manipulation, display, and visualization of data sets.
Acknowledgements#
This document is the result of interactions with students and colleagues over several years. Several individuals reviewed and provided feedback on early drafts and are acknowledged here.
David Woodruff, UC Davis
Javier Salmeron-Medrano, Naval Postgraduate School
Bethany Nicholson, John Siirola, Michael Bynum, and the Pyomo development team
Jasper M. H. van Doorn
Leon Lan