Compile error from If but not from Piecewise

Consider this:

point={1.,2.};
f1 = Compile[{{t, _Real}},
Evaluate[Block[{a, b}, {a, b} = point; If[t > 0., a*b*Sin[t], 0.]]]];
f1[2.]


CompiledFunction::cfse: Compiled expression a should be a machine-size real number. >> CompiledFunction::cfex: Could not complete external evaluation at instruction 5; proceeding with uncompiled evaluation. >>

(*0.909297 a b*)


and this:

f2 = Compile[{{t, _Real}},
Evaluate[Block[{a, b}, {a, b} = point;Piecewise[{{a*b*Sin[t], t > 0.}, {0., True}}]]]];
f2[2.]
(*1.81859*)


Why does the If version produce errors while the Piecewise one doesn't? From this post I believe the compiler transforms the Piecewise function into its equivalent If function before compilation, so f1 and f2 should be the same. Why the difference?

-
@Nasser I need the Evaluate because in my real code there are some piecewise functions I want to expand before compile, which makes the code run 2x faster in my case. – xslittlegrass Jul 26 '13 at 5:43
@xslittlegrass, then why do you not pass the Pieceswise as an argument to a function that will return the CompiledFunction? – user21 Jul 26 '13 at 6:59
If what you have is piecewise functions, have you tried PiecewiseExpand in place of Evaluate ? – b.gatessucks Jul 26 '13 at 7:13

By trial and error, this works. It is where you put the Evaluate

point = {1., 2.};
f1 = Compile[{{t, _Real}},
Block[{a, b}, {a, b} = point; Evaluate@If[t > 0., a*b*Sin[t], 0.]]]

f1[1.2]
Out[354]= 1.86408


for the other one

ff2 = Compile[{{t, _Real}},
Block[{a, b}, {a, b} = point;
Evaluate[Piecewise[{{a*b*Sin[t], t > 0.}, {0., True}}]]]];

ff2[1.2]
Out[9]= 1.86408


So, may be you can put the needed Evaluate around the expression in question instead of around the whole Block? Putting outside the Block seems to have not affected the If part for some reason. I am not a Mathematica language expert to know why that is. But the above works.

-

Compiling with If

The reason f1 does not work is that

Block[{a, b}, {a, b} = point; If[t > 0., a*b*Sin[t], 0.]]


evaluates to

If[t > 0., a b Sin[t], 0.]


That happens because If is HoldRest and t > 0. does not evaluate to True or False. When compiled, there are MainEvaluate calls to evaluate a and b, which will give an error if a or b is not a real numeric quantity. So while f1[2.] gives an error, the following

Block[{a, b}, {a, b} = point; f1[2.]]


yields

1.81859


Note what happens if a third argument is supplied to If:

f3 = Compile[{{t, _Real}},
Evaluate[Block[{a, b}, {a, b} = point;
If[t > 0., a*b*Sin[t], 0., -200.]]]]
(* CompiledFunction[{t}, -200., -CompiledCode-] *)


The value of f3[-1.], f3[2.] etc. will always be -200.

Compiling with Piecewise

While Piecewise is HoldAll, it evaluates each piece in turn until one of the conditions is True. What actually seems to happen is a little complicated and not completely described in the reference page Piecewise (more below). The behavior of Compile can be understood by the same means as for If:

Block[{a, b}, {a, b} = point; Piecewise[{{a * b * Sin[t], t > 0.}, {0., True}}]]
(* Piecewise[{{2.*Sin[t], t > 0.}}, 0.] *)


The values of a and b are substituted, and this is the expression that is compiled. So in both cases, If and Piecewise, there is not a problem with Compile. The difference is explained by the expression that is compiled.

The behavior of Piecewise: Why are do values of a, b end up in the Piecewise expression that is the output of Block? Consider

Piecewise[{{val$1, cond$1},
{val$2, cond$2},
...
]


Each condition cond$n is evaluated in turn. If cond$n evaluates to False, Piecewise proceeds to the next condition. If cond$n does not evaluate to False, then val$n is evaluated; if the condition evaluates to neither True nor False, then the values of val$n and cond$n become part of a Piecewise expression that is returned after evaluation. That is how the values of a and b end up in the output.

Applying this procedure to the OP's f2, we see that a*b*Sin[t] will be evaluated during the evaluation of Piecewise and the values of a and b will replace the variables.

Example: The first condition evaluates to False and x is not incremented by 1. The next evaluates to -3 > a; x is incremented by 10, printed, and the value {2. 10} becomes a value in the final Piecewise expression that is returned. Finally, the last condition is True, so the third value is evaluated; since not all the preceding conditions evaluated to False, a Piecewise expression is returned.

Clear[a, t];
x = 0;
t = -3;
Piecewise[{{Print[x += 1];   {1., x}, t > 0.},
{Print[x += 10];  {2., x}, t > a},
{Print[x += 100]; {3., x}, True}
}]
(* 10 *)
(* 110 *)
(* Piecewise[{{{2., 10}, -3 > a}}, {3., 110}] *)


I might note that Piecewise is described in the reference manual as representing "a piecewise function", but it's not a function in the Mathematica sense: if f = Piecewise[..] then f[x] does not evaluate to the value of the piecewise function at x.

That's an explanation of why OP's code behaves the way it does. Your f2 already works the way you expected, but if you'd like another way, then consider this:

point = {1., 2.};
f4 = With[{a = point[[1]], b = point[[2]]},
Compile[{{t, _Real}}, If[t > 0., a*b*Sin[t], 0.]]];
f4[2.]
(* 1.81859 *)

-