1

I am trying to use TikZ combined with the pythontex and cancel package to draw a (clip of the) plane of the rational numbers where all the rational numbers with a gcd not equal to 1 are crossed out.

This is my code so far:

\documentclass{article}
\usepackage{tikz}
\usepackage[makeroom]{cancel}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{pythontex}
\usetikzlibrary{intersections,positioning,arrows.meta}

\begin{pycode}
def ggt(a,b):
    r=a%b
    i=0
    erg=1
    while r:
        a=b
        b=r
        r=a%b
    return b
\end{pycode}

\begin{document}

\begin{tikzpicture}

    \draw[-Stealth] (0,-.5) -- (0,6) node [above] {$n\in \mathbb{N}$};
    \draw[-Stealth] (-5.5,0) -- (5.5,0) node [right] {$m\in\mathbb{Z}$};
    
    \foreach \m in {-4,...,4}
        \foreach \n in {1,...,5}{
            \ifnum\py{ggt(\m,\n)}=1 {
            
                \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};}
            \else {
                
                \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
            }\fi
        }
    
\end{tikzpicture}

\end{document}

The Problem: "! Missing number, treated as zero." I think, this is caused in the \ifnum statement, where I call \py{ggt(\m,\n)}, which apparently is not treated as a number.

My Question: Is there a way which allows LaTeX to read the return value of my python method as a "number"? Or is there any approach without using pythontex at all? Any help is much appreciated! Also, I'm still pretty new to LaTeX, so I apologize if I made any obvious or silly mistakes.

3
  • Welcome to TeX.SX! As far as I know, PythonTeX requires three compilations steps, the second of which will replace the Python-related macros in your documetn with the results parsed with Python. So, you somehow need to fix the part with the \ifnum clause, in such a way that it can work without Python in the first run. It is fine if the result is wrong at first, since this will be corrected in the third compilation run. Try to define a macro which you initially set to zero and which then is set to ggt(\m,\n) via Python. In the first run, \ifnum sees zero which won't break the code. Jul 7, 2022 at 18:56
  • On a second thought: You feed the Python function with TeX macros using a \foreach loop, which can not succeed because of the way the three-step compilation works. At least not to my understanding. Jul 7, 2022 at 19:17
  • 1
    With your second comment, you mean that the option with setting a macro to 0 at first run, then set it to ggt(\m,\n) won't work? Honestly, I wouldn't know how to do that too. Your answer however answered my question just perfectly, thank you so much! Jul 7, 2022 at 23:55

3 Answers 3

3

Since you also asked for alternative approaches:

You already use the tikz package that is able to do a lot of math calculations. So, you can just use the PGF function gcd(). You can invoke the relevant calculation with \pgfmathparse{gcd(\m,\n)} and the result is then stored in a macro called \pgfmathresult which you can use in your \if clause:

\documentclass{article}
\usepackage{tikz}
\usepackage[makeroom]{cancel}
\usepackage{amsmath,amsthm,amssymb}
\usetikzlibrary{intersections,positioning,arrows.meta}

\begin{document}

\begin{tikzpicture}

    \draw[-Stealth] (0,-.5) -- (0,6) node [above] {$n\in \mathbb{N}$};
    \draw[-Stealth] (-5.5,0) -- (5.5,0) node [right] {$m\in\mathbb{Z}$};
    
    \foreach \m in {-4,...,4}
        \foreach \n in {1,...,5}{
            \pgfmathparse{gcd(\m,\n)}
            \ifnum\pgfmathresult=1 {
            
                \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};}
            \else {
                
                \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
            }\fi
        }
    
\end{tikzpicture}

\end{document}

There is also a package named calculator which provides a macro \GCD that allows you to calculate the greatest common divisor on the fly and store it in a separate macro which you can then in the \if clause, like so:

\documentclass{article}
\usepackage{tikz}
\usepackage[makeroom]{cancel}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{calculator}
\usetikzlibrary{intersections,positioning,arrows.meta}

\begin{document}

\begin{tikzpicture}

    \draw[-Stealth] (0,-.5) -- (0,6) node [above] {$n\in \mathbb{N}$};
    \draw[-Stealth] (-5.5,0) -- (5.5,0) node [right] {$m\in\mathbb{Z}$};
    
    \foreach \m in {-4,...,4}
        \foreach \n in {1,...,5}{
            \GCD{\m}{\n}{\thisgcd}
            \ifnum\thisgcd=1 {
            
                \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};}
            \else {
                
                \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
            }\fi
        }
    
\end{tikzpicture}

\end{document}

Also, you can perform such calculations with Lua. I found a script to calculate the greatest common divisor with Lua in this nice answer. Note that you need to compile your document with LuaLaTeX instead of PDFLaTeX for this to work:

\documentclass{article}
\usepackage{tikz}
\usepackage[makeroom]{cancel}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{luacode}
\usetikzlibrary{intersections,positioning,arrows.meta}

\begin{luacode}
function gcd(a,b)
    if b ~= 0 then
        return gcd(b, a % b)
    else
        return math.abs(a)
    end
end
\end{luacode}

\newcommand\findgcd[1]{\directlua{tex.sprint(gcd(#1))}}

\begin{document}

\begin{tikzpicture}

    \draw[-Stealth] (0,-.5) -- (0,6) node [above] {$n\in \mathbb{N}$};
    \draw[-Stealth] (-5.5,0) -- (5.5,0) node [right] {$m\in\mathbb{Z}$};
    
    \foreach \m in {-4,...,4}
        \foreach \n in {1,...,5}{
            \ifnum\findgcd{\m,\n}=1 {
            
                \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};}
            \else {
                
                \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
            }\fi
        }
    
\end{tikzpicture}

\end{document}

All the above approaches will output the following:

enter image description here

1

Two problems here. (both requires understanding TeX to understand the fix.)

\py does not expand its arguments.

(type 3 of my answer on nesting macro. See also how to pass section numbers to python in pythontex? - TeX - LaTeX Stack Exchange -- there's no completely automatic solution without learning TeX programming yet.)

One solution here is to use \expanded (see the one inserted in the code below.) Alternative including edef, ExpandArgs, exp_args:Nx etc. (also see the linked questions from the answer linked above)

Warning: \expanded might not do what you want every time. Make sure you understand what the code does in case you want to modify it.

\py is not expandable.

(type 2 of my answer above.)

This issue is documented in the pythontex manual.

(I include a screenshot here. If you want to read the text, read the manual.)

pythontex documentation screenshot

One workaround is to follow that, and define a Python function that prints out the whole outer thing.

\documentclass{article}
\usepackage{tikz}
\usepackage[makeroom]{cancel}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{pythontex}
\usetikzlibrary{intersections,positioning,arrows.meta}

\begin{pycode}
def ggt(a,b):
    r=a%b
    i=0
    erg=1
    while r:
        a=b
        b=r
        r=a%b
    return b

def print_my_command(m, n):
    print(
            r'\ifnum' + str(ggt(m, n)) + r'''=1 {
            
                \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};}
            \else {
                
                \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
            }\fi'''
            )

\end{pycode}

\begin{document}

\begin{tikzpicture}

    \draw[-Stealth] (0,-.5) -- (0,6) node [above] {$n\in \mathbb{N}$};
    \draw[-Stealth] (-5.5,0) -- (5.5,0) node [right] {$m\in\mathbb{Z}$};
    
    \foreach \m in {-4,...,4}
        \foreach \n in {1,...,5}{
            \expanded{\pyc{print_my_command(\m,\n)}}
        }
    
\end{tikzpicture}

\end{document}

Although if you do this you might as well check in Python. Replace the print_my_command with

def print_my_command(m, n):
    if ggt(m, n)==1:
        print(r""" \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$}; """)
    else:
        print(r""" \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$}; """)

you can also replace the \m with the value of m on the Python side.


Another workaround, this one requires more TeX knowledge, is to use temporary variable as explained in my answer linked above.

\documentclass{article}
\usepackage{tikz}
\usepackage[makeroom]{cancel}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{pythontex}
\usetikzlibrary{intersections,positioning,arrows.meta}

\begin{pycode}
def ggt(a,b):
    r=a%b
    i=0
    erg=1
    while r:
        a=b
        b=r
        r=a%b
    return b

def my_set_ggt(m, n):
    print(r"\def\mytmpggt{" + str(ggt(m, n)) + "}")
\end{pycode}

\begin{document}

\begin{tikzpicture}

    \draw[-Stealth] (0,-.5) -- (0,6) node [above] {$n\in \mathbb{N}$};
    \draw[-Stealth] (-5.5,0) -- (5.5,0) node [right] {$m\in\mathbb{Z}$};
    
    \foreach \m in {-4,...,4}
        \foreach \n in {1,...,5}{
            \def\mytmpggt{0}
            \expanded{\pyc{my_set_ggt(\m,\n)}}
            \ifnum\mytmpggt=1 {
            
                \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};}
            \else {
                
                \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
            }\fi
        }
    
\end{tikzpicture}

\end{document}

Side note, the braces in \ifnum does not do what you think it does. Read the TeXbook (or TeX by Topic, TeX in a nutshell) if you want to learn.

1

Since you asked for alternative approaches:

You can replace pythontex with pyluatex.
You'll have to compile with LuaLaTeX: lualatex -shell-escape <yourfile.tex>

\documentclass{article}
\usepackage{tikz}
\usepackage[makeroom]{cancel}
\usepackage{amsmath,amsthm,amssymb}
\usepackage{pyluatex}                % <--- changed
\usetikzlibrary{intersections,positioning,arrows.meta}

\begin{python}                       % <--- changed
def ggt(a,b):
    r=a%b
    i=0
    erg=1
    while r:
        a=b
        b=r
        r=a%b
    return b
\end{python}                         % <--- changed

\begin{document}

\begin{tikzpicture}

    \draw[-Stealth] (0,-.5) -- (0,6) node [above] {$n\in \mathbb{N}$};
    \draw[-Stealth] (-5.5,0) -- (5.5,0) node [right] {$m\in\mathbb{Z}$};
    
    \foreach \m in {-4,...,4}
        \foreach \n in {1,...,5}{
            \ifnum\py{ggt(\m,\n)}=1 {
            
                \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};}
            \else {
                
                \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
            }\fi
        }
    
\end{tikzpicture}

\end{document}

You could then even work with the \pyif command:

\pyif{ggt(\m,\n) == 1}{
    \node (\m-\n) at (\m,\n) [circle, draw, inner sep=0, minimum size=0.6 cm, fill=white] {$\frac{\m}{\n}$};
}{
    \node (\m-\n) at (\m,\n) [circle, inner sep=0, minimum size=0.6 cm, fill=white] {$\xcancel{\frac{\m}{\n}}$};
}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .