Ilya Zakharevich on Thu, 11 Nov 1999 18:28:13 -0500 (EST)


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

Additions to tutorial


\section{Plotting}

Plotting functions use a multitude of flags, which are mostly power-of-2. To
simplify understanding give these flags symbolic names.  The following
code also decreases the default precision, so that the calculations
are performed much quickier (this is very important, since plotting
involves calculation of functions at many points).

\bprog
parametric=1; recursive = 2; norescale=4;
no\_x\_axis=8; no\_y\_axis=16; noframe=32;
points=64; points\_lines = 128; splines = 256; 
nw=0; sw=2; se=4; ne=6; left = 0; center = 1; right = 2; hgap = 16;
bottom = 0; vcenter = 4; top = 8; vgap = 32; relative=1;
\b{p} 9
\eprog

Start with something really simple:

\kbd{ploth(X=-2,2,sin(X\pow 7))}

You can see the limitations of the ``straightforward'' mode of plotting:
while the first several cycles of \kbd{sin} reach $-1$ and $1$, the cycles
which are closer to the left and right border do not. This is understandable,
since PARI is calculating $\sin X^7$ at many points, but these points
have no direct relationship to the ``interesting'' point on the graph
of this function.  No value close enough to the maxima and minima are
calculated, which leads to wrong turning points of the graph.

There is a way to fix this: one can ask PARI to use variable step which
smaller at the points where the graph of the function is more curved:

\kbd{ploth(X=-2,2,sin(X\pow 7),recursive)}

\noindent The precision near the edges of the graph is much better now.
However, the recursive plotting (named so since PARI subdivides intervals
until the graph becomes almost straight) can its own pitfalls.  Try

\kbd{ploth(X=-2,2,sin(X*7),recursive)}

\noindent Note that the graph looks correct far away, but it has a straight
interval near the origin, and some sharp corners as well.  This happens
because the graph is symmetric with respect to the origin, thus the middle 3
points calculated during the initial subdivision of $[-2,2]$ are exactly on
the same line.  To PARI this indicates that no further subdivision is needed,
and it plots the graph on this subinterval as a straight line.

There are many ways to circumvent this.  Say, one can make the right limit
2.1.  Or one can ask PARI for an initial subdivision into 16 points instead
of default 15:

\kbd{ploth(X=-2,2,sin(X*7),recursive,16)}

All these arrangements break the symmetry of the initial subdivision, thus
make the problem go away.  Eventually PARI will be able to better detect such
pathological cases, but currently some manual intervention may be required.

Function \kbd{ploth} has some additional enhancements which allow graphing
in situations when the calculation of the function takes a lot of time.  Let
us plot $\zeta(0.5+it)$:

\kbd{ploth(t=100,110,real(zeta(0.5+I*t)),,1000)}

\noindent This can take quite some time.  (1000 is close to the default for
many plotting devices, we want to specify it explicitely so that the result
do not depend on the output device.)  Try the recursive plot:

\kbd{ploth(t=100,110,real(zeta(0.5+I*t)),recursive)}

It takes approximately the same time.  Now try specifying fewer points,
but make PARI approximate the data by a smooth curve:

\kbd{ploth(t=100,110,real(zeta(0.5+I*t)),splines,100)}

This takes much less time, and the output is practically the same.  How to
compare these two outputs?  We will see it shortly.  Right now let us
plot both real and complex parts of $\zeta$ on the same graph:

\bprog
f(t) = z=zeta(0.5+I*t);[real(z),imag(z)]
ploth(t=100,110,f(t),,1000)
\eprog

Note how one half of the roots of the real and imaginary parts coincide.  Why
did we define a function \kbd{f(t)}?  To avoid calculation of $\zeta(0.5+it)$
twice for the same value of t.  Similarly, we can plot parametric graph:

\kbd{ploth(t=100,110,f(t),parametric,1000)}

Again, one can speed up the calculation with

\kbd{ploth(t=100,110,f(t),parametric+splines,100)}

If your plotting device supports it, you may ask PARI to show the points
in which it calculated your function:

\kbd{ploth(t=100,110,f(t),parametric+splines+points\_lines,100)}

As you can see, the points are very dense on the graph.  To see some crude
graph, one can even decrease the number of points to 30.  However, if you
decrease the number of points to 20, you can see that the approximation to
the graph now misses zero.  Using splines, one can create reasonable graphs
for larger values of t, say with

\kbd{ploth(t=10000,10004,f(t),parametric+splines+points\_lines,50)}

How can we compare two graphs of the same function plotted by different
methods?  Documentation shows that \kbd{ploth} does not provide any direct
method to do so.  However, it is possible, and even not very complicated.

The solution comes from the other direction.  PARI has a power mix of high
level plotting function with low level plotting functions, and these functions
can be combined together to obtain many different effects.  Return back
to the graph of $\sin X^7$.  Suppose we want to create an additional 
rectangular frame around our graph.  No problem!

First create a ``rectangle'' (this is a PARI term for a target of low-level
drawing primitives), name it rectangle 2 (any number between 0 andn 15 will
go):
\kbd{plotinit(2)}, \kbd{plotscale(2, 0, 1, 0, 1)}.  This makes a rectangle
which the size equal to the size of the output device, and makes the coordinate
system inside this rectangle go from 0 to 1 (for both $x$ and $y$).
Create a rectangular ``frame'' along the boundary of this rectangle:
\kbd{plotmove(2,0,0); plotbox(2,1,1)}.  Suppose we want to draw the graph
inside a subrectangle of this with the gaps on the bottom and on the left
of 0.1 of the size of the larger rectangle, and gaps 0.02 on the right
and on the top.

Create another rectangle (call it 3) of linear size 0.88 of the size of the
initial rectangle: \kbd{plotinit(3,0.88,0.88,relative)}.  Now graph the
function inside rectangle 3: \kbd{plotrecth(3,X=-2,2,sin(X\pow 7),recursive)}
and output rectangles 2 and 3 on the same plot:

\kbd{plotdraw([2,0,0,3,0.1,0.02],relative)}

\noindent (Funny offsets of the
rectangle 3 appear since the offsets are counted from upper-left corner.)
The output misses something comparing to the output of \kbd{ploth}: there
are no coordinates of the corners of the internal rectangle.  If your output
device supports mouse operations, you can find coordinates of particular
points of the graph, but it is nice to have something printed on
a hardcopy too.

However, it is easy to put $x$- and $y$-limits on the graph.  In the coordinate
system of the rectangle 2 the corners are $(0.1,0.1)$, $(0.1,0.98)$,
$(0.98,0.1)$, $(0.98,0.98)$.  We can mark lower $x$-limit
by doing \kbd{plotmove(2,0.1,0.1); plotstring(2,"-2.000",left+top+vgap)}.
$y$-coordinate is a little bit trickier, since in principle we do not know
it in advance (though for $\sin X^7$ it is very easy to guess).  Fortunately,
\kbd{plotrecth} returns the $x$- and $y$-limits.

Here is the complete program (rounding could have been done better,
is it possible in PARI?):

\bprog
plotinit(3,0.88,0.88,relative)
lims=plotrecth(3,X=-2,2,sin(X\pow 7),recursive)
limits=vector(4,i,round(1e4*lims[i])/1e4)
plotinit(2);          plotscale(2, 0, 1, 0, 1)
plotmove(2,0,0);      plotbox(2,1,1)
plotmove(2,0.1,0.1);  plotstring(2,limits[1],left+top+vgap)
plotstring(2,limits[3],bottom+vgap+right+hgap)
plotmove(2,0.98,0.1); plotstring(2,limits[2],right+top+vgap)
plotmove(2,0.1,0.98); plotstring(2,limits[4],right+hgap+top)
plotdraw([2,0,0,3,0.1,0.02],relative)
\eprog

We started with a trivial requirement: have an additional frame around
the graph, and it took some effort to do so.  But at least it was possible,
and PARI did the hardest part: creating the actual graph.
Now do a different thing: plot together the ``exact'' graph of $\zeta(0.5+it)$
together with one obtained from splines approximation.  We can emit
these graphs into two rectangles, say 0 and 1, then put these two
rectangles together on one plot.  Or we can emit these graphs into one
rectangle 0.

However, a problem arises: note how we
introduced a coordinate system in rectangle 2 of the above example, but we
did not introduce a coordinate system in rectangle 3.  Plotting a
graph into rectangle 3 automatically created a coordinate system
inside this rectangle (you could see this coordinate system in action
if your output device supports mouse operations).  If we use two different
methods of graphing, the bounding boxes of the graphs will not be exactly
the same, thus outputting the rectangles may be tricky.  Thus during
the second plotting we ask \kbd{plotrecth} to use the coordinate system of
the first plotting.  Let us add another plotting with fewer
points too:

\bprog
plotinit(0,0.9,0.9,relative)
plotrecth(0,t=100,110,f(t),parametric,300)
plotrecth(0,t=100,110,f(t),parametric+splines+points\_lines+norescale,30);
plotrecth(0,t=100,110,f(t),parametric+splines+points\_lines+norescale,20);
plotdraw([0,0.05,0.05],relative)
\eprog

This achieves what we wanted: we may compare different ways to plot a graph,
but the picture is confusing: which graph is what, and why there are multiple
boxes around the graph?  At least with some output devices one can control
how the output curves look like, so we can use this to distinguish different
graphs.  And the mystery of multiple boxes is also not that hard to solve:
they are bounding boxes for calculated points on each graph.  We can disable
output of bounding boxes with appropriate options for \kbd{plotrect}.
With these frills the script becomes:

\bprog
plotinit(0,0.9,0.9,relative)
plotpointtype(-1,0)                     \q\q\bs\bs set color of graph points
plotpointsize(0,0.4)                    \q\q\bs\bs use tiny markers (if available)
plotrecth(0,t=100,110,f(t),parametric+points,300)
plotpointsize(0,1)                      \q\q\bs\bs normal-size markers
plotlinetype(-1,1)                      \q\q\bs\bs set color of graph lines
plotpointtype(-1,1)                     \q\q\bs\bs set color of graph points
curve\_only = norescale + noframe + no\_x\_axis + no\_y\_axis
plotrecth(0,t=100,110,f(t),parametric+splines+points\_lines+curve\_only,30);
plotlinetype(-1,2)                      \q\q\bs\bs set color of graph lines
plotpointtype(-1,2)                     \q\q\bs\bs set color of graph points
plotrecth(0,t=100,110,f(t),parametric+splines+points\_lines+curve\_only,20);
plotdraw([0,0.05,0.05],relative)
\eprog

\noindent Plotting axes on the second and third graph would not hurt, but 
is not needed too, this is why we omit axes.  One can see that the
discrepancies between the exact graph and one based on 30 points exist,
but are pretty small.  Decreasing the number of points to 20 creates quite
noticable discrepancies.

Keep in mind that \kbd{plotlinetype}, \kbd{plotpointtype},
\kbd{plotpointsize} may do nothing on some terminals.  

What if we
want to create a high-resolution hardcopy of the plot?  There may be several
possible solutions.  First, the display output device may allow a
high-resolution hardcopy itself.  Say, PM display (with gnuplot output on
OS/2) pretends that its resolution is $19500\times 12500$, thus the data
PARI sends to it are already high-resolution, and printing is available
through the menubar.  Alternatively, with gnuplot output one can switch
the output plotting device to many different hardcopy devices:
\kbd{plotfile("plot.tex")}, \kbd{plotterm("texdraw")}.
After this all the plotting will go into file {\tt plot.tex} with whatever
output conventions gnuplot format {\tt texdraw} provides.  To switch output
back to normal, one needs to restore the initial plotting terminal, and
restore the initial output file by doing \kbd{plotfile("-")}.

One can combine PARI programming capabilities to produce multiple plots:

\bprog
plotfile("manypl1.gif")       \q\q\bs\bs Avoid switching STDOUT to binary mode
plotterm("gif=300,200")
wpoints = plothsizes()[1]       \q\q\bs\bs 300 x 200 is advice only
$\{$  for( k=1,6,
\q\q	plotfile("manypl"k".gif");
\q\q	ploth(x=-1,3,sin(x\pow k),0,wpoints)
\q   )
$\}$
\eprog

\noindent This plots 6 graphs of $\sin x^k$, $k=1\dots 6$ into 
$300\times 200$ GIF files {\tt manypl1.gif}\dots {\tt manypl6.gif}.

Additionally, one can ask PARI to output a plot into a PS file: just
use the command \kbd{psdraw} instead of \kbd{plotdraw} in the above examples
(or \kbd{psploth} instead of \kbd{ploth}).  See \kbd{psfile} argument
to \kbd{default} for how to change the output file for this operation.  Keep
in mind that the precision of PARI PS output will be the same as for the
current output device.

Now suppose we want to join many different small graphs into one picture.
We cannot use one rectangle for all the output as we did in the example
with $\zeta(0.5+it)$, since the graphs should go into different places.
Rectangles are a scarce commodity in PARI, there are only
16 user-accessible rectangles.  Does it mean that we cannot have more than
16 graphs on one picture?  Thanks to an additional operation of PARI
plotting engine, there is no such restrictions.  This operation is 
\kbd{plotcopy}.

The following script puts 4 different graphs on one plot using 2 rectangles
only, \kbd{2} and \kbd{3}:

\bprog
plotinit(2);         plotscale(2, 0, 1, 0, 1)
\vskip 0.15truecm
plotinit(3, 0.42, 0.42, relative);
plotrecth(3, x= -5, 5, sin(x), recursive)
plotcopy(3, 2, 0.05, 0.05, relative + nw)
\vskip 0.15truecm
plotmove(2, 0.05 + 0.42/2, 1 - 0.05/2)
plotstring(2,"Graph", center + vcenter)
\vskip 0.15truecm
plotinit(3, 0.42, 0.42, relative);
plotrecth(3, x= -5, 5, [sin(x),cos(2*x)], 0)
plotcopy(3, 2, 0.05, 0.05, relative + ne)
\vskip 0.15truecm
plotmove(2, 1 - 0.05 - 0.42/2, 1 - 0.05/2)
plotstring(2,"Multigraph", center + vcenter)
\vskip 0.15truecm
plotinit(3, 0.42, 0.42, relative);
plotrecth(3, x= -5, 5, [sin(3*x), cos(2*x)], parametric)
plotcopy(3, 2, 0.05, 0.05, relative + sw)
\vskip 0.15truecm
plotmove(2, 0.05 + 0.42/2, 0.5)
plotstring(2,"Parametric", center + vcenter)
\vskip 0.15truecm
plotinit(3, 0.42, 0.42, relative);
plotrecth(3, x= -5, 5, [sin(x), cos(x), sin(3*x),cos(2*x)], parametric)
plotcopy(3, 2, 0.05, 0.05, relative + se)
\vskip 0.15truecm
plotmove(2, 1 - 0.05 - 0.42/2, 0.5)
plotstring(2,"Multiparametric", center + vcenter)
\vskip 0.15truecm
plotlinetype(2,3)
plotmove(2, 0, 0)
plotbox(2, 1, 1)
\vskip 0.15truecm
plotdraw([2, 0, 0])
\bs\bs psdraw([2, 0, 0], relative)          \bs\bs if hardcopy is needed
\eprog

The rectangle \kbd{2} plays the role of accumulator, rectangle \kbd{3} is
used as a target to \kbd{plotrecth} only.  Immediately after plotting into 
rectangle \kbd{3} the contents is copied to accumulator.  Let us explain
numbers which appear in this example: we want to create 4 internal rectangles
with a gap 0.06 between them, and the outside gap 0.05 (of the size of the
plot).  This leads to the size 0.42 for each rectangle.  We also
put captions above each graph, centered in the middle of each gap.  There
is no need to have a special rectangle for captions, they go into accumulator
too.

To simplify positioning of the rectangles, the above example uses relative
"geographic" notation: the last argument of \kbd{plotcopy} specifies the
corner of the graph (say, northwest) one counts offset from.
(Positive offsets go into the rectangle.)

To demonstrate yet another useful plotting function, design a program to
plot Taylor polynomials for a $\sin x$ about 0.  For simplicity, make the
program good for any function, but assume that a function is odd, so only
odd-numbered Taylor series about 0 should be plotted.  Start with defining
some useful shortcuts

\bprog
xlim=13;    ordlim=25;     f(x)=sin(x);
default(seriesprecision,ordlim)
$\{$ farray(x)=
\q\q    vector((ordlim+1)/2,k,
\q\q\q\q\q\q            subst(Pol(f(1.*tt+tt\pow (2*k)*O(tt)),tt),
\q\q\q\q\q\q\q\q\q                  tt,x))
$\}$
\eprog

\noindent Function \kbd{farray(x)} returns a vector of Taylor polynomials for
$f(x)$.  We want to plot $f(x)$ into a rectangle, then make the rectangle
which is 1.2 times higher, and plot Taylor polynomials into the larger
rectangle.  Assume that the larger rectangle takes 0.9 of the final plot.

First of all, we need to measure the height of the smaller rectangle:

\bprog
plotinit(3, 0.9, 0.9/1.2, 1);
curve\_only=no\_x\_axis+no\_y\_axis+noframe;
lims = plotrecth(3, x= -xlim, xlim, f(x), recursive+curve\_only,16);
h = lims[4] - lims[3];
\eprog

\noindent Next step is to create a larger rectangle, and plot the Taylor
polynomials into the larger rectangle:

\bprog
plotinit(4, 0.9, 0.9,     1);
plotscale(4, lims[1], lims[2], lims[3]- h/10, lims[4] + h/10)
plotrecth(4, x= -xlim, xlim, farray(x), norescale);
\eprog

Here comes the central command of this example:

\kbd{plotclip(4)}

\noindent What does it do?  The command \kbd{plotrecth(\dots, norescale)}
scales the graphs according to coordinate system in the 
rectangle, but it does not pay any other attension to the size of
the rectangle.  Since \kbd{xlim} is 13, the Taylor polynomials take 
very large values in the interval \kbd{-xlim...xlim}.  In particular,
significant part of the graphs is going to be {\it outside} of the rectangle.
\kbd{plotclip} removes the parts of the
plottings which fall off the boundary of the rectangle.

\bprog
plotinit(2)
plotscale(2, 0.0, 1.0, 0.0, 1.0)
plotmove(2,0.5,0.975)
plotstring(2,"Multiple Taylor Approximations",center+vcenter)

plotdraw([2, 0, 0, 3, 0.05, 0.05 + 0.9/12, 4, 0.05, 0.05], relative)
\eprog

These commands draw a caption, and combine 3 rectangles (one with the
caption, one with the graph of the function, and one with graph of Taylor
polynomials) together.

This finishes our short survey of PARI plotting functions, but let us add
some remarks.  First of all, for a typical output device the picture is
composed of small colored squares (pixels) (as a very large checkerboard).
Each output rectangle is in fact a union of such squares.  Each drop
of paint in the rectangle will color a whole square it is in.  Since the
rectangle has a coordinate system, sometimes it is important to know how
this coordinate system is positioned with respect to the boundaries of
these squares.

The command \kbd{plotscale} describes a range of $x$ and $y$ in the
rectangle.  The limit values of $x$ and $y$ in the coordinate system are
coordinates {\it of the centers} of corner squares.  In particular,
if ranges of $x$ and $y$ are $[0,1]$, then the segment which connects (0,0)
with (0,1) goes along the {\it middle} of the left column of the rectangle.
In particular, if we made tiny errors in calculation of endpoints of this
segment, this will not change which squares the segment intersects, thus
the resulting picture will be the same.  (It is important to know such details
since many calculations in PARI are approximate.)

Another consideration is that all examples we did in this section were
using relative sizes and positions for the rectangles.  This is nice, since
different output devices will have very similar pictures, while we
did not need to care about particular resolution of the output device.
On the other hand,
using relative positions does not guarantie that the pictures will be
similar.  Why?  Even if two output devices have the same resolution,
the picture may be different.  The devices may use fonts of different
size, or may have a different ``unit of length''.

The information about the device in PARI is encoded in 6 numbers: resolution,
size of a character cell of the font, and unit of length, all separately
for horizontal and vertical direction.  These sizes are expressed as
numbers of pixels.  To inspect these numbers one may use the function
\kbd{plothsizes}.  The ``units of length'' are currently used to calculate
right and top gaps near graph rectangle of \kbd{ploth}, and gaps for
\kbd{plotstring}.  Left and bottom gaps near graph rectangle are calculate
using both units of length, and sizes of character boxes (so that there
is enough place to print limits of the graphs).

What does it show?  Using relative sizes during plotting produces {\tt
approximately} the same plotting on different devices, but does not
ensure that the plottings ``look the same''.  Moreover, ``looking the same''
is not a desirable target, ``looking tuned for the environment'' will
be much better.  If you want to produce such fine-tuned plottings, you need
to abandone a relative-size model, and do your plottings in units of
pixels.  To do this one removes flag \kbd{relative} from the above examples,
which will make size and offset arguments interpreted in units of pixels.
After querying sizes with \kbd{plothsizes} one can fine-tune sizes
and locations of subrectangles to the details of an arbitrary plotting device.

To check how good your fine-tuning is, you may test your graphs with a
medium-resolution plotting (as many display output devices are), and
with a low-resolution plotting (say, with \kbd{plotterm("dumb")} of gnuplot).