Message Boards Message Boards

Delayed evaluation and automatic memory management in Mathematica

GROUPS:

Hello everyone,

In this post I would like to share a programming technique I find interesting, as well as ask for feedback on it.

The problem

The MaTeX package typesets LaTeX directly within Mathematica. Unfortunately, every call to the MaTeX function involves one run of LaTeX, which is very slow: it takes about 0.5 seconds. Typesetting 20 formulae would take 10 seconds—much too slow.

Mathematica graphics

To mitigrate this, MaTeX can typeset a list of expressions with a single LaTeX run. This is much faster:

In[37]:= ClearMaTeXCache[]
MaTeX@Range[20]; // AbsoluteTiming

Out[38]= {0.480417, Null}

But it is not always convenient to put everything we want to compile into a single list. MaTeX is meant for creating figure labels and each label would usually appear in a different position in the Mathematica code we write. Could we still gather all these inputs, appearing in different places in the code, and compile them with a single run?

The solution

Here's how we can do this: delay the evaluation. Let's have a function called delayedMaTeX that doesn't evaluate immediately, but simply acts as a placeholder object. It also collects the inputs into a queue for later evaluation. Then we can trigger batch-evaluation with one command, which assigns the results to the placeholders.

We also want to prevent the unlimited accumulation of compiled results, which take up memory. Let's get rid of them as soon as they are no longer referenced anywhere. This can be achieved using the rarely used Temporary attribute.

For the following proof-of-concept example, let's forget that MaTeX can take options—taking that into account would just make the code longer and harder to understand without being educational. Let's load MaTeX and set some options right away, just so the examples below will look a bit nicer.

<< MaTeX`
SetOptions[MaTeX, "DisplayStyle" -> False, FontSize -> 16];

Turning off history prevents unwanted references to symbols. Then we can observe how unreferenced results get cleared.

$HistoryLength = 0;

The inputs will be queued up for evaluation in this variable:

queue = <||>;

Let the placeholder be called delayed. It holds a unique Temporary symbol that allows for reference tracking, as well as the TeX input itself. If the input is not yet in the queue, it adds it:

expr : delayed[s_Symbol, texcode_] /; Not@KeyExistsQ[queue, ToString[s]] :=
   (AppendTo[queue, ToString[s] -> texcode]; expr)

When the input is processed, the result will be assigned to the temporary symbol. The result from MaTeX always has head Graphics. If delayed sees this, it simply evaluates directly to the result:

delayed[g_Graphics, ___] := g

Finally let's add a nice printed form for these delayed expressions. This is irrelevant for the functioning of the system; it's purely for beautification.

delayed /: MakeBoxes[expr : delayed[_, texcode_], StandardForm | TraditionalForm] := 
  With[{boxes = ToBoxes@Tooltip[
      Panel[
       Style["$\[Ellipsis]$", "Code", ShowStringCharacters -> False, 
        Background -> None], FrameMargins -> 2
       ],
      texcode]},
    InterpretationBox[boxes, expr]
  ]

The delayedMaTeX function simply creates the temporary symbol (named as id$...) and places it within a delayed expression along with the TeX input:

delayedMaTeX[texcode_] :=
 With[{id = Unique[id]},
  SetAttributes[id, Temporary];
  delayed[id, texcode]
 ]

Finally we will have an update[] function that processes the queue:

update[] :=
 Module[{},
  queue = KeySelect[queue, NameQ]; (* drop keys that don't have a corresponding temporary symbol *)
  With[{syms = Symbol /@ Keys[queue]},
   syms = MaTeX[Values[queue]]
   ];
  queue = <||>;
 ]

We are ready to try it out now.

Let's put some MaTeX tick labels in a plot:

plot = Plot[Sin[x], {x, 0, 2 Pi},
  Ticks -> {
    Table[{x, delayedMaTeX[x]}, {x, 0, 2 Pi, Pi/3}],
    Table[{y, delayedMaTeX[y]}, {y, -1, 1, 1/2}]
    }
  ]

Mathematica graphics

Only the placeholders show for now. Let's process the queue:

In[25]:= update[] // AbsoluteTiming
Out[25]= {0.455054, Null}

It was fast. Let's look at the plot again:

plot

Mathematica graphics

Everything is in place now.

At this moment we have lots of temporary symbols holding the results:

In[17]:= Names["id*"]
Out[17]= {"id", "id$", "id$4271", "id$4272", "id$4273", "id$4274", \
"id$4275", "id$4276", "id$4277", "id$4278", "id$4279", "id$4280", \
"id$4281", "id$4282"}

They are gone as soon as plot is cleared:

In[18]:= plot =.

In[19]:= Names["id*"]
Out[19]= {"id", "id$"}

To wrap up

I hope you found this technique interesting. Any comments, improvements, additions, criticisms are welcome.

If you use MaTeX yourself and you would like to have this feature properly implemented and integrated into MaTeX, let me know. It would eliminate any remaining disadvantages MaTeX has relative to MathPSFrag. But given that MaTeX already uses a caching system, and that I personally don't find performance to be a problem, I'm not sure it's worth the effort.

POSTED BY: Szabolcs Horvát
Answer
11 months ago

enter image description here - another post of yours has been selected for the Staff Picks group, congratulations! We are happy to see you at the top of the "Featured Contributor" board. Thank you for your wonderful contributions, and please keep them coming!

POSTED BY: Moderation Team
Answer
11 months ago

Group Abstract Group Abstract