Fix: ResidualTerm can be negated and scaled (#885)#1200
Open
gaoflow wants to merge 2 commits into
Open
Conversation
ResidualTerm inherits __neg__ and __mul__ from _NonDiffusionTerm, which
assume a (coeff, var) constructor signature. ResidualTerm's constructor
takes (equation, underRelaxation) instead, so unary minus and scalar
multiplication both raised
TypeError: __init__() got an unexpected keyword argument 'coeff'
This breaks not only the obvious
-fipy.ResidualTerm(equation=eq)
2.0 * fipy.ResidualTerm(equation=eq)
but also any equation that puts a ResidualTerm on the right-hand side of
==, because Term.__sub__ distributes negation through the term tree:
lhs == diffusion + ResidualTerm(eq)
# Term.__eq__ -> self - other
# Term.__sub__ -> self + (-other)
# _BinaryTerm.__neg__ -> (-self.term) + (-self.other)
# -> _NonDiffusionTerm.__neg__(ResidualTerm) -> TypeError
Override __neg__ and __mul__ on ResidualTerm to fold the sign/scale into
a small internal multiplier (_scale) that _buildMatrix applies to the
residual vector at solve time. underRelaxation keeps its original
semantics (a relaxation factor in [0, 1]); the signed/scaled
contribution is the product of underRelaxation and _scale.
Verified end-to-end that
-RT produces -1 * (residual)
alpha*RT produces alpha * (residual)
at the level of the RHS vector emitted by _buildAndAddMatrices, and
that combining ResidualTerm with other terms via +, -, == and / now
works.
Doctests live in fipy/terms/residualTerm.py and exercise both the
scaling bookkeeping and the numerical RHS contribution; the module
is added to fipy/terms/test.py so the suite picks them up.
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 6 |
| Duplication | 0 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
The CI 'Analyze spelling' step rejects 'pre' as it splits hyphenated 'pre-multiplication'. Rewording to 'scalar multiplication' since the fix applies equally to alpha * RT and RT * alpha.
guyer
added a commit
that referenced
this pull request
Jun 16, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix:
ResidualTermcan be negated and scaledFixes #885.
Problem
ResidualTerminherits__neg__and__mul__from_NonDiffusionTerm, which assume a(coeff, var)constructor signature:ResidualTerm's constructor is__init__(self, equation, underRelaxation=1.)— there is nocoeffkeyword — so unary minus and scalar multiplication both raiseThat breaks the obvious cases (
-RT(eq),2.0 * RT(eq)) but, more insidiously, it also breaks every equation that puts aResidualTermon the right-hand side of==, becauseTerm.__sub__distributes negation through the term tree:So the workaround documented in #885 (parenthesise the LHS so the RHS never gets negated) is the only way
ResidualTermworks inside an equation today.Fix
Override
__neg__and__mul__onResidualTermto fold the sign/scale into a small internal_scalemultiplier that_buildMatrixapplies to the residual vector at solve time:underRelaxationkeeps its documented semantics (a relaxation factor); the signed/scaled contribution is the product ofunderRelaxationand_scale.__neg__and__mul__construct a freshResidualTermand stamp_scaleon it, so chains like-(-RT)and2 * (3 * RT)compose correctly.Verification
End-to-end at the RHS-vector level:
b_pos = [1, 0, ..., 0, -1](a diffusion-only residual on a linear ramp),b_neg = [-1, 0, ..., 0, 1],b_3 = [3, 0, ..., 0, -3]. The negation/scaling is applied exactly once per build.All four originally-broken paths from the issue now work:
and the doctest-discovered division
RT / 4.routes through__rmul__so it works too.Doctests live in
fipy/terms/residualTerm.pyand exercise both the_scalebookkeeping and the numerical RHS contribution. The module is added tofipy/terms/test.pysofipy_testpicks them up:The 54 vs the previous 50 includes the new
residualTermdoctests. The 13 failures are all pre-existing numpy-2.x repr-formatting drift in other terms ([[ 1. ...]]vs[[1. ...]]), unrelated to this patch.Scope
fipy/terms/residualTerm.py(the fix + doctests) andfipy/terms/test.py(one line to register the new doctest module). No public API change —ResidualTerm(equation, underRelaxation=1.)keeps its signature; the new_scaleis an internal field that defaults to 1 and is preserved by negation/scaling only.