# How to force Compile to return multiple results?

I would like to return two separate values from a compiled function, but Mathematica refuses to use the compiled version. Here is a narrowed-down example:

    cFunc = Compile[{{a, _Integer, 1}}, {a, a.a}];

(*
==> CompiledFunction::cfex: Could not complete external evaluation at instruction 3; proceeding with uncompiled evaluation. >>

==> {{1, 1, 1}, 3}
*)


Here a and a.a are hypothetic intermediate results of some long computation, that must be externalized in order for computation to go on outside the compiled function. I assume it is not possible to return lists that are not uniform (data type and dimensions). Is there any way to return more than one results from a compiled computation?

-

## 4 Answers

What about making the result uniform inside Compile and constructing things back afterwards? Like

   cFunc = Compile[
{{a, _Integer, 1}},
Join @@ {a, {a . a}}];
cFunc[{1, 3}]
Function[z, {Most[z],
Last[z]}][%]


which has the nice feature of not calling MainEvaluate:

    Needs["CompiledFunctionTools"]; cFunc // CompilePrint

(*
==>
1 argument
2 Integer registers
3 Tensor registers
Underflow checking off
Overflow checking off
Integer overflow checking on
RuntimeAttributes -> {}

T(I1)0 = A1
I0 = 4
Result = T(I1)2

1   I1 = DotVV[ T(I1)0, T(I1)0, I0]]
2   T(I1)1 ={ I1 }
3   T(I1)2 = Join[ T(I1)0, T(I1)1]]
4   Return

*)

-
I think your code is going to break in this case: cFunc = Compile[{{a, _Integer, 1}}, Join @@ {1.*a, {a.a}}];It makes some assuptions about a, that may not be true. Also, a single call to MainEvaluate is not so much of an issue, as long as it is not in a loop. – user21 Feb 15 '12 at 15:04
@ruebenko what is your analysis of my methods? – Mr.Wizard Feb 15 '12 at 15:11
@ruebenko: Definitely it is in a loop... But then the problem with Rolf's code is that joining and retrieving partial results from z might not be that easy. – István Zachar Feb 15 '12 at 23:55
why not? can you be more specific? – Rolf Mertig Feb 16 '12 at 0:16
I couldn't be more specific because your method is the one that I'm still using regularly, 4 years later : ) – István Zachar 2 days ago

You can use a technique like this:

cf = Compile[{{n, _Integer}, {m, _Integer}},
Module[{cpos = RandomSample[Range[n], m]},
Set[pos, cpos];
RandomReal[1, Length[cpos]]]];

{valres, posres} = Block[{pos}, {cf[10, 5], pos}]


The set works on a variable which is scoped with Block. Since this is done only once, the call to MainEvaluate is not a problem.

-
Am I correct in thinking that any solution must force a (one-time) call to MainEvaluate because compiled functions can only work with non-ragged arrays? – Szabolcs Feb 15 '12 at 14:20
I think there are special cases where this is not the case (e.g. Listable compiled functions that returns arrays of different length), but to the best of my knowledge, yes, you need a call to MainEvaluate. – user21 Feb 15 '12 at 14:24
@Szabolcs I guess it might be possible without the MainEvaluate call if in future support for passing InternalBags between the Mathematica VM and the interpreter is implemented. But for now, this is indeed the only way to do it as far as I know. – Oleksandr R. Feb 15 '12 at 14:27
@Szabolcs the problem there is that the error encountered by the VM, even though it occurs at the very end of the compiled code, forces a complete re-evaluation in the interpreter. Error control for compiled code isn't very sophisticated, unfortunately, so evaluation won't pick up in the interpreter where it left off in the VM. – Oleksandr R. Feb 15 '12 at 14:39
@Oleksandr I see. I wasn't sure if that was the case. cf = Compile[{a}, Print[a]]; cf[2] prints twice and shows that this is indeed what happens. But now it is not clear to me when MainEvaluate will force a complete re-evaluation and when it won't. – Szabolcs Feb 15 '12 at 14:42

A couple of ideas:

cfun = Compile[{{a, _Integer, 1}}, x[1] = a; x[2] = a.a;]
fun[a_?VectorQ] :=
Block[{x},
cfun[a];
{x[1], x[2]}
]

fun[{1,2,3,4,5}]

{{1, 2, 3, 4, 5}, 55}

cf2 = Compile[{{a, _Integer, 1}}, Sow[a]; Sow[a.a];];

Reap[cf2@{1, 2, 3, 4, 5}][[2, 1]]

{{1, 2, 3, 4, 5}, 55}

-
which also calls MainEvaluate – Rolf Mertig Feb 15 '12 at 14:37
@Rolf but it does not result in CompiledFunction::cfex isn't that the point? By the way I voted for yours; slick. – Mr.Wizard Feb 15 '12 at 14:40
@Mr.Wizard: nice ideas from you, too! Voted for it. Just try to understand how things work and what are the differences. – Rolf Mertig Feb 15 '12 at 14:42
@Rolf good point. I don't have CompilePrint in v7. I think your method will be the best if the function is low complexity. On a computationally complex function I think the overhead that my methods have will not matter, but I haven't tested that assertion. – Mr.Wizard Feb 15 '12 at 14:45
@Mr.Wizard, I think your methods are fine, you need to call MainEvaluate one more time than with my approach. (That's the only thing I could think of...) – user21 Feb 15 '12 at 15:36

I have been encountering this problem a lot recently. Since many related questions are linked to this post and I will share my solution here.

Advantages

• Can return multiple results (not necessarily have the same shape)

• Can return ragged lists

• Does not call MainEvaluate

## Basic idea

Flatten all the tensors into one single list, and include enough information to reconstruct them.

The first element of my list is the number of tensors / variables to return.

The $2$nd to $2 + var - 1$ th element corresponds to the rank of each tensor

The $2 + var$ to the $2 + var + rank_i -1$ th elements corresponds to the dimension of each tensor

## Construction inside Compile

### 1. Multiple return with different-dimension tensors (of different types)

Note:In my example there is no Complex or True|False, but since Re, Im and Boole are all compilable, they can be transformed to a real tensor and a integer tensor respectively.

This example illustrates returning 3 tensors with different dimensions.

cf1=Compile[{},
Module[{
m={{0,8,1,7},{1,9,2,6}},
n={0.301,0.98},
p={{{1,0},{2,7}},{{2,0},{0,0}}}},
Join[{3},
{TensorRank[m]},{TensorRank[n]},{TensorRank[p]},
Dimensions[m],Dimensions[n],Dimensions[p],
Flatten@m,Flatten@n,Flatten@p]
]
]


### 2. Return a ragged list (of arbitrary length)

This is a common case when a collection of Positions should be returned. This example illustrates adding 1D list of arbitrary length to the result programmatically.

cf2=Compile[{},Module[{var=0,rank={},dim={},res={},temp},
Do[temp=RandomReal[{0,1},RandomInteger[{1,10}]];
var++;
AppendTo[rank,TensorRank[temp]];
dim=Join[dim,Flatten@Dimensions[temp]];
res=Join[res,Flatten@temp];
,{i,1,3}];
Join[{var},rank,dim,res]]]


Neither of the examples have MainEvaluate when examining with CompilePrint.

## Extracting the lists

extractLists[list_?VectorQ] :=
Module[{vars = Round@First@list, rank, dim},
rank = Round@list[[2 ;; 1 + vars]];
dim = Round@
InternalPartitionRagged[
list[[2 + vars ;; 1 + vars + Total@rank]], rank];
MapThread[
ArrayReshape, {InternalPartitionRagged[
list[[2 + vars + Total@rank ;;]], Times @@@ dim], dim}]]


The results (the result of cf2 is random):

extractLists[cf1[]]
(*{{{0., 8., 1., 7.}, {1., 9., 2., 6.}}, {0.301,
0.98}, {{{1., 0.}, {2., 7.}}, {{2., 0.}, {0., 0.}}}}*)
extractLists[cf2[]]
(*{{0.895086, 0.716247, 0.626751, 0.457065, 0.709812, 0.118539,
0.504491, 0.40369}, {0.2376}, {0.159539, 0.398285, 0.0233042,
0.246191, 0.351316, 0.580408}}*)


## Notes

The type of the result is not conserved (Integer is converted to Real). This can be implemented by adding extra parameter before the rank info (I did not include it because it's not useful for my cases). Also I am not sure whether the performance of the code inside Compile is optimal. Feel free to edit if there are improvements.

-
Hmm... Isn't this the same method Rolf has suggested in his answer? I.e. flatten internal results and reconstruct outside the Compile. – István Zachar 2 days ago
@IstvánZachar true for your original problem. I meant to solve a general one. – happy fish 2 days ago
Yes, but still, the general idea was quite clear from Rolf's post. Well, it's nevertheless nice to have a more general approach, so here goes my +1. – István Zachar 2 days ago