Reputation: 321
I need to evaluate a sum over Cartesian product of variable number of sets. Assuming f[...] is a multivariate function, define
p[A__set] := Module[{Alist, args, iterators,it},
Alist = {A};
i = 1;
iterators = {it[i++], Level[#1, 1]} & /@ Alist;
args = Table[it[i], {i, Range[Length[Alist]]}];
Sum[f@@ args, Sequence @@ iterators ]
]
But then
p[set[1, 2, 3], set[11, 12, 13]]
Gives the error:
Sum::vloc: "The variable Sequence@@iterators cannot be localized so that it can be assigned to numerical values."
The following hack works:
p[A__set] := Module[{Alist, args, iterators,it,TmpSymbol},
Alist = {A};
i = 1;
iterators = {it[i++], Level[#1, 1]} & /@ Alist;
args = Table[it[i], {i, Range[Length[Alist]]}];
Sum@@TmpSymbol[f @@ args, Sequence @@ iterators ]
]
Then
p[set[1, 2, 3], set[11, 12]]
gives what I want:
f[1, 11] + f[1, 12] + f[2, 11] + f[2, 12] + f[3, 11] + f[3, 12]
I would like to know why the original does not.
As per belisarius there is much more elegant way to do this:
p[A__set] := Total[Outer[f, A],Length[{A}]];
Upvotes: 3
Views: 2588
Reputation: 24336
This has to do with evaluation order. Please see Tutorial: Evaluation as a reference.
Sum
has the Attribute HoldAll
:
Attributes[Sum]
{HoldAll, Protected, ReadProtected}
Because of this only arguments with certain heads such as Evaluate
or Sequence
or Symbols with upvalues will evaluate. You may think that your argument Sequence @@ iterators
has the head Sequence
, but it actually has the head Apply
:
HoldForm @ FullForm[Sequence @@ iterators]
Apply[Sequence, iterators]
Sum
expects literal arguments that match its declared syntax, and thus your code fails. You can force evaluation in several different ways. Arguably the most transparent is to add Evaluate
:
iterators = {{a, 1, 3}, {b, 5, 7}};
Sum[a^2/b, Evaluate[Sequence @@ iterators]]
107/15
More concisely you can leverage Function
, SlotSequence
, and Apply
; evaluation takes place since neither Apply
, nor Function
by default, has HoldAll
:
Sum[a^2/b, ##] & @@ iterators
107/15
Both of these have a potential problem however: if a
or b
received a global value the Symbol in the definition of iterators
will evaluate to this value causing another error:
a = 0;
Sum[a^2/b, ##] & @@ iterators
Sum::itraw: Raw object 0 cannot be used as an iterator. >>
Instead you can store the iterator lists in a Hold
expression and use the "injector pattern" to insert these values without complete evaluation:
iterators = Hold[{a, 1, 3}, {b, 5, 7}];
iterators /. _[x__] :> Sum[a^2/b, x]
107/15
Alternatively you could define iterators
as an upvalue:
Sum[args___, iterators] ^:= Sum[args, {a, 1, 3}, {b, 5, 7}]
Now simply:
Sum[a^2/b, iterators]
107/15
Please see my answers to Keep function range as a variable on Mathematica.SE for more examples, as this question is closely related. Specifically see setSpec
in my second answer which automates the upvalue creation.
Upvotes: 5
Reputation: 61046
There are many easier ways do that in Mathematica:
Total[Outer[f, {1, 2, 3}, {11, 12}, {a, b}],3]
(*
f[1, 11, a] + f[1, 11, b] + f[1, 12, a] + f[1, 12, b] +
f[2, 11, a] + f[2, 11, b] + f[2, 12, a] + f[2, 12, b] +
f[3, 11, a] + f[3, 11, b] + f[3, 12, a] + f[3, 12, b]
*)
Upvotes: 2