home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / World_Of_Computer_Software-02-385-Vol-1of3.iso / n / newmat06.zip / NEWMATB.TXT < prev    next >
Text File  |  1992-12-02  |  25KB  |  589 lines

  1. //$$ newmatb.txt                                   Design
  2.  
  3.  
  4. Copyright (C) 1991,2: R B Davies
  5.  
  6. Please treat this as an academic publication. You can use the ideas but
  7. please acknowledge in any publication.
  8.  
  9.  
  10.  
  11.                       Notes on the design of the package
  12.                       ==================================
  13.  
  14. Contents
  15. ========
  16.  
  17. What this is package for
  18. What size of matrices?
  19. Allow matrix expressions?
  20. Which matrix types?
  21. What element types?
  22. Naming convention
  23. Row and Column index ranges
  24. Structure of matrix objects
  25. Data storage - one block or several
  26. Data storage - by row or by column or other
  27. Storage of symmetric matrices
  28. Element access - method and checking
  29. Use iterators?
  30. Memory management - reference counting or status variable?
  31. Evaluation of expressions - use two stage method?
  32. How to overcome an explosion in number of operations
  33. Destruction of temporaries
  34. A calculus of matrix types
  35. Error handling
  36. Sparse matrices
  37. Complex matrices
  38.  
  39.  
  40. I describe some of the ideas behind this package, some of the decisions
  41. that I needed to make and give some details about the way it works. You
  42. don't need to read this file in order to use the package.
  43.  
  44. I don't think it is obvious what is the best way of going about
  45. structuring a matrix package. And I don't think you can figure this
  46. out with "thought" experiments. Different people have to try out
  47. different approaches. And someone else may have to figure out which is
  48. best. Or, more likely, the ultimate packages will lift some ideas from
  49. each of a variety of trial packages. So, I don't claim my package is an
  50. "ultimate" package, but simply a trial of a number of ideas.
  51.  
  52. But I do hope it will meet the immediate requirements of some people who
  53. need to carry out matrix calculations.
  54.  
  55.  
  56. What this is package for
  57. ------------------------
  58.  
  59. The package is used for the manipulation of matrices, including the
  60. standard operations such as multiplication as understood by numerical
  61. analysts, engineers and mathematicians. A matrix is a two dimensional
  62. array of numbers. However, very special operations such as matrix
  63. multiplication are defined specifically for matrices. This means that a
  64. "matrix" package tends to be different from a general "array" package.
  65.  
  66. I see a matrix package as providing the following
  67.  
  68. 1.   Evaluation of matrix expressions in a form familiar to
  69.      scientists and engineers. For example  A = B * (C + D.t());
  70. 2.   Access to the elements of a matrix;
  71. 3.   Access to submatrices;
  72. 4.   Common elementary matrix functions such as determinant and trace;
  73. 5.   A platform for developing advanced matrix functions such as eigen-
  74.      value analysis;
  75. 6.   Good efficiency and storage management;
  76. 7.   Graceful exit from errors (I don't provide this yet).
  77.  
  78. It may also provide
  79.  
  80. 8.   A variety of types of elements (eg real and complex);
  81. 9.   A variety of types of matrices (eg rectangular and symmetric).
  82.  
  83. In contrast an array package should provide
  84.  
  85. 1'.  Arrays can be copied with a single instruction; may have some
  86.      arithmetic operations, say + - and scalar + - * /, it may provide
  87.      matrix multiplication as a function;
  88. 2'.  High speed access to elements directly and with iterators;
  89. 3'.  Access to subarrays and rows (and columns?);
  90. 6'.  Good efficiency and storage management;
  91. 7'.  Graceful exit from errors;
  92. 8'.  A variety of types of elements (eg real and complex);
  93. 9'.  One, two and three dimensional arrays, at least, with starting
  94.      points of the indices set by user; may have arrays with ragged
  95.      rows.
  96.  
  97. It may be possible to amalgamate these two sets of requirements to some
  98. extent. However my package is definitely oriented towards the first set.
  99.  
  100. Even within the bounds set by the first set of requirements there is a
  101. substantial opportunity for variation between what different matrix
  102. packages might provide.
  103.  
  104. It is not possible to build a matrix package that will meet everyone's
  105. requirements. In many cases if you put in one facility, you impose
  106. overheads on everyone using the package. This both is storage required
  107. for the program and in efficiency. Likewise a package that is optimised
  108. towards handling large matrices is likely to become less efficient for
  109. very small matrices where the administration time for the matrix may
  110. become significant compared with the time to carry out the operations.
  111.  
  112. It is better to provide a variety of packages (hopefully compatible) so
  113. that most users can find one that meets their requirements. This package
  114. is intended to be one of these packages; but not all of them.
  115.  
  116. Since my background is in statistical methods, this package is oriented
  117. towards the kinds things you need for statistical analyses.
  118.  
  119.  
  120. What size of matrices?
  121. ----------------------
  122.  
  123. A matrix package may target small matrices (say 3 x 3), or medium sized
  124. matrices, or very large matrices. A package targeting very small
  125. matrices will seek to minimise administration. A package for medium
  126. sized or very large matrices can spend more time on administration in
  127. order to conserve space or optimise the evaluation of expressions. A
  128. package for very large matrices will need to pay special attention to
  129. storage and numerical properties.
  130.  
  131. This package is designed for medium sized matrices. This means it is
  132. worth introducing some optimisations, but I don't have to worry about
  133. the 64K limit that some compilers impose.
  134.  
  135.  
  136. Allow matrix expressions?
  137. -------------------------
  138.  
  139. I want to be able to write matrix expressions the way I would on paper.
  140. So if I want to multiply two matrices and then add the transpose of a
  141. third one I can write something like
  142.  
  143.    X = A * B + C.t();
  144.  
  145. I want this expression to be evaluated with close to the same efficiency
  146. as a hand-coded version. This is not so much of a problem with
  147. expressions including a multiply since the multiply will dominate the
  148. time. However, it is not so easy to achieve with expressions with just +
  149. and - .
  150.  
  151. A second requirement is that temporary matrices generated during the
  152. evaluation of an expression are destroyed as quickly as possible.
  153.  
  154. A desirable feature is that a certain amount of "intelligence" be
  155. displayed in the evaluation of an expression. For example, in the
  156. expression
  157.  
  158.    X = A.i() * B;
  159.  
  160. where i() denotes inverse, it would be desirable if the inverse wasn't
  161. explicitly calculated.
  162.  
  163.  
  164. Which matrix types?
  165. -------------------
  166.  
  167. As well as the usual rectangular matrices, matrices occuring repeatedly
  168. in numerical calculations are upper and lower triangular matrices,
  169. symmetric matrices and diagonal matrices. This is particularly the case
  170. in calculations involving least squares and eigenvalue calculations. So
  171. as a first stage these were the types I decided to include.
  172.  
  173. It is also necessary to have types row vector and column vector. In a
  174. "matrix" package, in contrast to an "array" package, it is necessary to
  175. have both these types since they behave differently in matrix
  176. expressions. The vector types can be derived for the rectangular matrix
  177. type, so having them does not greatly increase the complexity of the
  178. package.
  179.  
  180. The problem with having several matrix types is the number of versions
  181. of the binary operators one needs. If one has 5 distinct matrix types
  182. then a simple package will need 25 versions of each of the binary
  183. operators. In fact, we can evade this problem, but at the cost of some
  184. complexity.
  185.  
  186.  
  187. What element types?
  188. -------------------
  189.  
  190. Ideally we would allow element types double, float, complex and int, at
  191. least. It would be reasonably easy, using templates or equivalent, to
  192. provide a package which could handle a variety of element types.
  193. However, as soon as one starts implementing the binary operators between
  194. matrices with different element types, again one gets an explosion in
  195. the number of operations one needs to consider. Hence I decided to
  196. implement only one element type. But the user can decide whether this is
  197. float or double. The package assumes elements are of type Real. The user
  198. typedefs Real to float or double.
  199.  
  200. In retrospect, it would not be too hard to include matrices with complex
  201. matrix type.
  202.  
  203. It might also be worth including symmetric and triangular matrices with
  204. extra precision elements (double or long double) to be used for storage
  205. only and with a minimum of operations defined. These would be used for
  206. accumulating the results of sums of squares and product matrices or
  207. multistage Householder triangularisations.
  208.  
  209.  
  210. Naming convention
  211. -----------------
  212.  
  213. How are classes and public member functions to be named? As a general
  214. rule I have spelt identifiers out in full with individual words being
  215. capitalised. For example "UpperTriangularMatrix". If you don't like this
  216. you can #define or typedef shorter names. This convention means you can
  217. select an abbreviation scheme that makes sense to you.
  218.  
  219. The convention causes problems for Glockenspiel C++ on a PC feeding into
  220. Microsoft C. The names Glockenspiel generates exceed the the 32
  221. characters recognised by Microsoft C and ambiguities result. So it is
  222. necessary to #define shorter names.
  223.  
  224. Exceptions to the general rule are the functions for transpose and
  225. inverse. To make matrix expressions more like the corresponding
  226. mathematical formulae, I have used the single letter abbreviations, t()
  227. and i() .
  228.  
  229.  
  230. Row and Column index ranges
  231. ---------------------------
  232.  
  233. In mathematical work matrix subscripts usually start at one. In C, array
  234. subscripts start at zero. In Fortran, they start at one. Possibilities
  235. for this package were to make them start at 0 or 1 or be arbitrary.
  236. Alternatively one could specify an "index set" for indexing the rows and
  237. columns of a matrix. One would be able to add or multiply matrices only
  238. if the appropriate row and column index sets were identical.
  239.  
  240. In fact, I adopted the simpler convention of making the rows and columns
  241. of a matrix be indexed by an integer starting at one, following the
  242. traditional convention. In an earlier version of the package I had them
  243. starting at zero, but even I was getting mixed up when trying to use
  244. this earlier package. So I reverted to the more usual notation.
  245.  
  246.  
  247. Structure of matrix objects
  248. ---------------------------
  249.  
  250. Each matrix object contains the basic information such as the number of
  251. rows and columns and a status variable plus a pointer to the data
  252. array which is on the heap.
  253.  
  254.  
  255. Data storage - one block or several
  256. -----------------------------------
  257.  
  258. In this package the elements of the matrix are stored as a single array.
  259. Alternatives would have been to store each row as a separate array or a
  260. set of adjacent rows as a separate array. The present solution
  261. simplifies the program but limits the size of matrices in systems that
  262. have a 64k byte (or other) limit on the size of arrays. The large arrays
  263. may also cause problems for memory management in smaller machines.
  264.  
  265.  
  266. Data storage - by row or by column or other
  267. -------------------------------------------
  268.  
  269. In Fortran two dimensional arrays are stored by column. In most other
  270. systems they are stored by row. I have followed this later convention.
  271. This makes it easier to interface with other packages written in C but
  272. harder to interface with those written in Fortran.
  273.  
  274. An alternative would be to store the elements by mid-sized rectangular
  275. blocks. This might impose less strain on memory management when one
  276. needs to access both rows and columns.
  277.  
  278.  
  279. Storage of symmetric matrices
  280. -----------------------------
  281.  
  282. Symmetric matrices are stored as lower triangular matrices. The decision
  283. was pretty arbitrary, but it does slightly simplify the Cholesky
  284. decomposition program.
  285.  
  286.  
  287. Element access - method and checking
  288. ------------------------------------
  289.  
  290. We want to be able to use the notation A(i,j) to specify the (i,j)-th
  291. element of a matrix. This is the way mathematicians expect to address
  292. the elements of matrices. I consider the notation A[i][j] totally alien.
  293. However I may include it in a later version to help people converting
  294. from C. There are two ways of working out the address of A(i,j). One is
  295. using a "dope" vector which contains the first address of each row.
  296. Alternatively you can calculate the address using the formula
  297. appropriate for the structure of A. I use this second approach. It is
  298. probably slower, but saves worrying about an extra bit of storage. The
  299. other question is whether to check for i and j being in range. I do
  300. carry out this check following years of experience with both systems
  301. that do and systems that don't do this check.
  302.  
  303. I would hope that the routines I supply with this package will reduce
  304. your need to access elements of matrices so speed of access is not a
  305. high priority.
  306.  
  307.  
  308. Use iterators?
  309. --------------
  310.  
  311. Iterators are an alternative way of providing fast access to the
  312. elements of an array or matrix when they are to be accessed
  313. sequentially. They need to be customised for each type of matrix. I have
  314. not implemented iterators in this package, although some iterator like
  315. functions are used for some row and column functions.
  316.  
  317.  
  318. Memory management - reference counting or status variable?
  319. ----------------------------------------------------------
  320.  
  321. Consider the instruction
  322.  
  323.    X = A + B + C;
  324.  
  325. To evaluate this a simple program will add A to B putting the total in a
  326. temporary T1. Then it will add T1 to C creating another temporary T2
  327. which will be copied into X. T1 and T2 will sit around till the end of
  328. the block. It would be faster if the program recognised that T1 was
  329. temporary and stored the sum of T1 and C back into T1 instead of
  330. creating T2 and then avoided the final copy by just assigning the
  331. contents of T1 to X rather than copying. In this case there will be no
  332. temporaries requiring deletion. (More precisely there will be a header
  333. to be deleted but no contents).
  334.  
  335. For an instruction like
  336.  
  337.    X = (A * B) + (C * D);
  338.  
  339. we can't easily avoid one temporary being left over, so we would like
  340. this temporary deleted as quickly as possible.
  341.  
  342. I provide the functionality for doing this by attaching a status
  343. variable to each matrix. This indicates if the matrix is temporary so
  344. that its memory is available for recycling or deleting. Any matrix
  345. operation checks the status variables of the matrices it is working with
  346. and recycles or deletes any temporary memory.
  347.  
  348. An alternative or additional approach would be to use delayed copying.
  349. If a program requests a matrix to be copied, the copy is delayed until
  350. an instruction is executed which modifies the memory of either the
  351. original matrix or the copy. This saves the unnecessary copying in the
  352. previous examples. However, it does not provide the additional
  353. functionality of my approach.
  354.  
  355. It would be possible to have both delayed copy and tagging temporaries,
  356. but this seemed an unnecessary complexity. In particular delayed copy
  357. mechanisms seem to require two calls to the heap manager, using extra
  358. time and making building a memory compacting mechanism more difficult.
  359.  
  360.  
  361. Evaluation of expressions - use two stage method?
  362. -------------------------------------------------
  363.  
  364. Consider the instruction
  365.  
  366.    X = B - X;
  367.  
  368. The simple program will subtract X from B, store the result in a
  369. temporary T1 and copy T1 into X. It would be faster if the program
  370. recognised that the result could be stored directly into X. This would
  371. happen automatically if the program could look at the instruction first
  372. and mark X as temporary.
  373.  
  374. C programmers would expect to avoid the same problem with
  375.  
  376.    X = X - B;
  377.  
  378. by using an operator -= (which I haven't provided, yet)
  379.  
  380.    X -= B;
  381.  
  382. However this is an unnatural notation for non C users and it is much
  383. nicer to write
  384.  
  385.    X = X - B;
  386.  
  387. and know that the program will carry out the simplification.
  388.  
  389. Another example where this intelligent analysis of an instruction is
  390. helpful is in
  391.  
  392.    X = A.i() * B;
  393.  
  394. where i() denotes inverse. Numerical analysts know it is inefficient to
  395. evaluate this expression by carrying out the inverse operation and then
  396. the multiply. Yet it is a convenient way of writing the instruction. It
  397. would be helpful if the program recognised this expression and carried
  398. out the more appropriate approach.
  399.  
  400. To carry out this "intelligent" analysis of an instruction  matrix
  401. expressions are evaluated in two stages. In the the first stage a tree
  402. representation of the expression is formed.
  403.  
  404. For example (A+B)*C is represented by a tree
  405.  
  406.                     *
  407.                    / \
  408.                   +   C
  409.                  / \
  410.                 A   B
  411.  
  412. Rather than adding A and B the + operator yields an object of a class
  413. "AddedMatrix" which is just a pair of pointers to A and B. Then the *
  414. operator yields a "MultipliedMatrix" which is a pair of pointers to the
  415. "AddedMatrix" and C. The tree is examined for any simplifications and
  416. then evaluated recursively.
  417.  
  418. Further possibilities not yet included are to recognise A.t()*A and
  419. A.t()+A as symmetric or to improve the efficiency of evaluation of
  420. expressions like A+B+C, A*B*C, A*B.t()  [t() denotes transpose].
  421.  
  422. One of the disadvantages of the two-stage approach is that the types of
  423. matrix expressions are determined at run-time. So the compiler will not
  424. detect errors of the type
  425.  
  426.    Matrix M; DiagonalMatrix D; ....; D = M;
  427.  
  428. We don't allow conversions using = when information would be lost. Such
  429. errors will be detected when the statement is executed.
  430.  
  431.  
  432. How to overcome an explosion in number of operations
  433. ----------------------------------------------------
  434.  
  435. The package attempts to solve the problem of the large number of
  436. versions of the binary operations required when one has a variety of
  437. types. With n types of matrices the binary operations will each require
  438. n-squared separate algorithms. Some reduction in the number may be
  439. possible by carrying out conversions. However the situation rapidly
  440. becomes impossible with more than 4 or 5 types.
  441.  
  442. Doug Lea told me that it was possible to avoid this problem. I don't
  443. know what his solution is. Here's mine.
  444.  
  445. Each matrix type includes routines for extracting individual rows or
  446. columns. I assume a row or column consists of a sequence of zeros, a
  447. sequence of stored values and then another sequence of zeros. Only a
  448. single algorithm is then required for each binary operation. The rows
  449. can be located very quickly since most of the matrices are stored row by
  450. row. Columns must be copied and so the access is somewhat slower. As far
  451. as possible my algorithms access the matrices by row.
  452.  
  453. An alternative approach of using iterators will be slower since the
  454. iterators will involve a virtual function access at each step.
  455.  
  456. There is another approach. Each of the matrix types defined in this
  457. package can be set up so both rows and columns have their elements at
  458. equal intervals provided we are prepared to store the rows and columns
  459. in up to three chunks. With such an approach one could write a single
  460. "generic" algorithm for each of multiply and add. This would be a
  461. reasonable alternative to my approach.
  462.  
  463. I provide several algorithms for operations like + . If one is adding
  464. two matrices of the same type then there is no need to access the
  465. individual rows or columns and a faster general algorithm is
  466. appropriate.
  467.  
  468. Generally the method works well. However symmetric matrices are not
  469. always handled very efficiently (yet) since complete rows are not stored
  470. explicitly.
  471.  
  472. The original version of the package did not use this access by row or
  473. column method and provided the multitude of algorithms for the
  474. combination of different matrix types. The code file length turned out
  475. to be just a little longer than the present one when providing the same
  476. facilities with 5 distinct types of matrices. It would have been very
  477. difficult to increase the number of matrix types in the original
  478. version. Apparently 4 to 5 types is about the break even point for
  479. switching to the approach adopted in the present package.
  480.  
  481.  
  482. Destruction of temporaries
  483. --------------------------
  484.  
  485. Versions before version 5 of newmat did not work correctly with Gnu C++.
  486. This was because the tree structure used to represent a matrix
  487. expression was set up on the stack. This was fine for AT&T, Borland and
  488. Zortech C++. However Gnu C++ destroys temporary structures as soon as
  489. the function that accesses them finishes. The other compilers wait until
  490. the end of the current block. (It would be good enough for them to wait
  491. till the end of the expression.) To overcome this problem, there is now
  492. an option to store the temporaries forming the tree structure on the
  493. heap (created with new) and to delete them explicitly. Activate the
  494. definition of TEMPS_DESTROYED_QUICKLY to set this option. In fact, I
  495. suggest this be the default option as, with it, the package uses less
  496. storage and runs faster.
  497.  
  498. There still exist situations Gnu C++ will go wrong. These include
  499. statements like
  500.  
  501. A = X * Matrix(P * Q); Real r = (A*B)(3,4);
  502.  
  503. Neither of these kinds of statements will occur often in practice.
  504.  
  505.  
  506. A calculus of matrix types
  507. --------------------------
  508.  
  509. The program needs to be able to work out the class of the result of a
  510. matrix expression. This is to check that a conversion is legal or to
  511. determine the class of a temporary. To assist with this, a class
  512. MatrixType is defined. Operators +, -, *, >= are defined to calculate
  513. the types of the results of expressions or to check that conversions are
  514. legal.
  515.  
  516.  
  517. Error handling
  518. --------------
  519.  
  520. The package now does have a moderately graceful exit from errors.
  521. Originally I thought I would wait until exceptions became available in
  522. C++. Trying to set up an error handling scheme that did not involve an
  523. exception-like facility was just too complicated. Version 5 of this
  524. package included the beginnings of such a facility. The approach in the
  525. present package, attempting to implement C++ exceptions, is not
  526. completely satisfactory, but seems a good interim solution.
  527.  
  528. The exception mechanism cannot clean-up objects explicitly created by
  529. new. This must be explicitly carried out by the package writer or the
  530. package user. I have not yet done this with the present package so
  531. occasionally a little garbage may be left behind after an exception. I
  532. don't think this is a big problem, but it is one that needs fixing.
  533.  
  534. I identify five categories of errors:
  535.  
  536.    Programming error - eg illegal conversion of a matrix, subscript out
  537.    of bounds, matrix dimensions don't match;
  538.  
  539.    Illegal data error - eg Cholesky of a non-positive definite matrix;
  540.  
  541.    Out of space error - "new" returns a null pointer;
  542.  
  543.    Convergence failure - an iterative operation fails to converge;
  544.  
  545.    Internal error - failure of an internal check.
  546.  
  547. Some of these have subcategories, which are nicely represented by
  548. derived exception classes. But I don't think it is a good idea for
  549. package users to refer to these as they may change in the next release
  550. of the package.
  551.  
  552.  
  553. Sparse matrices
  554. ---------------
  555.  
  556. The package does not yet support sparse matrices.
  557.  
  558. For sparse matrices there is going to be some kind of structure vector.
  559. It is going to have to be calculated for the results of expressions in
  560. much the same way that types are calculated. In addition, a whole new
  561. set of row and column operations would have to be written.
  562.  
  563. Sparse matrices are important for people solving large sets of
  564. differential equations as well as being important for statistical and
  565. operational research applications. I think comprehensive matrix package
  566. needs to implement sparse matrices.
  567.  
  568.  
  569. Complex matrices
  570. ----------------
  571.  
  572. The package does not yet support matrices with complex elements. There
  573. are at least two approaches to including these. One is to have matrices
  574. with complex elements. This probably means making new versions of the
  575. basic row and column operations for Real*Complex, Complex*Complex,
  576. Complex*Real and similarly for + and -. This would be OK, except that I
  577. am also going to have to also do this for sparse and when you put these
  578. together, the whole thing will get out of hand.
  579.  
  580. The alternative is to represent a Complex matrix by a pair of Real
  581. matrices. One probably needs another level of decoding expressions but I
  582. think it might still be simpler than the first approach. There is going
  583. to be a problem with accessing elements and the final package may be a
  584. little less efficient that one using the previous representation.
  585. Neverthless, this is the approach I favour.
  586.  
  587. Complex matrices are used extensively by electrical engineers and really
  588. should be fully supported in a comprehensive package.
  589.