Skip to content

Fix LinearRepnVisitor to substitute fixed vars already in var_map - #4034

Open
cjck944084735-dot wants to merge 1 commit into
Pyomo:mainfrom
cjck944084735-dot:fix/linear-repn-fixed-var-after-registration
Open

Fix LinearRepnVisitor to substitute fixed vars already in var_map#4034
cjck944084735-dot wants to merge 1 commit into
Pyomo:mainfrom
cjck944084735-dot:fix/linear-repn-fixed-var-after-registration

Conversation

@cjck944084735-dot

Copy link
Copy Markdown

Fixes #3851 .

Summary/Motivation:

LinearRepnVisitor.record_monomial only applied the fixed-variable (constant) handling when the variable had not yet been registered in the visitor's var_map. When a variable was registered in bulk by the TemplateVarRecorder while it was still free and was fixed afterwards, subsequent walk_expression calls found it in the var_map and treated it as an unrestricted variable: fixed values were not substituted and expressions involving them were misclassified as nonlinear. This is exactly the failure mode reported in #3851, including through the LinearStandardFormCompiler's use of a TemplateVarRecorder.

Changes proposed in this PR:

  • record_monomial now checks var.fixed before the var_map membership test, so a fixed variable always contributes its value as a constant, regardless of when it was registered.
  • Two regression tests in pyomo/repn/tests/test_linear.py::TestFixedVarAfterRegistration, using the reporter's scenario (a variable registered by walking one expression while free, fixed, then used in another expression):
    • test_fixed_var_after_registration: x[1] - 1/x[2] with x[2].fix(2) now yields constant=-0.5, linear={x[1]: 1}, nonlinear=None;
    • test_fixed_var_substituted_before_registration: the fixed variable never becomes a linear term nor leaks into the nonlinear remainder.

After the change, the reporter's r2 is LinearRepn(mult=1, const=-0.5, linear={x[1]: 1}, nonlinear=None) instead of carrying nonlinear=-(1/x[2]).

Local verification

  • pyomo/repn/tests/: 357 passed (355 existing + 2 new), 48 skipped.
  • Broader pyomo/core/tests/: 4392 passed — no regressions.
  • black --check clean on the changed files.

AI-Use Disclosure

  • AI tools were NOT used during the preparation of this PR

or

  • AI tools contributed to the development of this PR

    • AI tools generated documentation (including the PR description/comments, code comments, and/or Sphinx documentation)
    • AI tools generated tests (baselines, examples, and/or code)
    • AI tools generated code (apart from tests)

    Review process (select ONE):

    • Rewritten: All AI-generated content was rewritten by me before being committed.
    • Reviewed/verified: I retained AI-generated content and verified it before committing. Verification included (as applicable):
      • Ran the code and fixed issues
      • Added and ran tests
      • Checked correctness/logic of code and tests
      • Checked for alignment with the contribution guide
      • Considered security implications
    • As-is: AI-generated content was commited directly to the repository

Notes for reviewers (optional): The behavior change is narrow: a fixed variable that was registered in var_map while free is now handled as a constant. Callers that relied on such variables appearing as linear terms would have received incorrect coefficients (the fixed value was never substituted), so this should be strictly a correctness improvement. One thing I would appreciate a second look at: whether any writer depends on fixed-but-registered variables still occupying a column in var_map/var_list.

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.

record_monomial only applied the fixed-variable (constant) handling
when the variable had not yet been registered in the visitor var_map.
When a variable was registered in bulk by the TemplateVarRecorder
while it was still free, and was fixed afterwards, subsequent
walk_expression calls treated it as an unrestricted variable: the
LinearStandardFormCompiler would then reject expressions involving it
as nonlinear even though they are linear after substitution.

Check var.fixed before the var_map membership test so fixed variables
always contribute their value as a constant.

Fixes Pyomo#3851
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

LinearStandardFormCompiler sometimes says expression are nonlinear even when variables are fixed

1 participant