-
Notifications
You must be signed in to change notification settings - Fork 529
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
fix division by zero error in linear presolve #3161
Conversation
pyomo/repn/plugins/nl_writer.py
Outdated
@@ -1779,7 +1785,8 @@ def _linear_presolve(self, comp_by_linear_var, lcon_by_linear_nnz, var_bounds): | |||
a = -coef2 / coef |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will you still get a divide-by-0 error if both coefficients are 0? Maybe something like:
m.c1 = pe.Constraint(expr=m.x + m.w == m.y + m.z + 1.5)
m.c2 = pe.Constraint(expr=m.z == -m.y)
m.c3 = pe.Constraint(expr=m.w == -m.x)
Maybe the right answer is to filter out 0's when we are performing the substitution?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If both coeffiicents are zero, the constraint should simplify to trivially satisfied (if b
is 0
) or trivially infeasible (if b
is not 0
). Let me test for that real quick.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, take a look at this version.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can work, but I wonder if there is a simpler implementation: (I think) the only way to get 0 coefficients is through previous substitutions. If that is the case, then, you could revert all the edits to this file and only change (starting around @ L1853):
# appropriately (that expr_info is persisting in the
# eliminated_vars dict - and we will use that to
# update other linear expressions later.)
+ old_nnz = len(expr_info.linear)
c = expr_info.linear.pop(_id, 0)
+ nnz = old_nnz - 1
expr_info.const += c * b
if x in expr_info.linear:
expr_info.linear[x] += c * a
+ if expr_info.linear[x] == 0:
+ nnz -= 1
+ coef = expr_info.linear.pop(x)
+ if not nnz:
+ if abs(expr_info.const) > TOL:
+ # constraint is trivially infeasible
+ raise InfeasibleConstraintException(
+ "model contains a trivially infeasible constrint "
+ f"{expr_info.const} == {coef}*{var_map[x]}"
+ )
+ # constraint is trivially feasible
+ eliminated_cons.add(con_id)
elif a:
expr_info.linear[x] = c * a
# replacing _id with x... NNZ is not changing,
# but we need to remember that x is now part of
# this constraint
comp_by_linear_var[x].append((con_id, expr_info))
continue
- # NNZ has been reduced by 1
- nnz = len(expr_info.linear)
- _old = lcon_by_linear_nnz[nnz + 1]
+ _old = lcon_by_linear_nnz[old_nnz]
if con_id in _old:
lcon_by_linear_nnz[nnz][con_id] = _old.pop(con_id)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That worked! I'll push shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, third time is the charm! Check out this version.
P.s. git is the best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not part of this conversation, but I had to +1 this. Git is the best.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ick. I think I lied to you. The block beginning if nnz == 0:
should be moved farther down:
@@ -1829,15 +1829,6 @@ class _NLWriter_impl(object):
if expr_info.linear[x] == 0:
nnz -= 1
coef = expr_info.linear.pop(x)
- if not nnz:
- if abs(expr_info.const) > TOL:
- # constraint is trivially infeasible
- raise InfeasibleConstraintException(
- "model contains a trivially infeasible constraint "
- f"{expr_info.const} == {coef}*{var_map[x]}"
- )
- # constraint is trivially feasible
- eliminated_cons.add(con_id)
elif a:
expr_info.linear[x] = c * a
# replacing _id with x... NNZ is not changing,
@@ -1847,6 +1838,15 @@ class _NLWriter_impl(object):
continue
_old = lcon_by_linear_nnz[old_nnz]
if con_id in _old:
+ if not nnz:
+ if abs(expr_info.const) > TOL:
+ # constraint is trivially infeasible
+ raise InfeasibleConstraintException(
+ "model contains a trivially infeasible constrint "
+ f"{expr_info.const} == {coef}*{var_map[x]}"
+ )
+ # constraint is trivially feasible
+ eliminated_cons.add(con_id)
lcon_by_linear_nnz[nnz][con_id] = _old.pop(con_id)
# If variables were replaced by the variable that
# we are currently eliminating, then we need to update
The issue is in the original location, it will check the linear NNZ for all constraints (including inequalities and nonlinear constraints). In the new location, it will only check for the linear equality constraints (which is what we want).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
80847ce
to
1acf618
Compare
Summary/Motivation:
Changes proposed in this PR:
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: