Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

C++ For Mathematicians (2006) [eng]

.pdf
Скачиваний:
193
Добавлен:
16.08.2013
Размер:
31.64 Mб
Скачать

316

C++ for Mathematicians

14.8.1 Introducing and installing the plotutils package

Let’s start with an assumption. We do not want to learn how graphics files (such as GIF, JPG, or EPS) work; we just want to think about drawing in mathematical terms. Line segments should be specified by their end points and circles by their centers and radii. Someone else should work out how these various graphics formats work and provide us with an easy way to draw. Fortunately, someone else has done exactly that.

The GNU plotutils package is free software that provides device-independent drawing tools with output to a variety of popular graphics file formats. The package can be downloaded from the following Web site,

http://www.gnu.org/software/plotutils/

The package arrives as a single compressed “tar” file5 in which all the plotutils files are held. Installation is mildly complicated. Installation instructions can be found in the files INSTALL and INSTALL.pkg, but these can scare the uninitiated. Follow this outline.

Unpack. It is necessary to extract the various source files for the plotutils files from the downloaded file. On some computers, this may be as simple as double-clicking the downloaded file. On a UNIX system (or in Cygwin on Windows), you can unpack with the following command,

tar xfz plotutils-2.4.1.tar.gz

(The precise file name may depend on the version you downloaded; be sure to fetch the latest.)

You should now have a folder on your hard drive named plotutils-2.4.1 (or something similar).

Configure. The next step is to run the configure program found in the folder you have just unpacked. This is done from a command line such as this:

./configure --enable-libplotter --prefix=directory

Here, directory is a directory where you want the package to be installed. This may be your home directory, a subdirectory of your home directory, or a public directory such as /usr/local or /usr/public. Please note that the installation procedure (described below) adds files to various subdirectories of these directories; if those subdirectories do not already exist, the install process creates them for you. These subdirectories are named bin, lib, include, and so on.

The option --enable-libpotter enables the C++ version of plotutils. (Without this option, only the C version is built.)

5The word tar is an acronym for tape archive; this file format provides a mechanism for packaging many files together and need not be associated with storing data on a tape.

Strings, Input/Output, and Visualization

317

Build. The package is built using a single command: make. Even on a fast computer, this process takes a while.

Create the documentation. The plotutils package comes with a reference manual (and other documentation). To build this, type make dvi. This creates the file plotutils.pdf in the info subdirectory. This can be read (and printed) using Adobe Acrobat Reader or another PDF viewer.

Install. Thus far, all the work of configuring and building the package takes place in the directory (folder) where you unpacked plotutils. The final step is to transfer the pieces to the directory you specified in the configuration step. This is quick and easy; type the command make install.

This copies header files (such as libplotter.h) to the include subdirectory of the directory you specified in the configuration process, library files to lib, executable programs to bin, and so on.

If you have a Linux computer, it is possible that plotutils is already installed. If not, a precompiled version can be found by visiting this Web site,

http://rpmfind.net/linux/RPM/index.html

and searching for plotutils. The package is then installed using Linux’s rpm command (for which you must have superuser privileges).

Installing plotutils is nontrivial, but not insurmountable. If possible, seek a friendly computer expert to assist you through this process. Rest assured that installing plotutils is the most difficult step in using it to draw pictures.

14.8.2 Drawing with plotutils—a first example

We begin by creating a program (see Program 14.11) to draw the symbol . It is easy to describe this symbol mathematically. It consists of two line segments and a circle. To be specific, the first line segment joins the points (−1,−1) to (1,1) and the

second line segment joins (−1,1) with (1,−1). The circle is centered at the origin

with radius 2. Once we set up plotutils, drawing the picture is just that easy. In broad strokes, the steps to produce the image are these.

Select the appropriate subclass of Plotter,

Declare the Plotter object with the appropriate arguments,

“Open” the Plotter and perform other initialization including establishing a coordinate system and pen attributes,

Draw the image, and

Close the Plotter.

Finally, we need to compile and run the program.

318

C++ for Mathematicians

Choosing the appropriate plotter type

The plotutils package is capable of producing various types of graphics files including gif, eps, and several others. Fortunately, the methods for creating images for these various formats are all identical. If we write code to produce one type of file and subsequently decide we prefer another type, it is easy to change the code to implement the new choice.

Each drawing is produced by an object that is a subclass of the type Plotter. To create a gif image, we declare an object of type GIFPlotter; for an eps image, we declare a PSPlotter object. For other graphics formats, see the file plotutils.pdf included in the info subdirectory.

At this point you may be wondering, which type of file should I choose? Here’s some guidance. Graphic image files can be divided into two broad categories: bit images and vector graphics. Bit image files are (essentially) a matrix of pixels; they are useful for representing photographs and are manipulated using “paint” style programs. On the other hand, vector graphic files represent their drawings using mathematical primitives such as line segments, ellipses, Bezier curves, and so forth. They are edited using “draw” style programs. For our first project, drawing , we use PSPlotter to produce an eps file. If were interested in drawing a picture of a Julia set (by plotting individual points), then we would use a GIFPlotter.

These various constructors are defined in the header file plotter.h, so the directive #include "plotter.h" is needed.

Arguments to the constructor

Regardless of which subtype of the class Plotter we choose, the declaration of the Plotter object is the same. The constructor requires four arguments like this:

PlotterType(input, output, error, parameters) where

input is an object of type ifstream such as cin. At present, this input stream is not used, so the simplest choice is to set this equal to cin.

output is an object of type ofstream such as cout. When the Plotter object writes its results, it sends it to this argument. Therefore, it is usually not a good idea to set this equal to cout. Rather, it is better to create a separate object of type ofstream to write to a file on disk. Thus, we need the directive #include <fstream> and to declare an output object like this:

ofstream pout("filename");

and use pout for the second argument to the constructor.

error is also an object of type fstream to which error messages are written. If something goes awry during the drawing process, the Plotter object reports the problem through this output stream. The simplest choice is to set this to cerr, the standard error output stream.

Strings, Input/Output, and Visualization

319

params is an object of type PlotterParams. The plotutils documentation explains various options that can be embedded in this object and then passed to the Plotter. However, the simplest thing to do is to specify no special options. Just declare an object of type PlotParams and pass that as the fourth argument to the constructor.

Thus, a Plotter object is created with code that looks like this:

ofstream pout("picture.eps"); PlotterParams params;

PSPlotter P(cin, pout, cerr, params);

With this in place, the plotter object P is created. See Program 14.11 lines 11–13.

Other initialization

After a Plotter object is constructed, there are a few additional steps to take before we can begin drawing. The first of these is mandatory: the Plotter needs to be “opened.” This is analogous to opening a file for writing. To open a Plotter we use its openpl() method. This method returns a negative value if anything goes amiss. See lines 16–19 of Program 14.11.

The next step is to establish a coordinate system. This is done by specifying the coordinates of the lower left and upper right corners of a square6 in the plane in which you wish to draw. For our project, the region extending from (−32 ,−32 ) to

( 3

, 3 ) is sufficient because it encompasses both line segments and the circle centered

2

2

 

 

at the origin of radius

 

 

2.

 

The coordinate system is established with the space or fspace methods. Both

of these take four arguments corresponding to the x and y coordinates of the lower left corner followed by the coordinates of the upper right corner. The only difference between these two methods is that space takes integer arguments whereas fspace takes real (the f is for float) arguments. Thus, on line 22 of Program 14.11 we have

P.fspace(-1.5, -1.5, 1.5, 1.5); but we could have used

P.space(-2, -2, 2, 2);

instead.

We are nearly ready to draw; all we need to do now is select our pen (this happens on lines 25 and 26 of Program 14.11). First we select the line width of the pen with the flinewidth method. The argument produces a line thickness relative to the overall size of your drawing area (established with the space/fspace method). The color is chosen with either the pencolorname or pencolor method. The pencolorname method takes a char* string argument giving the English name7

6It is possible to specify a nonsquare rectangular region, but this results in a distorted image. The drawing region you specified is mapped to a square “viewport”. If your region is not square, then it is rescaled horizontally or vertically to make it square. This causes circles to become ellipses, and so on.

7Of course, not all color names are recognized by this method, but the basic ones are.

320

C++ for Mathematicians

of the color you want whereas the pencolor takes three int arguments that specify the red, blue, and green components of the color. These arguments run from 0 to 65,535 (216 − 1). Invoking P.pencolor(0,0,0) loads the pen with black ink and P.pencolor(0xffff,0xffff,0xffff) gives white (this is an instance where base 16 is more convenient than decimal).

There are other pen attributes we could set to produce dotted lines. The documentation included with plotutils explains these. Later (Program 14.14) we use filltype and fillcolorname to draw circles with an opaque interior; the circle we draw in Program 14.11 has a transparent interior so we do not deal with this issue here.

Draw the picture and finalize

The picture is drawn with three simple statements. The two line segments (lines 29–30) are drawn with the line method:

P.line(-1,-1,1,1);

P.line(-1,1,1,-1);

(There is an analogous fline method that takes real-valued arguments.) The circle is drawn with the fcircle method (line 33):

P.fircle(0.,0.,sqrt(2.));

(There is an analogous circle method that takes integer-valued arguments.) There are many other drawing methods in the plotutils package for creating

rectangles, ellipses, circular and elliptical arcs, and Bezier curves. These can be combined into compound paths (e.g., polygons) which, if closed, can contain a fill color. One can also write text (in a variety of fonts) and plot individual points (or place marker symbols at specified coordinates). See the plotutils documentation for more information.

After all drawing is complete, we close the Plotter with the closepl() method (see line 36). This method returns a negative value if anything goes awry with this step.

Compiling and running the program

The plotutils package contains two important pieces: a header file plotter.h and a library file libplotter.a (or something similar).

We know that a #include "plotter.h" directive is needed but if the file is not in a standard location, then we need to tell the compiler where to find this file. We also need to tell the compiler to use the plotter library and (if necessary) where this file is located.

Suppose the program is named xo.cc. If plotter.h and the plotter library are in standard locations, the compiler command is as simple as this:

g++ xo.cc -lplotter

Strings, Input/Output, and Visualization

321

However, if the compiler cannot find these files, it will complain. For the g++ compiler (and many others), these are specified using the -I (for headers) and -L (for libraries) options. For example, suppose these files are located on your computer as follows.

Header file

/home/zelda/include/plotter.h

Library file

/home/zelda/lib/libplotter.a

To compile, use the following command.

g++ xo.cc -I/home/zelda/include -L/home/zelda/lib -lplotter

(See Appendix A.1.4 for more detail about the -I, -L, and -l options.)

When you run the program (named a.out or something similar) it produces the file xo.eps. You can now use this file in a LATEX document or for whatever purpose you wish. The output of the program is shown in Figure 14.2.

The full code for the program xo.cc follows.

Program 14.11: A program to draw the symbol .

1 #include <iostream>

2#include <fstream>

3 #include "plotter.h"

4using namespace std;

5

6const char* IMAGE_FILE = "xo.eps";

7

8int main(int argc, char** argv) {

9

10// Construct the plotter

11ofstream pout(IMAGE_FILE);

12PlotterParams params;

13PSPlotter P(cin, pout, cerr, params);

14

15// Open the plotter

16if (P.openpl() < 0) {

17cerr << "Unable to open plotter" << endl;

18return 1;

19}

20

21// Set up my coordinate system

22P.fspace(-1.5, -1.5, 1.5, 1.5);

23

24// Set drawing parameters

25P.flinewidth(0.05);

26P.pencolorname("black");

27

28// Draw the X

29P.line(-1,-1,1,1);

30P.line(-1,1,1,-1);

31

32// Draw the circle

33P.fcircle(0.,0.,sqrt(2.));

34

35 // Close the image

322

C++ for Mathematicians

36if (P.closepl() < 0) {

37cerr << "Unable to close plotter" << endl;

38return 1;

39}

40

41return 0;

42}

Figure 14.2: The symbol drawn by Program 14.11.

Although we can use C++ to draw pictures such as , mouse-driven drawing programs are more convenient for producing such simple diagrams. The advantage in using C++ to draw accrues when we want to visualize a mathematical object that requires nontrivial computation to produce.

We demonstrate this now with three examples.

14.8.3 Pascal’s triangle modulo two

Pascal’s triangle is a table of the binomial coefficients; the nth row (beginning with n = 0) contains the values n0 , n1 , . . . , nn . There are myriad interesting properties of binomial coefficients and Pascal’s triangle. One of the more striking is to look at Pascal’s triangle with entries taken modulo 2. That is, we plot a black point for each odd entry of Pascal’s triangle (and leave even entries blank).

Because the image is an array of dots (pixels), we use a GIFPlotter object to do the drawing. Although we could create an array to hold all the entries in Pascal’s triangle, we opt instead to create two arrays to hold individual rows (the current row built from the previous row).

Here is the code which should be self explanatory.

Program 14.12: A program to visualize Pascal’s triangle modulo 2.

1 #include <iostream>

2#include <fstream>

3 #include "plotter.h"

4using namespace std;

5

Strings, Input/Output, and Visualization

323

6const char* IMAGE_FILE = "pascal.gif";

7 const int NROWS = 2048;

8

9int main() {

10

11// Place to hold row of Pascal’s triangle and a copy to generate the

12// next row.

13int row[NROWS+1];

14int prev_row[NROWS+1];

15for (int i=0; i<=NROWS; i++) {

16row[i] = 0;

17prev_row[i] = 0;

18}

19

20// Construct the plotter

21PlotterParams params;

22ofstream pout(IMAGE_FILE);

23GIFPlotter P(cin, pout, cerr, params);

24

25// Open the plotter

26if (P.openpl() < 0) {

27cerr << "Unable to open plotter" << endl;

28return 1;

29}

30

31// Set up my coordinate system

32P.space(-1,-1,NROWS+1,NROWS+1);

33

34// Set drawing parameters

35P.pencolorname("black");

36

37// Generate rows of Pascal’s triangle (mod 2)

38for (int r=0; r<NROWS; r++) {

39row[0] = 1;

40for (int j=1; j<r; j++) {

41row[j] = (prev_row[j-1]+prev_row[j])%2;

42}

43row[r] = 1;

44

45// Plot points for this row

46for (int j=0; j<=r; j++) {

47if (row[j] == 1) {

48P.point(NROWS-r,j);

49}

50}

51

52// copy row to prev_row

53for (int j=0; j<=r; j++) prev_row[j] = row[j];

54}

55

56// Close the image

57if (P.closepl() < 0) {

58cerr << "Unable to close plotter" << endl;

59return 1;

60}

61

324

C++ for Mathematicians

62return 0;

63}

The output of this program (Figure 14.3) is the beautiful fractal known as Sierpinski’s triangle.

Figure 14.3: Visualizing Pascal’s triangle modulo 2.

14.8.4 Tracing the motion of a point moving randomly in a triangle

The next example visualizes the following process. Three fixed points (named A, B, and C) are located at the vertices of an equilateral triangle. A moving point X begins at one of these locations and then moves to a new location at random. At each step, the new location is the midpoint of one of the segments AX, BX, CX—each with probability 13 . Imagine that each time we find a new location for X, we plot a point in the plane. After many iterations, what do we see?

Program 14.13 draws a picture by precisely this mechanism. Because the image is a collection of pixels, it makes sense to use a GIFPlotter to produce a gif image

Strings, Input/Output, and Visualization

325

file.

The code is reasonably straightforward. One feature we want to emphasize is the procedure draw_point on lines 11–13. This procedure takes two arguments: a Plotter and a Point (see Chapter 6). This procedure is invoked inside main on line 78 where it is passed a GIFPlotter named P (and a Point named X). At first glance this may appear to be a programming error: the procedure requires a Plotter but is invoked with a GIFPlotter. This mismatch of types is not a problem because a GIFPlotter is a subclass of Plotter.

Program 14.13: A program to plot points in a triangle by a random method.

1 #include <iostream>

2#include <fstream>

3 #include "plotter.h"

4#include "Point.h"

5 #include "uniform.h"

6using namespace std;

7

8const char* IMAGE_FILE = "dance.gif";

9

10// Draw a point on a plotter.

11void draw_point(Plotter& P, const Point& X) {

12P.fpoint(X.getX(), X.getY());

13}

14

15int main(int argc, char** argv) {

16// check that user specified number of points

17if (argc < 2) {

18cerr << "Usage: " << argv[0] << " npoints" << endl;

19return 1;

20}

21

22 int NPOINTS;

23

24// convert 2nd arg to an integer; make sure its positive

25NPOINTS = atoi(argv[1]);

26if (NPOINTS < 1) {

27cerr << "Usage: " << argv[0] << " npoints" << endl;

28cerr << "where npoints is a positive integer" << endl;

29return 1;

30}

31

32// Set the corners of the triangle

33Point A(0.,0.);

34Point B(1.,0.);

35Point C(0.5, sqrt(3.)/2.);

36

37 Point X; // the dancing point

38

39// Construct the plotter

40PlotterParams params;

41ofstream pout(IMAGE_FILE);

42GIFPlotter P(cin, pout, cerr, params);

43