Pyomo / pyomo

An object-oriented algebraic modeling language in Python for structured optimization problems.
https://www.pyomo.org
Other
2.02k stars 518 forks source link

Return Constraint.Skip or Constraint.Infeasible when constraints resolve to a Boolean #2868

Closed pmlpm1986 closed 11 months ago

pmlpm1986 commented 1 year ago

Summary

Pyomo constraints that end up being true or false upon instantiation (i.e., that resolve "to a trivial Boolean") require modellers/programmers to explicitly account for those special cases, even when they should be ignored, namely if the constraint is always feasible. This is often the case when the constraints involve sets and empty sets are a possibility.

Conceptually (i.e., on paper), such a condition does not require special treatment since the constraints can be ignored. However, Pyomo requires these cases to handled individually, which is unintuitive, cumbersome and inefficient (since every user coming across these constraints will have to handle them individually).

I feel that making the constraint rules return Constraint.Skip (for True constraints) and Constraint.Infeasible (for False constraints) when these resolve to trivial Booleans would make writing mathematical programming problems in Pyomo more natural and intuitive.

Rationale

Mathematical programming languages should aim to be as intuitive and as close to the real thing as possible. Creating mathematical programming problems featuring possibly trivial constraints is not as simple as it should be in Pyomo. In particular, it is:

1) Unintuitive, since the mathematical description is insufficient; 2) Cumbersome, given that users have to account for each of these cases; 3) Inefficient, since every user will have to do something that would be better handled upstream.

Description

One possible solution is to create a method to do this conversion and call it within each constraint with the return object as the argument:

def constraint_handler(constraint_return):
    if type(constraint_return) == bool:
        return pyo.Constraint.Skip if constraint_return else pyo.Constraint.Infeasible
    return constraint_return
def possible_constraint_rule(model, m):
    return constraint_handler(...[constraint expression]...)
model.possible_constraint = pyo.Constraint(model.M, rule=possible_constraint_rule)

Perhaps this could also be done internally, which would make the code look cleaner.

However, I am unaware of the implications of using such a method.

jsiirola commented 11 months ago

This already exists through the pyomo.core.base.constraint.simple_constraint_rule decorator:

from pyomo.core.base.constraint import simple_constraint_rule

@simple_constraint_rule
def possible_constraint_rule(model, m):
    return [constraint expression]
model.possible_constraint = pyo.Constraint(model.M, rule=possible_constraint_rule)

That said, we generally do not recommend using it, as it can mask subtle and hard-to-diagnose modeling errors. The most common is the following:

from pyomo.core.base.constraint import simple_constraint_rule

@simple_constraint_rule
def possible_constraint_rule(model, m):
    [constraint expression]
model.possible_constraint = pyo.Constraint(model.M, rule=possible_constraint_rule)

(which results in all the constraints for model.possible_constraint to be silently generated and then omitted from the model)