9

I'm trying to plot an illustration of the fixed point method in which a function is evaluated repeatedly in a loop. The result should be a staircase or spiral plot on top of the plot of the function itself. I've read here about the use of \edef inside a foreach loop. The following MWE should show what I'm trying to do, but it seems to only perform a single iteration.

Notes:

  • the loop variable isn't used inside the loop.
  • perhaps it's an illusion that only single iteration is performed and the real issue is that \xprev and \xnext aren not updated at each pass through the loop. I tried placing the updates inside the \edef, but that gives an undefined control sequence error.
  • I also tried \pgfplotsinvokeforeach instead of \foreach, but only obtained other errors.
\documentclass{standalone}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
\begin{document}    

  \pgfmathdeclarefunction{g}{1}{\pgfmathparse{#1^2 - 2}}

  \begin{tikzpicture}
    \begin{axis} [
        xmin = -2.5, xmax = 2.5, ymin = -3, ymax = 3,
        axis x line = center, axis y line = center,
        domain=-2.5:2.5, samples=300,
      ]

      \addplot[ultra thick] {g(x)};  % graph of g
      \addplot[thin] {x};            % diagonal

      \def\xstart{-0.75}
      \pgfmathsetmacro{\xprev}{\xstart};
      \pgfmathsetmacro{\xnext}{g(\xprev)};
      \draw[thick, blue] (\xstart, 0) -- (\xstart, \xnext) -- (\xnext, \xnext);

      \foreach \i in {1, 2, 3}{
        \pgfmathsetmacro{\xprev}{\xnext}
        \pgfmathsetmacro{\xnext}{g(\xprev)}  % x <- g(x)
        \edef\plotoneiter{%
          \noexpand%
          \draw[thick, blue] ({\xprev}, {\xprev}) -- ({\xprev}, {\xnext}) -- ({\xnext}, {\xnext});
        }\plotoneiter%
      }

    \end{axis}
  \end{tikzpicture}

\end{document}

Thanks in advance for any hints!

EDIT: Here is a quick sketch of what I'm trying to achieve:

enter image description here

0

5 Answers 5

9

Here is how one can construct a spiral converging to the intersection point (-1,-1) of the parabola and the linear function. Note that the inverse of the function y = x^2 - 2 is used here. The construction requires only pgfplots.

\documentclass[border=24pt]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}

\begin{document}

\begin{tikzpicture}[scale=0.75]
  \begin{axis}[
      xmin = -2.5, xmax = 2.5,
      ymin = -2.5, ymax = 2.9,
      axis x line = center,
      axis y line = center,
      domain = -2.5:2.5,
      samples = 300,
      width=10cm, height=10cm,
    ]

    % Plots
    \addplot[thick, red] {x^2 - 2};   % parabola g(x)
    \addplot[thin, black] {x};        % diagonal y = x

    % Spiral (fixed-point iteration visualization)
    \pgfplotsextra{
      \def\x{0.75}  % x0 — initial value on the x-axis
      \draw[thick, blue]  (axis cs:\x, 0) -- (axis cs:\x, \x);

      % Iterations
      \foreach \i in {1,...,6} {    
        % Step 2: horizontal line from (x, x) to (x_next, x),
        % where g(x_next) = x  ⇒  x_next = -sqrt(x + 2) (negative root!)
        \pgfmathsetmacro{\xnext}{-sqrt(\x + 2)}

        % Draw two segments:
        \draw[thick, blue]
          (axis cs:\x, \x) --           % A_{2k} (on the diagonal)
          (axis cs:\xnext, \x) --       % A_{2k+1} (on the parabola, same y-level)
          (axis cs:\xnext, \xnext);     % A_{2k+2} (back to the diagonal)

        % Update x for the next iteration
        \global\let\x=\xnext
      }
    }
  \end{axis}
\end{tikzpicture}

\end{document} 

enter image description here

Edit.
The question raised by @Explorer in the comments prompted me to present another version of the code that does not use \pgfplotsextra. This is, of course, possible; it just requires certain adjustments. Below is the code without \pgfplotsextra. It produces the same figure.

\documentclass[border=24pt]{standalone}    
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}

\begin{document}

\begin{tikzpicture}[scale=0.75]
  \begin{axis}[
      xmin = -2.5, xmax = 2.5,
      ymin = -2.5, ymax = 2.9,
      axis x line = center,
      axis y line = center,
      domain = -2.5:2.5,
      samples = 300,
      width=10cm, height=10cm,
    ]

    % Graphs
    \addplot[thick, red] {x^2 - 2};   % parabola y = x^2 - 2
    \addplot[thin, black] {x};        % diagonal y = x

    % Spiral construction without \pgfplotsextra
    \def\x{0.75}  % initial x0
    \foreach \i in {1,...,6} {
      \pgfmathsetmacro{\xnext}{-sqrt(\x + 2)}
      % Expand \x and \xnext to actual numbers before passing to \draw
      \edef\temp{%
        \noexpand\draw[thick, blue]
          (\x, \x) --
          (\xnext, \x) --
          (\xnext, \xnext);
      }\temp
      \global\let\x=\xnext
    }

  \end{axis}
\end{tikzpicture}

\end{document}
4
  • Could you explain what \pgfplotsextra behaved here? I could only find that at here, it was used for "low-level path commands"? Why here need this? Thx! Commented yesterday
  • 1
    @Explorer thank you for your comment. You are absolutely right: \pgfplotsextra is mandatory for low-level PGF path building. However, for ordinary \draw it’s optional—useful here only to sidestep macro scoping/timing issues, not because pgfplots needs it to draw. That said, it’s easy to do without it, which I’ve reflected in the revised version of my answer. Commented yesterday
  • Actually.... I just don't know that \pgfplotsextra could "useful here only to sidestep macro scoping/timing issues". Impressive answer, tupvoted! Commented yesterday
  • Thank you! Knowing when to use \def, \xdef, \let or \pgfmathsetmacro feels like black magic. The correct path should start from the horizontal axis and move vertically to the curve, not to the diagonal. There was an error in my first sketch that probably mixed you up. Nevertheless, based on your script, I was able to make it work. Commented yesterday
6

I explored a luadraw version:

In the manual, at Page.19, in Figure 4, luadraw provide function sequence to do similar things:

result

But it not so fit this case, for OP want to start the stairs actually at "y0", and calculate the "x0" with inverse function "y=-√x+2" here. A slight modification of sequence function as below:

\documentclass{standalone}
\usepackage{luadraw}
\usepackage[svgnames]{xcolor}
\usepackage{fourier}
\begin{document}
    \begin{luadraw}{name=fixed_point_sequence}
        -- https://github.com/pfradin/luadraw/blob/78597fd001bdbf7e9e93a7b539ba308cd1b1c19d/files/luadraw_lines.lua#L110-L122
        function invsequence(finv, u0, n)
        -- (finv(u0),u0), (finv(finv(u0)),finv(u0)),...
            if (finv == nil) or (u0 == nil) or (n == nil) then return end
            local y, L = u0, {Z(0,u0)}
            for k = 1, n do
                x = finv(y)
                if x == nil then return L end
                table.insert(L,Z(x,y)); table.insert(L,Z(x,x))
                y = x
            end
            return L
        end
        local g = graph:new{window={-2.5,3,-2.5,2.5},size={10,10}} 
        local i, pi, sqrt = cpx.I, math.pi, math.sqrt
        local f1 = function(x) return x^2-2-x end
        local f2 = function(x) return x^2-2 end
        local f2i = function(x) return -sqrt(x+2) end
        local ell = solve(f1,-2,0)[1]
        local L = invsequence(f2i,0.75,5)
        local seg, z = {}, L[1]
        for k = 2, #L do
            table.insert(seg,{z,L[k]})
            z = L[k]
        end 
        g:Writeln("\\tikzset{->-/.style={decoration={markings, mark=at position #1 with {\\arrow{Stealth}}},postaction={decorate}}}")
        g:Daxes({0,1,1}, {arrows="-Stealth"})
        g:DlineEq(1,-1,0,"line width=0.8pt,ForestGreen")
        g:Dcartesian(f2, {x={-2.5,2.5},draw_options="line width=1.2pt,Crimson"}) 
        g:Dpolyline(seg,false,"->-=0.65,blue")
        g:Dlabel("$y_0=.75$",.75*i,{pos="E",node_options="blue"})
        g:Dseg({ell, ell*(1+i)},1,"dashed,gray")
        g:Dlabel("$\\ell\\approx"..round(ell,3).."$", ell,{pos="N"})
        g:Ddots(ell*(1+i)); 
        g:Labelcolor("Crimson")
        g:Dlabel("$y=x^2-2$",Z(1.8,1),{pos="E"})
        g:Labelcolor("ForestGreen"); g:Labelangle(g:Arg(1+i)*180/pi)
        g:Dlabel("$y=x$",Z(0.4,0.4),{pos="S",dist=0.1}) 
        g:Show()
    \end{luadraw}
\end{document}

Edit:

Thanks to nidarfp's comment.

The more elegant approach is to make good use of sym, which could swap "(u_n,f(u_{n+1})) to "(f(u_{n+1}),u_n)".

% https://tex.stackexchange.com/questions/754447/iterated-plot-with-tikz-pgfplots-and-foreach-loop
\documentclass{standalone}
\usepackage{luadraw}
\usepackage[svgnames]{xcolor}
\usepackage{fourier}
\begin{document}
    \begin{luadraw}{name=fixed_point_sequence}
        local g = graph:new{window={-2.5,3,-2.5,2.5},size={10,10}} 
        local i, pi, sqrt = cpx.I, math.pi, math.sqrt
        local f1 = function(x) return x^2-2-x end
        local f2 = function(x) return x^2-2 end
        local f2i = function(x) return -sqrt(x+2) end
        local ell = solve(f1,-2,0)[1]
        local L = sym( sequence(f2i,0.75,5), {0,1+i} )
        local seg, z = {}, L[1]
        for k = 2, #L do
            table.insert(seg,{z,L[k]})
            z = L[k]
        end 
        g:Writeln("\\tikzset{->-/.style={decoration={markings, mark=at position #1 with {\\arrow{Stealth}}},postaction={decorate}}}")
        g:Daxes({0,1,1}, {arrows="-Stealth"})
        g:DlineEq(1,-1,0,"line width=0.8pt,ForestGreen")
        g:Dcartesian(f2, {x={-2.5,2.5},draw_options="line width=1.2pt,Crimson"}) 
        g:Dpolyline(seg,false,"->-=0.65,blue")
        g:Dlabel("$y_0=.75$",.75*i,{pos="E",node_options="blue"})
        g:Dseg({ell, ell*(1+i)},1,"dashed,gray")
        g:Dlabel("$\\ell\\approx"..round(ell,3).."$", ell,{pos="N"})
        g:Ddots(ell*(1+i)); 
        g:Labelcolor("Crimson")
        g:Dlabel("$y=x^2-2$",Z(1.8,1),{pos="E"})
        g:Labelcolor("ForestGreen"); g:Labelangle(g:Arg(1+i)*180/pi)
        g:Dlabel("$y=x$",Z(0.4,0.4),{pos="S",dist=0.1}) 
        g:Show()
    \end{luadraw}
\end{document}

Both gives the following result:

result

2
  • 1
    You didn't have to write a new function by doing L = sym( sequence(f2i,0.5,5), {0,1+i} ). Commented yesterday
  • Good idea, I have tried something like xscale=-1 and yscale=-1, but I didn't succeed. I would edit later. Commented yesterday
5

Let me add another (!) answer using exactly the same algorithm as the OP (which I am not sure I understand what it does — but well).

My strategy here is to create a list of coordinates outside the axis environment (which can be quite a horror when combined with foreach...) and then use it directly in an \addplot ... coordinates instance.

\documentclass{article}
\usepackage{tikz}
\usepackage{pgfplots}
\pgfplotsset{compat=1.18}
 \usetikzlibrary {decorations.markings, arrows.meta}
\usepackage{etoolbox}
\begin{document}

\pgfmathdeclarefunction{g}{1}{\pgfmathparse{(#1)^2 - 2}}

\pgfmathsetmacro{\xstart}{-0.7}
\pgfmathsetmacro{\xnext}{g(\xstart)}
\xdef\mycoordlist{(\xstart,0) (\xstart,\xnext) (\xnext,\xnext)}
% debug data:
{0: \mycoordlist\par}
% create a coordinate list
\pgfplotsforeachungrouped \i in {1,...,4}{
    \pgfmathsetmacro{\xprev}{\xnext}
    \pgfmathsetmacro{\xnext}{g(\xprev)}
    \gdef\thisstep{(\xprev,\xprev) (\xprev,\xnext) (\xnext,\xnext)}
    % debug data...
    {\i: \thisstep\par}
    \edef\tmp{\noexpand\gappto{\noexpand\mycoordlist}{\thisstep}}\tmp
}

\begin{tikzpicture}[mark with arrow/.style={decoration={
    markings,% switch on markings
    mark=between positions 1/#1  and 1 step 1/#1 with {\arrow[blue]{Straight Barb}},
    }}
    ]
    \begin{axis} [
        xmin = -2.5, xmax = 2.5, ymin = -3, ymax = 3,
        axis x line = center, axis y line = center,
        domain=-2.5:2.5, samples=300,
        ]

        \addplot[ultra thick] {g(x)};  % graph of g
        \addplot[thin] {x};            % diagonal
        \addplot[blue, thick,
            mark with arrow=16, postaction={decorate}
        ] coordinates{\mycoordlist};
    \end{axis}
\end{tikzpicture}

\end{document}

a parabola and a lot of lines with arrows

Notice that it seems that the parentheses around #1 in (#1)^2 are needed.

A full expl3 solution for creating the list (and the function) would probably be much better...

Also, you can notice that the last point of every triade is repeated, so you really can omit it:

...
\xdef\mycoordlist{(\xstart,0) (\xstart,\xnext) }
...
    \gdef\thisstep{(\xprev,\xprev) (\xprev,\xnext) }
...
2
  • 1
    \pgfplotsforeachungrouped is a good approach to avoid \global\let...😀 Commented yesterday
  • Thank you! I like this solution because the tikzpicture part is generic and could be used to plot almost any iteration, not just the fixed point. Commented yesterday
3

While you wait for a TikZ answer, I’ll leave here an implementation with ConTeXt and MetaFun.

\starttext
  \startMPcode
    vardef sgn(expr x) = if x > 0:  1 elseif x < 0: -1 else: 0 fi enddef;
    path Line, Curve, Ray, Polygonal;
    pair IntersectionPoint, CurrentIntersectionPoint;

    u := 1.5cm;
    RayLength := 50cm;
    Curve := lmt_samplefunction[xmin = -2, xmax = 2, ymin = -1, ymax = 1, code = "x^2 - 1", tolerance = 0.00001];
    Line := ((-1.5,-1) -- (1.5,1));

    IntersectionPoint := Curve intersectionpoint Line;
    CurrentIntersectionPoint := point 0.7 of Line;
    Polygonal := CurrentIntersectionPoint;

    for i = 1 upto 20:
      if (i mod 2) = 1:
        Ray := CurrentIntersectionPoint -- xrelative (sgn(xpart IntersectionPoint - xpart CurrentIntersectionPoint) * RayLength);
        CurrentIntersectionPoint := Ray intersectionpoint Curve;
      else:
        Ray := CurrentIntersectionPoint -- yrelative (sgn(ypart IntersectionPoint - ypart CurrentIntersectionPoint) * RayLength);
        CurrentIntersectionPoint := Ray intersectionpoint Line;
      fi;
      Polygonal := Polygonal -- CurrentIntersectionPoint;
    endfor;


    draw Curve scaled u withpen pencircle scaled 1.25pt withcolor darkred;
    draw Line scaled u withpen pencircle scaled 1.25pt withcolor darkgreen;

    drawarrow ((-1.5,0) -- (1.5,0)) scaled u;
    drawarrow ((0,-1.5) -- (0,1.5)) scaled u;
    draw Polygonal scaled u withpen pencircle scaled 0.5pt withcolor darkblue;
  \stopMPcode
\stoptext

enter image description here

or with

autoarrows := true ;
for i = 0 upto length(Polygonal) - 1:
  drawarrow subpath(i, i+1) of Polygonal scaled u cutends 2pt withpen pencircle scaled 0.5pt withcolor darkblue;
endfor;

enter image description here

1
  • 1
    Thank you. I'm always very impressed with ConTeXt and MetaPost answers, but I've no idea how to use them. Commented 2 days ago
3

PSTricks has psFixpoint but its behavior is a bit different. It starts from the curve of f(x) rather than y=x.

\documentclass[pstricks,12pt,border=12pt]{standalone}
\usepackage{pst-plot}
\begin{document}
\begin{pspicture}[algebraic](-3,-3)(3,5)
    \psaxes[linecolor=gray!20]{->}(0,0)(-3,-3)(3,5)[$x$,0][$y$,45]
    \psplot[linecolor=blue,linewidth=2pt]{-2.5}{2.5}{x^2-2}
    \psplot[linewidth=2pt]{-2.5}{2.5}{x}
    \psFixpoint[linecolor=red,linewidth=0.5pt,linestyle=dashed]{0.75}{x^2-2}{6}
\end{pspicture}
\end{document}

Output

For those who know how to change the behavior, feel free to edit this answer.

1
  • Starting from the curve is indeed the correct behavior. The error was in my initial sketch, which I fixed a little bit later. Sorry about that. Commented yesterday

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.