# Running out of main memory when using loops in TikZ

I have a TikZ drawing (namely, pairs of rulers) that I want to replicate 12 times, side by side. I'm using foreach constructs to help me in this process and to draw the 5-cm, 1-cm and 1-mm ticks for the rulers. However, I am running out of memory when I try to draw them more than a third time.

I tried externalizing the process, without success, and I am reluctant to try to increase the main memory. How would I go about solving this?

Here's the MWE:

\documentclass[tikz,convert=false,12pt]{standalone}

\renewcommand*\familydefault{\sfdefault}
\usepackage[T1]{fontenc}
\usetikzlibrary{external}
\tikzexternalize

\begin{document}

%%%%  Any unit not specified is multiplied by 1 mm. This allows some fancy scaling.
\begin{tikzpicture}[x=1mm,line cap=rect]

%%%% We need 12 rulers, mirrored, for a grand total of 24 rulers. Each pair of
%%%% rulers will be offset 10 cm from the previous pair.

\foreach \i in {0,...,2}{ %%%% Ideally, this would be 11 and not 2.
\tikzset{xshift/.expanded=\i*100mm}

%%%% Draw the red millimeter tick marks.
\foreach \y in {0.1,0.3,...,69.9}
\draw [color=red, line width=0.15mm] (10mm,\y) -- (0mm,\y)
node[color=red] {};

%%%% Draw the black millimeter tick marks
\foreach \y in {0,0.2,...,69.8}
\draw [color=black, line width=0.15mm] (10mm,\y) -- (0mm,\y)
node[color=black] {};

%%%% Draw the red minor(centimeter) tick marks with the red labels.
\foreach \y in {1,3,...,69}
\draw [color=red, line width=1mm] (20mm,\y) -- (0mm,\y)
node[color=red, anchor=east] {\pgfmathprint{int(\y)}};

%%%% Draw the black minor (centimeter) tick marks with the black labels.
\foreach \y in {0,2,...,68}
\draw [color=black, line width=1mm] (20mm,\y) -- (0mm,\y)
node[color=black, anchor=east] { \pgfmathprint{int(\y)}};

%%%% Draw the black major (5-cm) tick marks with the black labels. This paints over
%%%% the minor tick marks in the same place.
\foreach \y in {0,10,...,70}
\draw [color=black,line width=2mm] (30mm,\y) -- (0mm,\y)
node[fill=white,anchor=east] {\Huge \textbf{{\pgfmathprint{int(\y)}}}};

%%%% Draw the red major (5-cm) tick marks with the red labels. This paints over
%%%% the minor tick marks in the same place.
\foreach \y in {5,15,...,65}
\draw [color=red, line width=2mm] (30mm,\y) -- (0mm,\y)
node[fill=white,anchor=east] {\Huge \textbf{\pgfmathprint{int(\y)}}};

%%%% Draw the vertical line that ties it all together.
\draw [line width=2mm](0mm, 0mm) -- coordinate (y axis mid) (0mm,700mm);

%%%% ------------------------------ REVERSED RULERS

%%%% Draw the red millimeter tick marks. Reversed.
\foreach \y in {0.1,0.3,...,69.9}
\draw [color=red, line width=0.15mm] (60mm,\y) -- (70mm,\y)
node[color=red] {};

%%%% Draw the black millimeter tick marks. Reversed
\foreach \y in {0,0.2,...,69.8}
\draw [color=black, line width=0.15mm] (60mm,\y) -- (70mm,\y)
node[color=black] {};

%%%% Draw the red minor tick marks with the red labels. Reversed
\foreach \y in {1,3,...,69}
\draw [color=red, line width=1mm] (50mm,\y) -- (70mm,\y)
node[color=red, anchor=west] {\pgfmathprint{int(\y)}};

%%%% Draw the black minor tick marks with the black labels. Reversed
\foreach \y in {0,2,...,68}
\draw [color=black, line width=1mm] (50mm,\y) -- (70mm,\y)
node[color=black, anchor=west] { \pgfmathprint{int(\y)}};

%%%% Draw the black major tick marks with the black labels. This paints over
%%%% the minor tick marks in the same place. Reversed
\foreach \y in {0,10,...,70}
\draw [color=black,line width=2mm] (40mm,\y) -- (70mm,\y)
node[fill=white,anchor=west] {\Huge \textbf{{\pgfmathprint{int(\y)}}}};

%%%% Draw the red major tick marks with the red labels. This paints over
%%%% the minor tick marks in the same place. Reversed
\foreach \y in {5,15,...,65}
\draw [color=red, line width=2mm] (40mm,\y) -- (70mm,\y)
node[fill=white,anchor=west] {\Huge \textbf{\pgfmathprint{int(\y)}}};

%%%% Draw the vertical line that ties it all together, reversed
\draw [line width=2mm](70mm, 0cm) -- coordinate (y axis mid) (70mm,700mm);

% %%%% Draw crop marks.
% \draw [very thin, color=black, loosely dashed](-1, 0cm) -- coordinate
% (y axis mid) (-1cm,71cm);

}

\end{tikzpicture}

\end{document}

%%% Local Variables:
%%% mode: latex
%%% LaTeX-command: "latex -shell-escape"
%%% TeX-master: t
%%% End:

-
Is a solution using luatex acceptable? –  JLDiaz Jun 29 at 20:20
I've never used luatex. Then again, no one has ever accused me of being a purist. –  Ricardo Jun 29 at 20:34
If you remove yours empty nodes (in the first two \foreach loops of each rule), the document compiles fine. –  Paul Gaborit Jun 30 at 8:19
@PaulGaborit, I removed the empty nodes and increased the loop to what it would ideally be and hit the same errors, whether externalizing or not. Did you do anything special to get it to compile? –  Ricardo Jun 30 at 16:11

# The idea

The idea is to code all those nested loops in lua, and from those loops, generate the tex (tikz) code to draw the individual marks. So, what TikZ finally "sees" are the the result of the loops, as a long sequence of simple line drawing commands. This way, TeX memory does not choke.

However, the number of tiny marks which are generated in the lua loops is huge, and the whole thing takes a lot of time to compile (12s per ruler in my machine, a grand total of 2:30 min). Perhaps it could be optimized if instead of sending each generated line to TeX, all are accumulated in a list, and then the whole list is sent. I have to try this, but I guess that the real bottleneck is TikZ parsing all those lines.

# The code

It is separated in two files, one for the lua code, the other for the main TeX document which uses it. To compile:

\$ lualatex main.tex


After a while, you'll get a pdf you can print or include in your another document (via \includegraphics).

## main.tex

The latex code is now minimal.

\documentclass[tikz,convert=false,12pt]{standalone}
\renewcommand*\familydefault{\sfdefault}
\usepackage[T1]{fontenc}
\directlua{dofile("drawrule.lua")}
\def\DrawAllRules{\directlua{DrawAllRules()}}

\begin{document}
%%%%  Any unit not specified is multiplied by 1 mm. This allows some fancy scaling.
\begin{tikzpicture}[x=1mm,line cap=rect]
\DrawAllRules
\end{tikzpicture}
\end{document}


## drawrlule.lua

I tried to keep the lua code as close as possible to your original tex code. I used some strings to hold the tikz commands which are repeated elsewhere, but parametrized with "printf" like placeholders

function DrawRule()
t = {}
mmcmd = "\\draw [color=%s, line width=0.15mm] (10mm,%f) -- (0mm,%f) node[color=%s] {};\n"
cmcmd = "\\draw [color=%s, line width=1mm] (20mm,%f) -- (0mm,%f) node[color=%s,anchor=east] {%d};\n"
cm5cmd= "\\draw [color=%s, line width=2mm] (30mm,%f) -- (0mm,%f) node[fill=white, anchor=east] {\\Huge\\textbf{%d}};\n"
-- Draw the red millimeter tick marks.
for y = 0.1, 69.9, 0.2 do     -- first, last, step
tex.print(string.format(mmcmd, "red", y, y, "red"))
end

--  Draw the black millimeter tick marks
for y = 0, 69.8, 0.2 do     -- first, last, step
tex.print(string.format(mmcmd, "black", y, y, "black"))
end
-- Draw the red minor(centimeter) tick marks with the red labels.
for y = 1, 69, 2 do     -- first, last, step
tex.print(string.format(cmcmd, "red", y, y, "red", y))    end

-- Draw the black minor (centimeter) tick marks with the black labels.     for y = 0, 68, 2 do     -- first, last, step
tex.print(string.format(cmcmd, "black", y, y, "black", y))
end
-- Draw the black major (5-cm) tick marks with the black labels. This paints over
-- the minor tick marks in the same place.
for y = 0,70,10 do
tex.print(string.format(cm5cmd, "black", y, y, y))
end

-- Draw the red major (5-cm) tick marks with the red labels. This paints over
-- the minor tick marks in the same place.
for y = 5, 65, 10 do
tex.print(string.format(cm5cmd, "red", y, y, y))
end

-- Draw the vertical line that ties it all together.
tex.print("\\draw [line width=2mm](0mm, 0mm) -- coordinate (y axis mid) (0mm,700mm);\n")
------------------------------ REVERSED RULERS
mmcmd = "\\draw [color=%s, line width=0.15mm] (60mm,%f) -- (70mm,%f) node[color=%s] {};\n"
cmcmd = "\\draw [color=%s, line width=1mm] (50mm,%f) -- (70mm,%f) node[color=%s,anchor=west] {%d};\n"
cm5cmd= "\\draw [color=%s, line width=2mm] (40mm,%f) -- (70mm,%f) node[fill=white, anchor=west] {\\Huge\\textbf{%d}};\n"

-- Draw the red millimeter tick marks. Reversed.
for y = 0.1, 69.9, 0.2 do     -- first, last, step
tex.print(string.format(mmcmd, "red", y, y, "red"))
end
--  Draw the black millimeter tick marks. Reversed.
for y = 0, 69.8, 0.2 do     -- first, last, step
tex.print(string.format(mmcmd, "black", y, y, "black"))    end

-- Draw the red minor(centimeter) tick marks with the red labels. Reversed.    for y = 1, 69, 2 do     -- first, last, step
tex.print(string.format(cmcmd, "red", y, y, "red", y))
end
-- Draw the black minor (centimeter) tick marks with the black labels. Reversed.
for y = 0, 68, 2 do     -- first, last, step
tex.print(string.format(cmcmd, "black", y, y, "black", y))
end

-- Draw the black major (5-cm) tick marks with the black labels. This paints over
-- the minor tick marks in the same place. Reversed.
for y = 0,70,10 do
tex.print(string.format(cm5cmd, "black", y, y, y))
end

-- Draw the red major (5-cm) tick marks with the red labels. This paints over
-- the minor tick marks in the same place. Reversed.
for y = 5, 65, 10 do
tex.print(string.format(cm5cmd, "red", y, y, y))
end

-- Draw the vertical line that ties it all together, reversed
tex.print("\\draw [line width=2mm](70mm, 0cm) -- coordinate (y axis mid) (70mm,700mm);\n")

-- %%%% Draw crop marks.
-- \draw [very thin, color=black, loosely dashed](-1, 0cm) -- coordinate
-- (y axis mid) (-1cm,71cm);
end

function DrawAllRules()  for i=0,11 do
tex.print(string.format("\\tikzset{xshift/.expanded=100mm}"))
DrawRule()  end
end

-
Hm... After writing this answer I realized that there is a much simpler one... Draw only a pair of rules in a external pdf, and then include it 12 times... blush –  JLDiaz Jun 29 at 21:22
Oh, wow. This works perfectly, but it will take me some time to wrap my head around the syntax. Thanks! –  Ricardo Jun 29 at 22:46