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 *)

Normalized Sums

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.