3

I want to generate a random geometric graph in TikZ. A certain amount of nodes are thrown randomly in a square, and each pair is connected if their distance is at most some fixed number r. I am able to generate the random nodes, but I'm having troubles connecting them.

Here's a code that generates the nodes:

\begin{tikzpicture}
\foreach \x in {1,...,300}{
    % generate random position
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    % draw and label
    \node (\x) at (\a*0.01,\b*0.01) {};
    \draw[fill] (\x) circle (1pt);      
};
\end{tikzpicture}

Which gives the following:

enter image description here

Now, say that I want each pair with distance <0.1 connected by a segment. How to do that? I tried playing around with some "if" statements but I can't get the syntax right.

2
  • 2
    Though possible, I would advice to do all the calculations outside of TeX, then export a list of coordinates and coordinate pairs in tikz grammar. Jul 31 at 7:59
  • Are you set on using 0.1 in the xyz coordinate system, i.e. without any unit, the same way you place the coordinates? (In this CS, 1 unit is equal to 1cm unless specified otherwise.) Jul 31 at 15:13

2 Answers 2

5

With 300 coordinates, this will take ages to compile, but theoretically, you can do something like this (I used only 50 coordinates, to illustrate the idea):

\documentclass[border=10pt]{standalone}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}
\foreach \x in {1,...,50}{
    % generate random position
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    % draw and label
    \coordinate (n\x) at (\a*0.01,\b*0.01);
    \draw[fill] (n\x) circle (1pt);      
};

\foreach \a in {1,...,50}{
    \path (n\a);
    \pgfgetlastxy{\ax}{\ay}
    \foreach \b in {\a,...,50}{
        \ifnum\a=\b\else
            \path (n\b);
            \pgfgetlastxy{\bx}{\by}
            \pgfmathtruncatemacro{\dist}{veclen((\bx - \ax),(\by - \ay))}
            \ifdim\dist pt<50pt
                \draw[green] (n\a) -- (n\b);
            \fi
        \fi
    }
}

\end{tikzpicture}

\end{document}

enter image description here


Thanks to Qrrbrbirlbel's comments, I modified the original code to make it compile faster, but it will still take some time to compile (it took Overleaf about 55 seconds for 300 nodes).

  1. Include the second into the first \foreach loop. It is sufficient to test the distances to all nodes that exist until this point. The less loops we have, the quicker the code will compile.
  2. Before calculating the veclen, test if the horizontal and vertical distance between the two nodes is already too large. Since the calculation of veclen takes some time, this will speed up the code.

I also added two macros to simplify adjustment of node count and maximum distance of the lines to draw.

\documentclass[border=10pt]{standalone}
\usepackage{tikz}

\begin{document}

\newcounter{nodecount}
\setcounter{nodecount}{300}

\newlength\maxdistance
\setlength\maxdistance{20pt}

\begin{tikzpicture}
\foreach \i in {1,...,\value{nodecount}}{
    % generate random position
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    % draw and label
    \coordinate (n\i) at (\a*0.01,\b*0.01);
    \draw[fill] (n\i) circle (1pt);      

    % check distances to all other existing nodes
    \foreach \j in {1,...,\i}{
        % do not draw line if nodes are identical
        \ifnum\i=\j\else                                 
            \path (n\i);
            \pgfgetlastxy{\ix}{\iy}
            \path (n\j);
            \pgfgetlastxy{\jx}{\jy}
            \pgfmathtruncatemacro{\distx}{\ix - \jx}
            \pgfmathtruncatemacro{\disty}{\iy - \jy}
            % only draw line if x distance of both nodes is not too large
            \ifdim\distx pt<\maxdistance
                % only draw line if y distance of both nodes is not too large
                \ifdim\disty pt<\maxdistance             
                    \pgfmathtruncatemacro{\distxy}{veclen((\distx),(\disty))}
                    % only draw line if distance of both nodes is not too large
                    \ifdim\distxy pt<\maxdistance 
                        \draw[green] (n\i) -- (n\j);
                    \fi
                \fi
            \fi
        \fi
    }
}

\end{tikzpicture}

\end{document}

enter image description here


Since TeX is not made for such complicated and repeated calculations, I would suggest that you use another programming language that is more capable of such computations. For example, you can use LuaLaTeX to compile the following code (takes about 2 seconds to compile):

\documentclass[border=10pt]{standalone}
\usepackage{tikz}

\begin{document}

\begin{tikzpicture}

\directlua{

n = 300

d = 1

x = {}
y = {}

for i = 1,n do

    a = math.random(-490,490)
    b = math.random(-490,490)
    
    x[i] = a*0.01
    y[i] = b*0.01

    tex.print('\\coordinate(' .. i .. ') at (' .. x[i] ..',' .. y[i] .. ');') 
    tex.print('\\draw[fill] (' .. i .. ') circle (1pt);') 

    for j = 1,i do
        t = math.sqrt((x[i] - x[j])^2 + (y[i] - y[j])^2)
        if t > 0 and t < d then
            tex.print('\\draw[green] (' .. i .. ') -- (' .. j .. ');') 
        end
    end
    
end

}

\end{tikzpicture}

\end{document}
2
  • For a Lua novice, this is finally an answer where I actually understand the Lua code. (Sure, it's simple math but I'll take it.) Jul 31 at 17:28
  • Actually, it is my first Lua code answer here on TeX.SX! So, it might not be the most elegant code ... Jul 31 at 17:29
3

I've got three solutions for you:

  1. uses PGF and TeX macros to test against the length
  2. uses TikZ' calc library.
  3. uses both the calc and the math library.

The first one is faster by a factor of about 3.5. The solutions 2 and 3 take about the same time. (The math is always the same, of course.)

The first one also checks whether it actually needs to do the veclen calculation because if the difference in x direction or in y direction already exceeds the demanded length (given via the test length value key), there's no need to do (for TeX) harder calculations.
It also uses \pgfmathveclen@ instead of \pgfmathveclen because the parameters are already evaluated and can then be proccessed by veclen directly.

Code 1 (PGF)

\documentclass[tikz]{standalone}
\tikzset{
  test length/.initial=5mm,
  test previous/.code 2 args={%
      \pgfpointdiff{\pgfpointanchor{dot-#1}{center}}
                   {\pgfpointanchor{dot-#2}{center}}%
      \pgfgetlastxy{\lastX}{\lastY}%
      \ifdim\lastX>\pgfkeysvalueof{/tikz/test length}\relax\else
        \ifdim\lastY>\pgfkeysvalueof{/tikz/test length}\relax\else
          \csname pgfmathveclen@\endcsname{\lastX}{\lastY}%
          \ifdim\pgfmathresult pt<\pgfkeysvalueof{/tikz/test length}\relax
            \tikzset{insert path=edge(dot-#2)}%
          \fi
        \fi
      \fi}}
\begin{document}
\pgfmathsetseed{652524}
\begin{tikzpicture}
\foreach \x in {1,...,300}{
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    \node[circle, fill, inner sep=+0pt] (dot-\x) at (\a*.01, \b*.01) {}
      foreach \y in {1,...,\x} { [test previous = {\x}{\y}] };}
\end{tikzpicture}
\end{document}

Code 2 (calc)

\documentclass[tikz]{standalone}
\usetikzlibrary{calc}
\makeatletter
\pgfkeys{/utils/if/.code n args={3}{%
  \pgfmathparse{#1}\ifdim\pgfmathresult pt=0pt\relax
    \expandafter\pgfutil@firstoftwo\else\expandafter\pgfutil@secondoftwo\fi
    {\pgfkeysalso{#3}}{\pgfkeysalso{#2}}}}
\makeatother
\tikzset{
  test length/.initial=5mm,
  test previous/.style 2 args={%
    insert path={let \p{diff} = ($(dot-#1)-(dot-#2)$) in},
    /utils/if = {veclen(\p{diff}) < \pgfkeysvalueof{/tikz/test length}}
                {insert path={(dot-#1)edge(dot-#2)}}}}
\begin{document}
\pgfmathsetseed{652524}
\begin{tikzpicture}
\foreach \x in {1,...,100}{
    \pgfmathrandominteger{\a}{-490}{490}
    \pgfmathrandominteger{\b}{-490}{490}
    \node[circle, fill, inner sep=+0pt] (dot-\x) at (\a*.01, \b*.01) {}
      foreach \y in {1,...,\x} { [test previous/.expanded = {\x}{\y}] };}
\end{tikzpicture}
\end{document}

Code 3 (\tikzmath)

\documentclass[tikz]{standalone}
\usetikzlibrary{calc,math}
\tikzset{test length/.initial=5mm}
\begin{document}
\pgfmathsetseed{652524}
\begin{tikzpicture}
\foreach \x in {1,...,300}{
  \pgfmathrandominteger{\a}{-490}{490}
  \pgfmathrandominteger{\b}{-490}{490}
  \node[circle, fill, inner sep=+0pt] (dot-\x) at (\a*.01, \b*.01) {};
  \tikzmath{
    coordinate \diff; int \y;
    for \y in {1,...,\x} {
      \diff = (dot-\x)-(dot-\y);
      if veclen(\diffx, \diffy) < \pgfkeysvalueof{/tikz/test length} then {
        {\draw (dot-\x)--(dot-\y);};
      };
    };
  }
}
\end{tikzpicture}
\end{document}

Output

enter image description here

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.