1. Trang chủ
  2. » Công Nghệ Thông Tin

C++ for Mathematicians An Introduction for Students and Professionals phần 5 pdf

52 535 1

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 52
Dung lượng 2,14 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

10.5 Class and file organization for PPoint and PLineBecause of the duality between points and lines in the projective plane, most ofthe data and code we use to represent these concepts

Trang 1

35 B.print(); cout << endl;

36 // cout << B.sum() << endl; // Illegal, sum is protected

TheBaseclass has two data members: a private integeraand a protected integer

b The class also includes a protected method namedsum, a public constructor, and

a public method namedprint

ClassChildhas no additional data members It has a public methodincrease_bthat increases the data memberb by one Note that it would not be possible forChildto have a similar method for increasinga The constructor forChildpassesits arguments on to its parent,Base, but then takes no further action (hence the curlybraces on line 17 do not enclose any statements)

Theprintmethod forChildusesBase’sprintmethod andsummethod.Class GrandChildadds an extra private data element, c The constructor forGrandChildpasses its first two arguments to its parent’s constructor and then usesthe third argument to set the value ofc

Theprintmethod forGrandChilduses its grandparent’sprintmethod thoughBase::print()invokes thesummethod, theGrandChildmethods cannotdirectly callsumbecause it is protected inBase, hence implicitly private inChild,and hence inaccessible inGrandChild

Al-Amainto illustrate all these ideas begins on line 30 The output of the programfollows

Trang 2

10.5 Class and file organization for PPoint and PLine

Because of the duality between points and lines in the projective plane, most ofthe data and code we use to represent these concepts in C++ are the same If at allpossible, we should avoid writing the same code twice for two reasons First, theinitial work in creating the programs is doubled More important, maintaining thecode is also made more difficult; if a change is required to the code, we need toremember to make that change twice When we are fussing with our programs andmaking a number of minor modifications, it is easy to forget to update both versions

To illustrate this, we deliberately include a subtle flaw in the first version of theprogram; we then repair the problem once (not twice)

To this end, we define three classes: a parent class namedPObjectthat contains

as much of the code as possible and two derived classes,PPointandPLine, thatinclude code particular to each These classes are defined in two files each: a header.hfile and a code.ccfile Figure 10.3 illustrates the organization

PObject

PObject.h PObject.cc

PPoint

PPoint.h PPoint.cc

PLine

PLine.h PLine.cc

Figure 10.3: Hierarchy of the PObject classes

The header filePObject.his used to declare thePObjectclass BothPPoint.handPLine.h require the directive#include "PObject.h" Programs that useprojective geometry need all three Rather than expecting the user to type multiple

#includedirectives, we create a convenient header file that includes everything weneed We call this header fileProjective.h; here it is

Program 10.3: Header file for all projective geometry classes, Projective.h

Trang 3

Notice that we do not require#include "PObject.h" because that file is ready#included byPPoint.handPLine.h All these files have the usual struc-ture to prevent multiple inclusion.

al-All told, we have seven files that implement points and lines in the projectiveplane

PObject.h The header file for thePObjectclass

PObject.cc The C++ code for thePObjectclass

PPoint.h The header file for thePPointclass

PPoint.cc The C++ code for thePPointclass

PLine.h The header file for thePLineclass

PLine.cc The C++ code for thePLineclass

Projective.h The only header file subsequent programs need to include to use

projective points and lines

The decision to write the program in seven different files is based on the principle

of breaking a problem down to manageable sizes and working on each piece arately We could have packed all this work into two larger files (one.hand one.cc)

sep-10.6 The parent class PObject

Data and functionality common to PPointandPLine are implemented in theclass PObject Using the ideas presented in Section 10.2, we map out the classPObject

Data A point or line inRP2is represented by homogeneous coordinates:(x,y,z) or [x,y,z] To hold these coordinates, we declare three private double variables,

x,y, andz Let us writex,y,z for the homogeneous coordinates of a generic

projective object (either a point of a line) (See Program 10.4, line 9.)Because 2,1,−5 and −4,−2,10 name the same object, it is useful to

choose a canonical triple One idea is to make sure thatx,y,z is a unit vector

(but then we have a sign ambiguity) The representation we use is to make thelast nonzero coordinate of the triple equal to 1 The motivation for this choice

is that a point in the Euclidean plane at coordinates(x,y) corresponds to the

point(x,y,1) in the projective plane.

If later we are unhappy with this decision, we can choose another manner

to store the homogeneous coordinates Because the coordinates are privatemembers ofPObject, we would only need to update the code forPObject

Trang 4

We provide public methodsgetX(), getY(), andgetZ() to inspect (butnot modify) the values held inx,y, andz, respectively (See Program 10.4lines 33–35.)

We reserve0,0,0 to stand for an invalid projective object We provide a

pub-lic methodis_invalid()to check if an object is invalid (See Program 10.4lines 37–39.)

Constructors It is natural to define a constructor for aPObjectwith three ters that set the homogeneous coordinates of the object The user might invokethe constructor like this:

parame-PObject P(2., 3., 5.);

Rather than holding this point as2,3,5, we use the canonical representation

0.4,0.6,1 (See Program 10.4 lines 25–30.)

To facilitate the conversion of user-supplied coordinates to canonical nates, we create a private helper procedurescale (See Program 10.4 line 10and Program 10.5 lines 4–19.)

coordi-All C++ classes ought to define a zero-argument constructor that creates adefault object of that class In this case, a sensible choice is the object0,0,1.

This corresponds to the origin (as a point) or the line at infinity (as a line) (SeeProgram 10.4 lines 21–24.)

Relations We want to be able to compare projective points (and lines) for equality,

inequality, and< (for sorting) To this end, we might be tempted to define

operator==in the public portion ofPObjectlike this:

public:

bool operator==(const PObject& that) const {

return ( (x==that.x) && (y==that.y) && (z==that.z) );}

Although this would give the desired result when comparing points to points

or lines to lines, it would also provide the unfortunate ability to compare points

to lines Were we to use the above method for testing equality, then we mightrun into the following situation

There are at least two problems with this approach First, the point(2,3,5) and

the line[2,3,5] are not equal even though they have the same homogeneous

Trang 5

coordinates Second, lines and points should not even be comparable by==.Code that compares a point to a line is almost certainly a bug; the best thing inthis situation is for the compiler to flag such an expression as an error.

We therefore take a different approach to equality testing We want the damental code that checks for equality to reside inside thePObjectclass be-cause that code is common to bothPPointandPLine We do this by declar-ing a protected method called equalsinside PObject (see Program 10.4line 14):

fun-protected:

bool equals(const PObject& that) const;

In the filePObject.ccwe give the code for this method (see Program 10.5lines 34–36):

bool PObject::equals (const PObject& that) const {

return ( (x==that.x) && (y==that.y) && (z==that.z) );}

Then, in the class definitions forPPointandPLinewe give the necessaryoperator definitions For example, inPLinewe have this:

Meet/Join Operation Given twoPPointsPandQ, we wantP+Qto return the linethrough those points Dually, givenPLinesLandM, we wantL*Mto returnthe point of intersection of these lines

In both cases, the calculations are the same: given the triplesx1,y1,z1 and

x2,y2,z2 we need to find a new triple x3,y3,z3 that is orthogonal to the first

two To do this, we calculate the cross product:

x3,y3,z3 = x1,y1,z1 × x2,y2,z2.

Therefore, inPObjectwe declare a protected method calledopthat is invoked

byoperator+ inPPointand operator*inPLine (See Program 10.4line 18 and Program 10.5 lines 138–148.)

Trang 6

Incidence Given a point and a line, we want to be able to determine if the point lies

on the line If the coordinates for these are(x,y,z) and [a,b,c], respectively, then we simply need to test if ax + by + cz = 0.

It does not make sense to ask if one line is incident with another, so we do notmake an “is incident with” method publicly available inPObject Rather, wemake a protectedincidentmethod (that calls, in turn, a privatedotmethodfor calculating dot product) (See Program 10.4 lines 11,16 and Program 10.5lines 21–23,47–49.)

Then,PPointandPLinecan declare their own public methods that accessincident Details on this later

Collinearity/Concurrence Are three given points collinear? Are three given lines

concurrent? If the three objects have coordinatesx1,y1,z1, x2,y2,z2, and

x3,y3,z3, then the answer is yes if and only if the vectors are linearly

depen-dent We check this by calculating

Random points/lines There is no way to generate a point uniformly at random in

the Euclidean plane, but there is a sensible way in which we can do this forthe projective plane Recall that points inRP2correspond to lines through theorigin inR3 Thus, to select a point at random inRP2 we generate a pointuniformly at random on the unit ball centered at the origin An efficient way to

perform this latter task is to select a vector v uniformly in[−1,1]3 If v > 1,

then we reject v and try again.

The method for generating a random line is precisely the same

We therefore include a public methodrandomizethat resets the coordinates

of thePObjectby the algorithm we just described (See Program 10.4 line 31and Program 10.5 lines 25–32.)

To choose a random line through a point is similar Suppose the point is

(x,y,z) The line [a,b,c] should be chosen so that [a,b,c] is orthogonal to (x,y,z) To do this, we find an orthonormal basis for (x,y,z) ⊥that we denote

{(a1,b1,c1),(a2,b2,c2)} We then choose t uniformly at random in [0,2π].The random line[a,b,c] is given by

a = a1cost + a2sint b = b1cost + b2sint c = c1cost + c2sint.

Trang 7

Rather than generate t and then compute two trigonometric functions, we can

obtain the pair(cost,sint) by choosing a point uniformly at random in the unit

disk (inR2) and then scaling

The technique for selecting a random point on a given line is exactly the same.Thus, we define a protectedrand_perpmethod inPObject(Program 10.4line 17 and Program 10.5 lines 79–136)

Therand_perpmethod is used byrand_pointinPLineandrand_line

14 bool equals(const PObject& that) const;

15 bool less(const PObject& that) const;

16 bool incident(const PObject& that) const;

17 PObject rand_perp() const;

18 PObject op(const PObject& that) const;

Trang 8

34 double getY() const { return y; }

35 double getZ() const { return z; }

36

37 bool is_invalid() const {

38 return (x==0.) && (y==0.) && (z==0.);

21 double PObject::dot(const PObject& that) const {

22 return x*that.x + y*that.y + z*that.z;

34 bool PObject::equals(const PObject& that) const {

35 return ( (x==that.x) && (y==that.y) && (z==that.z) );

36 }

37

38 bool PObject::less(const PObject& that) const {

39 if (x < that.x) return true;

Trang 9

40 if (x > that.x) return false;

41 if (y < that.y) return true;

42 if (y > that.y) return false;

43 if (z < that.z) return true;

73 double det = a1*b2*c3 + a2*b3*c1 + a3*b1*c2

74 - a3*b2*c1 - a1*b3*c2 - a2*b1*c3;

75

76 return det == 0.;

77 }

78

79 PObject PObject::rand_perp() const {

80 if (is_invalid()) return PObject(0,0,0);

81

82 double x1,y1,z1; // One vector orthogonal to (x,y,z)

83 double x2,y2,z2; // Another orthogonal to (x,y,z) and (x1,y1,z1) 84

85 if (z == 0.) { // If z==0, take (0,0,1) for (x1,y1,y2)

Trang 10

96 else { // y and z both nonzero, use (0,-z,y)

143 double c1 = y*that.z - z*that.y;

144 double c2 = z*that.x - x*that.z;

145 double c3 = x*that.y - y*that.x;

146

147 return PObject(c1,c2,c3);

148 }

Trang 11

10.7 The classes PPoint and PLine

With the code forPObjectin place, we are ready to finish our work by writing thefiles for the classesPPointandPLine We do this work in four files: PPoint.h,PPoint.cc,PLine.h, andPLine.cc(Programs 10.6 through 10.9)

As we start to write these files, we meet a chicken-and-egg problem Which do

we define first: the class PPointor the class PLine? For us, this is more than

a philosophical conundrum SomePLinemethods need to refer toPPoints Forexample, operator*acts on lines to produce points and PLine’srand_pointmethod returns aPPoint Dually, some of thePPointmethods require thePLineclass

Here is how we solve this dilemma In the PPoint.hfile, before we give thedefinition ofPPointwe have the following statement (see line 6 of Program 10.6),

Dually, we include the statementclass PPoint;in the filePLine.h

We focus our attention on the classPPoint The analysis ofPLineis similar.For the classPPointwe have five constructors At first, this may seem to be toomany, but we show how to do this easily

Of course, we want a three-argument constructorPPoint(a,b,c)that createsthe point(a,b,c) We also want a zero-argument constructorPPoint()that createsthe point(0,0,1) corresponding to the origin.

It makes sense to define a two-argument constructorPPoint(a,b)to create thepoint(a,b,1) In a sense, this maps the Euclidean point (a,b) to its natural corre-

spondent in theRP2

What should be the action of a single-argument constructor? The real number a

can be identified with the point(a,0) on the x-axis, and this in turn corresponds to (a,0,1) in RP2 In summary, we have the following constructors and their effects

Constructor form Point createdPPoint P(); (0,0,1)

PPoint P(a); (a,0,1)

PPoint P(a,b); (a,b,1)

PPoint P(a,b,c); (a,b,c)

The great news is that we can implement these four constructors with a single

definition using default parameters.

Trang 12

For any C++ procedure (class method or free-standing procedure), default valuescan be specified In the.hfile, where procedures are declared, we use a syntax such

as this:

return_type procedure_name(type arg1 = val1, type arg2 = val2, );

Then, when the procedure is used, any missing parameters are replaced by their fault values Let’s look at a concrete example We declare a procedure namednextthat produces the next integer after a given integer (This is a contrived example, but

de-we want to keep things simple.) In the header file de-we put the following,

int next(int num, int step=1);

And in the.ccfile, we have the code,

int next(int num, int step) {

return num+step;

}

Notice that the argumentstepis given a default value,1 However, the argumentnumis not given a default It is permissible to specify only a subset of the argumentsthat receive default values, but if an argument has a default value, all arguments toits right must also have default values

Notice that the optional arguments are not reiterated in the.ccfile

Consider the following code

int a,b,c;

a = next(5);

b = next(5,1);

c = next(5,2);

This will setaandbequal to 6 andcequal to 7

Alternatively, we could have given an inline definition ofnextin the header filelike this:

inline int next(int num, int step=1) { return num+step; }

Returning toPPoint, the four constructors (with zero to threedoublearguments)can all be declared at once like this:

PPoint(double a=0., double b=0., double c=1.)

See line 10 of Program 10.6 The action of this constructor is simply to pass thethree arguments up to its parent (line 11) and then there is nothing else to do Toshow there is nothing else, we give a pair of open/close braces that enclose emptyspace (line 12)

The class PPointneeds one more constructor Recall that PObjectprovidesmethods such asrand_perpandopthat returnPObjects However, when these areused byPPointorPLine, thePObjectvalues need to be converted to typePPoint

orPLine as appropriate To do thisPPointprovides a constructor that accepts asingle argument of typePObject Here is the simple code (see also lines 14–15 ofProgram 10.6)

Trang 13

PPoint(const PObject& that) :

PObject(that.getX(), that.getY(), that.getZ()) { }

This code sends thex,y, andzvalues held inthatup to the parent constructor andthen does nothing else Now, if we want to assign aPObjectvalue to aPPointobject, we can do it in the following ways

R = X; // implicit conversion (compiler figures out what to do)

Here is thePPoint.hheader file

Program 10.6: Header file for the PPoint class

14 PPoint(const PObject& that) :

15 PObject(that.getX(), that.getY(), that.getZ()) { }

Trang 14

The parts ofPPointnot given inPPoint.hare defined inPPoint.ccwhich wepresent next.

Program 10.7: Program file for the PPoint class

1 #include "Projective.h"

2

3 ostream& operator<<(ostream& os, const PPoint& P) {

4 os << "(" << P.getX() << "," << P.getY() << "," << P.getZ() << ")";

Trang 15

14 PLine(const PObject& that) :

15 PObject(that.getX(), that.getY(), that.getZ()) { }

3 ostream& operator<<(ostream& os, const PLine& P) {

4 os << "[" << P.getX() << "," << P.getY() << "," << P.getZ() << "]";

Trang 16

17 return incident(that);

18 }

10.8 Discovering and repairing a bug

With the projective point and line classes built, it is time to test our code Here is

a simple main to perform some checks

Program 10.10: A main to test theRP2classes

20 cout << "Is P on L? " << P.is_on(L) << endl;

21 cout << "Does M have P? " << M.has(P) << endl;

28 cout << "Is Q on L? " << Q.is_on(L) << endl;

29 cout << "Does M have Q? " << M.has(Q) << endl;

Trang 17

When this program is run, we have the following output:

The random point P is (-1.32445,0.591751,1)

Two lines through P are L = [6.51303,12.8875,1]

on here!?

All these problems stem from the same underlying cause: roundoff Rememberthat adoublevariable is a rational approximation to a real number Two real quanti-ties that are computed differently may result indoublevalues that are different Forexample, consider this code:

cout << "They are different" << endl;

cout << "Difference = " << x-y << endl;

The bad news is we need to rewrite some of our code to correct this problem Thegreat news is that we only need to repairPObject The childrenPPointandPLineinherit the improvements

Trang 18

To begin, let us identify the places in the code forPObjectwhere exact equality

is sought

• Theequalsmethod requires exact equality of the three coordinates

• Theincidentmethod requires zero to be the exact result of thedotproductmethod

• Thedependentprocedure requires zero to be the exact value of the nant

determi-In lieu of exact equality, we can require that the values be within a small tolerance.What tolerance should we use? We make that quantity user selectable initialized withsome default value (say 10−12)

To implement this idea we add a private staticdoublevariable namedtoleranceand define a constant nameddefault_toleranceset to 10−12

InsidePObjectwe define two inline public static methods:set_toleranceandget_tolerance Here is the revised header file

Program 10.11: Header file for the PObject class (version 2)

13 double dot(const PObject& that) const;

14 static double tolerance;

15

16 protected:

17 bool equals(const PObject& that) const;

18 bool less(const PObject& that) const;

19 bool incident(const PObject& that) const;

20 PObject rand_perp() const;

21 PObject op(const PObject& that) const;

Trang 19

44 double getX() const { return x; }

45 double getY() const { return y; }

46 double getZ() const { return z; }

47

48 bool is_invalid() const {

49 return (x==0.) && (y==0.) && (z==0.);

Program 10.12: Program file for the PObject class (version 2)

Trang 20

21 }

22

23 double PObject::dot(const PObject& that) const {

24 return x*that.x + y*that.y + z*that.z;

36 bool PObject::equals(const PObject& that) const {

37 double d = abs(x-that.x) + abs(y-that.y) + abs(z-that.z);

38

39 return d <= tolerance;

40 }

41

42 bool PObject::less(const PObject& that) const {

43 if (x < that.x) return true;

44 if (x > that.x) return false;

45 if (y < that.y) return true;

46 if (y > that.y) return false;

47 if (z < that.z) return true;

48 return false;

49 }

50

51 bool PObject::incident(const PObject& that) const {

52 return abs(dot(that)) <= tolerance;

Trang 21

77 double det = a1*b2*c3 + a2*b3*c1 + a3*b1*c2

78 - a3*b2*c1 - a1*b3*c2 - a2*b1*c3;

79

80 return abs(det) <= PObject::get_tolerance();

81 }

82

83 PObject PObject::rand_perp() const {

84 if (is_invalid()) return PObject(0,0,0);

85

86 double x1,y1,z1; // One vector orthogonal to (x,y,z)

87 double x2,y2,z2; // Another orthogonal to (x,y,z) and (x1,y1,z1) 88

89 if (z == 0.) { // If z==0, take (0,0,1) for (x1,y1,y2)

Trang 22

147 double c1 = y*that.z - z*that.y;

148 double c2 = z*that.x - x*that.z;

149 double c3 = x*that.y - y*that.x;

The random point P is (-0.479902,-0.616199,1)

Two lines through P are L = [0.384191,1.32364,1]

previ-Finally, the method we use for testing near equality can be improved For example,

we check if two projective objects are equal by computing their L1 distance andcomparing againsttolerance Alternatively, to checkx1,y1,z1 and x2,y2,z2

for equality, we might consider a test such as this:

|x1− x2| + |y1− y2| + |z1− z2|

|x1| + |x2| + |y1| + |y2| + |z1| + |z2| ≤ε.

Whatever equality test you feel is most appropriate, it is only necessary to edit onemethod (equalsinPObject) to implement your choice

Trang 23

10.9 Pappus revisited

We close this section with a program to illustrate Pappus’s Theorems and the use

of the near-equality testing

Program 10.13: A program to illustrate Pappus’s theorem and its dual

22 cout << "Three points on the first line are " << endl

23 << P1 << endl << P2 << endl << P3 << endl;

30 cout << "Three points on the second line are " << endl

31 << Q1 << endl << Q2 << endl << Q3 << endl;

38 cout << "The three points constructed are " << endl;

39 cout << X1 << endl << X2 << endl << X3 << endl;

Trang 24

63 cout << "The three lines through the first point are " << endl

64 << L1 << endl << L2 << endl << L3 << endl;

71 cout << "The three lines through the second point are " << endl

72 << M1 << endl << M2 << endl << M3 << endl;

79 cout << "The three lines constructed are " << endl;

80 cout << X1 << endl << X2 << endl << X3 << endl;

Trang 25

Enter desired tolerance > 0

You set the tolerance to 0

The two lines are

TROUBLE! The three points are not collinear!!

The two points are

Enter desired tolerance > 1e-16

You set the tolerance to 1e-16

The two lines are

Trang 26

(0.160804,-3.85329,1)

(-0.310677,-1.18769,1)

They are collinear, as guaranteed by Pappus’s theorem

The two points are

Enter desired tolerance > 1e-12

You set the tolerance to 1e-12

The two lines are

They are collinear, as guaranteed by Pappus’s theorem

The two points are

Ngày đăng: 12/08/2014, 12:20

TỪ KHÓA LIÊN QUAN

TÀI LIỆU CÙNG NGƯỜI DÙNG

TÀI LIỆU LIÊN QUAN