Do / Return behave differently in compilation - why?

I am wondering if this is a bug or documented behavior?

f1 = Function[v, Do[If[v[[i]] < 0, Return[v[[i]]]], {i, 1, Length[v]}]] c1 = Compile[{{v, _Integer, 1}}, Do[If[v[[i]] < 0, Return[v[[i]]]], {i, 1, Length[v]}]] 

Applying them to a list that does not contain negative numbers, we get different results:

 In[66]:= Through[{f1, c1}[{1, 2, 3}]] Out[66]= {Null, 3} 

This caused an error when I tried to compile a short function (actually its modified version).

Do one does not show the problem:

 c2 = Compile[{}, Do[i, {i, 5}]] c2[] (* returns nothing, as expected *) 
+8
wolfram-mathematica
source share
4 answers

As @Pillsy and @Leonid noted in the answers, the problem is that the original function sometimes returns Null , and sometimes an integer. In contrast, a compiled function always returns an integer. In V8, we see this with CompilePrint :

 Needs["CompiledFunctionTools`"] CompilePrint @ Compile[{{v,_Integer,1}},Do[If[v[[i]]<0,Return[v[[i]]]],{i,1,Length[v]}]] 

which under V8.0.4 produces this result:

  1 argument 1 Boolean register 6 Integer registers 1 Tensor register Underflow checking off Overflow checking off Integer overflow checking on RuntimeAttributes -> {} T(I1)0 = A1 I3 = 0 I0 = 1 Result = I5 1 I2 = Length[ T(I1)0] 2 I4 = I3 3 goto 10 4 I5 = Part[ T(I1)0, I4] 5 B0 = I5 < I3 6 if[ !B0] goto 10 7 I5 = Part[ T(I1)0, I4] 8 goto 11 9 goto 10 10 if[ ++ I4 < I2] goto 4 11 goto 12 12 Return 

We see that the result of the compiled function is that it ends in an integer register I5 . Following the stream of decompiled instructions, we see that if there is no match, then I5 will contain the last element of the list.

Compiler behavior may change between versions of Mathematica. I think it is reasonable to say that the compiler should issue at least a warning in cases where the type of the return result is ambiguous.

+3
source share

I would say that this is a mistake with how Compile works, but it is not surprising that it does not work correctly. Compile makes rather specific assumptions about not only its inputs (here v will be a list of integers), but also its outputs. It is assumed that compiled functions return values โ€‹โ€‹of the same type, and this type must be one of the types acceptable as input for the compiled function: True|False , Integer , etc., And the arrays are the same. Obviously, it would be better if the function complained about the message and then returned Null , but in order to be a well-designed function to compile, you need to provide the corresponding integer return value as defalult.

EDIT to clarify output types, for Szabolcs "comment below.

+5
source share

I would not say that this is a mistake. As @Pillsy noted, the Compile -d function is more limited since it should always return the same type. Since Do is the scope, Return inside Do breaks out of Do , not Function . Therefore, in some cases, it returns a vector element, and in others, Null . Strictly speaking, as written, a function should not compile at all. However, you can be more flexible and assume that the author of the function knows better and will discard the answer in this particular case. With this interpretation, Compile can arbitrarily give an answer in this case. Here it is intended to create the last item in the list. And I think this is no more ad-hoc than creating a fixed number every time. I also think that such corner cases cannot be avoided when a much more flexible symbolic code is compiled. Compile might have stricter rules in this case and require some meaningful return (of the same type) in all cases, but it is not clear to me whether this will really be useful. In a sense, all of C is what the compiler assumes is that you know what you are doing, but allows you to create a lot of undefined if you are not careful.

+4
source share

Some additional information you may find helpful. Consider this:

 In[26]:= f1 = Function[v, Do[If[v[[i]] < 0, Return[v[[i]]]], {i, 1, Length[v]}]; last = 1;]; In[27]:= last Out[27]= last In[28]:= f1[{-1, 2, 3}] In[29]:= last Out[29]= 1 

Despite the fact that the function had to return to the first element that it received for last = 1, Therefore, as others noted, Return does not work. This will not be fixed as too much code depends on this behavior.

Now you can use:

 In[30]:= f2 = Function[v, Module[{}, Do[If[v[[i]] < 0, Return[v[[i]], Module]], {i, 1, Length[v]}]; last2 = 1;]]; In[31]:= f2[{-1, 2, 3}] Out[31]= -1 In[32]:= last2 Out[32]= last2 

Which behaves as expected. Unfortunately, however,

 In[33]:= c1 = Compile[{{v, _Integer, 1}}, Module[{}, Do[If[v[[i]] < 0, Return[v[[i]], Module]], {i, 1, Length[v]}]; ] ]; 

will not compile.

Here's how to do it.

 In[137]:= c1=Compile[{{v,_Integer,1}}, Module[{res=1}, Do[If[v[[i]]<0,res=v[[i]];Break[]],{i,1,Length[v]}]; If[res==1,Internal`CompileError[]]; res ] ,"RuntimeOptions"->{"RuntimeErrorHandler"->Function[Null]}] In[140]:= c1[{1,2,3,1}] In[141]:= c1[{1,2,3,-1}] Out[141]= -1 

Check the output.

 In[139]:= CompilePrint[c1] 

Some additional notes: "RuntimeErrorHandler" โ†’ The [Null] function is a function! Think about it for a second. You can thow message something!

So something like this works.

 cfquietfail = Compile[{{x, _Real, 1}}, Exp[x], "RuntimeOptions" -> {"WarningMessages" -> False, "RuntimeErrorHandler" -> Function[Message[MyFunctionName::"I can complain here!"]; Throw[$Failed]]}]; Catch[ cfquietfail[{1000.}]] 

Hope this is helpful.

+3
source share

All Articles