pyomo icon indicating copy to clipboard operation
pyomo copied to clipboard

[PEP]: Deprecate Params being callable

Open emma58 opened this issue 9 months ago • 2 comments

Summary

Perhaps I'm poking a bear (but maybe it's the one we're running from?), but I don't see any value in the fact that ParamDatas implement __call__ when you could just as easily ask for their value in other ways. We recently ran into a quite hard to track down modeling bug because of this, so I would argue the downsides outweigh the benefits.

Description

Because ParamData implements __call__ with an optional argument (exception) that only gets used if the Param doesn't have a value (not all that common), you can call a Param that has a value and pass any single argument you want:

from pyomo.environ import *

m = ConcreteModel()
m.p = Param(initialize=12.2)
# I can call m.p with *any* argument I want, and it will be ignored (because p has a value, 
# so we don't care that the arg should've been a bool
print(m.p(3))
print(m.p(m.p))
print(m.p('hi'))
print(m.p(print))
#....

But this means, that if I make the following one-character typo, I have a lot of hours of debugging to do:

m.J = Set(initialize=range(10))
m.x = Var(m.J, bounds=(0, 10))
m.y = Var(bounds=(2, 20))
m.obj = Objective(expr=m.p(m.y + sum(m.x[j] for j in m.J)))
# whoops, I meant:
# m.obj = Objective(expr=m.p * (m.y + sum(m.x[j] for j in m.J)))

While the mistake in the case above is maybe easy to track down since the objective will be constant, this wasn't so easy to track down when this typo was in one of 6 objective terms in a 280-line Pyomo model.

Additional information

If for some reason we really think Params should be callable, I might still argue that we should at least deprecate the exception argument so that this can't happen.

emma58 avatar Mar 09 '25 00:03 emma58

This is related to #2922

blnicho avatar Mar 10 '25 15:03 blnicho

I would be all for removing "call to evaluate". Unfortunately, if we remove it, we probably need to remove it from all Expression objects (ParamData supports it for consistency with all other "pyomo objects" in expression trees). That would require a bump to Pyomo 7. If we decide to start moving in this direction, we need to put a deprecation notice in ASAP.

If we remove "call-to-evaluate", we will need to standardize on an alternate recommended approach to resolving expressions and other pyomo objects to "native" types. My guess is that we will just use value() for that (as it and "call-to-evaluate" are currently synonyms). We might consider updating that, though. Currently value() returns a float-like thing. I think we should update value() to return whatever the appropriate "native representation" is:

  • for regular numeric expressions it should be float-like
  • for logical expressions it should be bool-like
  • for numeric expressions with units it should be a pint Quantity
  • for matrix expressions it should be a numpy ndarray
  • ...

Then, if a user really cared what type they got back we would have as_ methods (e.g., as_float, as_quantity, as_bool, as_matrix, ...) which had a "deterministic" return type.

jsiirola avatar Mar 10 '25 16:03 jsiirola