-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Description
Check duplicate issues.
- Checked for duplicates
Description
Summary
When a RooFormulaVar is created with unused parameters in its parameter list, writing the workspace to a ROOT file and reading it back causes the @N positional indices to silently map to wrong variables. This produces incorrect numerical results without any error or warning.
Description
RooFormulaVar (via expr:: factory syntax) accepts a formula string with positional references (@0, @1, ...) and a list of parameters. Since ROOT 6.30, only the parameters that the formula actually depends on are kept in actualVars (unused ones are pruned). However, the formula string retains the original @N indices. After a write/read cycle, the pruned actualVars list is shorter, so the @N indices map to different positions — silently producing wrong results.
Example
// Create: 4 parameters, formula uses @0, @2, and 'd' by name. @1 (b) is unused.
w.factory("a[2.0, -10, 10]");
w.factory("b[99.0, -10, 10]"); // unused in formula
w.factory("c[3.0, -10, 10]");
w.factory("d[4.0, -10, 10]");
w.factory("expr::f('@0 * @2 + d', a, b, c, d)");
// Before serialization: @0=a, @2=c → f = a*c + d = 2*3 + 4 = 10 ✓After writeToFile() and reading back:
actualVars= {a, c, d} (b was pruned)@2now maps tod(position 2 in pruned list) instead ofc- ROOT's own interpretation confirms:
'[a]*[d]+d'
Mechanism
- Construction:
expr::f('@0*@2+d', a, b, c, d)—@0=a,@1=b,@2=c,@3=d - Pruning (ROOT 6.30+): only used variables kept →
actualVars= {a, c, d} - Serialization:
actualVars(3 entries) and formula string'@0*@2+d'written to file - Deserialization:
actualVars= {a(0), c(1), d(2)}. Formula@0*@2+dis re-parsed:@0→a(correct),@2→d(WRONG — should be c)
Relation to existing issues
This is the binary (TFile) serialization counterpart of #17291, which reported the same class of parameter ordering bug in the JSON/HS3 serialization path and was fixed in ROOT 6.34.04 via PR #17292.
Suggested fix
After deserialization, when getFormula() reconstructs the RooFormula from _formExpr and the pruned _actualVars, the @N indices in the formula string should be re-mapped to match the new positions in actualVars. Alternatively, the formula string could be stored in a serialization-stable form (e.g. using named references instead of positional indices).
Reproducer
A self-contained macro is attached: reproduce_formulavar_bug.C
root -l -b -q reproduce_formulavar_bug.C
Key output:
=== Before serialization ===
Formula: @0 * @2 + d (expect a*c + d = 2*3 + 4 = 10)
f = 10
=== After deserialization ===
Formula: @0 * @2 + d (still expect a*c + d = 2*3 + 4 = 10)
f = 10 ← cached value, appears correct
Formula details:
Formula: '@0*@2+d'
Interpretation: '[a]*[d]+d' ← ROOT confirms @2 maps to d, not c
In use : (a,d) ← c is no longer used!
--- Diagnostic: change c from 3 to 100 ---
If @2=c: expect 204
If @2=d: expect 12 (no change from c)
f = 12 ← confirms @2 ≠ c
--- Diagnostic: change d from 4 to 100 ---
If @2=c: expect 106
If @2=d: expect 300
f = 30 ← d dominates the result
ROOT version
Tested on ROOT 6.36.06, 6.38 and 6.30.02. Based on the release notes, the behavior was introduced in ROOT 6.30.
Installation method
conda-forge
Operating system
MacOS, Sonoma 14.6.1
Additional context
No response