A Pyomo Design Question

18 views
Skip to first unread message

Bill Hart

unread,
Apr 30, 2018, 2:28:55 PM4/30/18
to Pyomo Developers
There's an interesting discrepancy in Pyomo that I'd like some input on.  The expression system (both recent revisions and old) does not wrap numeric values in a NumericConstant() object.  Rather, numeric values are first-class objects in the expression tree.

However, when we add numeric values to components, we do wrap them.  So, if I have:

m.e = Expression(expr=1.0)

then 

m.e.expr is a numeric constant.

So, under the hood we're wrapping numeric values with constants.  This has ramifications for expression management, since this can introduce NumericConstant() objects into expressions where they wouldn't normally arise.  Also, I suspect that there is a performance impact as well.

The key benefit for doing this wrapping is that it makes the management of expressions uniform.  You don't need checks in the Objective class for whether you have a numeric constant before calling standard methods (e.g. __call__() or is_fixed() or is_potentially_variable()).

However, I'm not sure if this uniformity is justified w.r.t. the complication it introduces in expression management.  Perhaps the biggest argument for keeping this wrapping is the use of __call__(), which users probably would prefer instead of calling value():

  value(m.e.expr)   vs   m.e.expr()

Thoughts?  Even if we decide to keep wrapping, I'm inclined to make a special wrapping constant value, which is easily recognized and stripped during expression management.

--Bill

Gabriel Hackebeil

unread,
Apr 30, 2018, 2:36:11 PM4/30/18
to pyomo-de...@googlegroups.com
I think it probably makes more sense to not wrap the value when it is assigned. I believe I added that when Expression was written because it seemed like we were making the assumption in so many places that things in the expression tree would always be subclasses of NumericValue. If that is no longer the case, then I support leaving whatever they assign to it alone.

Also, the Expression object itself has a call method, and that could be written to use the value function. Actually, it looks like I’m already doing this on the kernel expression() component.

Gabe

--
You received this message because you are subscribed to the Google Groups "Pyomo Developers" group.
To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-develope...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Laird, Carl Damon

unread,
Apr 30, 2018, 3:33:04 PM4/30/18
to pyomo-de...@googlegroups.com

Sorry, having a little trouble parsing this. Are you saying:

 

If we have only a float added to a component as the expression (i.e., m.obj = Objective(expr=3.0)) then we get a NumericConstant that wraps the float, but if the float is part of a longer expression (i.e. m.obj = Objective(expr=m.x + 3.0)) that we do NOT get a NumericConstant object wrapping the float “3.0”?

 

Is that the discrepancy?

 

Regards,

 

Carl.

--

Bill Hart

unread,
Apr 30, 2018, 3:46:25 PM4/30/18
to Pyomo Developers
Yes.  That's the discrepancy.

--Bill

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-developers+unsubscribe@googlegroups.com.

Laird, Carl Damon

unread,
Apr 30, 2018, 3:49:40 PM4/30/18
to pyomo-de...@googlegroups.com

Again, so I understand. If we *always* wrap floats in a NumericConstant, then we have certain methods guaranteed to be valid for all nodes of an expression tree. However, if we do not always wrap, then we have to do the checks for python POD first, correct?

 

Any major performance predictions either way?

 

 

 

From: <pyomo-de...@googlegroups.com> on behalf of Bill Hart <whar...@gmail.com>


Reply-To: "pyomo-de...@googlegroups.com" <pyomo-de...@googlegroups.com>
Date: Monday, April 30, 2018 at 2:46 PM
To: Pyomo Developers <pyomo-de...@googlegroups.com>

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-develope...@googlegroups.com.


For more options, visit https://groups.google.com/d/optout.

--

You received this message because you are subscribed to the Google Groups "Pyomo Developers" group.

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-develope...@googlegroups.com.

Bill Hart

unread,
Apr 30, 2018, 4:02:09 PM4/30/18
to Pyomo Developers
Right.

So in the current expression core, we do these checks.  The key justification for those is that we're wasting memory and runtime by allocating wrapper classes for numeric values.

The potential win for completely eliminating these wrapper objects is that as_numeric() shows up in a surprising number of core routines.  But I think that's an archaic way of checking if we have a constant value.

As Gabe noted, we can probably shield the user from this change in many instances.  But I think there will be some changes where users assume that they have an expression tree and don't realize that a numeric value is a valid expression.

--Bill

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-developers+unsubscribe@googlegroups.com.


For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Pyomo Developers" group.

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-developers+unsubscribe@googlegroups.com.

Siirola, John D

unread,
Apr 30, 2018, 6:20:41 PM4/30/18
to pyomo-de...@googlegroups.com

There was a significant performance and memory benefit of moving away from NumericConstants in expression trees.  However, we never made that change for the actual *component* APIs.  That is, if you ask for information about a component (e.g., m.e.expr(), m.c.body(), m.obj.expr(), m.c.lower()) you were guaranteed to get a derivative of Pyomo’s NumericValue class.  That allowed users to blindly use the __call__ notation for evaluation, or check is_fixed, etc. without any need to check “isinstance(x, NumericValue)” first.  The argument at the time was that when we were changing the storage within the expression system, we were changing an internal API, whereas changing the component attributes would change a public / user API.

 

For the record, `y = as_numeric(x)` is a utility method to efficiently return x as a NumericValue.  That is, expressions, Vars, (mutable) Params , etc were just returned, and constants were wrapped in NumericConstant before being returned.  While that guaranteed “y.is_constant()” would return he correct value, that was far from the only purpose.

 

john

 

From: pyomo-de...@googlegroups.com [mailto:pyomo-de...@googlegroups.com] On Behalf Of Bill Hart
Sent: Monday, April 30, 2018 2:02 PM
To: Pyomo Developers <pyomo-de...@googlegroups.com>

Subject: Re: [EXTERNAL] A Pyomo Design Question

 

Right.

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-develope...@googlegroups.com.


For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Pyomo Developers" group.

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-develope...@googlegroups.com.


For more options, visit https://groups.google.com/d/optout.

--
You received this message because you are subscribed to the Google Groups "Pyomo Developers" group.

To unsubscribe from this group and stop receiving emails from it, send an email to pyomo-develope...@googlegroups.com.

William Hart

unread,
Apr 30, 2018, 8:11:50 PM4/30/18
to pyomo-de...@googlegroups.com
John,

So part of my question is whether we think we need that sort of API consistency.  It's not obvious to me that we do.  And even if we say yes, I'm inclined to treat this as a wrapper object that is associated with the compenent.
Reply all
Reply to author
Forward
0 new messages