Pyomo / pyomo

An object-oriented algebraic modeling language in Python for structured optimization problems.
https://www.pyomo.org
Other
1.9k stars 490 forks source link

Unscaling model after solving using propagate_solution(scaled_model, original_model) throws error #3273

Closed fahim831 closed 1 month ago

fahim831 commented 1 month ago

Summary

I am trying to scale, solve, and unscale a pyomo model. The code itself worked well until I tried to scale but it didn't get a solution probably due to scaling issues. Now, after scaling, the model solves but I get an error when I try to scale it back. Unfortunately, I couldn't find any application in the doc on how to actually use the ```propagate_solution''' method.

I will try to provide a few lines of code to summarize what I'm doing without making it extensively long. The last line is what gives me the error

Steps to reproduce the issue

# example.py
from pyomo.environ import *
from pyomo.dae import *

model = ConcreteModel()

model.t = ContinuousSet(bounds=(0, 5))
model.x = ContinuousSet(bounds=(0, 3))
model.y = Var(model.t, model.x)
model.z = Var(model.t, model.x)
model.dydt = DerivativeVar(model.y, wrt=model.t)
model.dzdt = DerivativeVar(model.z, wrt=model.t)
model.dydx = DerivativeVar(model.y, wrt=model.x)
model.dzdx = DerivativeVar(model.z, wrt=model.x)

2 differential equations here in y and z
2 algebraic equations here in y and z

Discretize both y and z

# create the scaling factors
model.scaling_factor = Suffix(direction=Suffix.EXPORT)
model.scaling_factor[model.diffeq1] = 10 # scale diffeq1
model.scaling_factor[model.z] = 0.1 # scale z
# transform the model
scaled_model = TransformationFactory('core.scale_model').create_using(model)

solver = SolverFactory('ipopt')
results = solver.solve(scaled_model, tee=True)

model = TransformationFactory('core.scale_model').propagate_solution(scaled_model, model)

Error Message

$ # ERROR: evaluating object as numeric value: scaled_dydt[0,0]
        (object: <class 'pyomo.core.base.var._GeneralVarData'>)
    No value for uninitialized NumericValue object scaled_dydt[0,0]
Traceback (most recent call last):
  File "/Users/$USER/filename", line 291, in <module>
    model = TransformationFactory('core.scale_model').propagate_solution(scaled_model, model)
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/$USER/anaconda3/envs/lib/python3.12/site-packages/pyomo/core/plugins/transform/scaling.py", line 326, in propagate_solution
    value(scaled_v[k]) / component_scaling_factor_map[scaled_v[k]],
    ^^^^^^^^^^^^^^^^^^
  File "/Users/$USER/anaconda3/envs/watertap-dev/lib/python3.12/site-packages/pyomo/common/numeric_types.py", line 318, in value
    raise ValueError(
ValueError: No value for uninitialized NumericValue object scaled_dydt[0,0]

Information on your system

Pyomo version: 6.7.1 Python version: 3.12 Operating system: Mac OS Sonoma How Pyomo was installed (PyPI, conda, source): conda Solver (if applicable): ipopt

Robbybp commented 1 month ago

Hi @fahim831, I was able to reproduce your issue with the following modification of your example:

# example.py
from pyomo.environ import *
from pyomo.dae import *

model = ConcreteModel()

model.t = ContinuousSet(bounds=(0, 5))
model.x = ContinuousSet(bounds=(0, 3))
model.y = Var(model.t, model.x)
model.z = Var(model.t, model.x)
model.dydt = DerivativeVar(model.y, wrt=model.t)
model.dzdt = DerivativeVar(model.z, wrt=model.t)
model.dydx = DerivativeVar(model.y, wrt=model.x)
model.dzdx = DerivativeVar(model.z, wrt=model.x)

# 2 differential equations here in y and z
# 2 algebraic equations here in y and z
# 
# Discretize both y and z

@model.Constraint(model.t, model.x)
def diffeq1(m, t, x):
    return m.dydt[t, x] == m.z[t, x] - m.y[t, x]

model.obj = Objective(
    expr=sum(model.y[t, x]**2 + model.z[t, x]**2 for t in model.t for x in model.x),
    sense=minimize,
)

TransformationFactory("dae.finite_difference").apply_to(model, wrt=model.t, nfe=10, scheme="BACKWARD")
TransformationFactory("dae.finite_difference").apply_to(model, wrt=model.x, nfe=10, scheme="BACKWARD")

# create the scaling factors
model.scaling_factor = Suffix(direction=Suffix.EXPORT)
model.scaling_factor[model.diffeq1] = 10 # scale diffeq1
model.scaling_factor[model.z] = 0.1 # scale z
# transform the model
scaled_model = TransformationFactory('core.scale_model').create_using(model)

solver = SolverFactory('ipopt')
results = solver.solve(scaled_model, tee=True)

model = TransformationFactory('core.scale_model').propagate_solution(scaled_model, model)

This is because the derivative variables at (0,0) are (a) not initialized and (b) not used in the model (with backwards difference, anyway). A workaround is to initialize the derivative variables, e.g. model.dydt = DerivativeVar(model.y, wrt=model.t, initialize=0), but we should probably handle this in propagate_solution anyway. I'll look into it and see if this is a quick fix.

fahim831 commented 1 month ago

Hi @Robbybp, thanks. I was able to make it run after initializing all the derivatives at 0. Although, after it runs and finds a solution, when I call model.y, I get this new error (probably unrelated to the propagate_solution definition, however): AttributeError: 'NoneType' object has no attribute 'y'

I am guessing the solver is outputting a false positive for the optimal solution being found? Because when I check the results from solver.solve, I get this:

Solution: 
- number of solutions: 0
  number of solutions displayed: 0
Robbybp commented 1 month ago

From a quick glance, this is probably because propagate_solution returns None, and we are resetting model to this return value. Just changing the last line to

TransformationFactory('core.scale_model').propagate_solution(scaled_model, model)

should fix that.

fahim831 commented 1 month ago

Thanks! That was the issue.

Robbybp commented 1 week ago

https://github.com/Pyomo/pyomo/issues/2817 appears to be related.