Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Blocks and Model Transformations

Open this notebook in Colab.

This notebook is part of the SECQUOIA Research Group Pyomo Tutorial. It adapts the structured-modeling and transformation themes from the public Pyomo workshop tutorials into short exercises. Install GLPK before running the mixed-integer examples locally; see Setup and Solvers for solver notes.

Learning objectives

By the end of this chapter, you should be able to:

  • use Block objects to organize variables, expressions, and constraints by subsystem;

  • write block rules that create repeatable model structure;

  • inspect a structured model without flattening away the hierarchy;

  • explain the difference between a model and a solver-ready formulation;

  • apply a Pyomo transformation and compare the transformed formulation to the original model.

import pyomo.environ as pyo


def solve_with_glpk(model):
    solver = pyo.SolverFactory("glpk")
    if not solver.available(False):
        print("GLPK is not available; the model was built but not solved.")
        return None
    results = solver.solve(model)
    print(f"termination: {results.solver.termination_condition}")
    return results

1. Structured modeling with Blocks

Large algebraic models usually have repeated structure: a plant has units, a network has arcs, a scenario model has one block per scenario, and a decomposition algorithm often needs one block per subproblem. A Block lets you keep that structure in the Pyomo object model instead of encoding every component at the top level.

Exercise 1.1. Build a product block

The example below creates one block per product. Each block owns its local production variable, setup decision, parameters, expression, and capacity-linking constraint. The top-level model only contains the shared labor limit and objective.

def build_product_model():
    products = ["standard", "premium"]
    margin = {"standard": 8, "premium": 14}
    labor = {"standard": 2, "premium": 5}
    capacity = {"standard": 40, "premium": 25}
    setup = {"standard": 20, "premium": 30}

    model = pyo.ConcreteModel()
    model.PRODUCTS = pyo.Set(initialize=products)

    def product_block(block, product):
        block.make = pyo.Var(bounds=(0, capacity[product]))
        block.open = pyo.Var(within=pyo.Binary)
        block.margin = pyo.Param(initialize=margin[product])
        block.labor = pyo.Param(initialize=labor[product])
        block.capacity = pyo.Param(initialize=capacity[product])
        block.setup = pyo.Param(initialize=setup[product])
        block.revenue = pyo.Expression(expr=block.margin * block.make)
        block.setup_cost = pyo.Expression(expr=block.setup * block.open)
        block.capacity_link = pyo.Constraint(expr=block.make <= block.capacity * block.open)

    model.product = pyo.Block(model.PRODUCTS, rule=product_block)
    model.labor_limit = pyo.Constraint(
        expr=sum(model.product[p].labor * model.product[p].make for p in model.PRODUCTS) <= 100
    )
    model.profit = pyo.Objective(
        expr=sum(model.product[p].revenue - model.product[p].setup_cost for p in model.PRODUCTS),
        sense=pyo.maximize,
    )
    return model


structured_model = build_product_model()
solve_with_glpk(structured_model)

for product in structured_model.PRODUCTS:
    block = structured_model.product[product]
    print(
        f"{product:8s}: make={pyo.value(block.make):5.1f}, "
        f"open={pyo.value(block.open):3.0f}, revenue={pyo.value(block.revenue):6.1f}"
    )
print(f"profit: {pyo.value(structured_model.profit):.1f}")
termination: optimal
standard: make= 40.0, open=  1, revenue= 320.0
premium : make=  4.0, open=  1, revenue=  56.0
profit: 326.0

Exercise 1.2. Inspect the model hierarchy

A block-structured model is still a normal Pyomo model. You can traverse it as a tree when that is useful, or ask Pyomo for all active variables and constraints when a solver or analysis routine needs the flattened view.

print("Local variables by product block:")
for product in structured_model.PRODUCTS:
    block = structured_model.product[product]
    local_vars = [var.local_name for var in block.component_objects(pyo.Var)]
    local_cons = [con.local_name for con in block.component_objects(pyo.Constraint)]
    print(f"  {block.name}: vars={local_vars}, constraints={local_cons}")

flat_vars = list(structured_model.component_data_objects(pyo.Var, active=True))
flat_cons = list(structured_model.component_data_objects(pyo.Constraint, active=True))
print(f"flattened variable count: {len(flat_vars)}")
print(f"flattened constraint count: {len(flat_cons)}")
Local variables by product block:
  product[standard]: vars=['make', 'open'], constraints=['capacity_link']
  product[premium]: vars=['make', 'open'], constraints=['capacity_link']
flattened variable count: 4
flattened constraint count: 3

Exercise 1.3. Add a shared constraint outside the blocks

Blocks do not prevent coupling. The labor constraint above couples the product blocks through the top-level model. Add a second shared constraint that limits total setup decisions to one product, then resolve. How does the production plan change?

one_setup_model = build_product_model()
one_setup_model.one_setup = pyo.Constraint(
    expr=sum(one_setup_model.product[p].open for p in one_setup_model.PRODUCTS) <= 1
)
solve_with_glpk(one_setup_model)

for product in one_setup_model.PRODUCTS:
    block = one_setup_model.product[product]
    print(f"{product:8s}: make={pyo.value(block.make):5.1f}, open={pyo.value(block.open):3.0f}")
print(f"profit: {pyo.value(one_setup_model.profit):.1f}")
termination: optimal
standard: make= 40.0, open=  1
premium : make=  0.0, open=  0
profit: 300.0

2. Model transformations

A model is the representation you want to write and maintain. A formulation is the algebraic form handed to a solver. Pyomo transformations rewrite one model representation into another while keeping the original modeling intent explicit.

Exercise 2.1. Relax integrality

The next cell relaxes the binary setup variables to continuous variables between 0 and 1. This is not the same problem as the mixed-integer model, but it is a useful diagnostic formulation: the relaxed objective is an upper bound for the maximization model.

relaxed_model = build_product_model()
pyo.TransformationFactory("core.relax_integer_vars").apply_to(relaxed_model)
solve_with_glpk(relaxed_model)

for product in relaxed_model.PRODUCTS:
    block = relaxed_model.product[product]
    print(
        f"{product:8s}: make={pyo.value(block.make):5.1f}, "
        f"open={pyo.value(block.open):5.2f}, open_bounds={block.open.bounds}"
    )
print(f"relaxed profit: {pyo.value(relaxed_model.profit):.1f}")
termination: optimal
standard: make= 40.0, open= 1.00, open_bounds=(0, 1)
premium : make=  4.0, open= 0.16, open_bounds=(0, 1)
relaxed profit: 351.2

Exercise 2.2. Compare model and formulation counts

The transformed model keeps the same block hierarchy, but the setup variables no longer have a binary domain. Use the counts and the solution values to explain why the relaxed formulation is easier for a linear solver and why its solution may not be implementable.

for name, model in [("mixed-integer", structured_model), ("relaxed", relaxed_model)]:
    binary_vars = [var for var in model.component_data_objects(pyo.Var) if var.is_binary()]
    print(
        f"{name:13s}: vars={len(list(model.component_data_objects(pyo.Var))):2d}, "
        f"constraints={len(list(model.component_data_objects(pyo.Constraint, active=True))):2d}, "
        f"binary_vars={len(binary_vars)}"
    )
mixed-integer: vars= 4, constraints= 3, binary_vars=2
relaxed      : vars= 4, constraints= 3, binary_vars=0

Exercises to continue

  1. Rebuild the lot-sizing model from Pyomo Fundamentals with one block per time period.

  2. Add a block-level expression for each period’s holding and backlog cost.

  3. Relax the period setup variables, solve the relaxed formulation, and compare the result to the original mixed-integer model.

References

The Pyomo Block and transformation patterns used here are standard Pyomo modeling tools Bynum et al., 2021.

References
  1. Bynum, M. L., Hackebeil, G. A., Hart, W. E., Laird, C. D., Nicholson, B. L., Siirola, J. D., Watson, J.-P., & Woodruff, D. L. (2021). Pyomo–optimization modeling in python (Third, Vol. 67). Springer Science & Business Media.