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

Multiple dispatch for relational expression generation #3483

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
b7226e8
Move ARG_TYPE and base dispatcher utilities to expr_common
jsiirola Feb 20, 2025
8a93fbd
Use multiple dispatch for relational expression generation
jsiirola Feb 20, 2025
ad5985c
Update tests to reflect more consistent relational arg processing
jsiirola Feb 20, 2025
a76abac
Update imports to import from actual source
jsiirola Feb 20, 2025
a7c0908
Merge branch 'main' into relational-multiple-dispatch
jsiirola Feb 21, 2025
05297ed
Merge branch 'main' into relational-multiple-dispatch
jsiirola Feb 25, 2025
f78bc94
Merge branch 'main' into relational-multiple-dispatch
jsiirola Mar 2, 2025
e858b8f
Merge branch 'main' into relational-multiple-dispatch
emma58 Mar 3, 2025
7779930
Merge branch 'main' into relational-multiple-dispatch
jsiirola Mar 8, 2025
8d1e7a5
Merge branch 'main' into relational-multiple-dispatch
jsiirola Mar 13, 2025
a5524e5
Merge branch 'main' into relational-multiple-dispatch
jsiirola Mar 25, 2025
61c433f
Addressing review comments
jsiirola Mar 25, 2025
9efb375
Refactor to support reuse for testing relational expressions
jsiirola Mar 25, 2025
20d6067
Rename to maintain consistency
jsiirola Mar 25, 2025
f7fc053
Merge branch 'main' into relational-multiple-dispatch
jsiirola Apr 1, 2025
99d1be3
Merge branch 'main' into relational-multiple-dispatch
jsiirola Apr 2, 2025
2f5ac1f
NFC: address reviewer comment
jsiirola Apr 2, 2025
c977e5b
Merge remote-tracking branch 'me/numeric_expr-test-driver' into relat…
jsiirola Apr 3, 2025
c2d60e2
Reverse the priority for resolving generic asnumeric/mutable/invalid …
jsiirola Apr 3, 2025
fea56b1
Update docs / fix grammar typo
jsiirola Apr 3, 2025
69f5f4f
Support testing exception message in the dispatch tester
jsiirola Apr 3, 2025
f1bc18f
Add dispatcher tests for equality expressions
jsiirola Apr 3, 2025
2aa4fcf
Add testing for <= dispatcher
jsiirola Apr 3, 2025
353c9a9
Comparing expressions should check Inequality strict-ness
jsiirola Apr 3, 2025
7b636d1
Add strict inequality dispatcher tests
jsiirola Apr 3, 2025
7eec611
Update tests to track change in inequality representation
jsiirola Apr 3, 2025
e1509c2
NFC: apply black
jsiirola Apr 3, 2025
ac5d88d
NFC: fix typo
jsiirola Apr 3, 2025
1f5e1e7
Merge branch 'main' into relational-multiple-dispatch
blnicho Apr 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions pyomo/core/expr/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,9 @@
)

#
# FIXME: remove circular dependencies between relational_expr and numeric_expr
# FIXME: remove circular dependencies between logical_expr and numeric_expr
#

# Initialize relational expression functions
numeric_expr._generate_relational_expression = (
relational_expr._generate_relational_expression
)

# Initialize logicalvalue functions
boolean_value._generate_logical_proposition = logical_expr._generate_logical_proposition

Expand Down
2 changes: 0 additions & 2 deletions pyomo/core/expr/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
# This software is distributed under the 3-clause BSD License.
# ___________________________________________________________________________

import enum

from pyomo.common.dependencies import attempt_import
from pyomo.common.numeric_types import native_types
from pyomo.common.modeling import NOTSET
Expand Down
2 changes: 1 addition & 1 deletion pyomo/core/expr/boolean_value.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@

from pyomo.common.deprecation import deprecated
from pyomo.common.modeling import NOTSET
from pyomo.common.numeric_types import native_types, native_logical_types
from pyomo.core.expr.expr_common import _type_check_exception_arg
from pyomo.core.expr.numvalue import native_types, native_logical_types
from pyomo.core.expr.expr_common import _and, _or, _equiv, _inv, _xor, _impl
from pyomo.core.pyomoobject import PyomoObject

Expand Down
8 changes: 7 additions & 1 deletion pyomo/core/expr/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,11 @@ def handle_sequence(node: collections.abc.Sequence, pn: List):
return list(node)


def handle_inequality(node: collections.abc.Sequence, pn: List):
pn.append((type(node), node.nargs(), node.strict))
return node.args


def _generic_expression_handler():
return handle_expression

Expand All @@ -83,7 +88,8 @@ def _generic_expression_handler():
handler[NPV_ExternalFunctionExpression] = handle_external_function_expression
handler[AbsExpression] = handle_unary_expression
handler[NPV_AbsExpression] = handle_unary_expression
handler[RangedExpression] = handle_expression
handler[InequalityExpression] = handle_inequality
handler[RangedExpression] = handle_inequality
handler[list] = handle_sequence


Expand Down
118 changes: 114 additions & 4 deletions pyomo/core/expr/expr_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@

TO_STRING_VERBOSE = False

_eq = 0
_le = 1
_lt = 2

# logical propositions
_and = 0
_or = 1
Expand Down Expand Up @@ -83,6 +79,120 @@ class ExpressionType(enums.Enum):
LOGICAL = 2


class NUMERIC_ARG_TYPE(enums.IntEnum):
MUTABLE = -2
ASNUMERIC = -1
INVALID = 0
NATIVE = 1
NPV = 2
PARAM = 3
VAR = 4
MONOMIAL = 5
LINEAR = 6
SUM = 7
OTHER = 8


class RELATIONAL_ARG_TYPE(enums.IntEnum, metaclass=enums.ExtendedEnumType):
__base_enum__ = NUMERIC_ARG_TYPE

INEQUALITY = 100
INVALID_RELATIONAL = 101
Comment on lines +96 to +100
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this have equality in it? Why doesn't it?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is the list of argument types for relational expressions. As an Equality expression is not allowed to be the argument for another numeric / relational expression, we will never need to do special dispatch for it (it will fall back on getting mapped to INVALID).



def _invalid(*args):
return NotImplemented


def _recast_mutable(expr):
expr.make_immutable()
if expr._nargs > 1:
return expr
elif not expr._nargs:
return 0
else:
return expr._args_[0]


def _unary_op_dispatcher_type_mapping(dispatcher, updates, TYPES=NUMERIC_ARG_TYPE):
#
# Special case (wrapping) operators
#
def _asnumeric(a):
a = a.as_numeric()
return dispatcher[a.__class__](a)

def _mutable(a):
a = _recast_mutable(a)
return dispatcher[a.__class__](a)

mapping = {
TYPES.ASNUMERIC: _asnumeric,
TYPES.MUTABLE: _mutable,
TYPES.INVALID: _invalid,
}

mapping.update(updates)
return mapping


def _binary_op_dispatcher_type_mapping(dispatcher, updates, TYPES=NUMERIC_ARG_TYPE):
#
# Special case (wrapping) operators
#
def _any_asnumeric(a, b):
b = b.as_numeric()
return dispatcher[a.__class__, b.__class__](a, b)

def _asnumeric_any(a, b):
a = a.as_numeric()
return dispatcher[a.__class__, b.__class__](a, b)

def _asnumeric_asnumeric(a, b):
a = a.as_numeric()
b = b.as_numeric()
return dispatcher[a.__class__, b.__class__](a, b)

def _any_mutable(a, b):
b = _recast_mutable(b)
return dispatcher[a.__class__, b.__class__](a, b)

def _mutable_any(a, b):
a = _recast_mutable(a)
return dispatcher[a.__class__, b.__class__](a, b)

def _mutable_mutable(a, b):
if a is b:
# Note: _recast_mutable is an in-place operation: make sure
# that we don't call it twice on the same object.
a = b = _recast_mutable(a)
else:
a = _recast_mutable(a)
b = _recast_mutable(b)
return dispatcher[a.__class__, b.__class__](a, b)

mapping = {}

# Because ASNUMERIC and MUTABLE re-call the dispatcher, we want to
# resolve ASNUMERIC first, MUTABLE second, and INVALID last. That
# means we will add them to the dispatcher dict in opposite order so
# "higher priority" callbacks override lower priority ones.

mapping.update({(i, TYPES.INVALID): _invalid for i in TYPES})
mapping.update({(TYPES.INVALID, i): _invalid for i in TYPES})

mapping.update({(i, TYPES.MUTABLE): _any_mutable for i in TYPES})
mapping.update({(TYPES.MUTABLE, i): _mutable_any for i in TYPES})
mapping[TYPES.MUTABLE, TYPES.MUTABLE] = _mutable_mutable

mapping.update({(i, TYPES.ASNUMERIC): _any_asnumeric for i in TYPES})
mapping.update({(TYPES.ASNUMERIC, i): _asnumeric_any for i in TYPES})
mapping[TYPES.ASNUMERIC, TYPES.ASNUMERIC] = _asnumeric_asnumeric

mapping.update(updates)
return mapping


@deprecated(
"""The clone counter has been removed and will always return 0.

Expand Down
Loading
Loading