3

I want to automatically Compile an expression in between a routine of certain analytical manipulations. The number of arguments for the function may change for every run, so I would like to avoid hardcoding the arguments into my call of Compile. Consider this MWE:

vars = {x, y};
expr = Sin[x + y];
fun1 = With[{e = expr}, Compile[{{x, _Real}, {y, _Real}}, e]];

The disadvantage here is that I need to provide {{x, _Real}, {y, _Real}} as variables explicitly. I would rather like to be able to do something like

fun2 = With[{e = expr, v = Transpose[{vars, ConstantArray[_Real, Length@vars]}]},
            Compile[v, e]]

where knowing vars in advance allows me to avoid manually computing v, print it and then copy-paste into Compile. All variants of fun2 I could come up with boil down to the issue that Compile only recognizes a single variable as input. Is there any way to achieve the desired functionality?

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Lukas
  • 2,702
  • 1
  • 13
  • 20

2 Answers2

4

This can be accomplished through Hold[] trickery:

vars = {x, y}; expr = Sin[x + y];

fun1 = Hold[Compile][Transpose[PadRight[{vars}, {2, Automatic}, {_Real}]],
                     expr] // ReleaseHold;

An alternative is to start with a Hold[] expression, inject any needed changes with With[], and then use Apply[]:

fun1 = With[{vlist = Transpose[PadRight[{vars}, {2, Automatic}, {_Real}]]},
            Compile @@ Hold[vlist, expr]];

Test:

fun1[π/6, π/3]
   1.
J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
  • better perhaps to start with a HoldComplete than a List – b3m2a1 Mar 29 '18 at 05:02
  • Hmm, I don't think it works with Hold[] or HoldComplete[] for the second version, because the part that assembles the sequence of arguments won't fire inside a Hold[], unless you MapAt[] an Evaluate[]. – J. M.'s missing motivation Mar 29 '18 at 05:04
  • 2
    I meant something like With[{vars=varList}, Compile@@Hold[vars, expr]]. Allows you to keep expr held. – b3m2a1 Mar 29 '18 at 05:28
  • Yes, injection should work; I'll edit in a bit. – J. M.'s missing motivation Mar 29 '18 at 05:29
  • @J.M. Thanks a lot. I clearly need to get used to Hold a lot more. Nice and compact solution. Side question: Is there a specific reason why you are using the PadRight based way to generate the list of variables instead of my way? Is it just personal taste or is there more behind it? – Lukas Mar 29 '18 at 06:32
  • @Lukas, I use PadRight[] in case vars has a lot of variables in it, and I don't want to bother counting. Your method with ConstantArray[] is fine and equivalent. – J. M.'s missing motivation Mar 29 '18 at 07:28
  • @J.M. Thanks for your explanation! – Lukas Mar 29 '18 at 08:40
3

Often I do things like this with Replace as a destructuring function. Here's a super convoluted example that uses Replace, Hold, and Thread to define a Compile spec that holds all its arguments and auto-detects its variables (also not gonna lie it was kinda fun to write):

Options[compileWithVars] =
  Join[
   {
    "TypeMap" -> Automatic
    },
   Options[Compile]
   ];
compileWithVars[
  varList : {__Symbol} | Automatic : Automatic, 
  expr_, 
  ops___?OptionQ
  ] :=
 With[
  {
   varsHeld =
    Replace[Hold[varList], 
     {
      Hold[Automatic] :>
       DeleteDuplicates@
        Cases[Hold[expr], 
         s_Symbol?(Function[Null, Context[#] == $Context, 
             HoldFirst]) :> Hold[s],
         Infinity
         ],
      l_List :> Thread[l]
      }
     ],
   tm =
    Replace[
     Lookup[Flatten@{ops}, "TypeMap", Automatic ],
     Automatic :> {_ -> {_Real}}
     ]
   },
  Replace[
   Thread[
    Map[Replace[Join[#, Hold @@ (# /. tm)], Hold[l__] :> Hold[{l}]] &,
      varsHeld ], Hold],
   {
    Hold[vl : {__List}] :>
     Apply[
      Compile,
      HoldComplete[
       vl,
       expr,
       Evaluate@FilterRules[
         Flatten@{ops},
         Options[Compile]
         ]
       ]
      ],
    sheesh_ :>
     Failure["BadVarList",
      <|

       "MessageTemplate" :> "Var spec `` is invalid (originally ``)",
       "MessageParameters" :> {sheesh, 
         HoldForm @@ Thread[varsHeld, Hold]}
       |>
      ]
    }
   ]
  ]

Then we do it like so:

compileWithVars[Sin[x + y]]

blah

Or we can tell it that we expect x to be a {_Integer, 1} and everything else to default to {_Real, 2}:

compileWithVars[z*Cos[θ]*Sin[x + y], 
 "TypeMap" -> {Hold[x] -> {_Integer, 1}, _ -> {_Real, 2}}]

blib

b3m2a1
  • 46,870
  • 3
  • 92
  • 239