Pyomo / pyomo

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

Propagate State from scaling tools throws an error if any variable is uninitialized #2817

Closed dallan-keylogic closed 1 day ago

dallan-keylogic commented 1 year ago

Summary

propagate_state from Pyomo's scaling tools cannot handle if any variables in the model are uninitialized.

In the original IDAES model this was encountered in (the ControlVolume1D), we have unpaired material and energy difference terms along either the x=0 or x=1 boundary (depending on the discretization direction). Presently, we use the fact those terms are uninitialized in initialization methods to determine whether the user has provided an initial guess or not, so just initializing them at zero isn't an immediate solution.

Steps to reproduce the issue

Minimum example:

import pyomo.environ as pyo

m = pyo.ConcreteModel()
m.x = pyo.Var()
m.y = pyo.Var()
m.z = pyo.Var()

m.x.value = 1
m.y.value = 1

@m.Constraint()
def eqn1(b):
    return 10 * b.y == b.x

m.scaling_factor = pyo.Suffix(direction=pyo.Suffix.EXPORT)
m.scaling_factor[m.x] = 0.1
m.scaling_factor[m.eqn1] = 0.1

pyomo_scaling = pyo.TransformationFactory('core.scale_model')

m_scaled = pyomo_scaling.create_using(m, rename=False)

pyomo_scaling.propagate_solution(m_scaled, m)

Error Message

ERROR: evaluating object as numeric value: z
        (object: <class 'pyomo.core.base.var.ScalarVar'>)
    No value for uninitialized NumericValue object z
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[3], line 26
     21 m_scaled = pyomo_scaling.create_using(m, rename=False)
     23 # solver_obj = pyo.SolverFactory('ipopt')
     24 # solver_obj.solve(m_scaled, tee=True)
---> 26 pyomo_scaling.propagate_solution(m_scaled, m)

File ~\.conda\envs\idaes-newer\lib\site-packages\pyomo\core\plugins\transform\scaling.py:360, in ScaleModel.propagate_solution(self, scaled_model, original_model)
    356 original_v = original_model.find_component(original_v_path)
    358 for k in scaled_v:
    359     original_v[k].set_value(
--> 360         value(scaled_v[k]) / component_scaling_factor_map[scaled_v[k]],
    361         skip_validation=True,
    362     )
    363     if check_reduced_costs and scaled_v[k] in scaled_model.rc:
    364         original_model.rc[original_v[k]] = scaled_model.rc[scaled_v[k]] * component_scaling_factor_map[
    365             scaled_v[k]] / objective_scaling_factor

File ~\.conda\envs\idaes-newer\lib\site-packages\pyomo\core\expr\numvalue.py:140, in value(obj, exception)
    138     tmp = obj(exception=True)
    139     if tmp is None:
--> 140         raise ValueError(
    141             "No value for uninitialized NumericValue object %s"
    142             % (obj.name,))
    143     return tmp
    144 except TemplateExpressionError:
    145     # Template expressions work by catching this error type. So
    146     # we should defer this error handling and not log an error
    147     # message.

ValueError: No value for uninitialized NumericValue object z

Pyomo version 6.5.1a0

Robbybp commented 1 month ago

@dallan-keylogic I have a PR open to fix this, #3275. What would you expect to happen for an uninitialized (None-valued) variable in the scaled model? I could see these variable being skipped (leaving the values in the original model unchanged), or could see None being propagated just like any other value, potentially overriding a value in the original model.

dallan-keylogic commented 1 month ago

@Robbybp Thank you for fixing this. I'd say propagate the None and log a warning if the variable is not None in the unscaled model. I don't expect such a situation to arise often.

Also, could you please comment on my check_parallel_jacobian on the desired behavior for the tolerance? I'd like to finalize that behavior so I can fix the failing tests.