Accessing Pyomo variable values and objective function value

I built some pretty big mixed integer programming models in Pyomo a few years ago (back in the Pyomo 2.x and 3.x days I believe). After neglecting them for a while, I returned to them this year and, not surprisingly, tons of improvements and changes had been made to Pyomo. I'm using Pyomo 5.0.1 and spent a bunch of time updating my old models and driver scripts to be compliant with the new version. In doing so, I had a bunch of trouble doing relatively simple things that used to work in older versions. For example, accessing variable values and the objective function value after successfully solving a problem, proved to be tricky. I read the docs, snooped numerous example scripts in various GitHub repos and did a lot of reading in the Pyomo forums and StackOverflow. In doing so, I ran across several different ways to access these values. I'd like to find out which approaches are considered the "right" or Pyomothonic way. I also just wanted to create a simple notebook that illustrates this confusion in the hopes that I, and others, might learn something.

So, I'm using the simple production model example that came with Pyomo (some version ago). After loading and solving, we'll explore a bunch of different ways to access both variable values and the objective function value.

In [2]:
import pyomo.opt
from pyomo.environ import *
In [3]:
# Import model
import prod

# Create the model instance
instance = prod.model.create_instance("prod.dat")
In [4]:
instance.pprint()
1 Set Declarations
    P : Dim=0, Dimen=1, Size=2, Domain=None, Ordered=False, Bounds=None
        ['bands', 'coils']

4 Param Declarations
    a : Size=2, Index=P, Domain=Any, Default=None, Mutable=False
        Key   : Value
        bands :   200
        coils :   140
    b : Size=1, Index=None, Domain=Any, Default=None, Mutable=False
        Key  : Value
        None :    40
    c : Size=2, Index=P, Domain=Any, Default=None, Mutable=False
        Key   : Value
        bands :    25
        coils :    30
    u : Size=2, Index=P, Domain=Any, Default=None, Mutable=False
        Key   : Value
        bands :  6000
        coils :  4000

1 Var Declarations
    X : Size=2, Index=P
        Key   : Lower : Value : Upper : Fixed : Stale : Domain
        bands :  None :  None :  None : False :  True :  Reals
        coils :  None :  None :  None : False :  True :  Reals

1 Objective Declarations
    Total_Profit : Size=1, Index=None, Active=True
        Key  : Active : Sense    : Expression
        None :   True : maximize : 30*X[coils] + 25*X[bands]

2 Constraint Declarations
    Limit : Size=2, Index=P, Active=True
        Key   : Lower : Body     : Upper  : Active
        bands :   0.0 : X[bands] : 6000.0 :   True
        coils :   0.0 : X[coils] : 4000.0 :   True
    Time : Size=1, Index=None, Active=True
        Key  : Lower : Body                                           : Upper : Active
        None :  -Inf : 0.007142857142857143*X[coils] + 0.005*X[bands] :     b :   True

9 Declarations: P a b c u X Total_Profit Time Limit
In [5]:
# Setup the optimizer
opt = pyomo.opt.SolverFactory("cbc")

# Optimize
results = opt.solve(instance)

# Write the output
results.write(num=1)
# ==========================================================
# = Solver Results                                         =
# ==========================================================
# ----------------------------------------------------------
#   Problem Information
# ----------------------------------------------------------
Problem: 
- Name: tmpma7o0956.pyomo
  Lower bound: -192000.0
  Upper bound: -192000.0
  Number of objectives: 1
  Number of constraints: 6
  Number of variables: 3
  Number of nonzeros: 7
  Sense: minimize
# ----------------------------------------------------------
#   Solver Information
# ----------------------------------------------------------
Solver: 
- Status: ok
  User time: -1.0
  Termination condition: optimal
  Error rc: 0
  Time: 0.015891075134277344
# ----------------------------------------------------------
#   Solution Information
# ----------------------------------------------------------
Solution: 
- number of solutions: 0
  number of solutions displayed: 0

In Pyomo 4.x, results from solving an optimization problem are automatically loaded into the model instance. See http://www.pyomo.org/blog/2015/12/26/updating-to-pyomo-41 for more details. So, how to interrogate the instance object to get things like the objective function value or variable values?

Variable values

Invidual variable values

Invididual variable values can be accessed via their value property. This is the approach shown in the Pyomo documentation in Sec 18.6.2.

In [6]:
instance.X['coils'].value
Out[6]:
1400
In [7]:
for p in instance.P:
    print(instance.X[p].value)
1400
6000

Variable values can also be accessed in a few other ways. I'm guessing from the documentation that the value property approach shown above is the "Pyomothonic" way to do it. However, I've seen the following two approaches used in various Pyomo problem example galleries.

In [8]:
value(instance.X['coils'])
Out[8]:
1400
In [9]:
instance.X['coils']()
Out[9]:
1400

What is type of an individual variable object? What about its string representation?

In [10]:
type(instance.X['coils'])
Out[10]:
pyomo.core.base.var._GeneralVarData
In [12]:
str(instance.X['coils'])
Out[12]:
'X[coils]'

All variable values

We can loop over the collection of variable objects in the problem instance and access their values as follows. This approach is based on Section 18.6.3 in the Pyomo 4 docs. I guess I'm confused as to the need for using getattr instead of simply accessing the object variable used in the for statement. As we see below, both v and varobject have type class 'pyomo.core.base.var.IndexedVar'.

In [19]:
for v in instance.component_objects(Var, active=True):
    print ("Variable component object",v)
    print ("Type of component object: ", str(type(v))[1:-1]) # Stripping <> for nbconvert
    varobject = getattr(instance, str(v))
    print ("Type of object accessed via getattr: ", str(type(varobject))[1:-1])
    for index in varobject:
        print ("   ", index, varobject[index].value)
Variable component object X
Type of component object:  class 'pyomo.core.base.var.IndexedVar'
Type of object accessed via getattr:  class 'pyomo.core.base.var.IndexedVar'
    coils 1400
    bands 6000

Why can't we just do this?

In [20]:
for v in instance.component_objects(Var, active=True):
    print ("Variable component object",v)
    for index in v:
        print ("   ", index, v[index].value)
Variable component object X
    coils 1400
    bands 6000

Objective function value

I've found accessing the objective function value even less clear. I can't seem to find the suggested way to do it in the official docs and have run across various approaches in examples in various GitHub repos. Here's a bunch of examples that work to various degrees, though some appear to be deprecated. I wonder which is the Pyomothonic way.

In [21]:
# Various ways of trying to print out objective function value

print('\ninstance.Total_Profit.value')
print(instance.Total_Profit.value)

print('\ninstance.Total_Profit.value()')
print(instance.Total_Profit.value())

print('\nvalue(instance.Total_Profit)')
print(value(instance.Total_Profit))

print('\nvalue(instance.Total_Profit())')
print(value(instance.Total_Profit()))

print("\nobjective_object = getattr(instance, 'Total_Profit')")
objective_object = getattr(instance, 'Total_Profit')

print('\nobjective_object.value')
print(objective_object.value)

print('\nvalue(objective_object)')
print(value(objective_object.value))

print('\nobjective_object()')
print(objective_object())
instance.Total_Profit.value
WARNING: DEPRECATED: The .value property getter on SimpleObjective is deprecated. Use the .expr property getter instead
30*X[coils] + 25*X[bands]

instance.Total_Profit.value()
WARNING: DEPRECATED: The .value property getter on SimpleObjective is deprecated. Use the .expr property getter instead
192000

value(instance.Total_Profit)
192000

value(instance.Total_Profit())
192000

objective_object = getattr(instance, 'Total_Profit')

objective_object.value
WARNING: DEPRECATED: The .value property getter on SimpleObjective is deprecated. Use the .expr property getter instead
30*X[coils] + 25*X[bands]

value(objective_object)
WARNING: DEPRECATED: The .value property getter on SimpleObjective is deprecated. Use the .expr property getter instead
192000

objective_object()
192000

What is the right way to access the objective function value?