12

I'm running a Table function which will take too much time to complete.

I wanted to know if there's a way to retrieve the results computed so far.

random
  • 197
  • 2
  • 7
  • 2
    I am afraid the answer is no. Could it be that your computer is low on memory because you accidentally asked for too long a table to be created ? Does it work when you make the table several elements long ? – Sasha Jun 24 '11 at 16:12
  • 1
    @random, interesting question, but as Sasha said, not really. However, it may be worth your while to post the slow code here as we may be able to suggest alternatives that will run within the allotted time. Also, from my experience `Table` can be slow, and alternatives do exist. – rcollyer Jun 24 '11 at 16:14
  • The problem is that this function has been running for 2 days straight, and I forgot to add an AppendTo, so the result of the function will only be assigned to the variable when it finishes running. Because i can't afford to wait a week for the results, I needed to access what the function computed so far, and save that. If that's not possible I'll have to abort the function and loose everything. Since I can interrupt the evaluation and resume it and it will know from where to resume, I'm assuming it is storing the data somewhere, I just don't know where or how to access it. – random Jun 24 '11 at 16:20
  • 1
    @random AppendTo is a bad idea unless the cost of a single loop is huge and the number of elements small... See my reply – acl Jun 24 '11 at 16:23
  • @random, then you'll either have to wait until it finishes as it is to late to do anything, or stop it and write something that is faster. As per Sasha's question, how's your computer's memory doing? If it is heavily loaded, this may not finish any time soon. If possible, rewrite it on a different computer. – rcollyer Jun 24 '11 at 16:24
  • @acl thanks for the explanation. But surely Mathematica must have a temporary storage of what has been computed so far. Isn't there a temporary variable that i may access? @rcollyer the memory is doing fine, the problem is that this function just takes its time and I have to run it 8405 times – random Jun 24 '11 at 16:38
  • 1
    @random Not that I know of and, if Sasha says you can't, I'd guess you can't! – acl Jun 24 '11 at 16:43
  • Ok fair enough, thank you all for your input. – random Jun 24 '11 at 16:49

3 Answers3

19

The proposed solution

Here is a version of Table that is Abort-able and will keep the intermediate results collected so far. It is a modified version of the solution posted here.

ClearAll[abortableTable];
SetAttributes[abortableTable, HoldAll];
abortableTable[expr_, iter__List] :=
  Module[{indices, indexedRes, sowTag},
  SetDelayed @@ 
     Prepend[Thread[Map[Take[#, 1] &, List @@ Hold @@@ Hold[iter]], 
       Hold], indices];
  indexedRes =
     If[# === {}, #, First@#] &@Last@Reap[
        CheckAbort[Do[Sow[{expr, indices}, sowTag], iter], {}], sowTag];
  AbortProtect[
     Map[First,
       SplitBy[indexedRes,
          Table[
            With[{i = i}, Function[Slot[1][[2, i]]]], 
            {i, Length[Hold[iter]] - 1}]], 
       {-3}]]];

It should be able to take the same iterator specification as Table.

How it works

Here is how it works. The first statement (SetDelayed @@...) "parses" the iterators, assuming that they are each of the form {iteratorSymbol_,bounds__}, and assigns the list of iterator variables to the variable indices. The construction with Hold is needed to prevent possible evaluation of iterator variables. There are many ways to do this, I used just one of them. Here is how it works:

In[44]:= 
{i, j, k} = {1, 2, 3}; 
Prepend[Thread[Map[Take[#, 1] &, List @@ Hold @@@ 
   Hold[{i, 1, 10}, {j, 1, 5}, {k, 1, 3}]], Hold], indices]

Out[45]= Hold[indices, {i, j, k}] 

Using SetDelayed @@ the-above will then naturally produce the delayed definition of the form indices:={i,j,k}. I assigned the values to indices i,j,k to demonstrate that no unwanted evaluation of them happens when using this construct.

The next statement produces a list of collected results, where each result is grouped in a list with the list of indices used to produce it. Since indices variable is defined by delayed definition, it will evaluate every time afresh, for a new combination of indices. Another crucial feature used here is that the Do loop accepts the same iterator syntax as Table (and also dynamically localizes the iterator variables), while being a sequential (constant memory) construct. To collect the intermediate results, Reap and Sow were used. Since expr can be any piece of code, and can in particular also use Sow, a custom tag with a unique name is needed to only Reap those values that were Sown by our function, but not the code it executes. Since Module naturally produces (temporary) symbols with unique name, I simply used a Module - generated variable without a value, as a tag. This is a generally useful technique.

To be able to collect the results in the case of Abort[] issued by the user interactively or in the code, we wrap the Do loop in CheckAbort. The code that is executed on Abort[] ({} here) is largely arbitrary in this approach, since the collection of results is anyway done by Sow and Reap, although may be useful in a more elaborate version that would save the result into some variable provided by the user and then re-issue the Abort[] (the functionality not currently implemented).

As a result, we get into a variable indexedRes a flat list of the form

{{expr1, {ind11,ind21,...indn1}},...,{exprk, {ind1k,ind2k,...indnk}}

where the results are grouped with the corresponding index combination. We need these index combinations to reconstruct the multi-dimensional resulting list from a flat list. The way to do it is to repeatedly split the list according to the value of i-th index. The function SplitBy has this functionality, but we need to provide a list of functions to be used for splitting steps. Since the index of i-th iterator index in the sublist {expr,{ind1,...,indn}} is 2,i, the function to do the splitting at i-th step is #[[2, i]]&, and we need to construct the list of such functions dynamically to feed it to SplitBy. Here is an example:

In[46]:= Table[With[{i = i}, Function[Slot[1][[2, i]]]], {i, 5}]

Out[46]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[2, 5]] &}

The With[{i=i},body] construct was used to inject the specific values of i inside pure functions. The alternatives to inject the value of i into Function do exist, such as e.g.:

In[75]:= 
Function[Slot[1][[2, i]]] /. Map[List, Thread[HoldPattern[i] -> Range[5]]]

Out[75]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[2, 5]] &}

or

In[80]:= Block[{Part}, Function /@ Thread[Slot[1][[2, Range[5]]]]]

Out[80]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[ 2, 5]] &}

or

In[86]:= Replace[Table[{2, i}, {i, 5}], {inds__} :> (#[[inds]] &), 1]

Out[86]= {#1[[2, 1]] &, #1[[2, 2]] &, #1[[2, 3]] &, #1[[2, 4]] &, #1[[ 2, 5]] &}

but are probably even more obscure (perhaps except the last one).

The resulting nested list has a proper structure, with sublists {expr,{ind1,...,indn}} being at level -3 (third level from the bottom). By using Map[First,lst,{-3}], we remove the index combinations, since the nested list has been reconstructed already and they are no longer needed. What remains is our result - a nested list of resulting expressions, whose structure corresponds to the structure of a similar nested list produced by Table. The last statement is wrapped in AbortProtect - just in case, to make sure that the result is returned before the possible Abort[] fires.

Example of use

Here is an example where I pressed Alt+. (Abort[]) soon after evaluating the command:

In[133]:= abortableTable[N[(1+1/i)^i],{i,20000}]//Short
Out[133]//Short= {2.,2.25,2.37037,2.44141,<<6496>>,2.71807,2.71807,2.71807}

It is almost as fast as Table:

In[132]:= abortableTable[N[(1+1/i)^i,20],{i,10000}]//Short//Timing
Out[132]= {1.515,{2.0000000000000000000,2.2500000000000000000,<<9997>>,2.7181459268252248640}}

In[131]:= Table[N[(1+1/i)^i,20],{i,10000}]//Short//Timing
Out[131]= {1.5,{2.0000000000000000000,2.2500000000000000000,<<9997>>,2.7181459268252248640}}

But it does not auto-compile while Table does:

In[134]:= Table[N[(1+1/i)^i],{i,10000}]//Short//Timing
Out[134]= {0.,{2.,2.25,2.37037,2.44141,<<9993>>,2.71815,2.71815,2.71815}}

One can code the auto-compilation and add it to the above solution, I just did not do it since it will be a lot of work to do it right.

EDIT

I rewrote the function to make some parts both more concise and easier to understand. Also, it is about 25 % faster than the first version, on large lists.

ClearAll[abortableTableAlt];
SetAttributes[abortableTableAlt, HoldAll];
abortableTableAlt[expr_, iter : {_Symbol, __} ..] :=
  Module[{indices, indexedRes, sowTag, depth =  Length[Hold[iter]] - 1},
   Hold[iter] /. {sym_Symbol, __} :> sym /. Hold[syms__] :> (indices := {syms});
   indexedRes =  Replace[#, {x_} :> x] &@ Last@Reap[
      CheckAbort[Do[Sow[{expr, indices}, sowTag], iter], Null],sowTag];
   AbortProtect[
      SplitBy[indexedRes, Array[Function[x, #[[2, x]] &], {depth}]][[##,1]] & @@ 
      Table[All, {depth + 1}]
   ]];
Community
  • 1
  • 1
Leonid Shifrin
  • 22,449
  • 4
  • 68
  • 100
  • 2
    That's quite some function. Thanks for posting it, and as an aside note thanks for writing your awesome Mathematica book, it was really handy and a good read. – random Jun 24 '11 at 16:56
  • Well, I am happy that you liked it. Unfortunately, the book as it is now does not cover a lot of useful things. I hope to soon address this issue in one way or another (and people on this forum are certainly doing this already). – Leonid Shifrin Jun 24 '11 at 17:00
  • @random if you find an answer useful, you can always upvote it :) (I appear to be the only one to have upvoted Leonid's answer up to now, so I am guessing you're not aware of the upvote business) – acl Jun 24 '11 at 17:08
  • 1
    @acl I wanted to upvote both yours and Leonid's but since I just joined SO an hour ago, until a couple of minutes ago I didn't have enough reputation to do that. Apparently now I have enough, upvotes given ;) – random Jun 24 '11 at 17:16
  • 1
    Pretty dense function. I feel it would serve many readers if you could add a little bit of explanation. – Sjoerd C. de Vries Jun 26 '11 at 07:13
6

Unfortunately no. If you want to do something like lst=Table[f[i],{i,1,10000}] so that if aborted you still have results, you could do

Clear[lst2];
lst2 = {};
(Do[lst2 = {lst2, f[i]}, {i, 1, 10000}];
lst2=Flatten[lst2];) // Timing

which, for undefined f, takes 0.173066s on my machine, while lst = Table[f[i], {i, 1, 100000}] takes roughly 0.06s (ie, Table it is 3 times faster at the expense of not being interruptible).

Note that the obvious "interruptible" solution, lst = {}; Do[AppendTo[lst, f[i]], {i, 1, 100000}] takes around 40s, so don't do that: use linked lists and flatten at the end, like in my first example (however, that will break if f[i] returns a list, and more care is then needed).

acl
  • 6,490
  • 1
  • 27
  • 33
  • @Leonid I know, I even mention it in my answer. Your solution is more general though (as usual). – acl Jun 24 '11 at 17:07
  • Oops, sorry. I was not reading carefully enough. I will remove my comment, and +1. – Leonid Shifrin Jun 24 '11 at 17:11
  • If `f[i]` returns a list, you can just wrap its result like `resultWrapper[f[i]]`. Then `Flatten` won't go inside the wrapper. – Ruslan Jan 13 '17 at 16:21
3

Another solution is to export results of intermediate computations to a running log file as described in this answer by WReach (see the "File-backed In-memory Approach" section). With this you will newer loose results of intermediate computations and will always be able to investigate what is computed so far.

P.S. I think usage of Monitor as suggested in this recent Mathematica tip on twitter is also useful in such cases:

Monitor[Table[Integrate[1/(x^n + 1), x], {n, 20}], 
 ProgressIndicator[n, {1, 20}]]
Community
  • 1
  • 1
Alexey Popkov
  • 9,355
  • 4
  • 42
  • 93
  • How does Monitor help in retrieving the results obtained so far? Knowing the value of n doesn't really do you much good. – Sjoerd C. de Vries Jun 26 '11 at 06:59
  • @Sjoerd It is not for obtaining computed values, just for the information on how many points are already computed. It is just additional note, my answer to the question is above the `P.S.`. – Alexey Popkov Jun 26 '11 at 07:06