home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-386-Vol-2of3.iso / b / bump.zip / BUMP.DOC < prev    next >
Text File  |  1992-02-10  |  18KB  |  359 lines

  1.  
  2.                                  BUMP 1.1
  3.  
  4.                 A Beginner's Understandable Matrix Package 
  5.                                in Borland C++
  6.  
  7. From the dawn of programming, people have wanted to be able to write
  8. something like
  9.  
  10.      Matrix A(4,4), B(4,4), C(4,4), D(4,4);
  11.      ...
  12.      A = (B + C)* D;
  13.  
  14. and get in the A matrix the result expected from matrix algebra.  The best
  15. that languages like Fortran, Basic, Pascal, or C could offer, however, was
  16. either a lot of function calls or loops on subscripts.  Special matrix
  17. interpreters were developed which, however, limited what you could do.  If
  18. what you wanted to do, say graph the diagonal of a matrix, couldn't be done
  19. by your package, you were stuck.  Also, you frequently had to master large
  20. manuals for one special-purpose program.   With C++, however, it is
  21. possible for the user to define a data type "Matrix" and then write
  22. functions to "overload" the =, +, and * operators so that when the compiler
  23. recognizes that, say, a + is between two matrices, it will call the user-
  24. written function to add the two matrices.  At the same time, you have all
  25. the flexibility of C and C++ and very little to learn in the way of
  26. special-purpose instructions.   It is just a matter of writing the
  27. necessary code to define the Matrix and Vector data types and to overload
  28. the operators.  
  29.  
  30. Since matrices are so widely used, I had expected that it would be easy to
  31. find examples in books of exactly how to define the data types and write
  32. the overloaded operator functions.  I found hints and fragments, but no
  33. fully worked out satisfactory solution.  In particular, the examples in the
  34. books tended to be bad about loosing track of memory that had been
  35. allocated for temporary matrices (like (B + C) in the above example), so
  36. that repeated use of equations like the one above would eventually stop the
  37. program.  Design of the destructors was of the essence for this problem,
  38. and it gets scant attention in most introductions to C++.  On the bulletin
  39. boards, I found two extensive examples at the opposite extreme, Newmat and
  40. Yamp.  They were too complicated to serve as examples.  I learned some
  41. things from them but could not begin to really follow how they worked.  So
  42. I set out on my own to see if I could apply what I thought I knew.  It was
  43. a time-consuming process full of mistakes and enigmatic compiler messages. 
  44. But the result, BUMP, seems well worth the trouble.
  45.  
  46. In BUMP you will find applications of most of the C++ techniques.  It has
  47. classes with functions, derived classes with inheritance, non-trivial
  48. constructors and destructors, overloaded operators, and even a virtual
  49. function.  In the hope that it may be useful to others both as an example
  50. and as a useable product, I have made it publicly available.  I hope that
  51. BUMP may help you over the hump to effective use of C++.
  52.  
  53. BUMP is intentionally a minimal package so that the user can understand it
  54. and extend it in directions of interest to him.  Before trying to read the
  55. code, however, it is probably a good idea to use BUMP as it is a bit so
  56. that you know what its functions do before you trying to read them.
  57. Accordingly, I'll first explain how to use it, and then add a few notes on
  58. reading the code.
  59.  
  60. Use of BUMP
  61.  
  62. To use BUMP as it is, you write a program in C++.  Don't worry if you know
  63. only C and not C++; the code you write can be pure C except for your use of
  64. BUMP's matrix and vector objects.  You must compile your program with the
  65. Borland C++ compiler, so the extension of its filename should be ".cpp". 
  66. Your program should have among the header lines the line 
  67.      #include "bump.h";
  68.  
  69. and the linker command should link in bump.obj.  Specific directions for
  70. compiling and linking the example are given below. 
  71.  
  72. Those simple measures are all that is necessary to be able to write a
  73. program like the following, which illustrates some of the capabilities of
  74. BUMP:
  75.  
  76.      Vector a(4),at(1,4),c(4),ct(1,4);
  77.      Matrix A(4,4),B(4,4),C(4,4),D(4,1),E(1,1);
  78.  
  79.      // Read in the matrix from an ASCII file and display it
  80.      A.ReadA("amatrix");
  81.      A.Display("Here is the A matrix:");
  82.  
  83.      // Pre-multiply it by a scalar and divide it by a scalar
  84.      B = 2.*A;
  85.      B.Display("This is 2.*A.");
  86.      B = A/2.;
  87.      B.Display("and this is A/2.");
  88.      tap();
  89.  
  90.      // Invert it. A ! in front of the matrix is the sign for inverse.
  91.      B = !A; 
  92.      B.Display("and here is B = !A ( A inverse):");
  93.  
  94.      // Multiply two matrices:
  95.      C = A*B;
  96.      // This next Display will have column width of 15 and 6 decimal places
  97.      C.Display("This is A*!A (should be I):",15,6);
  98.  
  99.      // Parentheses work as expected:
  100.      B = (A + A)*!A;
  101.      B.Display("B = (A+A)*!A. Should be 2I");
  102.  
  103.      C = A*A;
  104.      C.Display("A*A:");
  105.      C = (A+A)*(A+A);
  106.      C.Display("(A+A)*(A+A). Should be 4 times the matrix above.");
  107.  
  108.      // Take the transpose. A ~ in front of a matrix indicates transpose.
  109.      B = ~A;
  110.      A.Display("Here is A again.");
  111.      B.Display("and this is ~A, A transpose.");
  112.  
  113.      // Now some tests with vectors
  114.      a.ReadA("avector");
  115.      at = ~a;
  116.      a.Display("This is the a vector:");
  117.      at.Display("and this is its transpose. The difference doesn't show.");
  118.      D << a;
  119.      D.Display("And this is D << a :");
  120.  
  121.      c = A*a;
  122.      c.Display("This is  A*a");
  123.      ct = ~a*~A;
  124.      ct.Display("This is ~a*~A. Should look the same as the above.");
  125.  
  126.      // Tests of the / operator.  A/A is A'A computed without taking A'.
  127.      B = ~A*A;
  128.      B.Display("This is ~A*A");
  129.      B = A/A;
  130.      B.Display("This is A/A. Should be the same as the above.");
  131.  
  132.      c = A/a;
  133.      ct = a/A;
  134.      c.Display("This is A/a:");
  135.      ct.Display("This is a/A. Should look like the above.");
  136.  
  137.      E = a/a;
  138.      B = at/at;
  139.      E.Display("This is a/a:");
  140.      B.Display("This is at/at:");
  141.      printf("\nEnd of test.\n");
  142.      }
  143.  
  144. From this example, we see that BUMP has an extended data type Matrix.  A
  145. matrix has a function (ReadA) to read data into it from an ASCII file and
  146. another function (Display) to display the matrix on the screen.  The =,
  147. +, -, and * operators have their expected definitions.  Parentheses work in
  148. the expected way. A ~ in front of a Matrix calculates its transpose, while
  149. a ! in front of a Matrix calculates its inverse (by a primitive algorithm).
  150. Neither affects the Matrix itself. (Perhaps you don't like ~ for
  151. transposition and ! for inversion.  The ' operator, however, is not
  152. available in C++ for overloading; the ^, which I would have preferred for
  153. inversion, is a binary operator in C++ and cannot be used for a unary
  154. process like inversion or transposition.  There is, in fact, not much
  155. alternative to ~ and ! for these two unary operations.)
  156.  
  157. BUMP also has a Vector data type. All the same functions and operators
  158. (except, of course, !) apply to Vectors, and to Vectors and Matrices
  159. together, where dimensions are appropriate. 
  160.  
  161. Pre-multiplication of a Vector or Matrix by a float (a scalar) has been
  162. defined, as has division of a Matrix or Vector by a float.  I did not
  163. bother to define post-multiplication of a vector or matrix by a float.
  164.  
  165. Matrix division does not occur, so the / operator is available to mean
  166. something else.  In statistical computations, expressions like X'X or Z'X -
  167. - ~X*X or ~Z*X in BUMP notation -- occur frequently with X' or Z' being
  168. large matrices so that it undesirable to clog up scarce memory by actually
  169. taking the transpose.  These products can, of course, be computed directly
  170. from X and Z.  That is what Z/X does in BUMP; it computes ~Z*X without ever
  171. creating ~Z.
  172.  
  173.  
  174.    To convert a Vector, v, to a Matrix, M, one uses
  175.  
  176.      M << v;
  177.  
  178. Finally, element i of Vector v is denoted by v[i] on either side of the =
  179. sign, while element (i,j) of Matrix M is denoted by M[i][j] on either side
  180. of an =.  The Vector elements are range-checked on both sides of an = sign.
  181. Thus, if v has 20 elements and you ask for v[23], you will get an error
  182. message. The range checking on the Matrix element is only on the row index. 
  183. (A reference to B[i][j] actually calls the B[i] function in BUMP, which
  184. returns a pointer to the ith row of B.  The [j] then functions as
  185. subscripts normally do in C.)  You can use these functions to do anything
  186. to your matrices which BUMP does not do for you.  For example, if you want
  187. to zero out all the negative elements in the vector x, which has n elements
  188. beginning with 1, then you just do
  189.  
  190.      for(int i = 1; i <= n; i++)
  191.           if(x[i] < 0) x[i] = 0;
  192.  
  193. Thus, you have all the flexibility of C or C++, plus having a nice language
  194. for common matrix operations.  
  195.  
  196. The BUMP package includes an example program, called test.cpp.  To build
  197. it, type (at the DOS prompt) "bump test".  To run it, type "test".  When
  198. you have created your own test, say, "mine.cpp", you would just type "bump
  199. mine" to compile and link the program and "mine" to run it.  You will find
  200. that this process is using the bump.bat file, the bump.mak "Make" file, and
  201. bump.res "response" file for the linker.
  202.  
  203. Perhaps a word of explanation is needed as to why BUMP has the Vector data
  204. type as well as the Matrix data type.  The representation of matrices in
  205. BUMP is that used in Numerical Recipes in C, by William H. Press, et al.
  206. (Cambridge University Press).  This representation uses a vector of
  207. pointers to the rows of the matrix.  Its great advantage is that it allows
  208. arrays of more than 64K bytes to be handled by a 16-bit compiler.  It also
  209. gives the user control over whether the subscripts start at 0, 1, or
  210. whatever.  It is, however, a very inefficient way to represent a matrix
  211. which happens to be a column vector, so that it needs as many pointers to
  212. rows as it has elements.  BUMP therefore has a special data type for
  213. vectors, Vector. 
  214.  
  215. The full form for the declaration of a matrix or vector is
  216.  
  217. Matrix A(nr,nc,temp,fr,fc);
  218. Vector x(nr,nc,temp,fr,fc);
  219.  
  220. where     nr   is the number of rows (no default value);
  221.           nc   is the number of columns (default of 1 for vectors);
  222.           temp is 'y' if the object is temporary, to be destroyed by the
  223.                first operator to use it.  Objects created by an operator
  224.                all have temp set to 'y'.  This device is crucial for
  225.                throwing away the temporaries that get created in the
  226.                evaluation of expressions.  Normally, when a user declares
  227.                an object, he will want temp = 'n', not temporary, which is
  228.                the default value.  All objects are deleted if they "go out
  229.                of scope", that is, if the program exits from the function
  230.                in which they were declared.   
  231.           fr   is the first row; default is 1;
  232.           fc   is the first column; default is 1.
  233. Examples:
  234.      Matrix    A(100,5);           is a 100 X 5 non-temporary matrix with
  235.                                    first row = 1 and first column = 1.
  236.      Matrix    A(35,10,'n',60,0);  is a 35 X 10 non-temporary matrix with
  237.                                    first row = 60 and first column = 0.
  238.  
  239. BUMP has a few functions not needed in the examples above.  If A is a
  240. Vector or Matrix object, then
  241.      A.rows()            gives the number of rows.
  242.      A.columns()         gives the number of columns.
  243.      A.firstrow()        gives the first row (default = 1).
  244.      A.lastrow()         gives the last row.
  245.      A.firstcolumn()     gives the first column (default = 1).
  246.      A.lastcolumn()      gives the last column.
  247.      A.freeh()           frees all the heap memory occupied by A.  This is
  248.                          useful when you need A for several operations but
  249.                          then no longer need it and want to use the space
  250.                          it occupies for something else.  Don't try to use
  251.                          A after doing A.freeh().
  252.  
  253.      For a Matrix only, we also have
  254.      A.invert(int startrow, int endrow)
  255.           Invert the matrix and return the value of its determinant as a
  256.           double.  Note that this operation turns the Matrix A into its
  257.           inverse, whereas !A leaves A unchanged.  Start the pivot
  258.           operations in row startrow and stop when the pivot has been in
  259.           endrow.  If these arguments are omitted, the pivoting starts in
  260.           the first row and continues through the last, to produce the true
  261.           inverse.  The algorithm is Gauss-Jordan pivoting with no
  262.           niceties.  Don't trust it if your matrix poses any problems for
  263.           inversion.
  264.  
  265. Study of the BUMP code
  266.  
  267. To study the C++ techniques used in BUMP, you should begin with the bump.h
  268. file.  You will see that both the Vector and Matrix types are derived from
  269. a class called "vorm"  -- Vector OR Matrix.  Mostly "vorm" just has numbers
  270. of rows and columns and the like.  It does, however, contain one function,
  271. freet(), which is exactly the same for both vectors and matrices. This
  272. freet(), however, calls freeh(), which must be different for vectors and
  273. matrices.  Hence, freeh() is declared in vorm as a virtual function.  The
  274. real versions of freeh() are declared in Vector and Matrix.
  275.  
  276. The "temp" tag in the declaration of the Matrix and Vector data types is
  277. the crucial element for avoiding loss of heap memory as the program
  278. executes.  When an operator creates a matrix or vector in order to have a
  279. place to put what it computes, it sets the "temp" tag of the created object
  280. equal to 'y' to indicate that the object is temporary.  As far as I can
  281. see, temporary objects are needed only once.  Hence, whenever an operator
  282. uses a temporary object, it releases all of the heap memory allocated to it
  283. and marks it (v = 0 or m = 0) so that the automatically called destructor
  284. will not release that memory a second time.  This freeing is done by calls
  285. to freet() in the operator functions.  This freet(), declared in vorm,
  286. tests the temporary flag and, if a temporary is found, frees all the heap
  287. memory allocated object by a call to freeh().  (Hence the name "freet":
  288. FREE Temporary, while "freeh" is FREE Heap memory.)
  289.  
  290. Another crucial point to be noted is described in the long comment in
  291. Vector::Vector(Vector& a) in bump.cpp.  By watching the debugger, I learned
  292. that when a function returns an object that was originally declared in that
  293. function and is therefore "local" to the function, it automatically calls,
  294. before it returns, a constructor to copy the object that is being returned.
  295. The original will then be deleted by an automatic call to the destructor.
  296. This process, if left unchecked, will fragment the heap memory.  BUMP uses
  297. its temp tag so that the constructor detects what is happening and simply
  298. steals the content of the original local object and sets up a flag so that
  299. the destructor, to which that local object is headed, knows not to delete
  300. that content.  Thus, the fragmentation of heap memory is avoided.  I found
  301. watching this process with the debugger very helpful.  
  302.  
  303. The other thing that it took a long time for me to get into my head is the
  304. meaning of the much-used operator & in function declarations.  In
  305.  
  306.      float& Vector::operator [] (int i)
  307.  
  308. for example, what is returned is NOT a pointer to a float, but an lvalue
  309. for the float.  One of the return statements in this function is 
  310.  
  311.      return (v[i-fr]);
  312.  
  313. where v[i-fr] is just a plain old float.  It is the & in the declaration of
  314. the function that turns this float into the lvalue for the float.  Now the
  315. magic thing about lvalues is that on the right of an = sign they are just
  316. numbers, but on the left of an = they are the place the number is stored. 
  317. Thus, if we have
  318.  
  319.      Vector A(10);
  320.      float x;
  321.  
  322.      x = 2.;
  323. then
  324.      x = A[6];
  325. will call the float& Vector::operator [] (int i) function to get the value
  326. of the sixth element of A to put into x, while
  327.  
  328.      A[6] = x;
  329.  
  330. calls exactly the same function to get the address at which to store x so
  331. that it will be the sixth element of A.  Such is the magic of the lvalue
  332. and the & operator. 
  333.  
  334. In the storage of matrices, I have used exactly the device from Numerical
  335. Recipes in C.  In a Matrix with fr and fc (first row and first column) both
  336. 1, m[1][1] will be the very first piece of storage allocated for the
  337. matrix.  In that case, m[0][0] would be outside the range of m and
  338. reference to it would be an error.  I did not follow this convention for
  339. Vectors, where the first element is always in v[0].  I considered switching
  340. over the Vector convention to the Matrix convention, but found that to do
  341. so would simplify only 6 expressions and would complicate or at least not
  342. simplify 31 expressions.  So I did not switch. 
  343.  
  344.                                    * * *
  345.  
  346. BUMP is public domain.  If you find bugs, I would like to know about them. 
  347. My own intentions are to leave BUMP pretty much as it is except for bug
  348. fixes.  I will be working on integrating it into other programs and will
  349. probably develop a facility for sparse matrices, but this will not go into
  350. BUMP, which needs to be left simple enough that the neophyte -- like me --
  351. can understand it.  
  352.  
  353.      Clopper Almon
  354.      Department of Economics
  355.      University of Maryland
  356.      College Park, MD 20742
  357.      Internet: CA10@umail.umd.edu
  358.      Compuserve: 73377,1466
  359.