Clip and scale calculate_variable_from_constraint#3993
Conversation
| # | ||
| # Re-set the variable value to trigger any warnings WRT the final | ||
| # variable state | ||
| variable.set_value(variable.value) |
There was a problem hiding this comment.
We could just add skip_validation=True here to silence the warnings. And potentially expose this option in the user-facing function. Not saying this is necessarily better than clipping, just want to make sure it's considered.
We could also transform calculate_variable_from_constraint into a bound-constrained solver by "clipping" the variable when taking the Newton step, as done by interior point methods.
There was a problem hiding this comment.
I'd rather have the warnings if we end up setting the variable value to something outside its bounds.
I would be okay with converting it to a bound-constrained solver. For the initial version of this PR, I wanted to keep the changes minor as to not break any existing workflow. I wonder whether turning on bound clipping would cause some of the CubicComplementarityVLE examples to break in IDAES---we've seen examples where earlier blocks converge to the wrong root, causing later blocks to have no feasible solution that satisfies the variable bounds, even though a solution exists if the entire VLE were solved on a single block. I think the moral of that story is "You need a better initialization method for VLE".
There was a problem hiding this comment.
Maybe skip_validation if it violates the bound by more than the tolerance?
I guess my problem with clipping is that you're just trading some bound infeasibility for some equality infeasibility. I would want to make sure that we didn't make the constraint infeasible (by more than tolerance) after the clipping. But I'm also now realizing that, since these are single-variable problems, clipping is not so different than respecting bounds during the solve iterations.
There was a problem hiding this comment.
we've seen examples where earlier blocks converge to the wrong root, causing later blocks to have no feasible solution that satisfies the variable bounds, even though a solution exists if the entire VLE were solved on a single block
I would love to see an example of this. I have some ideas related to bound propagation among blocks (e.g., #3573) that I would like to test on a real problem.
There was a problem hiding this comment.
Maybe skip_validation if it violates the bound by more than the tolerance?
I guess my problem with clipping is that you're just trading some bound infeasibility for some equality infeasibility. I would want to make sure that we didn't make the constraint infeasible (by more than tolerance) after the clipping.
Right now, if clipping the variable causes the constraint to become infeasible, it reverts to the unclipped value and logs a warning.
But I'm also now realizing that, since these are single-variable problems, clipping is not so different than respecting bounds during the solve iterations.
With any sort of nonlinear solution method, there's always the risk that changing the intermediate iterations causes the problem to converge to either a different solution or a stationary point for the constraint residual. In general, though, I would guess that clipping as part of the line search would give a better chance at converging to a solution that satisfies the bound (if such a solution exists).
There are two possible implementations:
- Do the thing same as IPOPT: if a candidate step violates a bound, set
alpha = 0.5 * alphaand repeat. - Just jump directly to the bound. If taking a step at the bound also results in bound violation, then we've found a local minimizer and terminate.
I think I prefer option (2), because we don't have to worry about how to determine we've reached a bound---we just jump straight to the bound. With option (1), we could end up spending a long time taking tiny line search steps before the sufficient decrease condition is violated.
Edit: Another thing to keep in mind is consistency between the three methods used in the function. The first shortcut method assumes the constraint is of the form x == y, the second assumes it's of the form y == m*x + b. Neither one of those methods checks bounds, and I'd prefer not to slow them down by adding checks.
There was a problem hiding this comment.
In general, though, I would guess that clipping as part of the line search would give a better chance at converging to a solution that satisfies the bound (if such a solution exists).
Good point. I don't have a strong preference on the implementation if it is demonstrated to work, as long as this is opt-in behavior (for now).
| scale_problem: `bool` | ||
| Whether to take variable and constraint scaling into account | ||
| when calculating if the constraint residual tolerance is | ||
| satisfied or if the initial derivative is zero. |
There was a problem hiding this comment.
If this is just scaling tolerances, I'm not sure it belongs in this function. The caller can always use the scaling factor to compute a new tolerance if they want. I also don't love calling SuffixFinder within this function.
There was a problem hiding this comment.
If this is just scaling tolerances, I'm not sure it belongs in this function.
It also applies scaling factors when calculating if a problem has a derivative of effectively zero.
The caller can always use the scaling factor to compute a new tolerance if they want.
That's true, but that means I'm going to move the scaling here into solve_strongly_connected_components.
I also don't love calling SuffixFinder within this function.
Is the concern that it adds too much overhead to each function call?
Fixes # #3985, part of #3785, and point 4 of #3571
Summary/Motivation:
Projects like PrOMMiS are increasingly relying on IDAES's
BlockTriangularizationInitializer, which in turn relies onsolve_strongly_connected_components. However, in models that need significant amounts of scaling, the initialization raises an error that the constraint residuals are not small. That's becauseBlockTriangularizationInitializertakes constraint scaling into account butsolve_strongly_connected_componentsdoes not.This PR begins addressing this issue by adding an option to scale the variable and constraint in
calculate_variable_from_constraint. Additionally, it adds an option to clip variable values that fall outside their bounds back inside those bounds, because we have a ton of log spam from Pyomo warning about variables getting set to values barely outside their bounds.Changes proposed in this PR:
AI-Use Disclosure
or
AI tools contributed to the development of this PR
Review process (select ONE):
Notes for reviewers (optional): I set the default values of these new options to
Falsein order to avoid anything breaking from using the new options. However, I think users will usually want to set those options toTrue. On the IDAES side, I can set those options in the configuration ofBlockTriangularizationInitializer, but consider what the default behavior in Pyomo should be.Additionally, when clipping the values back to their bounds, I check the residual to see whether to accept the clipping or not. If the clipped value does not satisfy the convergence condition, it gets set to the unclipped value. What should the default behavior be?
Finally, I noticed the line search done here doesn't use the Armijo sufficient decrease condition. Is that intended behavior?
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: