Path: blob/main/notebooks/appendix/functional-programming-pyomo.ipynb
663 views
Functional Programming with Pyomo
Pyomo, an abbreviation for Python Optimization Modeling Objects, might not appear to be a suitable choice for functional programming. Functional programming, after all, relies on pure, stateless functions that operate on immutable data. Pyomo's workflow, instead, is based mutating a model's state using transformations and solvers. These approaches would seem to be fundamentally incompatible at first glance.
The objective of this notebook is to demonstrate how encapsulating Pyomo transformations and solvers within relatively pure functions, along with a simple PyomoMonad, can create a functional programming environment for optimization that supports a flexible and expressive optimization workflows.
Pyomo and Optimization Workflows
A typical Pyomo workflow begins with a problem statement and then proceeds through a series of steps that, coupled with inputs of data, model transformations, and solvers, results in a solution.

A mathematical model is created from a problem statement and model of the available data.
A Python code is prepared that can read relevant data and generates an instance of a Pyomo ConcreteModel.
Optional transformations may be applied to convert the model into a solvable form.
A solver is selected and applied to compute a solution and load into the model object.
A suitable solution report is extracted from the solved model object.
Given the range of possible problem types, modeling choices, transformations, and solvers, this can be complex workflow with opportunities for generating exceptions, and the need to log and display intermediate results.
This notebook implements the Pyomo workflow by applying abstractions from functional programming and category theory. A simple PyomoMonad is proposed to enable the utilization of pure functions at each stage, from model construction to the presentation of the optimal solution.
Functional Programming and Monads
This section provides a brief and generic introduction to functional programming and monads. Readers familiar with these topics can skip forward to the section where these concepts are applied to the Pyomo workflow.
Pure functions
A characteristic feature of functional programming is the use of "pure" functions. Pure functions always returns the same result for identical arguments with no side effects such as changing non-local variables or call-by-reference arguments, printing, or data base interactions. Because there are no side effects, there is no change in state between invocations of a pure function. All results of calling a pure function are restricted to the result returned by the function.
There are important practical advantages for pure function that result from from these restrictions. For example, pure functions are generally short with a single purpose, they can be executed concurrency because there is no common state, are easily tested, and allow recursion. These advantages make pure functions useful building blocks for complex applications.
Python lambda functions are examples of pure functions. Here we create a series of three lambda functions:
trimthat strips white space from a string,upperthat converts a string to upper case,exclaimthat appends an exclamation symbol.
These can be nested in various combinations and applied to a string.
Function composition
A composition of these functions is useful for combining the effects of these functions. Given two functions, and ,
the composition of and is defined as
where
To demonstrate, a new function enthuse is created from the composition of trim, upper and exclaim.
Composition is associative. Given three functions
then
map, filter, and functools.reduce
The functions map, filter, and functools.reduce extend the utility of pure functions. Note that map actually returns an iterator, so list is required to apply the function to every element of the data given in map.
reduce applies a function of two values recursively to a tuple, list, or iterator to reduce it to a single cumulative value. This provides a convenient means to construct of composition of two or more functions.
Why monads?
Composing pure functions is a highly expressive and flexible style of programming, but consider what happens when something happens that causes a change of state. In the following cell, for example, an exception is generated when a composite function is applied to an input with the wrong type.
This may not be a desirable result for a more complex workflow. This is where monads fit in.
A monad wraps the inputs and outputs of a pure function into a structure that can perform additional computation. There two basic methods for monads:
unit which wraps a value into a monad.
bind which chains pure functions together.
The Maybe monad
The following cell shows a Python implementation for a Maybe monad. The Maybe monad wraps a value the may contain a value. If the result of bind produces an error, the value stored is None and a flag is set.
To see how this works, let's create an instance of Maybe and bind the composite function enthuse.
The important thing to notice is that the pure functions previously defined can be applied and composed with no changes. All of the boilerplate code need to check for errors has been encapsulated in the Maybe monad.
Why do this? One benefit of Maybe is that we now have a way to avoid or capture error messages produced when from a composition of functions.
The Failure monad
The Failure monad is designed to handle exceptions. Without modifying any of the previously defined functions, Failure captures the first exception and pushes it to the end of a chain of calculations. All of the boilerplate is in the monad, not the functions.
A Pyomo monad
In brief, a monad is a structure that wraps a value inside an object so that it can be passed through a series of functions.
The functions are assumed to be stateless and without side effect. In other words, given the same input the functions will always the same output. The purpose of the monad is handle situations where a function results in
for restricting access to a value. The reasons for restricting access are specific to the application. For Pyomo models, for example,
spe that are specific to the scenario. In Pyomo, for example, we ca
This first implementation of a Pyomo monad comprises three elements:
Wrapping Pyomo solvers and transformers as functions. The solvers and transfers clone the model to avoid changes in the state of the model that is passed by reference.
A PyomoMonad with a unit and bind functions. The monad will "lift" the Pyomo model by adding attributes for status and an effect. The effect is what will executed when the monad is executed.
Pyomo models are created by model builders that take problem data and produce a Pyomo model encapsulated in a PyomoMonad. The monad can include an optional effect. The effect should produce any desired side effects, such as plots or reports written to files, and return any desired output from the model. The effect is called by call the monad.
Import Pyomo and solvers
Wrapping Pyomo solvers and transformations as "pure" functions
Create some "pure" functions for processing Pyomo models. These are
PyomoMonad
The an implementation of a simple Pyomo monad. The monad can bind a functions that operates on a model object. A bound function must return a model object. Errors are captured and added to the status. An effect can be specified that will be the returned by the monad when called as a function. The ">>" operator is overridden to provide a little syntax sugar for representing workflows.
Example: Production Planning
Example: Batch Reactor
The following example demonstrates a somewhat longer workflow where Pyomo.DAE is used to transform a set of differential equations into a nonlinear optimization problem that can be solved with ipopt. The model output includes a plot and a few key model values.
Problem statement
Consider a sequence of two chemical reactions
that take place in batch reactor under isothermal conditions. The feed initially consists of pure . The goal is to maximize the production of . Ultimately it is necessary to account for the turnaround time needed between successive batches, but as a first attempt we will settle for finding the maximum achievable concentration of , and time required to achieve that concentration.
Mathematical model
Let and refer to the concentrations of and , respectively. Then the problem is to find where
Rescaling time so that yields the more tractable optimization problem
where is now embedded as a parameter within the model, and the integration is now over a fixed interval .
Pyomo model builder
Executing a workflow
A typical use of PyomoMonad.
Alterntive syntax
Show how the workflow functions can be composed into a single operation.
Composition
Show how a workflow can be written as a composition of functions.