13
$\begingroup$

I have a list of arguments (which in reality is lengthy):

arguments = {a, b, c}
arguments2 = {a_, b_, c_}
f[Sequence@@arguments2] := a + b + c

Note: It seems awkward to define two lists here, there should be a better way to do this.

And I want to numerically integrate a function of those arguments:

int[Sequence@@arguments2] := NIntegrate[f[Sequence@@arguments], {x, 0, 1}]

This does not work because of the HoldAll property of NIntegrate.

Is there a way to do this correctly?

$\endgroup$

4 Answers 4

11
$\begingroup$

If your question is a duplicate of Injecting a sequence of expressions into a held expression the simplest solution is the same, the so-called "injector pattern":

{a, b, c} /. _[args__] :> NIntegrate[f[args], {x, 0, 1}]
NIntegrate[f[a, b, c], {x, 0, 1}]

I did not close this as a duplicate however because it seems you would like to approach this problem differently.

You could store your symbols in a single object, e.g.:

syms = Hold[a, b, c];

Then create the Pattern sequence from these:

toPatSeq = ReleaseHold @ Quiet @ Replace[#, x_ :> x_, {1}] &;

toPatSeq @ syms
Sequence[a_, b_, c_]

Now you could define your function with:

syms /. _[args__] :>
  (int[toPatSeq @ syms] := NIntegrate[f[args], {x, 0, 1}])

Check:

? int
Global`int

int[a_,b_,c_]:=NIntegrate[f[a,b,c],{x,0,1}]

This works even if the symbols a, b, c, have values assigned.


If as this example shows you only want an arbitrary sequence of symbols you could also make use of Unique:

syms = Hold @@ Table[Unique[], {3}];

syms /. _[args__] :>
  (int[toPatSeq @ syms] := NIntegrate[f[args], {x, 0, 1}])

?int
Global`int

int[$1_,$2_,$3_]:=NIntegrate[f[$1,$2,$3],{x,0,1}]
$\endgroup$
8
$\begingroup$

Preamble

I will use this question as a pretext to demonstrate a more complex and powerful version of an injector pattern. I have been profitably using it for a while now, starting long before this pattern got its current name, but it seems like this powerful generalization is still widely unknown, while being often quite useful.

Nested injector pattern

The essence of nested injector pattern is that sometimes we need to inject more than one different piece of code in a single go. But a simple injector pattern does not allow it. The nested injector pattern allows one to accumulate several such expressions by using nested rules. As an example, I will use a task I faced recently while preparing this answer. To be specific, we may start with held code like this:

Hold[n + n^2*f[n - 1]]

and need to end up with a code like this:

Hold[f[n - 1, n + n^2*# &]]

following these steps:

  • hunt the code for f[x___]
  • replace all such entries with Slot (#)
  • convert the held code inside Hold to a pure function
  • construct a call to f with this pure function as a second argument

While this can be done step by step, in the straightforward implementation of this strategy we won't be able to reuse expressions found at intermediate steps for each of the steps outlined, and the resulting code will be much heavier and less elegant.

Here is the code adopted from the answer I linked to, which does implement this strategy using a nested injector pattern:

Cases[
   heldBody, 
   p : _f :>
      (Hold[p] /. Hold[f[x___]] :>
          (heldBody /. HoldPattern[p] -> # /. Hold[code_] :> 
               Hold[f[x, Function[code]]])), 
   Infinity, 
   1
]

If you look carefully at the nested rule in it, you will see how nested injector pattern works. First, we find an entry which matches _f (this is done by Cases). The rule wraps it in Hold and then uses a one-level injector pattern:

Hold[p] /. Hold[f[x___]] :> ...

But instead of directly injecting x somewhere, we use another one-level injector pattern inside:

(Hold[p] /. Hold[f[x___]] :>
    ((heldBody /. HoldPattern[p] -> #) /. Hold[code_] :> ...

transforming heldBody as heldBody /. HoldPattern[p] -> # along the way. Note that this intermediate transformation makes use of p which was found dynamically in this same rule application, just earlier. Now, apart from this transformation which we needed, if you look at entire rule you will notice that by using a (in this case) double-injector pattern, the rule was able to collect two different pieces of unevaluated code in one go (x and code)

p : _f :>
   (Hold[p] /. Hold[f[x___]] :>
       (heldBody /. HoldPattern[p] -> # /. Hold[code_] :> 
           Hold[f[x, Function[code]]]))

and inject them in proper places.

There is no limit to how many different code pieces you may be able to collect with this technique, you will just need to make use of an n-level injector pattern if you need n pieces of code. At some point, this may hurt readability though, so perhaps some balance is needed, and if you need too many, it may mean that you have to write a proper parser.

The case at hand

In the case at hand, here is a double-injector pattern that does the job:

{a_, b_, c_} /. {patt__} :> 
   (Hold[a, b, c] /. Hold[args___] :> 
        SetDelayed @@ Hold[int[patt], NIntegrate[f[args], {x, 0, 1}]])

I used SetDelayed @@ Hold[lhs, rhs] pattern to avoid variable renaming.

$\endgroup$
6
  • $\begingroup$ The OP has the arguments a,b,c in a list. How do you get them from there into the part Hold[a,b,c] of your code at the end (without literally pasting them there of course)? $\endgroup$ Mar 22, 2013 at 19:46
  • $\begingroup$ @SjoerdC.deVries I interpreted the problem so that it is not important how there are initially stored. One could start with the arguments: Unevaluated[{a,b,c}]/.{args__}:> ({a_,b_,c_} /. {patt___} :> SetDelayed @@ Hold[int[patt]], NIntegrate[f[args], {x, 0, 1}]]). Does this address your concern? $\endgroup$ Mar 22, 2013 at 19:55
  • $\begingroup$ Actually not. Apart from the syntax error (Hold[int[patt]] has a ] too much), you again paste the arguments in the code instead of using the variable (argument) that contains them. I really believe that you should work from the named list as this seems part of the question (the OP mentions a really lengthy list). $\endgroup$ Mar 22, 2013 at 20:09
  • $\begingroup$ @SjoerdC.deVries Sorry for the syntax, I did not test this one (usually I always test). To address your objection: Hold[argument]/.OwnValues[argument]/.Hold[{args__}]:>..., the rest the same as before. $\endgroup$ Mar 22, 2013 at 20:11
  • $\begingroup$ Great, works as a charm. $\endgroup$ Mar 22, 2013 at 20:25
3
$\begingroup$

Pure functions

Why not use pure functions? It seems like arguments is just a bunch of placeholders, so one can fall back on using symbolic placeholders a la Mathematica (## stands for "all arguments", also meaning that there could be any number of them):

f = Plus[##] &;
int = NIntegrate[f[##], {x, 0, 1}] &;
NIntegrate[f[1, 2, 3 x], {x, 0, 1}]
4.5

Non-pure function version

ClearAll[a, b, c, f, int];
arguments = {a, b, c};
f[Sequence @@ (Pattern[#, _] & /@ arguments)] := Evaluate[Plus @@ arguments];
With[{arguments = arguments}, int[Sequence @@ (Pattern[#, _] & /@ arguments)] := 
   NIntegrate[f[Sequence @@ arguments], {x, 0, 1}]];

DownValues@f
DownValues@int
{f[1, 2, 3], int[1, 2, 3 x]}
{HoldPattern[f[a_, b_, c_]] :> a + b + c}

{HoldPattern[int[a_, b_, c_]] :>    NIntegrate[f[Sequence @@ {a, b, c}], {x, 0, 1}]}

{3, 4.5}

Note, that symbols {a, b, c} only stand for argument placeholders, they should not have any values associated with them!

$\endgroup$
7
  • $\begingroup$ Be aware that this will not work if the symbols a, b, or c have values assigned. $\endgroup$
    – Mr.Wizard
    Mar 22, 2013 at 14:29
  • 1
    $\begingroup$ @Mr.Wizard Sure, but wasn't it the point of OP to set up a, b, c as fixed symbolic arguments? Practically, in this case it would be simpler to only maintain the number of arguments instead of their names. $\endgroup$ Mar 22, 2013 at 14:31
  • $\begingroup$ Good point. I should address that. $\endgroup$
    – Mr.Wizard
    Mar 22, 2013 at 14:35
  • $\begingroup$ I still cannot vote for this method because Evaluate only works at level one in an expression and that may not be sufficient. $\endgroup$
    – Mr.Wizard
    Mar 22, 2013 at 14:40
  • $\begingroup$ @Mr.Wizard Ooh, ok, I think a bit more on this... :) $\endgroup$ Mar 22, 2013 at 14:53
3
$\begingroup$
arguments = {a, b, c} 

arguments2 = Pattern[#, _] & /@ arguments

{a_, b_, c_}

Evaluate[ f @@ arguments2] := a + b + c

Block[{f},
  With[{g = f @@ arguments},
   ClearAll[int];
   Evaluate[ int @@ arguments2] := NIntegrate[g, {x, 0, 1}]
   ]
 ]

?int

Global`int

int[a_,b_,c_]:=NIntegrate[f[a,b,c],{x,0,1}]

int[1, 2, 3]

6.

$\endgroup$
3
  • $\begingroup$ Be aware that this will not work if the symbols a, b, or c have values assigned. $\endgroup$
    – Mr.Wizard
    Mar 22, 2013 at 14:29
  • $\begingroup$ @Mr.Wizard True, will be looking to improve that. $\endgroup$ Mar 22, 2013 at 14:50
  • $\begingroup$ @Mr.Wizard Come to think of it, if you read the question it is clear that the OP has a list of undefined symbols. $\endgroup$ Mar 22, 2013 at 20:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.