Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consolidate walker logic in LP/NL representations #3015

Merged
merged 33 commits into from
Oct 23, 2023

Conversation

jsiirola
Copy link
Member

Fixes # .

Summary/Motivation:

This PR reduces repetition in the codebase by consolidating beforeChild and exitNode logic in the LP and NL representation walkers into standard implementations of BeforeChildDispatcher and ExitNodeDispatcher. By consolidating logic we can ensure that all coefficients / values in the walker return data structures are guaranteed to be native_numeric_types, allowing us to simplify (and slightly speed up) the LP and NL walkers.

As part of this, we are making a change in the definition of the native_numeric_types set by removing complex from that set, and registering complex types in a separate native_complex_types set. This change makes for a more consistent definition of native_numeric_types: it contains only data types that are "equivalent to float" and can be emitted as part of standard solver interfaces.

Changes proposed in this PR:

  • Define BeforeChildDispatcher and ExitNodeDispatcher classes to centralize the definition and management of the multiple dispatch dictionaries used in the representation walkers
  • Remove complex from native_numeric_types (and put in a separate native_complex_types set
  • Expand type registration documentation
  • Update / expand some of the numpy type registrations

Legal Acknowledgement

By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution:

  1. I agree my contributions are submitted under the BSD license.
  2. I represent I am authorized to make the contributions and grant the license. If my employer has rights to intellectual property that includes these contributions, I represent that I have received permission to make contributions and grant the required license on behalf of that employer.

@codecov
Copy link

codecov bot commented Oct 12, 2023

Codecov Report

Attention: 10 lines in your changes are missing coverage. Please review.

Comparison is base (12be54c) 87.84% compared to head (db38e94) 87.87%.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3015      +/-   ##
==========================================
+ Coverage   87.84%   87.87%   +0.03%     
==========================================
  Files         769      769              
  Lines       89541    89516      -25     
==========================================
+ Hits        78657    78665       +8     
+ Misses      10884    10851      -33     
Flag Coverage Δ
linux 85.20% <96.61%> (+0.03%) ⬆️
osx 75.00% <96.04%> (+0.02%) ⬆️
other 85.38% <96.61%> (+0.02%) ⬆️
win 82.42% <96.04%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
pyomo/common/numeric_types.py 92.94% <100.00%> (+0.53%) ⬆️
pyomo/core/kernel/register_numpy_types.py 100.00% <100.00%> (ø)
pyomo/repn/plugins/lp_writer.py 93.65% <ø> (+0.59%) ⬆️
pyomo/repn/quadratic.py 93.08% <100.00%> (ø)
pyomo/repn/util.py 99.38% <100.00%> (+0.23%) ⬆️
pyomo/util/calc_var_value.py 100.00% <100.00%> (ø)
pyomo/common/dependencies.py 97.68% <84.61%> (-0.60%) ⬇️
pyomo/repn/linear.py 97.75% <97.32%> (+1.28%) ⬆️
pyomo/repn/plugins/nl_writer.py 95.47% <95.14%> (+1.45%) ⬆️

... and 1 file with indirect coverage changes

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@emma58 emma58 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really cool--I like it! Mostly just questions to make sure I'm understanding...

"""
A utility function for updating the set of types that are
recognized to handle numeric values.
def RegisterNumericType(new_type: type):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't really bother me (as that would by hypocritical), but wouldn't it be PEP8-ier for these be snake case rather than camel case since they're functions?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - they should be snake case. But as this has been an "official" API for a long time, we would need to rename it with a deprecation path (which I'm OK with, but maybe as a separate PR?).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see. A separate PR makes sense.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to do that... We have these in several places.

Comment on lines +106 to +107
expressions. The type should be compatible with :py:class:`float`
(that is, store a scalar and be castable to a Python float).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a copy-paste typo? Should they be castable to int rather than float? (I mean, Python is happy to cast floats to ints but...)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Believe it or not, it is not a typo. The assumption in the Numeric expression system is that the value of any expression should be float-like.

This set is actually not used anywhere in Pyomo. I think it was added in the dawn of time on speculation that it might be useful. Maybe the right thing is to deprecate it entirely?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be in favor of deprecating it before someone starts using it and we get really confused. :P

Comment on lines +174 to +180
def RegisterLogicalType(new_type: type):
"""Register the specified type as a "logical type".

A utility function for registering new types as "native logical
types". Logical types can be leaf nodes in Pyomo logical
expressions. The type should be compatible with :py:class:`bool`
(that is, store a scalar and be castable to a Python bool).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What goes in native_logical_types that doesn't go in native_boolean_types? Or is this a backwards compatibility thing?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a backwards compatibility thing. native_boolean_types is the deprecated set and was replaced by native_logical_types. I toyed with making them explicit aliases for each other, but elected to not continue mucking with a deprecated API.

_CONSTANT = ExprType.CONSTANT


class BeforeChildDispatcher(collections.defaultdict):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah, this is very cute... I like it.

Comment on lines +355 to +361
# @staticmethod
# def _before_var(visitor, child):
# pass

# @staticmethod
# def _before_named_expression(visitor, child):
# pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are these commented out?

Copy link
Member Author

@jsiirola jsiirola Oct 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly because I don't like ABCMeta for classes in tight loops. These are two methods that are required by register_dispatcher, but are not implemented in the base class (the LP and NL implementations are completely different). They are commented out to remind developers of an expected API...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But isn't the advantage of ABCMeta that it will actually yell at you when you don't implement the things you expect to be there?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to Emma's comment.

Comment on lines -430 to -431
if offset.__class__ not in int_float:
offset = float(offset)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you have to cast these anymore? Is it a change from this PR, or was it already redundant?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new walkers are better at catching non-float-like types when processing the leaf nodes, so we no longer have to re-verify the return types from the walker.

Copy link
Contributor

@mrmundt mrmundt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One NFC that you can ignore if you want (it's a typo that crate-ci doesn't catch, but I added it to the October list, so it'll be caught soon regardless).

pyomo/common/dependencies.py Outdated Show resolved Hide resolved
"""
A utility function for updating the set of types that are
recognized to handle numeric values.
def RegisterNumericType(new_type: type):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we're going to do that... We have these in several places.

Comment on lines +355 to +361
# @staticmethod
# def _before_var(visitor, child):
# pass

# @staticmethod
# def _before_named_expression(visitor, child):
# pass
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to Emma's comment.

@jsiirola jsiirola merged commit 47a9b26 into Pyomo:main Oct 23, 2023
30 checks passed
@jsiirola jsiirola deleted the repn-dry-lp-nl-beforechild branch October 23, 2023 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants