6
\documentclass{standalone}
\usepackage{tikz}

\begin{document}
\begin{tikzpicture}
    \draw (-1,0) -- (5,0);
    \draw (0,-1) -- (0,3);
    \draw[red, thick, samples=100] plot[domain=0.35:4.2]
    (\x, {0.6*cos((4.5*(\x-4)+2.1) r)-1.2*sin((\x-4) r)+0.1*\x+0.2});
\end{tikzpicture}
\end{document}

enter image description here

Black Mild
  • 17,569
Fotis K
  • 195

3 Answers3

10

Without digging too deep into the underlying code of TikZ, you could do the following (but note that the aim of this solution is rather to mark the maximum on the path and not to calculate its exact coordinate for which other software is better suited than TeX):

  1. Name the path whose maximum you are looking for using name path global and place it into a scope to define a local bounding box.
  2. Draw a path from the upper left to the upper right corner of this bounding box and name this path. (To find the minimum, draw the path from the lower left to the lower right corner.)
  3. Find the intersection of the two named paths (and get the coordinates).

(I only later found that Henri Menke also used this approach to come up with a nice alternative solution.)

Full example:

\documentclass{standalone}
\usepackage{tikz}
\usetikzlibrary{intersections, calc}

\begin{document} \begin{tikzpicture}

\draw (-1,0) -- (5,0);
\draw (0,-1) -- (0,3);

\begin{scope}[local bounding box=myplotbox]
    \draw[name path global=myplot, red, thick, samples=100] plot [domain=0.35:4.2]
        (\x, {0.6*cos((4.5*(\x-4)+2.1) r)-1.2*sin((\x-4) r)+0.1*\x+0.2});
\end{scope}

\path[name path=myplotmax] (myplotbox.north west) -- (myplotbox.north east);
\fill[name intersections={of=myplot and myplotmax, by={mymax}}]
    let \p1=(mymax) in (mymax) circle (2pt) node[above] {\x1, \y1};

\end{tikzpicture} \end{document}

enter image description here


Edit:

From this, you can create a custom macro that can simplify things. It automatically creates two coordinates plot X max and plot X min where you can later attach things to.

\documentclass[tikz, border=10pt]{standalone}
\usetikzlibrary{intersections, calc}

\NewDocumentCommand{\plotwithminmax}{ m O{} O{} m }{ \begin{scope}[local bounding box=plotbox #1] \draw[name path global=plot #1, #2] plot [#3] #4; \end{scope}

\path[name path=plot #1 maxline] 
    (plotbox #1.north west) -- (plotbox #1.north east);
\path[name intersections={of={plot #1} and {plot #1 maxline},     
    by={plot #1 max}}];

\path[name path=plot #1 minline] 
    (plotbox #1.south west) -- (plotbox #1.south east);
\path[name intersections={of={plot #1} and {plot #1 minline},     
    by={plot #1 min}}];

}

\begin{document} \begin{tikzpicture}

\draw (-1,0) -- (5,0);
\draw (0,-1) -- (0,3);

\plotwithminmax{A}[thick, red, samples=100][domain=0.35:4.2]{
    (\x, {0.6*cos((4.5*(\x-4)+2.1) r)-1.2*sin((\x-4) r)+0.1*\x+0.2}) 
}
\fill let \p1=(plot A max) in 
    (plot A max) circle (2pt) node[above] {\x1, \y1};

\end{tikzpicture}

\begin{tikzpicture}

\draw (-1,0) -- (5,0);
\draw (0,-1) -- (0,3);

\plotwithminmax{B}[thick, dotted, samples=100][domain=-0.35:3.2]{ 
    (\x, {0.6*cos((4.5*(\x-4)+2.1) r)-1.2*sin((\x-4) r)+0.1*\x+0.2}) 
}
\node[draw=blue, label={below:min}] at (plot B min) {};
\node[draw=blue, label={above:max}] at (plot B max) {};

\draw[orange] (plot B min) -| (plot B max); 

\end{tikzpicture} \end{document}

enter image description here



Edit (maybe rather a separate answer)

You could also use Lua to compute the maxima and minima. As the other answers already gave nice approaches about how to to this computationally, I tought that adding this to the above answer could maybe improve it. Since Lua is part of LuaLaTeX, you can run the code below without the need to use any other additional programming languages.

This is, however, only a rough approach. A major drawback is that you would need to insert the plot function twice. Maybe someone wants to improve this approach in this regard.

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

\tikzset{ max/.style={ circle, inner sep=2pt, fill=red }, min/.style={ circle, inner sep=2pt, fill=blue } }

\begin{document}

\begin{tikzpicture} \draw (-1,0) -- (5,0); \draw (0,-1) -- (0,3); \draw[red, thick, samples=100] plot[domain=0.35:4.2] (\x, {0.6cos((4.5(\x-4)+2.1) r)-1.2sin((\x-4) r)+0.1\x+0.2});

\begin{luacode}

function f(x) return 0.6math.cos(4.5(x-4)+2.1)-1.2math.sin(x-4)+0.1x+0.2 end

a = 0 b = 4

xcoords = {} for i = a, b, .01 do xcoords[#xcoords+1] = i end

localmin = {} localmax = {}

for i = 2, (#xcoords-2) do if (f(xcoords[i-1]) < f(xcoords[i]) and f(xcoords[i]) > f(xcoords[i+1])) then localmax[#localmax+1] = {xcoords[i], f(xcoords[i])} end if (f(xcoords[i-1]) > f(xcoords[i]) and f(xcoords[i]) < f(xcoords[i+1])) then localmin[#localmin+1] = {xcoords[i], f(xcoords[i])} end end

for i = 1, #localmax do tex.print('\node[max] at (' .. localmax[i][1] ..',' .. localmax[i][2] .. ') {};') end

for i = 1, #localmin do tex.print('\node[min] at (' .. localmin[i][1] ..',' .. localmin[i][2] .. ') {};') end

\end{luacode}

\end{tikzpicture}

\end{document}

enter image description here

  • Thank you, but I have to do this many times for each point and in this case I prefer to find the coordinates from another program e.g. geogebra. – Fotis K Aug 10 '22 at 20:59
  • +1 For me there is some compilation error - works with \fill ... circle[radius=2pt] ... – hpekristiansen Aug 10 '22 at 21:02
  • @hpekristiansen Yes, I also just discovered this. Changed already. Thanks! – Jasper Habicht Aug 10 '22 at 21:11
  • @FotisK You can always create custom macros to simplify repetitive things ... – Jasper Habicht Aug 10 '22 at 21:15
  • 1
    @FotisK you'd better explain your needs more clearly. If you described what you're gonna do with x or y coordinate, the answer could be more satisfying. – antshar Aug 10 '22 at 22:14
  • 1
    I need to find the coordinates of all local vertices and use them for nodes, lines... that will depend on these coordinates. @JasperHabicht's answer is very enlightening, but I think there is a very small deviation from the actual values, maybe not. – Fotis K Aug 11 '22 at 00:59
  • for the case of several global maximal point, e.g. sin(x), this solution still need to be improved. I know this limitation is from LaTeX's numerical accuracy – Black Mild Aug 11 '22 at 06:46
  • @BlackMild Yes, this solution can only get the first maximum or minumum, and it is more suited to mark this point on the graph. It is not really made for exactly calculating the maxima or minima of the plot. But I think, TeX due to its limited accuracy is not the right language to do such calculations anyways. – Jasper Habicht Aug 11 '22 at 06:48
  • @antshar Thank you for hinting to this answer which uses essentially the same approach. – Jasper Habicht Aug 11 '22 at 08:23
7

Making use the high accuracy of Asymptote, I program for automatically finding all local maximal/minimal points on the graph of given function. The idea/algorithm is direct and simple: we divide the graph into small pieces by the array of points y[n], then compare y[k] > y[k-1] and y[k] > y[k+1], etc.. The local maximal points are dotted with red. The local minimal points are dotted with blue.

Notice that in this critical (in any sense) case, we can not use the built-in path routines mintimes, maxtimes, times.

enter image description here

// http://asymptote.ualberta.ca/
// Find all local maximal.minimal points
unitsize(1cm);
import graph;
real f(real x){return (.6*cos(4.5*(x-4)+2.1)-1.2*sin(x-4)+.1*x+.2);}

//real f(real x){return .5+sin(3x);} real xmin=-2; real xmax=10.2; xaxis("$x$",xmin-.5,xmax+.5); yaxis("$y$"); guide p=graph(f,xmin,xmax,Spline); draw(p,darkgreen+.6pt); int n=200; real h=(xmax-xmin)/n; real[] x,y; for (int k=0; k<n; ++k) { x.push(xmin+hk);
y.push(f(xmin+h
k)); } //write(x,y);

for (int k=2; k<n-2; ++k){ if ( (y[k] > y[k-1]) & (y[k] > y[k+1]) ) dot( (x[k],y[k]),red); if ( (y[k] < y[k-1]) & (y[k] < y[k+1]) ) dot( (x[k],y[k]),blue); }

shipout(bbox(5mm,invisible));

For f(x)=.5+sin(3x), we get

enter image description here

Even more, this procedure can be used to find all intersection point of the graph with the horizontal line through a given point A. In other words, more or less does the job for the following routines

enter image description here

enter image description here

// http://asymptote.ualberta.ca/
// Find all intersection points with horizontal line through given point A
unitsize(1cm);
import graph;
real f(real x){return .5+sin(3x);}
real xmin=-2;
real xmax=10.2;
xaxis("$x$",xmin-.5,xmax+.5);
yaxis("$y$");
guide p=graph(f,xmin,xmax,Spline);
draw(p,darkgreen+.6pt); 
pair A=(1,.6);
draw((xmin,A.y)--(xmax,A.y),blue);

int n=5000; real h=(xmax-xmin)/n; real[] x,y; for (int k=0; k<n; ++k) { x.push(xmin+hk);
y.push(f(xmin+h
k)); }

for (int k=2; k<n-2; ++k) if ( (y[k]-A.y)*(y[k+1]-A.y)<0) dot( ((x[k]+x[k])/2,A.y),red);

shipout(bbox(5mm,invisible));

Black Mild
  • 17,569
5

There's a quick and easy approach using the sagetex package. It gives you access to a CAS, called Sage and the Python programming language. Copy/paste the code below into the Sage Cell Server.

f = 0.6*cos((4.5*(x-4)+2.1))-1.2*sin((x-4))+0.1*x+0.2
a = 0.35
b = 4.2
xcoords = [i for i in srange(a,b,.01)]
local_min = []
local_max = []
for i in range(1,len(xcoords)-2):
    if f(x=xcoords[i-1])<f(x=xcoords[i]) and    f(x=xcoords[i])>f(x=xcoords[i+1]):
        local_max+=[[xcoords[i],f(x=xcoords[i])]]
    if f(x=xcoords[i-1])>f(x=xcoords[i]) and f(x=xcoords[i])<f(x=xcoords[i+1]):
        local_min+=[[xcoords[i],f(x=xcoords[i])]]

if len(local_min)>0: for i in range(0,len(local_min)): print(local_min[i][0],local_min[i][1]) if len(local_max)>0: for i in range(0,len(local_max)): print(local_max[i][0],local_max[i][1])

print("minima at ",local_min) print("maxima at ",local_max)

Now press Enter to get:

enter image description here The basic idea is that a local max is defined when the point on the left and right have a smaller function value. The code starts at .35 and goes to 4.2 in steps of .01. Whenever it encounters a local max (min) it adds it to the list. I've printed out the list of maxima (minima) at the end. These x values (y values) can be easily inserted into your document (via the \sage{} command) or used in your pictures as I've illustrated in the LaTeX code below:

\documentclass[border=2mm]{standalone}
\usepackage{tikz,sagetex}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\begin{document}
\begin{sagesilent}
f = 0.6*cos((4.5*(x-4)+2.1))-1.2*sin((x-4))+0.1*x+0.2
a = 0.35
b = 4.2
xcoords = [t for t in srange(a,b,.01)]
ycoords = [f(x=t).n(digits=6) for t in srange(a,b,.01)]
local_min = []
local_max = []
for i in range(1,len(xcoords)-2):
    if f(x=xcoords[i-1])<f(x=xcoords[i]) and f(x=xcoords[i])>f(x=xcoords[i+1]):
        local_max+=[[xcoords[i],f(x=xcoords[i])]]
    if f(x=xcoords[i-1])>f(x=xcoords[i]) and f(x=xcoords[i])<f(x=xcoords[i+1]):
        local_min+=[[xcoords[i],f(x=xcoords[i])]]

output = r"\begin{tikzpicture}" output += r"\begin{axis}[xmin=%f,xmax=%f,ymin= %f,ymax=%f]"%(a,b,-1,3) output += r"\addplot[thin, red, unbounded coords=jump] coordinates {" for i in range(0,len(xcoords)-1): if (ycoords[i])<-1 or (ycoords[i])>3: output += r"(%f , inf) "%(xcoords[i]) else: output += r"(%f , %f) "%(xcoords[i],ycoords[i]) output += r"};"

if len(local_min)>0: output+= r"\addplot[black,only marks,mark options={mark size=.75pt}] coordinates {" for i in range(0,len(local_min)): output += r"(%f , %f) "%(local_min[i][0],local_min[i][1]) output += r"};"

if len(local_max)>0: output+=r"\addplot[green,only marks,mark options={mark size=.75pt}] coordinates {" for i in range(0,len(local_max)): output += r"(%f , %f) "%(local_max[i][0],local_max[i][1]) output += r"};" output += r"\end{axis}" output += r"\end{tikzpicture}" \end{sagesilent} \sagestr{output} \end{document}

The output from Cocalc is below: enter image description here

Note, in the code above

if len(local_min)>0:
        output+= r"\addplot[black,only marks,mark options={mark size=.75pt}] coordinates {"
        for i in range(0,len(local_min)):
            output += r"(%f , %f) "%(local_min[i][0],local_min[i][1])
        output += r"};"

which is saying if there is at least 1 local minimum then add the coordinate as a black dots to the picture. The local maxima are in green. In Python, the first item in a list is the 0th item. So local_min[0][0] and local_min[0][1] will give the x coordinate and y coordinate of the first local minimum.

Since Sage is not part of LaTeX you will need to install it on your computer or, even better, open a free Cocalc account. You can be up and running in 5 to 10 minutes. Just copy/paste the LaTeX code into a LaTeX document and press Build to compile. Changing the function will allow you to quickly generate new graphs with min (max) plotted.

DJP
  • 12,451