5
$\begingroup$

How do I manually escape out of a compiled function without forcing an error and compromising its speed?

Here is an example:

f::zero = "x is zero.";

f = Compile[{{x, _Complex}}, If[x == 0, Return[1/x], x^2], 
"RuntimeOptions" -> {"RuntimeErrorHandler" -> 
  Function[Message[f::zero]; Throw[$Failed]]}];

As you can see, I want f to throw an error when x=0, but in order to do that, I needed to artificially think there actually was an error by returning 1/x. Here is the usage:

Catch[f[0]]

CompiledFunction::cfne : Numeriacal Error encountered; proceeding with uncompiled evaluation. >>
f::zero : x is zero

  1. How do I prevent the kernel from proceeding with uncompiled evaluation of the artificial 1/0 like it says (which I believe compromises speed).

  2. How do I escape out of a CompiledFunction without mimicking an error?

$\endgroup$
  • 2
    $\begingroup$ You could use a wrapper function that calls the compiled function and that catches numeric error codes, for instance in this case negative numbers and acts on them. $\endgroup$ – Sjoerd C. de Vries Nov 29 '14 at 13:51
  • $\begingroup$ The uncompiled evaluation on returning an infinite or not-a-number value seems to be hard-wired in the VM. I don't think it's possible to avoid that except as described by Sjoerd. $\endgroup$ – Oleksandr R. Nov 29 '14 at 14:37
  • $\begingroup$ Ok, it looks like @SjoerdC.deVries 's method works and doesn't compromise the speed too much (at least in the cases I'm working on) $\endgroup$ – QuantumDot Nov 29 '14 at 17:54
  • $\begingroup$ @SjoerdC.deVries would you kindly turn your comment into an answer. I'll accept and this question will go off the unanswered list. $\endgroup$ – QuantumDot Nov 19 '15 at 10:30
  • $\begingroup$ @QuantumDot OK, done $\endgroup$ – Sjoerd C. de Vries Nov 19 '15 at 22:30
4
$\begingroup$
f::zero = "x is zero.";

f = Compile[{{x, _Complex}}, 
   If[x == 0, Throw[Message[f::zero]; $Failed]; Return[1/x], x^2]];

Catch[f[0]]

Out

Or

f = With[{cf = Compile[{{x, _Complex}}, If[x == 0, Throw[Message[f::zero]; $Failed], x^2]]}, 
     Catch[cf[#]] &];

f[0]

Out

In the case of x == 0 there will be one (thanks to the comment by Oleksandr) MainEvaluate call, but it shouldn't have a more significant impact on the speed than using a wrapper function.

$\endgroup$
  • $\begingroup$ Why not return $Failed, rather than throwing it, so the calling function doesn't have to be wrapped in Catch to have good behavior? $\endgroup$ – m_goldberg Nov 20 '15 at 0:42
  • $\begingroup$ @m_goldberg Because of type inconsistency. $\endgroup$ – Karsten 7. Nov 20 '15 at 0:46
  • 2
    $\begingroup$ This is an excellent solution, especially if you're going to have to postprocess the return value anyway. Incidentally, the two calls out to the top level can be made into one by moving Message inside Throw, although I'm not sure that this really matters in practice. $\endgroup$ – Oleksandr R. Nov 20 '15 at 1:03
  • $\begingroup$ @OleksandrR. Although I couldn't find a measurable speed difference, I do prefer having only one MainEvaluate. Thanks. $\endgroup$ – Karsten 7. Nov 20 '15 at 3:16
4
$\begingroup$
ClearAll[f, fc]
f::zero = "x is zero.";

(* compiled part *)
fc =
  Compile[{{x, _Complex}},
   (* First part of the code; can be anything 
      .
      .
   *)
   If[x == 0, (* Error test*)
    {0, 0}, (* Error response, first part is status, second part is dummy *)
    {1, x^2} (* Normal response, first part is status, second part actual result *)
   ]
];

(* wrapper part *)
f[x_] := With[{res = fc[x]}, 
  If[Chop[res[[1]]] == 0, (* check status *)
    Message[f::zero]; $Failed, (* error *)
    res[[2]] (* normal result *)
  ]]

f[0]

Mathematica graphics

f[1]
(* 1. + 0. I *)

or with MichaelE2's suggestion:

ClearAll[f, fc]
f::zero = "x is zero.";

fc =
  Compile[{{x, _Complex}},
   (* First part of the code *)
   If[x == 0, (* Error test*)
    {},(* Error response, empty list *)
    {x^2} (* Normal response, list with result *)
    ]
   ];
f[x_] := With[{res = fc[x]}, If[res == {}, Message[f::zero]; $Failed, First@res]]
$\endgroup$
  • $\begingroup$ I might consider indicating the error by returning an empty array and a normal result in extra set of braces: E.g., If[x == 0, {}, {x^2}]. The empty array needs to be of the same rank as the other result. (Just an alternative for folks to choose from.) $\endgroup$ – Michael E2 Nov 19 '15 at 22:41
  • $\begingroup$ @MichaelE2 Good idea. Updated the answer. $\endgroup$ – Sjoerd C. de Vries Nov 19 '15 at 22:58
  • $\begingroup$ Catch and Throw are compilable. Therefore this answer could easily be modified to have an early escape. $\endgroup$ – Karsten 7. Nov 20 '15 at 2:51
  • $\begingroup$ @MichaelE2 There will be an extra ArrayReshape when using an empty array. Shouldn't be significant for the speed, though. $\endgroup$ – Karsten 7. Nov 20 '15 at 3:02
  • 2
    $\begingroup$ @MichaelE2 the other great advantage of using Throw, and which I think would outweigh any performance differences, is that you can put in it whatever you want--any values generated inside the compiled code; arbitrary top-level code; diagnostic messages... it is a lot more useful than just a numeric flag. $\endgroup$ – Oleksandr R. Nov 20 '15 at 11:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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