How can we simplify equivalent sums over different index variables?
As we can read in "Details and Options" of Sum
documentation:
The iteration variable i is treated as local, effectively using
Block
.
So index of summation is dynamically scoped, thus Sum[x[i], {i, 2, n - 1}]
can give different result than Sum[x[j], {j, 2, n - 1}]
if i
or j
is present in body of x
function, e.g.:
ClearAll[x, i]
x[arg_] := arg + i
Sum[x[i], {i, 2, n - 1}]
(* -2 - n + n^2 *)
Sum[x[j], {j, 2, n - 1}]
(* 1/2 (-2 - 4 i - n + 2 i n + n^2) *)
If we want to be sure that summation index will not interfere with any symbols coming from evaluation of x[i]
we should manually localize summation indices using lexical scoping construct: Module
:
Module[{i}, Sum[x[i], {i, 2, n - 1}]]
(* 1/2 (-2 - 4 i - n + 2 i n + n^2) *)
Module[{j}, Sum[x[j], {j, 2, n - 1}]]
(* 1/2 (-2 - 4 i - n + 2 i n + n^2) *)
but this will not help with equation simplifications.
Sum
normalization
To help Mathematica in simplifying expressions involving Sum
we can manually "normalize" sums by renaming summation indices and shifting summation bounds. Following implementation renames variables lexically present in Sum
expression, so it automatically ensures lexical scoping for renamed variables, similarly as Module
in above examples. In some situations this may be unwanted, so make sure to understand difference between dynamic and lexical scoping, in this context, before using following functions.
NormalizeSum
function will replace consecutive dummy variables in Sum
expressions with SummationIndex[1]
, SummationIndex[2]
, etc. which will be formatted as i₁
, i₂
, ...
Lower summation bound, if finite will be shifted to value of "DefaultBound"
option (by default 0
), otherwise upper bound, if finite, will be shifted to default bound, if nothing is known about finiteness of both bounds they are not shifted.
BeginPackage["SumNormalization`"];
Unprotect["`*"];
ClearAll["`*"]
NormalizeSum::usage = "NormalizeSum[expr] \
normalizes dummy sumation indices in Sum expressions present in expr. \
i-th dummy index is renamed to SummationIndex[i], \
summation bounds are shifted such that, \
if lower bound is finite, then new lower bound is zero, \
otherwise if upper bound is finite, then new upper bound is zero.";
SummationIndex::usage = "SummationIndex[i] \
represents i-th dummy summation index in Sum expression.";
Begin["`Private`"];
ClearAll["`*"]
finiteQ[_DirectedInfinity] = False;
finiteQ[x_] := TrueQ@Refine[x != ComplexInfinity]
shiftBounds[i_, min_?finiteQ, max_, default_] :=
{i + min - default, default, max - min + default}
shiftBounds[i_, min_, max_?finiteQ, default_] :=
{i + max - default, min - max + default, default}
shiftBounds[i_, min_, max_, _] := {i, min, max}
NormalizeSum // Options = {"DefaultBound" -> 0};
NormalizeSum[expr_, opts : OptionsPattern[]] := expr /.
(h : Sum | Inactive[Sum])[x_, iterators__, sumOpts : OptionsPattern[]] :>
Module[
{
default = OptionValue[NormalizeSum, opts, "DefaultBound"],
assumpt = OptionValue[Sum, sumOpts, Assumptions],
rules = {}, usedIndices = {}, heldIterators
},
heldIterators = Join @@ Replace[Unevaluated@{iterators}, {
{i_, min_:1, max_} :>
RuleCondition@Module[{newIndex, newMin, newMax, replIndex},
newIndex = SummationIndex[Length@rules +1];
{newMin, newMax} = Unevaluated@{min, max} /. rules;
{replIndex, newMin, newMax} =
Assuming[
assumpt && Element[usedIndices, Complexes],
shiftBounds[newIndex, newMin, newMax, default]
];
AppendTo[usedIndices, newIndex];
AppendTo[rules, HoldPattern@i -> replIndex];
Hold@Evaluate@{newIndex, newMin, newMax}
],
i_ :> Hold@i
}, {1}];
h @@ (Join[Hold[x] /. rules, heldIterators, Hold[sumOpts]])
]
call : NormalizeSum[__, nonOpt : Except@OptionsPattern[], OptionsPattern[]] /;
Message[NormalizeSum::nonopt,
HoldForm@nonOpt, HoldForm@1, HoldForm@call
] :=
"NeverReached"
NormalizeSum[] /;
Message[NormalizeSum::argx, HoldForm@NormalizeSum, HoldForm@0] :=
"NeverReached"
NormalizeSum // SyntaxInformation =
{"ArgumentsPattern" -> {_, OptionsPattern[]}};
NormalizeSum // Protect;
$summationIndexDisplayFunction = StyleBox[SubscriptBox["i", #], Italic]&;
SummationIndex /: MakeBoxes[
index : SummationIndex[i_Integer?Positive],
form : StandardForm | TraditionalForm
] :=
TemplateBox[{MakeBoxes[i, form]}, "SummationIndex",
DisplayFunction -> $summationIndexDisplayFunction,
InterpretationFunction -> (RowBox[{"SummationIndex", "[", #, "]"}]&)
]
call : SummationIndex@Except[i_Integer?Positive] /;
Message[SummationIndex::intp, HoldForm@call, HoldForm@1] :=
"NeverReached"
SummationIndex[args : PatternSequence[] | PatternSequence[_, __]] /;
Message[SummationIndex::argx,
HoldForm@SummationIndex, HoldForm@Evaluate@Length@Hold[args]] :=
"NeverReached"
SummationIndex // SyntaxInformation = {"ArgumentsPattern" -> {_}};
End[];
EndPackage[];
Example of sum with one non-dummy index j
and three dummy indices: i
with both bounds symbolic, and k
with upper symbolic bound and implicit lower bound, by default equal to 1
, and l
with lower bound depending on other index i
. We assign values to indices to make sure there's no evaluation leak. Sum is normalized assuming that lower symbolic bound is an integer (thus it's finite).
ClearAll[f, i, j, k, n, m, o]
i = 100; j = 200; k = 300; l = 400;
Sum[f[i] j + k, {i, m, n}, j, {k, o}, {l, i, p}]
(* Sum[k + j*f[i], {i, m, n}, j, {k, o}, {l, i, p}] *)
Assuming[n \[Element] Integers, % // NormalizeSum]
(* Sum[1 + j*f[n + SummationIndex[1]] + SummationIndex[2],
{SummationIndex[1], m - n, 0}, j, {SummationIndex[2], 0, -1 + o},
{SummationIndex[3], 0, -n + p - SummationIndex[1]}] *)
(* Use some example numeric values of bounds to check that un-normalized and normalized sums represent same mathematical expression. *)
{%%, %} /. {m -> 9, n -> 11, o -> 2, p -> 12}
(* {800 (3 + 199 f[9]) + 600 (3 + 199 f[10]) + 400 (3 + 199 f[11]),
800 (3 + 199 f[9]) + 600 (3 + 199 f[10]) + 400 (3 + 199 f[11])} *)
SameQ @@ %
(* True *)
Now back to original problem:
ClearAll[x, n]
Sum[x[i], {i, 2, n - 1}] == Sum[x[j], {j, 2, n - 1}] // NormalizeSum
(* True *)
Here is a very naive attempt to do this (thanks to Xavier's comment for the suggestion of using a local variable to handle more general cases):
ClearAll[sumsEqualQ]
sumsEqualQ[
Sum[expr1_, {iterVar1_, min1_, max1_}],
Sum[expr2_, {iterVar2_, min2_, max2_}]
] := Module[{iter, exprsEqual},
exprsEqual = (expr1 /. iterVar1 -> iter) === (expr2 /.
iterVar2 -> iter);
Which[
(* same expressions, same boundaries *)
exprsEqual && min1 === min2 && max1 === max2,
True,
(* same expressions, different boundaries *)
exprsEqual && (min1 =!= min2 || max1 =!= max2),
False,
(* different expressions *)
! exprsEqual,
False
]
]
which correctly gives
In[78]:= sumsEqualQ[
Sum[x[i], {i, 2, n - 1}],
Sum[x[j], {j, 2, n - 1}]
]
Out[78]= True
In[127]:= sumsEqualQ[
Sum[x[i], {i, 2, n - 1}],
Sum[y[j], {j, 2, n - 1}]
]
Out[127]= False
In[130]:= sumsEqualQ[
Sum[i x[i], {i, 2, n - 1}],
Sum[i y[j], {j, 2, n - 1}]
]
Out[130]= False
sumsEqualQ[
Sum[x[i], {i, 2, n - 1}],
Sum[x[j], {j, 2, n}]
]
False
If the function is defined, then the two sums are identified as equal. Here are five variations, three are pure functions and two are regular (impure) function. All return True
:
Sum[Function[u, 3 + u^2][i], {i, 2, n - 1}] ==
Sum[Function[v, 3 + v^2][j], {j, 2, n - 1}]
Sum[(3 + #) &, {i, 2, n - 1}] == Sum[(3 + #) &, {j, 2, n - 1}]
f = (3 + #) &;
Sum[f[i], {i, 2, n - 1}] == Sum[f[j], {j, 2, n - 1}]
g[n_] := 5*n^2 - Log[n];
Sum[g[i], {i, 2, n - 1}] == Sum[g[j], {j, 2, n - 1}]
h[n_] := Sqrt[n];
Sum[h[i], {i, -n, n}] == Sum[h[j], {j, -n, n}]
The final one might be a little overzealous in returning True
, since the Sqrt
is multiply defined.