Skip to content

contrib.cp: Adding beforeChild handling for bools in logical expressions #3315

Merged
emma58 merged 8 commits intoPyomo:mainfrom
emma58:fix-logical-to-disjunctive-bug
Aug 5, 2024
Merged

contrib.cp: Adding beforeChild handling for bools in logical expressions #3315
emma58 merged 8 commits intoPyomo:mainfrom
emma58:fix-logical-to-disjunctive-bug

Conversation

@emma58
Copy link
Copy Markdown
Contributor

@emma58 emma58 commented Jul 10, 2024

Fixes # .

Summary/Motivation:

This fixes a bug in the logical-to-disjunctive walker where we would hit our own carefulness in terms of not mixing algebraic and logical expressions. Specifically, the following code

from pyomo.environ import *
from pyomo.contrib.cp.transform.logical_to_disjunctive_walker import (
    LogicalToDisjunctiveVisitor,
)

m = ConcreteModel()
m.a = BooleanVar()
e = m.a.equivalent_to(True)

visitor = LogicalToDisjunctiveVisitor()
m.cons = visitor.constraints
m.z = visitor.z_vars

visitor.walk_expression(e)

resulted in this error in the walker:

Traceback (most recent call last):
  File "/home/esjohn/src/pyomo/pyomo/contrib/cp/tests/thing.py", line 14, in <module>
    visitor.walk_expression(e)
  File "/home/esjohn/src/pyomo/pyomo/core/expr/visitor.py", line 276, in walk_expression
    result = self._process_node(root, RECURSION_LIMIT)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/esjohn/src/pyomo/pyomo/core/expr/visitor.py", line 489, in _process_node_bx
    return self.exitNode(node, data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/esjohn/src/pyomo/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py", line 262, in exitNode
    return _operator_dispatcher[node.__class__](self, node, *data)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/esjohn/src/pyomo/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py", line 95, in _dispatch_equivalence
    _dispatch_or(visitor, node, 1 - a, b),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/esjohn/src/pyomo/pyomo/contrib/cp/transform/logical_to_disjunctive_walker.py", line 113, in _dispatch_or
    visitor.constraints.add((1 - z) + sum(args) >= 1)
                                      ^^^^^^^^^
TypeError: unsupported operand type(s) for +: 'LinearExpression' and 'bool'

Changes proposed in this PR:

  • Checks for bool types specifically in beforeChild in the logical-to-disjunctive walker, and casts them to integer so they will be binary.
  • Adds a test

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
Copy Markdown

codecov bot commented Jul 11, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 88.50%. Comparing base (53d5cad) to head (18e5f48).
Report is 90 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3315      +/-   ##
==========================================
+ Coverage   86.46%   88.50%   +2.04%     
==========================================
  Files         801      868      +67     
  Lines       96758    99223    +2465     
==========================================
+ Hits        83661    87819    +4158     
+ Misses      13097    11404    -1693     
Flag Coverage Δ
linux 86.30% <100.00%> (+0.03%) ⬆️
osx 75.61% <100.00%> (+0.05%) ⬆️
other 86.49% <100.00%> (+0.03%) ⬆️
win 83.80% <100.00%> (+0.04%) ⬆️

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

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

Comment on lines 246 to 254
if child.__class__ is bool:
# If we encounter a bool, we are going to need to treat it as
# binary explicitly because we are finally pedantic enough in the
# expression system to not allow some of the mixing we will need
# (like summing a LinearExpression with a bool)
return False, int(child)

if child.__class__ in EXPR.native_types:
return False, child
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is fine, however two questions:

  1. Would the following be more efficient:
if child.__class__ in EXPR.native_types:
    if child.__class__ is bool:
        # If we encounter a bool, we are going to need to treat it as
        # binary explicitly because we are finally pedantic enough in the
        # expression system to not allow some of the mixing we will need
        # (like summing a LinearExpression with a bool)
        return False, int(child)
    return False, child
  1. are there concerns with float constants here? For Param, we error if the value is fractional, but not here. If the same concern applies, then maybe we should just do:
if child.__class__ in EXPR.native_types:
    val = int(child)
    if val != child:
        raise ValueError("...")
    return False, val

(which covers both the fractional values and the bool case)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh, good catch! But I think it's actually the reverse: I shouldn't check in Param or here because it won't be required when we probably someday allow relational expressions to be in logical expressions here. And we are checking for integrality in places where we need it (e.g., atmost, atleast, and exactly)

@blnicho
Copy link
Copy Markdown
Member

blnicho commented Jul 23, 2024

Waiting for @emma58 to reply to @jsiirola's question before merging

emma58 and others added 3 commits July 23, 2024 16:07
@emma58 emma58 merged commit 13f1cc0 into Pyomo:main Aug 5, 2024
@emma58 emma58 deleted the fix-logical-to-disjunctive-bug branch August 5, 2024 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

No open projects

Development

Successfully merging this pull request may close these issues.

4 participants