pyomo

Pyomo 6.8.0 deprecation warning for filter= and validate=


Starting with Pyomo 6.8.0, constructing a model component like this gives a deprecation warning:

mod.NEW_SET = Set(
    dimen=2,
    initialize=mod.OLD_SET,
    filter=lambda m, x, y: m.my_param[x] > 0
)

The deprecation warning says:

'filter=' callback signature matched (block, *value).  Please update the 
callback to match the signature (block, value).

This message is unclear, but I take it to mean that I need to rewrite the code above like this:

mod.NEW_SET = Set(
    dimen=2,
    initialize=mod.OLD_SET,
    filter=lambda m, xy: m.my_param[xy[0]] > 0
)

I can certainly do this, but it seems like it is a step backward in readability, since I can't use named variables to identify the components of the index. It's also a big deviation from how other rules are used -- the rule for an Expression, Constraint, Parameter or indexed Set always accepts a reference to the model and separate arguments for all the index keys.

Is this really the only way to write filters or validation rules for Pyomo to avoid a deprecation warning?


Solution

  • Actually, the change in the Set API was to remove a discrepancy between the Set API and the Expression, Constraint, and Param APIs. As you point out, the general rule API is rule(block, *indices), where indices is the index of the component data being constructed (and () if it is a scalar component). Unfortunately, Set's validate= and filter= arguments used a signature that looked the same, but was very different: rule(block, *set_item); that is, the arguments past the first were the (expanded) individual set member (value) that was being validated / filtered and not the index of the SetData that was being constructed. Because the SetData index was not passed to the function, you could not have a validate or filter for an Indexed Set that was dependent on the Set index (see issue #2655). Adding to the confusion, Param's validate= used a different API: rule(block, value, *indices).

    Pyomo 6.8.0 attempted to standardize Set by adopting the Param API. This meant that the value passed to the filter / validate callback is now passed as a single argument instead of being expanded. This approach resolves an ambiguity when dealing with jagged (dimen==None) sets (where individual set members are not all tuples of the same length). If both the value and the set index were expanded, it would be ambiguous where the value ended and the index began. Not expanding the value resolves this and is also consistent with the Param API.

    Now, what to do about the deprecation warning. Your proposal is certainly valid. The other is to use (ideally module-scoped) regular functions instead of anonymous functions (lambdas), where you can unpack the value to make the logic easier to follow:

    def NEW_SET_filter(m, val):
        """Only allow members x, y where my_param[y] is strictly positive"""
        x, y = val
        return m.my_param[y] > 0
    
    mod.NEW_SET = Set(
        dimen=2,
        initialize=mod.OLD_SET,
        filter=NEW_SET_filter,
    )
    

    While using module-scoped functions is more verbose, they are generally preferable over lambdas. First, they can make documentation a little easier. More importantly, anonymous (lambda) functions are generally not compatible with deepcopying (model cloning) and pickling, whereas module-scoped functions are.