How to plot a complicated multi-variable function in a tex document automatically, not just importing an image
Here is a solution by means of pgfplots + lualatex (i.e. it has to be compiled with lualatex P.tex
:
\documentclass{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.9}
\begin{document}
\begin{tikzpicture}
\directlua{
Y = function(x,m,n)
return math.abs(1-(1-((x+1)/2)^2)^m)^n
end
N1 = function(x,m,n)
return (-Y(0,m,n) + (Y(0,m,n)-1)*x + Y(x,m,n))/ (1-2*Y(0,m,n))
end
}
\pgfmathdeclarefunction{N1}{3}{%
\edef\pgfmathresult{\directlua{tex.print(N1(\pgfmathfloatvalueof{#1},\pgfmathfloatvalueof{#2},\pgfmathfloatvalueof{#3}))}}%
}%
\begin{axis}[
axis lines=center,
enlargelimits,
tick align=inside,
no markers,
legend entries={$N1(x,0.2,0.5)$\\$N1(x,1,5)$\\$N1(x,1,10)$\\},
domain=-1:0.99999,
samples=150,
minor tick num=4,
]
\addplot {N1(x,0.2,0.5)};
\addplot {N1(x,1,5)};
\addplot {N1(x,1,10)};
\end{axis}
\end{tikzpicture}
\end{document}
some remarks:
- I have used Lua in order to benefit from its higher accuracy - the current formulation of N1 suffers from numerical instability near x=-1 (i.e. small errors in the input add up to huge errors in the output). This requires higher precision than TeX offers by means of its builtin methods.
- I ignored the possibility to reuse the value for
y(0,m,n)
. It does not hurt to compute it twice. - Add
cycle list={dashed,black,black}
to the option list of the axis to get your plot styles - I prefered the defaylt cycle list (withno markers
) such that you can identify each in a legend (which I added as well). - Note that I chose to define a PGF math function
N1
by means of\pgfmathdeclarefunction
. This allows us to useN1(x,m,n)
inside of a PGF math expression (in particular: in\addplot {<expression>}
). The argument{3}
means that the new function takes three arguments, available as#1
,#2
, and#3
as in a macro. Currently, these arguments are in some internal format generated by the floating point library (in TeX), so we need to convert them back to something understood by Lua (by means of\pgfmathfloatvalueof
. - since I never plotted
Y
directly, I did not generate a\pgfmathdeclarefunction
forY
-- after all, the Lua code can compute this in a self-contained fashion. - Note that
\pgfmathdeclarefunction
is supposed to assign results to the macro\pgfmathresult
, which is the purpose of\edef\pgfmathresult
. Thetex.print
is Lua's way to report results back to TeX.
In general, it would be safe to formulate the function by means of PGF's math engine which results in
\documentclass{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.9}
\begin{document}
\begin{tikzpicture}
\pgfmathdeclarefunction{Y}{3}{%
\pgfmathparse{abs(1-(1-((#1+1)/2)^2)^(#2))^(#3)}%
}%
\pgfmathdeclarefunction{N1}{3}{%
\pgfmathparse{(-Y(0,#2,#3) + (Y(0,#2,#3)-1)*(#1) + Y(#1,#2,#3))/ (1-2*Y(0,#2,#3)}%
}%
\begin{axis}[
axis lines=center,
enlargelimits,
tick align=inside,
no markers,
legend entries={$N1(x,0.2,0.5)$\\$N1(x,1,5)$\\$N1(x,1,10)$\\},
domain=-1:0.99999,
samples=150,
minor tick num=4,
]
\addplot {N1(x,0.2,0.5)};
\addplot {N1(x,1,5)};
\addplot {N1(x,1,10)};
\end{axis}
\end{tikzpicture}
\end{document}
Note the visible artifact near x=-1; it originates in the numerically instable function formulation near x=-1 combined with TeX's limited precision (unless I am mistaken).
You can use gnuplot
to plot these. For instance, you can use the following code:
\documentclass{standalone}
\makeatletter\newwrite\verbatim@out\makeatother
\usepackage{gnuplottex}
\usepackage{epstopdf}
\begin{document}
\begin{gnuplot}[terminal=epslatex]
set samples 2000 # Set to get more accurate, but slower
set parametric
set xtics -1,.5,1
set ytics -1,.5,1
set trange [-1:1] # Parametric plot range
set xrange [-1.1:1.1] # Axis range
set zeroaxis
set border 0
set xtics axis
set ytics axis
y(x,m,n) = (abs(1-(1-((x+1)/2)**2)**m))**n
N(x,m,n) = (-y(0,m,n) + (y(0,m,n) - 1)*x + y(x,m,n))/(1 - 2*y(0,m,n))
plot t,N(t,0.2,0.5) title "", t,N(t,1,5) title "", t,N(t,1,10) title ""
\end{gnuplot}
\end{document}
You need to compile it with the --shell-escape
option, and have gnuplot installed.
As you can see in the code, it is easy to define and use functions in gnuplot, even with multiple parameters.
Rendering:
* Edit * Added dashed patterns, legend and a couple of labels;
also an array mn[]
of (m,n)
pairs
is used to create a function N1(m,n)(x)
instead of the array of functions.
% f.tex:
%
\documentclass{article}
\usepackage[inline]{asymptote}
\usepackage{lmodern}
\begin{document}
\begin{figure}
\centering
\begin{asy}[width=8cm]
size(9cm);
import graph;
import fontsize;
defaultpen(fontsize(9pt));
pen dashed=linetype(new real[] {4,4});
pen longdashed=linetype(new real[] {12,4});
pen dotted=linetype(new real[] {0,3});
pen[] fpen={
gray+longdashed,
black+dotted,
black+dashed
};
real y(real x,real m,real n){
return abs(1-(1-((x+1)/2)^2)^m)^n;
}
typedef real Func(real);
Func N1(real m,real n){
return
new real(real x){
return (-y(0,m,n)+(y(0,m,n)-1)*x+y(x,m,n))/(1-2y(0,m,n));
};
}
pair[] mn={(0.2,0.5),(1,5), (1,10)};
real xmin=-1, xmax=1;
xaxis(xmin,xmax,LeftTicks(Label(LeftSide),Step=0.5,step=0.1,OmitTick(0)));
yaxis(RightTicks(Step=0.5,step=0.1,OmitTick(0)));
real penwidth=1bp;
real m,n;
for(int i=0;i<mn.length;++i){
m=mn[i].x; n=mn[i].y;
draw(graph(N1(m,n),xmin,xmax,n=400),fpen[i]+penwidth
,legend="$N_1("+string(m)+","+string(n)+")$"
);
}
label("$x$",1.1*(xmax,0),S); // 1.1*(xmax,0) is a location,
// alignment S == (0,-1) means "South"
m=mn[2].x; n=mn[2].y;
label("$("+string(m)+","+string(n)+")$",
(0.6,N1(m,n)(0.6))
,SW
);
add(
legend(linelength=0.5legendlinelength,nullpen) // here nullpen means no frame
,point(NE),SW,UnFill
);
\end{asy}
\caption{Family of functions $N_1(x,m,n)$}
\end{figure}
\end{document}
Process it as follows:
pdflatex f.tex
asy f-*.asy
pdflatex f.tex
* ======== first version ======== *
Plotting of such families of functions is straightforward
with the Asymptote
(which is part of the TeXLive
distribution for quite a while).
Inside the asy
environment, the function N1(m,n)
uses parameters m
and 'n'
to create a new real-valued function which takes one real argument.
All functions that have to be plotted are collected in array f[]
and then plotted inside a loop, using a prepared array of pens fpen[]
.
% f.tex:
%
\documentclass{article}
\usepackage[inline]{asymptote}
\usepackage{lmodern}
\begin{document}
\begin{figure}
\centering
\begin{asy}[width=7cm]
import graph;
import fontsize;
defaultpen(fontsize(9pt));
pen dashed=linetype(new real[] {4,4});
pen[] fpen={
deepblue+dashed,
black,
orange
};
real y(real x,real m,real n){
return abs(1-(1-((x+1)/2)^2)^m)^n;
}
typedef real Func(real);
Func N1(real m,real n){
return
new real(real x){
return (-y(0,m,n)+(y(0,m,n)-1)*x+y(x,m,n))/(1-2y(0,m,n));
};
}
Func[] f={ N1(0.2,0.5), N1(1,5), N1(1,10) };
real xmin=-1, xmax=1;
xaxis(xmin,xmax,LeftTicks(Label(LeftSide),Step=0.5,step=0.1,OmitTick(0)));
yaxis(RightTicks(Step=0.5,step=0.1,OmitTick(0)));
real penwidth=1bp;
for(int i=0;i<f.length;++i){
draw(graph(f[i],xmin,xmax),fpen[i]+penwidth);
}
\end{asy}
\caption{Family of functions $N_1(x,m,n)$}
\end{figure}
\end{document}
Process it as follows:
pdflatex f.tex
asy f-*.asy
pdflatex f.tex
P.S. I hope you don't count the step asy f-*.asy
as too much of extra work.