Pyomo is an optimization modeling framework
Instead of a detailed math-level modeling tool like Pulp and ORTools that defines variables, intermediate variables, constraints, etc in precise math-like implementation codes,
Pyomo defines the problem from a higher level, which still specifies the variables and links between components of a problem, but no need to go very detailed implementation level.
Here is an example:
Example problem Description (Plain Language):
A plant produces goods.
A warehouse ships goods to meet demand.
Goods flow one way: from plant to warehouse.
The plant has a maximum production capacity.
The warehouse must meet a fixed demand.
Objective: minimize production while meeting demand.
Standard linear program form is as below, and this is how ortools would program with line by line.
Decision variables
p >= 0 (production at plant)
s >= 0 (shipment from warehouse)
Parameters
C = plant capacity
D = warehouse demand
Constraints
p <= C (plant capacity)
s <= p (can only ship what is produced)
s >= D (must meet demand)
Objective
minimize p
The Same Problem in Pyomo (in Logical Form):
from pyomo.environ import *
m = ConcreteModel()
# data
m.capacity = Param(initialize=100)
m.demand = Param(initialize=80)
# decisions
m.produce = Var(domain=Nonnegative Reals)
m.ship = Var(domain=NonNegativeReals)
# rules
m.capacity_limit = Constraint(expr = m.produce <= m.capacity)
m.flow_balance = Constraint(expr = m.ship <= m.produce)
m.meet_demand = Constraint(expr = m.ship >= m.demand)
# objective
m.obj = Objective(expr = m.produce, sense=minimize)
IN this program, you describe:
decisions to make
rules that must hold
what you want to optimize
Pyomo handles the math. It actually translates the logic into LP math and codes for execution in a solver.
This is especially helpful when you have step functions, intermediate variables, etc. that are just for implementation.
The Implicit DAG Behind the Model
Although you do not draw it, Pyomo builds a DAG internally:
capacity -> produce -> ship -> objective
demand ------------> ship
Parameters feed decisions
Decisions feed constraints
Objective depends on decisions
No cycles
This DAG is later flattened into LP form for the solver.
Now assuming you have different plants and warehouses behave slightly different,
you might program them as classes, to capture the common and different things,
and use pyomo to link them together. Sort of object oriented programming here.
A base class for components. The connect function or named anything, is for connecting components.
from pyomo.environ import *
class Component(Block):
def connect(self, other):
"""Define logical connections to other components."""
pass
Plant Component
A plant has a capacity
decides how much to produce
exposes its output to others
Here it defines the variables and constraint, i.e. production capacity and output to warehouse. obviously output must be less than capacity.
This still looks quite similar to those definitions in Pulp and ortools.
class Plant(Component):
def build(self):
self.capacity = Param(initialize=100)
self.output = Var(domain=NonNegativeReals)
self.capacity_limit = Constraint(
expr = self.output <= self.capacity
)
Warehouse Component
A warehouse has demand
decides how much to ship
takes input from a plant
class Warehouse(Component):
def build(self):
self.demand = Param(initialize=80)
self.input = Var(domain=NonNegativeReals)
self.ship = Var(domain=NonNegativeReals)
self.flow_limit = Constraint(
expr = self.ship <= self.input
)
self.demand_rule = Constraint(
expr = self.ship >= self.demand
)
Connecting Components with a DAG
Now we connect the plant and warehouse together logically.
It creates each component and build it individually to initialize it and setup each's variables and constraints.
The constraint that connects plant output to warehouse input establishes the link between plant and warehouse.
m = ConcreteModel()
m.plant = Plant()
m.plant.build()
m.warehouse = Warehouse()
m.warehouse.build()
# DAG connection: plant -> warehouse
m.link = Constraint(
expr = m.warehouse.input == m.plant.output
)
# system-level objective
m.obj = Objective(expr = m.plant.output, sense=minimize)
In this way,
Each component is independent
Data flows one way (plant → warehouse)
The DAG is explicit and readable
Objective spans components cleanly
THis helps with modelling a large system.
You can work on a subsystem without worrying about other subsystem or having a good overview of the overall system at the beginning.
And incrementally add new subsystems.
That is why it is more like a framework, besides being relatively high level in defining problems.
Scaling to Many Plants and Warehouses
Once defined using components, and dag concept, you can scale the problem easily.
Now assume you have multiple warehouses and plants with different sizes.
m = ConcreteModel()
m.plants = Block([1, 2])
m.warehouses = Block([1, 2])
for i in m.plants:
m.plants[i] = Plant()
m.plants[i].build()
m.plants[i].capacity.set_value(100 + 20*i)
for j in m.warehouses:
m.warehouses[j] = Warehouse()
m.warehouses[j].build()
m.warehouses[j].demand.set_value(60 + 10*j)
m.links = ConstraintList()
m.links.add(m.warehouses[1].input == m.plants[1].output)
m.links.add(m.warehouses[2].input == m.plants[2].output)
Now it can declare and connect multiple components in loops.
FOr a small problem, the benefit is probably not much, but for a large system with various components, it is logically clear.
Final Takeaway
Pyomo always builds a DAG internally
You rarely need to think about it for small models
For large systems, DAG‑based component design is powerful
You model rules and relationships, not equations and rows
Pyomo compiles logic into math automatically
Think like a system designer.
Let Pyomo handle the algebra.