Fix ZeroBaseForm holes in derivative expansion and action#494
Conversation
GateauxDerivativeRuleset had no rule for ZeroBaseForm itself, so differentiating an already-zero BaseForm (e.g. a Hessian of a Matrix) raised "Rule not set for ZeroBaseForm" in expand_derivatives. map_integrands' FormSum branch collapsed to a bare `0` via `sum([])` when every component vanished, silently dropping the argument shape; apply_derivatives had a matching fallback that rebuilt an argument-less Form([]) instead of a properly shaped ZeroBaseForm. action() only expanded derivatives when the top-level object was already a Form/BaseFormOperator, so a CoefficientDerivative reachable only through a FormSum (e.g. from an affine residual F - b) survived unexpanded inside the resulting lazy Action. Widened the check to match adjoint()'s existing BaseForm handling. Finally, ZeroBaseForm inherited BaseForm.empty(), which always returns False -- give it its own empty() returning True.
|
Note from Claude refering to https://github.com/firedrakeproject/firedrake/blob/49f19c307b1aa1cfa1386705f31ed72975e65c23/firedrake/formmanipulation.py#L92 I did not generalize this to |
Matches the existing # type: ignore on the neighboring Matrix rule, whose signature is likewise incompatible with the Expr-typed base process() the singledispatchmethod registry expects.
Summary
GateauxDerivativeRulesethad no rule forZeroBaseFormitself, so differentiating an already-zeroBaseForm(e.g. a Hessian of aMatrix) raisedAssertionError: Rule not set for ZeroBaseForminexpand_derivatives.map_integrands'sFormSumbranch collapsed to a bare0viasum([])when every component vanished, silently dropping the argument shape;apply_derivativeshad a matching fallback that rebuilt an argument-lessForm([])instead of a properly shapedZeroBaseForm.action()only expanded derivatives when the top-level object was already aForm/BaseFormOperator, so aCoefficientDerivativereachable only through aFormSum(e.g. from an affine residualF - b) survived unexpanded inside the resulting lazyAction. Widened the check to matchadjoint()'s existingBaseFormhandling.ZeroBaseForminheritedBaseForm.empty(), which always returnsFalse— gave it its ownempty()returningTrue.Test plan
pytest test/(full suite, 969 passed)test/test_duals.py: nested differentiation of aMatrix(Hessian), aFormSumofCofunctions whose components all vanish under differentiation, andaction()on aFormSumno longer leaving aCoefficientDerivativebehind.ruff check🤖 Generated with Claude Code