├── .gitignore ├── README.md ├── composer.json ├── css └── style.css ├── images └── README.md ├── index.php └── lib ├── Masonry.php └── PEAR ├── Math ├── CompactedTuple.php ├── Matrix.php ├── Tuple.php ├── Vector.php ├── Vector2.php ├── Vector3.php └── VectorOp.php └── PEAR.php /.gitignore: -------------------------------------------------------------------------------- 1 | .tags 2 | .tags_sorted_by_file 3 | project.sublime-workspace 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # php-masonry 2 | 3 | ### PHP script to create a random masonry-like mosaic 4 | 5 | ## Demo 6 | http://nabble.nl/demo/masonry -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hongaar/php-masonry", 3 | "description": "PHP script to create a random masonry-like mosaic", 4 | "require": { 5 | "php": ">=5.2.0" 6 | }, 7 | "autoload": { 8 | "classmap": ["lib/Masonry.php"] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | /* ============================================================================= 2 | HTML5 Boilerplate CSS: h5bp.com/css 3 | ========================================================================== */ 4 | 5 | article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } 6 | audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } 7 | audio:not([controls]) { display: none; } 8 | [hidden] { display: none; } 9 | 10 | html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; } 11 | html, button, input, select, textarea { font-family: sans-serif; color: #222; } 12 | body { margin: 0; font-size: 1em; line-height: 1.4; } 13 | 14 | ::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } 15 | ::selection { background: #fe57a1; color: #fff; text-shadow: none; } 16 | 17 | a { color: #00e; } 18 | a:visited { color: #551a8b; } 19 | a:hover { color: #06e; } 20 | a:focus { outline: thin dotted; } 21 | a:hover, a:active { outline: 0; } 22 | 23 | abbr[title] { border-bottom: 1px dotted; } 24 | b, strong { font-weight: bold; } 25 | blockquote { margin: 1em 40px; } 26 | dfn { font-style: italic; } 27 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } 28 | ins { background: #ff9; color: #000; text-decoration: none; } 29 | mark { background: #ff0; color: #000; font-style: italic; font-weight: bold; } 30 | pre, code, kbd, samp { font-family: monospace, serif; _font-family: 'courier new', monospace; font-size: 1em; } 31 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; } 32 | q { quotes: none; } 33 | q:before, q:after { content: ""; content: none; } 34 | small { font-size: 85%; } 35 | 36 | sub, sup { font-size: 75%; line-height: 0; position: relative; vertical-align: baseline; } 37 | sup { top: -0.5em; } 38 | sub { bottom: -0.25em; } 39 | 40 | ul, ol { margin: 1em 0; padding: 0 0 0 40px; } 41 | dd { margin: 0 0 0 40px; } 42 | nav ul, nav ol { list-style: none; list-style-image: none; margin: 0; padding: 0; } 43 | 44 | img { border: 0; -ms-interpolation-mode: bicubic; vertical-align: middle; } 45 | 46 | svg:not(:root) { overflow: hidden; } 47 | 48 | figure { margin: 0; } 49 | 50 | form { margin: 0; } 51 | fieldset { border: 0; margin: 0; padding: 0; } 52 | label { cursor: pointer; } 53 | legend { border: 0; *margin-left: -7px; padding: 0; white-space: normal; } 54 | button, input, select, textarea { font-size: 100%; margin: 0; vertical-align: baseline; *vertical-align: middle; } 55 | button, input { line-height: normal; } 56 | button, input[type="button"], input[type="reset"], input[type="submit"] { cursor: pointer; -webkit-appearance: button; *overflow: visible; } 57 | button[disabled], input[disabled] { cursor: default; } 58 | input[type="checkbox"], input[type="radio"] { box-sizing: border-box; padding: 0; *width: 13px; *height: 13px; } 59 | input[type="search"] { -webkit-appearance: textfield; -moz-box-sizing: content-box; -webkit-box-sizing: content-box; box-sizing: content-box; } 60 | input[type="search"]::-webkit-search-decoration, input[type="search"]::-webkit-search-cancel-button { -webkit-appearance: none; } 61 | button::-moz-focus-inner, input::-moz-focus-inner { border: 0; padding: 0; } 62 | textarea { overflow: auto; vertical-align: top; resize: vertical; } 63 | input:valid, textarea:valid { } 64 | input:invalid, textarea:invalid { background-color: #f0dddd; } 65 | 66 | table { border-collapse: collapse; border-spacing: 0; } 67 | td { vertical-align: top; } 68 | 69 | .chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; } 70 | 71 | 72 | 73 | /* ===== Primary Styles ======================================================== 74 | Author: 75 | ========================================================================== */ 76 | 77 | 78 | 79 | 80 | 81 | html, body { 82 | width: 100%; 83 | height: 100%; 84 | } 85 | 86 | 87 | #main { 88 | position: relative; 89 | width: 100%; 90 | height: 100%; 91 | } 92 | 93 | #main div { 94 | position: absolute; 95 | background-repeat: no-repeat; 96 | background-position: 50% 50%; 97 | background-size: cover; 98 | } 99 | 100 | footer { 101 | position: fixed; 102 | display: block; 103 | background-color: white; 104 | padding: 5px; 105 | right: 20px; 106 | bottom: 20px; 107 | } 108 | 109 | 110 | 111 | /* ============================================================================= 112 | Media Queries 113 | ========================================================================== */ 114 | 115 | @media only screen and (min-width: 35em) { 116 | /* Style adjustments for viewports that meet the condition */ 117 | } 118 | 119 | /* ============================================================================= 120 | Non-Semantic Helper Classes 121 | ========================================================================== */ 122 | 123 | .ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } 124 | .ir br { display: none; } 125 | .hidden { display: none !important; visibility: hidden; } 126 | .visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } 127 | .visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } 128 | .invisible { visibility: hidden; } 129 | .clearfix:before, .clearfix:after { content: ""; display: table; } 130 | .clearfix:after { clear: both; } 131 | .clearfix { *zoom: 1; } 132 | 133 | /* ============================================================================= 134 | Print Styles 135 | ========================================================================== */ 136 | 137 | @media print { 138 | * { background: transparent !important; color: black !important; box-shadow:none !important; text-shadow: none !important; filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */ 139 | a, a:visited { text-decoration: underline; } 140 | a[href]:after { content: " (" attr(href) ")"; } 141 | abbr[title]:after { content: " (" attr(title) ")"; } 142 | .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */ 143 | pre, blockquote { border: 1px solid #999; page-break-inside: avoid; } 144 | thead { display: table-header-group; } /* h5bp.com/t */ 145 | tr, img { page-break-inside: avoid; } 146 | img { max-width: 100% !important; } 147 | @page { margin: 0.5cm; } 148 | p, h2, h3 { orphans: 3; widows: 3; } 149 | h2, h3 { page-break-after: avoid; } 150 | } -------------------------------------------------------------------------------- /images/README.md: -------------------------------------------------------------------------------- 1 | put some image files in this directory -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Fork me on GitHub 18 | 19 |
20 | minWidth = 5; 32 | $masonry->maxWidth = 15; 33 | $masonry->minHeight = 10; 34 | $masonry->maxHeight = 30; 35 | $masonry->fillTopProbability = 5; 36 | $masonry->fillHalfwayProbability = 2; 37 | } else { 38 | $masonry->multiplier = 10; 39 | $masonry->ysize = 10; 40 | $masonry->minWidth = 1; 41 | $masonry->maxWidth = 4; 42 | $masonry->minHeight = 3; 43 | $masonry->maxHeight = 7; 44 | } 45 | // set xsize to allow for maximum width/height of all images 46 | $masonry->xsize = count($images) * ($masonry->maxWidth / ($masonry->ysize / $masonry->maxHeight)); 47 | 48 | try { 49 | $position = $masonry->create($images); 50 | } catch (Exception $e) { 51 | echo $e->getMessage(); 52 | } 53 | 54 | foreach($images as $k => $v) : ?> 55 | 56 |
%; left: %; width: %; height: %; background-image: url();'>
57 | 58 | 59 | 60 |
61 | 62 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /lib/Masonry.php: -------------------------------------------------------------------------------- 1 | minArea * ($this->minWidth * $this->minHeight)) >= ($this->maxArea * ($this->maxWidth * $this->maxHeight))) { 40 | throw new Exception('Min/max area overlap, please adjust settings'); 41 | } 42 | 43 | // create matrix 44 | $this->matrix = Math_Matrix::makeMatrix($this->ysize, $this->xsize, -1); 45 | 46 | // variable to define whether filling is active 47 | $fill = false; 48 | 49 | // return array 50 | $positions = array(); 51 | 52 | foreach($array as $k => $v) { 53 | 54 | // get next empty space 55 | $pos = $this->searchNextPosition(); 56 | 57 | // fill horizontal? 58 | if ($pos[1] == 0) { // are we at the top? 59 | if ($pos[0] != 0) { // are we not at [0,0] ? 60 | if (rand(1, $this->fillTopProbability) == 1) { // roll a dice for top position fill 61 | // calculate fill position 62 | $fill = $this->searchXBoundFrom($pos[0]) + $this->minWidth; 63 | if ($this->debug) { 64 | echo 'fill from top x='. $fill . '
'; 65 | } 66 | } else { 67 | $fill = false; 68 | } 69 | } 70 | } elseif ($fill === false) { // fill from halfway? 71 | if (rand(1, $this->fillHalfwayProbability) == 1) { // roll a dice halfway position fill 72 | // calculate fill position 73 | $fill = $this->searchXBoundFrom($pos[0]) + $this->minWidth; 74 | if ($this->debug) { 75 | echo 'fill x='. $fill . '
'; 76 | } 77 | } 78 | } 79 | 80 | // get dimensions 81 | $rand = $this->randomDimensions(); 82 | $randWidth = $rand['width']; 83 | $randHeight = $rand['height']; 84 | 85 | if ($fill !== false) { 86 | $randWidth = $fill - $pos[0]; 87 | } 88 | 89 | // height available? 90 | $heightAvailable = $this->availableHeightFrom($pos[0], $pos[1]); 91 | if ($heightAvailable <= $this->minHeight) { 92 | $height = $heightAvailable; 93 | } else if ($heightAvailable <= $this->minHeight * 2) { 94 | $height = $heightAvailable; 95 | } else { 96 | $height = min($randHeight, $heightAvailable - $this->minHeight); 97 | } 98 | // random width 99 | $width = $randWidth; 100 | 101 | // debug 102 | if ($this->debug) { 103 | echo $k . ': ' . $height . '(' . $heightAvailable . ')x' . $width . ' @ [' . $pos[0] . ', ' . $pos[1] . ']
'; 104 | } 105 | 106 | // set ids 107 | for($x = $pos[0]; $x < $width + $pos[0]; $x++) { 108 | for($y = $pos[1]; $y < $height + $pos[1]; $y++) { 109 | $this->matrix->setElement($y, $x, $k); 110 | } 111 | } 112 | 113 | // set item position 114 | $positions[$k] = array( 115 | 'x' => $pos[0] * $this->multiplier, 116 | 'y' => $pos[1] * $this->multiplier, 117 | 'w' => $width * $this->multiplier, 118 | 'h' => $height * $this->multiplier 119 | ); 120 | } 121 | 122 | // debug: print matrix 123 | if ($this->debug) { 124 | echo '
'.$this->matrix->toString('%3.0f').'
'; 125 | die(); 126 | } 127 | 128 | // return array 129 | return $positions; 130 | } 131 | 132 | public function searchNextPosition() { 133 | for($x = 0; $x < $this->xsize; $x++) { 134 | $col = $this->matrix->getCol($x); 135 | if (($y = array_search('-1', $col)) !== false) { 136 | return array($x, $y); 137 | } 138 | } 139 | throw new Exception('Grid X size too small, please enlarge'); 140 | } 141 | 142 | public function searchXBoundFrom($x) { 143 | for($i = $x; $i < $this->xsize; $i++) { 144 | $col = $this->matrix->getCol($i); 145 | if (max($col) == -1) { 146 | return $i; 147 | } 148 | } 149 | } 150 | 151 | public function availableHeightFrom($x, $y) { 152 | // get the column 153 | $col = $this->matrix->getCol($x); 154 | // make array start at correct y pos 155 | for($i = 0; $i < $y; $i++) { 156 | array_shift($col); 157 | } 158 | foreach($col as $k => $v) { 159 | if ($v > -1) { 160 | return $k; 161 | } 162 | } 163 | // entire height available 164 | return $this->ysize - $y; 165 | } 166 | 167 | public function randomHeight() 168 | { 169 | return rand($this->minHeight, $this->maxHeight); 170 | } 171 | 172 | public function randomWidth() 173 | { 174 | return rand($this->minWidth, $this->maxWidth); 175 | } 176 | 177 | public function randomDimensions() 178 | { 179 | $min = ($this->minArea * ($this->minHeight * $this->minWidth)); 180 | $max = ($this->maxArea * ($this->maxHeight * $this->maxWidth)); 181 | do { 182 | $width = $this->randomWidth(); 183 | $height = $this->randomHeight(); 184 | $area = $width * $height; 185 | } while ($area < $min || $area > $max); 186 | return array( 187 | "width" => $width, 188 | "height" => $height 189 | ); 190 | } 191 | } -------------------------------------------------------------------------------- /lib/PEAR/Math/CompactedTuple.php: -------------------------------------------------------------------------------- 1 | | 17 | // +----------------------------------------------------------------------+ 18 | // 19 | // $Id: CompactedTuple.php 304045 2010-10-05 00:16:53Z clockwerx $ 20 | // 21 | 22 | class Math_CompactedTuple { 23 | 24 | var $data; 25 | 26 | function Math_CompactedTuple ($arg) 27 | { 28 | if (is_array($arg)) { 29 | $this->data = $this->_genCompactedArray($arg); 30 | } elseif (is_object($arg) && get_class($arg) == "math_tuple") { 31 | $this->data = $this->_genCompacterArray($arg->getData()); 32 | } else { 33 | $msg = "Incorrect parameter for Math_CompactedTuple constructor. ". 34 | "Expecting an unidimensional array or a Math_Tuple object,". 35 | " got '$arg'\n"; 36 | PEAR::raiseError($msg); 37 | } 38 | return true; 39 | } 40 | 41 | function getSize() { 42 | return count($this->_genUnCompactedArray($this->data)); 43 | } 44 | 45 | function getCompactedSize() { 46 | return count($this->data); 47 | } 48 | 49 | function getCompactedData() { 50 | return $this->data; 51 | } 52 | 53 | function getData() { 54 | return $this->_genUnCompactedArray($this->data); 55 | } 56 | 57 | function addElement($value) { 58 | $this->data[$value]++; 59 | } 60 | 61 | function delElement($value) { 62 | if (in_array($value, array_keys($this->data))) { 63 | $this->data[$value]--; 64 | if ($this->data[$value] == 0) 65 | unset ($this->data[$value]); 66 | return true; 67 | } 68 | return PEAR::raiseError("value does not exist in compacted tuple"); 69 | } 70 | 71 | function hasElement($value) { 72 | return in_array($value, array_keys($this->data)); 73 | } 74 | 75 | function _genCompactedArray($arr) { 76 | if (function_exists("array_count_values")) { 77 | return array_count_values($arr); 78 | } else { 79 | $out = array(); 80 | foreach ($arr as $val) 81 | $out[$val]++; 82 | return $out; 83 | } 84 | } 85 | 86 | function _genUnCompactedArray($arr) { 87 | $out = array(); 88 | foreach ($arr as $val=>$count) 89 | for($i=0; $i < $count; $i++) 90 | $out[] = $val; 91 | return $out; 92 | } 93 | } 94 | 95 | ?> 96 | -------------------------------------------------------------------------------- /lib/PEAR/Math/Matrix.php: -------------------------------------------------------------------------------- 1 | | 17 | // +----------------------------------------------------------------------+ 18 | // 19 | // Matrix definition and manipulation package 20 | // 21 | // $Id: Matrix.php 304049 2010-10-05 00:30:02Z clockwerx $ 22 | // 23 | 24 | require_once 'lib/PEAR/PEAR.php'; 25 | require_once 'Vector.php'; 26 | 27 | /** 28 | * Defines a matrix object. 29 | * 30 | * A matrix is implemented as an array of arrays such that: 31 | * 32 | *
  33 |  * [0][0] [0][1] [0][2] ... [0][M]
  34 |  * [1][0] [1][1] [1][2] ... [1][M]
  35 |  * ...
  36 |  * [N][0] [n][1] [n][2] ... [n][M]
  37 |  * 
38 | * 39 | * i.e. N rows, M colums 40 | * 41 | * Originally this class was part of NumPHP (Numeric PHP package) 42 | * 43 | * @author Jesus M. Castagnetto 44 | * @access public 45 | * @version 1.0 46 | * @package Math_Matrix 47 | */ 48 | class Math_Matrix {/*{{{*/ 49 | 50 | // Properties /*{{{*/ 51 | 52 | /**#@+ 53 | * @access private 54 | */ 55 | 56 | /** 57 | * Contains the array of arrays defining the matrix 58 | * 59 | * @var array 60 | * @see getData() 61 | */ 62 | var $_data = null; 63 | 64 | /** 65 | * The number of rows in the matrix 66 | * 67 | * @var integer 68 | * @see getSize() 69 | */ 70 | var $_num_rows = null; 71 | 72 | /** 73 | * The number of columns in the matrix 74 | * 75 | * @var integer 76 | * @see getSize() 77 | */ 78 | var $_num_cols = null; 79 | 80 | /** 81 | * A flag indicating if the matrix is square 82 | * i.e. if $this->_num_cols == $this->_num_rows 83 | * 84 | * @var boolean 85 | * @see isSquare() 86 | */ 87 | var $_square = false; 88 | 89 | /**#@+ 90 | * @access private 91 | * @var float 92 | */ 93 | /** 94 | * The smallest value of all matrix cells 95 | * 96 | * @see getMin() 97 | * @see getMinMax() 98 | */ 99 | var $_min = null; 100 | 101 | /** 102 | * The biggest value of all matrix cells 103 | * 104 | * @see getMax() 105 | * @see getMinMax() 106 | */ 107 | var $_max = null; 108 | 109 | /** 110 | * The Euclidean norm for the matrix: sqrt(sum(e[i][j]^2)) 111 | * 112 | * @see norm() 113 | */ 114 | var $_norm = null; 115 | 116 | /** 117 | * The matrix determinant 118 | * 119 | * @see determinant() 120 | */ 121 | var $_det = null; 122 | 123 | /** 124 | * Cutoff error used to test for singular or ill-conditioned matrices 125 | * 126 | * @see determinant(); 127 | * @see invert() 128 | */ 129 | var $_epsilon = 1E-18; 130 | 131 | /*}}}*/ 132 | 133 | /**#@+ 134 | * @access public 135 | */ 136 | 137 | /** 138 | * Constructor for the matrix object 139 | * 140 | * @param array|Math_Matrix $data a numeric array of arrays of a Math_Matrix object 141 | * @return object Math_Matrix 142 | * @see $_data 143 | * @see setData() 144 | */ 145 | function Math_Matrix($data = null) {/*{{{*/ 146 | if (!is_null($data)) { 147 | $this->setData($data); 148 | } 149 | }/*}}}*/ 150 | 151 | /** 152 | * Validates the data and initializes the internal variables (except for the determinant). 153 | * 154 | * The validation is performed by by checking that 155 | * each row (first dimension in the array of arrays) 156 | * contains the same number of colums (e.g. arrays of the 157 | * same size) 158 | * 159 | * @param array $data array of arrays of numbers or a valid Math_Matrix object 160 | * @return boolean|PEAR_Error true on success, a PEAR_Error object otherwise 161 | */ 162 | function setData($data) {/*{{{*/ 163 | if (Math_Matrix::isMatrix($data)) { 164 | if (!$data->isEmpty()) { 165 | $this->_data = $data->getData(); 166 | } else { 167 | return $errObj; 168 | } 169 | } elseif (is_array($data) || is_array($data[0])) { 170 | // check that we got a numeric bidimensional array 171 | // and that all rows are of the same size 172 | $nc = 0; 173 | if (!empty($data[0])) { 174 | $nc = count($data[0]); 175 | } 176 | 177 | $nr = count($data); 178 | $eucnorm = 0; 179 | $tmp = array(); 180 | for ($i=0; $i < $nr; $i++) { 181 | if (count($data[$i]) != $nc) { 182 | return PEAR::raiseError('Invalid data, cannot create/modify matrix.'. 183 | ' Expecting an array of arrays or an initialized Math_Matrix object'); 184 | } 185 | for ($j=0; $j < $nc; $j++) { 186 | if (!is_numeric($data[$i][$j])) { 187 | return PEAR::raiseError('Invalid data, cannot create/modify matrix.'. 188 | ' Expecting an array of arrays or an initialized Math_Matrix object'); 189 | } 190 | $data[$i][$j] = (float) $data[$i][$j]; 191 | $tmp[] = $data[$i][$j]; 192 | $eucnorm += $data[$i][$j] * $data[$i][$j]; 193 | } 194 | } 195 | $this->_num_rows = $nr; 196 | $this->_num_cols = $nc; 197 | $this->_square = ($nr == $nc); 198 | $this->_min = !empty($tmp)? min($tmp) : null; 199 | $this->_max = !empty($tmp)? max($tmp) : null; 200 | $this->_norm = sqrt($eucnorm); 201 | $this->_data = $data; 202 | $this->_det = null; // lazy initialization ;-) 203 | return true; 204 | } else { 205 | return PEAR::raiseError('Invalid data, cannot create/modify matrix.'. 206 | ' Expecting an array of arrays or an initialized Math_Matrix object'); 207 | } 208 | }/*}}}*/ 209 | 210 | /** 211 | * Returns the array of arrays. 212 | * 213 | * @return array|PEAR_Error an array of array of numbers on success, a PEAR_Error otherwise 214 | */ 215 | function getData () {/*{{{*/ 216 | if ($this->isEmpty()) { 217 | return PEAR::raiseError('Matrix has not been populated'); 218 | } else { 219 | return $this->_data; 220 | } 221 | }/*}}}*/ 222 | 223 | /** 224 | * Sets the threshold to consider a numeric value as zero: 225 | * if number <= epsilon then number = 0 226 | * 227 | * @acess public 228 | * @param number $epsilon the upper bound value 229 | * @return boolean|PEAR_Error true if successful, a PEAR_Error otherwise 230 | */ 231 | function setZeroThreshold($epsilon) {/*{{{*/ 232 | if (!is_numeric($epsilon)) { 233 | return PEAR::raisError('Expection a number for threshold, using the old value: '.$this->_epsilon); 234 | } else { 235 | $this->_epsilon = $epsilon; 236 | return true; 237 | } 238 | }/*}}}*/ 239 | 240 | /** 241 | * Returns the value of the upper bound used to minimize round off errors 242 | * 243 | * @return float 244 | */ 245 | function getZeroThreshold() {/*{{{*/ 246 | return $this->_epsilon; 247 | }/*}}}*/ 248 | 249 | /** 250 | * Checks if the matrix has been initialized. 251 | * 252 | * @return boolean TRUE on success, FALSE otherwise 253 | */ 254 | function isEmpty() {/*{{{*/ 255 | return ( empty($this->_data) || is_null($this->_data) ); 256 | }/*}}}*/ 257 | 258 | 259 | /** 260 | * Returns an array with the number of rows and columns in the matrix 261 | * 262 | * @return array|PEAR_Error an array of integers on success, a PEAR_Error object otherwise 263 | */ 264 | function getSize() {/*{{{*/ 265 | if ($this->isEmpty()) 266 | return PEAR::raiseError('Matrix has not been populated'); 267 | else 268 | return array($this->_num_rows, $this->_num_cols); 269 | }/*}}}*/ 270 | 271 | /** 272 | * Checks if it is a square matrix (i.e. num rows == num cols) 273 | * 274 | * @return boolean TRUE on success, FALSE otherwise 275 | */ 276 | function isSquare () {/*{{{*/ 277 | if ($this->isEmpty()) { 278 | return PEAR::raiseError('Matrix has not been populated'); 279 | } else { 280 | return $this->_square; 281 | } 282 | }/*}}}*/ 283 | 284 | /** 285 | * Returns the Euclidean norm of the matrix. 286 | * 287 | * Euclidean norm = sqrt( sum( e[i][j]^2 ) ) 288 | * 289 | * @return float|PEAR_Error a number on success, a PEAR_Error otherwise 290 | */ 291 | function norm() {/*{{{*/ 292 | if (!is_null($this->_norm)) { 293 | return $this->_norm; 294 | } else { 295 | return PEAR::raiseError('Uninitialized Math_Matrix object'); 296 | } 297 | }/*}}}*/ 298 | 299 | /** 300 | * Returns a new Math_Matrix object with the same data as the current one 301 | * 302 | * @return object Math_Matrix|PEAR_Error a Math_Matrix objects on succes, a 303 | * PEAR_Error otherwise. 304 | */ 305 | function cloneMatrix() {/*{{{*/ 306 | if ($this->isEmpty()) { 307 | return PEAR::raiseError('Matrix has not been populated'); 308 | } else { 309 | return new Math_Matrix($this->_data); 310 | } 311 | }/*}}}*/ 312 | 313 | 314 | /** 315 | * Sets the value of the element at (row,col) 316 | * 317 | * @param integer $row 318 | * @param integer $col 319 | * @param numeric $value 320 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 321 | */ 322 | function setElement($row, $col, $value) {/*{{{*/ 323 | if ($this->isEmpty()) { 324 | return PEAR::raiseError('Matrix has not been populated'); 325 | } 326 | if ($row >= $this->_num_rows && $col >= $this->_num_cols) { 327 | return PEAR::raiseError('Incorrect row and column values'); 328 | } 329 | if (!is_numeric($value)) { 330 | return PEAR::raiseError('Incorrect value, expecting a number'); 331 | } 332 | $this->_data[$row][$col] = $value; 333 | return true; 334 | }/*}}}*/ 335 | 336 | /** 337 | * Returns the value of the element at (row,col) 338 | * 339 | * @param integer $row 340 | * @param integer $col 341 | * @return number|PEAR_Error a number on success, a PEAR_Error otherwise 342 | */ 343 | function getElement($row, $col) {/*{{{*/ 344 | if ($this->isEmpty()) { 345 | return PEAR::raiseError('Matrix has not been populated'); 346 | } 347 | if ($row >= $this->_num_rows && $col >= $this->_num_cols) { 348 | return PEAR::raiseError('Incorrect row and column values'); 349 | } 350 | return $this->_data[$row][$col]; 351 | }/*}}}*/ 352 | 353 | /** 354 | * Returns the row with the given index 355 | * 356 | * This method checks that matrix has been initialized and that the 357 | * row requested is not outside the range of rows. 358 | * 359 | * @param integer $row 360 | * @param optional boolean $asVector whether to return a Math_Vector or a simple array. Default = false. 361 | * @return array|Math_Vector|PEAR_Error an array of numbers or a Math_Vector on success, a PEAR_Error otherwise 362 | */ 363 | function getRow ($row, $asVector = false) {/*{{{*/ 364 | if ($this->isEmpty()) { 365 | return PEAR::raiseError('Matrix has not been populated'); 366 | } 367 | if (is_integer($row) && $row >= $this->_num_rows) { 368 | return PEAR::raiseError('Incorrect row value'); 369 | } 370 | if ($asVector) { 371 | $classes = get_declared_classes(); 372 | if (!in_array("math_vector", $classes) || !in_array("math_vectopop", $classes)) { 373 | return PEAR::raiseError ("Classes Math_Vector and Math_VectorOp undefined". 374 | " add \"require_once 'Math/Vector/Vector.php'\" to your script"); 375 | } 376 | return new Math_Vector($this->_data[$row]); 377 | } else { 378 | return $this->_data[$row]; 379 | } 380 | }/*}}}*/ 381 | 382 | /** 383 | * Sets the row with the given index to the array 384 | * 385 | * This method checks that the row is less than the size of the matrix 386 | * rows, and that the array size equals the number of columns in the 387 | * matrix. 388 | * 389 | * @param integer $row index of the row 390 | * @param array $arr array of numbers 391 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 392 | */ 393 | function setRow ($row, $arr) {/*{{{*/ 394 | if ($this->isEmpty()) { 395 | return PEAR::raiseError('Matrix has not been populated'); 396 | } 397 | if ($row >= $this->_num_rows) { 398 | return PEAR::raiseError('Row index out of bounds'); 399 | } 400 | if (count($arr) != $this->_num_cols) { 401 | return PEAR::raiseError('Incorrect size for matrix row: expecting '.$this->_num_cols 402 | .' columns, got '.count($arr).' columns'); 403 | } 404 | for ($i=0; $i < $this->_num_cols; $i++) { 405 | if (!is_numeric($arr[$i])) { 406 | return PEAR::raiseError('Incorrect values, expecting numbers'); 407 | } 408 | } 409 | $this->_data[$row] = $arr; 410 | return true; 411 | }/*}}}*/ 412 | 413 | /** 414 | * Returns the column with the given index 415 | * 416 | * This method checks that matrix has been initialized and that the 417 | * column requested is not outside the range of column. 418 | * 419 | * @param integer $col 420 | * @param optional boolean $asVector whether to return a Math_Vector or a simple array. Default = false. 421 | * @return array|Math_Vector|PEAR_Error an array of numbers or a Math_Vector on success, a PEAR_Error otherwise 422 | */ 423 | function getCol ($col, $asVector=false) {/*{{{*/ 424 | if ($this->isEmpty()) { 425 | return PEAR::raiseError('Matrix has not been populated'); 426 | } 427 | if (is_integer($col) && $col >= $this->_num_cols) { 428 | return PEAR::raiseError('Incorrect column value'); 429 | } 430 | for ($i=0; $i < $this->_num_rows; $i++) { 431 | $ret[$i] = $this->getElement($i,$col); 432 | } 433 | if ($asVector) { 434 | $classes = get_declared_classes(); 435 | if (!in_array("math_vector", $classes) || !in_array("math_vectopop", $classes)) { 436 | return PEAR::raiseError ("Classes Math_Vector and Math_VectorOp undefined". 437 | " add \"require_once 'Math/Vector/Vector.php'\" to your script"); 438 | } 439 | return new Math_Vector($ret); 440 | } else { 441 | return $ret; 442 | } 443 | }/*}}}*/ 444 | 445 | /** 446 | * Sets the column with the given index to the array 447 | * 448 | * This method checks that the column is less than the size of the matrix 449 | * columns, and that the array size equals the number of rows in the 450 | * matrix. 451 | * 452 | * @param integer $col index of the column 453 | * @param array $arr array of numbers 454 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 455 | */ 456 | function setCol ($col, $arr) {/*{{{*/ 457 | if ($this->isEmpty()) { 458 | return PEAR::raiseError('Matrix has not been populated'); 459 | } 460 | if ($col >= $this->_num_cols) { 461 | return PEAR::raiseError('Incorrect column value'); 462 | } 463 | if (count($arr) != $this->_num_cols) { 464 | return PEAR::raiseError('Incorrect size for matrix column'); 465 | } 466 | for ($i=0; $i < $this->_num_rows; $i++) { 467 | if (!is_numeric($arr[$i])) { 468 | return PEAR::raiseError('Incorrect values, expecting numbers'); 469 | } else { 470 | $err = $this->setElement($i, $col, $arr[$i]); 471 | if (PEAR::isError($err)) { 472 | return $err; 473 | } 474 | } 475 | 476 | } 477 | return true; 478 | }/*}}}*/ 479 | 480 | /** 481 | * Swaps the rows with the given indices 482 | * 483 | * @param integer $i 484 | * @param integer $j 485 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 486 | */ 487 | function swapRows($i, $j) {/*{{{*/ 488 | $r1 = $this->getRow($i); 489 | if (PEAR::isError($r1)) { 490 | return $r1; 491 | } 492 | $r2 = $this->getRow($j); 493 | if (PEAR::isError($r2)) { 494 | return $r2; 495 | } 496 | $e = $this->setRow($j, $r1); 497 | if (PEAR::isError($e)) { 498 | return $e; 499 | } 500 | $e = $this->setRow($i, $r2); 501 | if (PEAR::isError($e)) { 502 | return $e; 503 | } 504 | return true; 505 | }/*}}}*/ 506 | 507 | /** 508 | * Swaps the columns with the given indices 509 | * 510 | * @param integer $i 511 | * @param integer $j 512 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 513 | */ 514 | function swapCols($i, $j) {/*{{{*/ 515 | $r1 = $this->getCol($i); 516 | if (PEAR::isError($r1)) { 517 | return $r1; 518 | } 519 | $r2 = $this->getCol($j); 520 | if (PEAR::isError($r2)) { 521 | return $r2; 522 | } 523 | $e = $this->setCol($j, $r1); 524 | if (PEAR::isError($e)) { 525 | return $e; 526 | } 527 | $e = $this->setCol($i, $r2); 528 | if (PEAR::isError($e)) { 529 | return $e; 530 | } 531 | return true; 532 | }/*}}}*/ 533 | 534 | /** 535 | * Swaps a given row with a given column. Only valid for square matrices. 536 | * 537 | * @param integer $row index of row 538 | * @param integer $col index of column 539 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 540 | */ 541 | function swapRowCol ($row, $col) {/*{{{*/ 542 | if (!$this->isSquare() || !is_int($row) || !is_int($col)) { 543 | return PEAR::raiseError("Parameters must be row and a column indices"); 544 | } 545 | $c = $this->getCol($col); 546 | if (PEAR::isError($c)) { 547 | return $c; 548 | } 549 | $r = $this->getRow($row); 550 | if (PEAR::isError($r)) { 551 | return $r; 552 | } 553 | $e = $this->setCol($col, $r); 554 | if (PEAR::isError($e)) { 555 | return $e; 556 | } 557 | $e = $this->setRow($row, $c); 558 | if (PEAR::isError($e)) { 559 | return $e; 560 | } 561 | return true; 562 | }/*}}}*/ 563 | 564 | /** 565 | * Returns the minimum value of the elements in the matrix 566 | * 567 | * @return number|PEAR_Error a number on success, a PEAR_Error otherwise 568 | */ 569 | function getMin () {/*{{{*/ 570 | if ($this->isEmpty()) { 571 | return PEAR::raiseError('Matrix has not been populated'); 572 | } else { 573 | return $this->_min; 574 | } 575 | }/*}}}*/ 576 | 577 | /** 578 | * Returns the maximum value of the elements in the matrix 579 | * 580 | * @return number|PEAR_Error a number on success, a PEAR_Error otherwise 581 | */ 582 | function getMax () {/*{{{*/ 583 | if ($this->isEmpty()) { 584 | return PEAR::raiseError('Matrix has not been populated'); 585 | } else { 586 | return $this->_max; 587 | } 588 | }/*}}}*/ 589 | 590 | /** 591 | * Gets the position of the first element with the given value 592 | * 593 | * @param numeric $val 594 | * @return array|PEAR_Error an array of two numbers on success, FALSE if value is not found, and PEAR_Error otherwise 595 | */ 596 | function getValueIndex ($val) {/*{{{*/ 597 | if ($this->isEmpty()) { 598 | return PEAR::raiseError('Matrix has not been populated'); 599 | } 600 | for ($i=0; $i < $this->_num_rows; $i++) { 601 | for ($j=0; $j < $this->_num_cols; $j++) { 602 | if ($this->_data[$i][$j] == $val) { 603 | return array($i, $j); 604 | } 605 | } 606 | } 607 | return false; 608 | }/*}}}*/ 609 | 610 | /** 611 | * Gets the position of the element with the minimum value 612 | * 613 | * @return array|PEAR_Error an array of two numbers on success, FALSE if value is not found, and PEAR_Error otherwise 614 | * @see getValueIndex() 615 | */ 616 | function getMinIndex () {/*{{{*/ 617 | if ($this->isEmpty()) { 618 | return PEAR::raiseError('Matrix has not been populated'); 619 | } else { 620 | return $this->getValueIndex($this->_min); 621 | } 622 | }/*}}}*/ 623 | 624 | /** 625 | * Gets the position of the element with the maximum value 626 | * 627 | * @return array|PEAR_Error an array of two numbers on success, FALSE if value is not found, and PEAR_Error otherwise 628 | * @see getValueIndex() 629 | */ 630 | function getMaxIndex () {/*{{{*/ 631 | if ($this->isEmpty()) { 632 | return PEAR::raiseError('Matrix has not been populated'); 633 | } else { 634 | return $this->getValueIndex($this->_max); 635 | } 636 | }/*}}}*/ 637 | 638 | /** 639 | * Transpose the matrix rows and columns 640 | * 641 | * @return boolean|PEAR_Error TRUE on success, PEAR_Error otherwise 642 | */ 643 | function transpose () {/*{{{*/ 644 | /* John Pye noted that this operation is defined for 645 | * any matrix 646 | if (!$this->isSquare()) { 647 | return PEAR::raiseError("Transpose is undefined for non-sqaure matrices"); 648 | } 649 | */ 650 | list($nr, $nc) = $this->getSize(); 651 | $data = array(); 652 | for ($i=0; $i < $nc; $i++) { 653 | $col = $this->getCol($i); 654 | if (PEAR::isError($col)) { 655 | return $col; 656 | } else { 657 | $data[] = $col; 658 | } 659 | } 660 | return $this->setData($data); 661 | }/*}}}*/ 662 | 663 | /** 664 | * Returns the trace of the matrix. Trace = sum(e[i][j]), for all i == j 665 | * 666 | * @return number|PEAR_Error a number on success, PEAR_Error otherwise 667 | */ 668 | function trace() {/*{{{*/ 669 | if ($this->isEmpty()) { 670 | return PEAR::raiseError('Matrix has not been populated'); 671 | } 672 | if (!$this->isSquare()) { 673 | return PEAR::raiseError('Trace undefined for non-square matrices'); 674 | } 675 | $trace = 0; 676 | for ($i=0; $i < $this->_num_rows; $i++) { 677 | $trace += $this->getElement($i, $i); 678 | } 679 | return $trace; 680 | }/*}}}*/ 681 | 682 | /** 683 | * Calculates the matrix determinant using Gaussian elimination with partial pivoting. 684 | * 685 | * At each step of the pivoting proccess, it checks that the normalized 686 | * determinant calculated so far is less than 10^-18, trying to detect 687 | * singular or ill-conditioned matrices 688 | * 689 | * @return number|PEAR_Error a number on success, a PEAR_Error otherwise 690 | */ 691 | function determinant() {/*{{{*/ 692 | if (!is_null($this->_det) && is_numeric($this->_det)) { 693 | return $this->_det; 694 | } 695 | if ($this->isEmpty()) { 696 | return PEAR::raiseError('Matrix has not been populated'); 697 | } 698 | if (!$this->isSquare()) { 699 | return PEAR::raiseError('Determinant undefined for non-square matrices'); 700 | } 701 | $norm = $this->norm(); 702 | if (PEAR::isError($norm)) { 703 | return $norm; 704 | } 705 | $det = 1.0; 706 | $sign = 1; 707 | // work on a copy 708 | $m = $this->cloneMatrix(); 709 | list($nr, $nc) = $m->getSize(); 710 | for ($r=0; $r<$nr; $r++) { 711 | // find the maximum element in the column under the current diagonal element 712 | $ridx = $m->_maxElementIndex($r); 713 | if (PEAR::isError($ridx)) { 714 | return $ridx; 715 | } 716 | if ($ridx != $r) { 717 | $sign = -$sign; 718 | $e = $m->swapRows($r, $ridx); 719 | if (PEAR::isError($e)) { 720 | return $e; 721 | } 722 | } 723 | // pivoting element 724 | $pelement = $m->getElement($r, $r); 725 | if (PEAR::isError($pelement)) { 726 | return $pelement; 727 | } 728 | $det *= $pelement; 729 | // Is this an singular or ill-conditioned matrix? 730 | // i.e. is the normalized determinant << 1 and -> 0? 731 | if ((abs($det)/$norm) < $this->_epsilon) { 732 | return PEAR::raiseError('Probable singular or ill-conditioned matrix, normalized determinant = ' 733 | .(abs($det)/$norm)); 734 | } 735 | if ($pelement == 0) { 736 | return PEAR::raiseError('Cannot continue, pivoting element is zero'); 737 | } 738 | // zero all elements in column below the pivoting element 739 | for ($i = $r + 1; $i < $nr; $i++) { 740 | $factor = $m->getElement($i, $r) / $pelement; 741 | for ($j=$r; $j < $nc; $j++) { 742 | $val = $m->getElement($i, $j) - $factor*$m->getElement($r, $j); 743 | $e = $m->setElement($i, $j, $val); 744 | if (PEAR::isError($e)) { 745 | return $e; 746 | } 747 | } 748 | } 749 | // for debugging 750 | //echo "COLUMN: $r\n"; 751 | //echo $m->toString()."\n"; 752 | } 753 | unset($m); 754 | if ($sign < 0) { 755 | $det = -$det; 756 | } 757 | // save the value 758 | $this->_det = $det; 759 | return $det; 760 | }/*}}}*/ 761 | 762 | /** 763 | * Returns the normalized determinant = abs(determinant)/(euclidean norm) 764 | * 765 | * @return number|PEAR_Error a positive number on success, a PEAR_Error otherwise 766 | */ 767 | function normalizedDeterminant() {/*{{{*/ 768 | $det = $this->determinant(); 769 | if (PEAR::isError($det)) { 770 | return $det; 771 | } 772 | $norm = $this->norm(); 773 | if (PEAR::isError($norm)) { 774 | return $norm; 775 | } 776 | if ($norm == 0) { 777 | return PEAR::raiseError('Undefined normalized determinant, euclidean norm is zero'); 778 | } 779 | return abs($det / $norm); 780 | }/*}}}*/ 781 | 782 | /** 783 | * Inverts a matrix using Gauss-Jordan elimination with partial pivoting 784 | * 785 | * @return number|PEAR_Error the value of the matrix determinant on success, PEAR_Error otherwise 786 | * @see scaleRow() 787 | */ 788 | function invert() {/*{{{*/ 789 | if ($this->isEmpty()) { 790 | return PEAR::raiseError('Matrix has not been populated'); 791 | } 792 | if (!$this->isSquare()) { 793 | return PEAR::raiseError('Determinant undefined for non-square matrices'); 794 | } 795 | $norm = $this->norm(); 796 | $sign = 1; 797 | $det = 1.0; 798 | // work on a copy to be safe 799 | $m = $this->cloneMatrix(); 800 | if (PEAR::isError($m)) { 801 | return $m; 802 | } 803 | list($nr, $nc) = $m->getSize(); 804 | // Unit matrix to use as target 805 | $q = Math_Matrix::makeUnit($nr); 806 | if (PEAR::isError($q)) { 807 | return $q; 808 | } 809 | for ($i=0; $i<$nr; $i++) { 810 | $ridx = $this->_maxElementIndex($i); 811 | if ($i != $ridx) { 812 | $sign = -$sign; 813 | $e = $m->swapRows($i, $ridx); 814 | if (PEAR::isError($e)) { 815 | return $e; 816 | } 817 | $e = $q->swapRows($i, $ridx); 818 | if (PEAR::isError($e)) { 819 | return $e; 820 | } 821 | } 822 | $pelement = $m->getElement($i, $i); 823 | if (PEAR::isError($pelement)) { 824 | return $pelement; 825 | } 826 | if ($pelement == 0) { 827 | return PEAR::raiseError('Cannot continue inversion, pivoting element is zero'); 828 | } 829 | $det *= $pelement; 830 | if ((abs($det)/$norm) < $this->_epsilon) { 831 | return PEAR::raiseError('Probable singular or ill-conditioned matrix, normalized determinant = ' 832 | .(abs($det)/$norm)); 833 | } 834 | $e = $m->scaleRow($i, 1/$pelement); 835 | if (PEAR::isError($e)) { 836 | return $e; 837 | } 838 | $e = $q->scaleRow($i, 1/$pelement); 839 | if (PEAR::isError($e)) { 840 | return $e; 841 | } 842 | // zero all column elements execpt for the current one 843 | $tpelement = $m->getElement($i, $i); 844 | for ($j=0; $j<$nr; $j++) { 845 | if ($j == $i) { 846 | continue; 847 | } 848 | $factor = $m->getElement($j, $i) / $tpelement; 849 | for ($k=0; $k<$nc; $k++) { 850 | $vm = $m->getElement($j, $k) - $factor * $m->getElement($i, $k); 851 | $vq = $q->getElement($j, $k) - $factor * $q->getElement($i, $k); 852 | $m->setElement($j, $k, $vm); 853 | $q->setElement($j, $k, $vq); 854 | } 855 | } 856 | // for debugging 857 | /* 858 | echo "COLUMN: $i\n"; 859 | echo $m->toString()."\n"; 860 | echo $q->toString()."\n"; 861 | */ 862 | } 863 | $data = $q->getData(); 864 | /* 865 | // for debugging 866 | echo $m->toString()."\n"; 867 | echo $q->toString()."\n"; 868 | */ 869 | unset($m); 870 | unset($q); 871 | $e = $this->setData($data); 872 | if (PEAR::isError($e)) { 873 | return $e; 874 | } 875 | if ($sign < 0) { 876 | $det = -$det; 877 | } 878 | $this->_det = $det; 879 | return $det; 880 | }/*}}}*/ 881 | 882 | /** 883 | * Returns a submatrix from the position (row, col), with nrows and ncols 884 | * 885 | * @return object Math_Matrix|PEAR_Error Math_Matrix on success, PEAR_Error otherwise 886 | */ 887 | function &getSubMatrix ($row, $col, $nrows, $ncols) {/*{{{*/ 888 | if (!is_numeric($row) || !is_numeric($col) 889 | || !is_numeric($nrows) || !is_numeric($ncols)) { 890 | return PEAR::raiseError('Parameters must be a initial row and column, and number of rows and columns in submatrix'); 891 | } 892 | list($nr, $nc) = $this->getSize(); 893 | if ($row + $nrows > $nr) { 894 | return PEAR::raiseError('Rows in submatrix more than in original matrix'); 895 | } 896 | if ($col + $ncols > $nc) { 897 | return PEAR::raiseError('Columns in submatrix more than in original matrix'); 898 | } 899 | $data = array(); 900 | for ($i=0; $i < $nrows; $i++) { 901 | for ($j=0; $j < $ncols; $j++) { 902 | $data[$i][$j] = $this->getElement($i + $row, $j + $col); 903 | } 904 | } 905 | $obj = new Math_Matrix($data); 906 | return $obj; 907 | }/*}}}*/ 908 | 909 | 910 | /** 911 | * Returns the diagonal of a square matrix as a Math_Vector 912 | * 913 | * @return object Math_Vector|PEAR_Error Math_Vector on success, PEAR_Error otherwise 914 | */ 915 | function &getDiagonal() {/*{{{*/ 916 | if ($this->isEmpty()) { 917 | return PEAR::raiseError('Matrix has not been populated'); 918 | } 919 | if (!$this->isSquare()) { 920 | return PEAR::raiseError('Cannot get diagonal vector of a non-square matrix'); 921 | } 922 | list($n,) = $this->getSize(); 923 | $vals = array(); 924 | for ($i=0; $i<$n; $i++) { 925 | $vals[$i] = $this->getElement($i, $i); 926 | } 927 | return new Math_Vector($vals); 928 | }/*}}}*/ 929 | 930 | /** 931 | * Returns a simple string representation of the matrix 932 | * 933 | * @param optional string $format a sprintf() format used to print the matrix elements (default = '%6.2f') 934 | * @return string|PEAR_Error a string on success, PEAR_Error otherwise 935 | */ 936 | function toString ($format='%6.2f') {/*{{{*/ 937 | if ($this->isEmpty()) { 938 | return PEAR::raiseError('Matrix has not been populated'); 939 | } 940 | $out = ""; 941 | for ($i=0; $i < $this->_num_rows; $i++) { 942 | for ($j=0; $j < $this->_num_cols; $j++) { 943 | // remove the -0.0 output 944 | $entry = $this->_data[$i][$j]; 945 | if (sprintf('%2.1f',$entry) == '-0.0') { 946 | $entry = 0; 947 | } 948 | $out .= sprintf($format, $entry); 949 | } 950 | $out .= "\n"; 951 | } 952 | return $out; 953 | }/*}}}*/ 954 | 955 | /** 956 | * Returns an HTML table representation of the matrix elements 957 | * 958 | * @return a string on success, PEAR_Error otherwise 959 | */ 960 | function toHTML() {/*{{{*/ 961 | if ($this->isEmpty()) { 962 | return PEAR::raiseError('Matrix has not been populated'); 963 | } 964 | $out = "\n\t\n\t\n\t\t"; 967 | for ($i=0; $i < $this->_num_cols; $i++) { 968 | $out .= ""; 969 | } 970 | $out .= "\n\t\n"; 971 | for ($i=0; $i < $this->_num_rows; $i++) { 972 | $out .= "\t\n\t\t"; 973 | for ($j=0; $j < $this->_num_cols; $j++) { 974 | $out .= ""; 975 | } 976 | $out .= "\n\t"; 977 | } 978 | return $out."\n
Matrix"; 965 | $out .= "
"; 966 | $out .= $this->_num_rows."x".$this->_num_cols."".$i."
".$i."".$this->_data[$i][$j]."
\n"; 979 | }/*}}}*/ 980 | 981 | // private methods 982 | 983 | /** 984 | * Returns the index of the row with the maximum value under column of the element e[i][i] 985 | * 986 | * @access private 987 | * @return an integer 988 | */ 989 | function _maxElementIndex($r) {/*{{{*/ 990 | $max = 0; 991 | $idx = -1; 992 | list($nr, $nc) = $this->getSize(); 993 | $arr = array(); 994 | for ($i=$r; $i<$nr; $i++) { 995 | $val = abs($this->_data[$i][$r]); 996 | if ($val > $max) { 997 | $max = $val; 998 | $idx = $i; 999 | } 1000 | } 1001 | if ($idx == -1) { 1002 | $idx = $r; 1003 | } 1004 | return $idx; 1005 | }/*}}}*/ 1006 | 1007 | 1008 | // Binary operations 1009 | 1010 | /**#@+ 1011 | * @access public 1012 | */ 1013 | 1014 | /** 1015 | * Adds a matrix to this one 1016 | * 1017 | * @param object Math_Matrix $m1 1018 | * @return boolean|PEAR_Error TRUE on success, PEAR_Error otherwise 1019 | * @see getSize() 1020 | * @see getElement() 1021 | * @see setData() 1022 | */ 1023 | function add ($m1) {/*{{{*/ 1024 | if (!Math_Matrix::isMatrix($m1)) { 1025 | return PEAR::raiseError("Parameter must be a Math_Matrix object"); 1026 | } 1027 | if ($this->getSize() != $m1->getSize()) { 1028 | return PEAR::raiseError("Matrices must have the same dimensions"); 1029 | } 1030 | list($nr, $nc) = $m1->getSize(); 1031 | $data = array(); 1032 | for ($i=0; $i < $nr; $i++) { 1033 | for ($j=0; $j < $nc; $j++) { 1034 | $el1 = $m1->getElement($i,$j); 1035 | if (PEAR::isError($el1)) { 1036 | return $el1; 1037 | } 1038 | $el = $this->getElement($i,$j); 1039 | if (PEAR::isError($el)) { 1040 | return $el; 1041 | } 1042 | $data[$i][$j] = $el + $el1; 1043 | } 1044 | } 1045 | if (!empty($data)) { 1046 | return $this->setData($data); 1047 | } else { 1048 | return PEAR::raiseError('Undefined error'); 1049 | } 1050 | }/*}}}*/ 1051 | 1052 | /** 1053 | * Substracts a matrix from this one 1054 | * 1055 | * @param object Math_Matrix $m1 1056 | * @return boolean|PEAR_Error TRUE on success, PEAR_Error otherwise 1057 | * @see getSize() 1058 | * @see getElement() 1059 | * @see setData() 1060 | */ 1061 | function sub (&$m1) {/*{{{*/ 1062 | if (!Math_Matrix::isMatrix($m1)) { 1063 | return PEAR::raiseError("Parameter must be a Math_Matrix object"); 1064 | } 1065 | if ($this->getSize() != $m1->getSize()) { 1066 | return PEAR::raiseError("Matrices must have the same dimensions"); 1067 | } 1068 | list($nr, $nc) = $m1->getSize(); 1069 | $data = array(); 1070 | for ($i=0; $i < $nr; $i++) { 1071 | for ($j=0; $j < $nc; $j++) { 1072 | $el1 = $m1->getElement($i,$j); 1073 | if (PEAR::isError($el1)) { 1074 | return $el1; 1075 | } 1076 | $el = $this->getElement($i,$j); 1077 | if (PEAR::isError($el)) { 1078 | return $el; 1079 | } 1080 | $data[$i][$j] = $el - $el1; 1081 | } 1082 | } 1083 | if (!empty($data)) { 1084 | return $this->setData($data); 1085 | } else { 1086 | return PEAR::raiseError('Undefined error'); 1087 | } 1088 | }/*}}}*/ 1089 | 1090 | /** 1091 | * Scales the matrix by a given factor 1092 | * 1093 | * @param numeric $scale the scaling factor 1094 | * @return boolean|PEAR_Error TRUE on success, PEAR_Error otherwise 1095 | * @see getSize() 1096 | * @see getElement() 1097 | * @see setData() 1098 | */ 1099 | function scale ($scale) {/*{{{*/ 1100 | if (!is_numeric($scale)) { 1101 | return PEAR::raiseError("Parameter must be a number"); 1102 | } 1103 | list($nr, $nc) = $this->getSize(); 1104 | $data = array(); 1105 | for ($i=0; $i < $nr; $i++) { 1106 | for ($j=0; $j < $nc; $j++) { 1107 | $data[$i][$j] = $scale * $this->getElement($i,$j); 1108 | } 1109 | } 1110 | if (!empty($data)) { 1111 | return $this->setData($data); 1112 | } else { 1113 | return PEAR::raiseError('Undefined error'); 1114 | } 1115 | }/*}}}*/ 1116 | 1117 | /** 1118 | * Multiplies (scales) a row by the given factor 1119 | * 1120 | * @param integer $row the row index 1121 | * @param numeric $factor the scaling factor 1122 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 1123 | * @see invert() 1124 | */ 1125 | function scaleRow($row, $factor) {/*{{{*/ 1126 | if ($this->isEmpty()) { 1127 | return PEAR::raiseError('Uninitialized Math_Matrix object'); 1128 | } 1129 | if (!is_integer($row) || !is_numeric($factor)) { 1130 | return PEAR::raiseError('Row index must be an integer, and factor a valid number'); 1131 | } 1132 | if ($row >= $this->_num_rows) { 1133 | return PEAR::raiseError('Row index out of bounds'); 1134 | } 1135 | $r = $this->getRow($row); 1136 | if (PEAR::isError($r)) { 1137 | return $r; 1138 | } 1139 | $nr = count($r); 1140 | for ($i=0; $i<$nr; $i++) { 1141 | $r[$i] *= $factor; 1142 | } 1143 | return $this->setRow($row, $r); 1144 | }/*}}}*/ 1145 | 1146 | /** 1147 | * Multiplies this matrix (A) by another one (B), and stores 1148 | * the result back in A 1149 | * 1150 | * @param object Math_Matrix $m1 1151 | * @return boolean|PEAR_Error TRUE on success, PEAR_Error otherwise 1152 | * @see getSize() 1153 | * @see getRow() 1154 | * @see getCol() 1155 | * @see setData() 1156 | * @see setZeroThreshold() 1157 | */ 1158 | function multiply(&$B) {/*{{{*/ 1159 | if (!Math_Matrix::isMatrix($B)) { 1160 | return PEAR::raiseError ('Wrong parameter, expected a Math_Matrix object'); 1161 | } 1162 | list($nrA, $ncA) = $this->getSize(); 1163 | list($nrB, $ncB) = $B->getSize(); 1164 | if ($ncA != $nrB) { 1165 | return PEAR::raiseError('Incompatible sizes columns in matrix must be the same as rows in parameter matrix'); 1166 | } 1167 | $data = array(); 1168 | for ($i=0; $i < $nrA; $i++) { 1169 | $data[$i] = array(); 1170 | for ($j=0; $j < $ncB; $j++) { 1171 | $rctot = 0; 1172 | for ($k=0; $k < $ncA; $k++) { 1173 | $rctot += $this->getElement($i,$k) * $B->getElement($k, $j); 1174 | } 1175 | // take care of some round-off errors 1176 | if (abs($rctot) <= $this->_epsilon) { 1177 | $rctot = 0.0; 1178 | } 1179 | $data[$i][$j] = $rctot; 1180 | } 1181 | } 1182 | if (!empty($data)) { 1183 | return $this->setData($data); 1184 | } else { 1185 | return PEAR::raiseError('Undefined error'); 1186 | } 1187 | }/*}}}*/ 1188 | 1189 | /** 1190 | * Multiplies a vector by this matrix 1191 | * 1192 | * @param object Math_Vector $v1 1193 | * @return object Math_Vector|PEAR_Error Math_Vector on success, PEAR_Error otherwise 1194 | * @see getSize() 1195 | * @see getRow() 1196 | * @see Math_Vector::get() 1197 | */ 1198 | function &vectorMultiply(&$v1) {/*{{{*/ 1199 | if (!Math_VectorOp::isVector($v1)) { 1200 | return PEAR::raiseError ("Wrong parameter, a Math_Vector object"); 1201 | } 1202 | list($nr, $nc) = $this->getSize(); 1203 | $nv = $v1->size(); 1204 | if ($nc != $nv) { 1205 | return PEAR::raiseError("Incompatible number of columns in matrix ($nc) must ". 1206 | "be the same as the number of elements ($nv) in the vector"); 1207 | } 1208 | $data = array(); 1209 | for ($i=0; $i < $nr; $i++) { 1210 | $data[$i] = 0; 1211 | for ($j=0; $j < $nv; $j++) { 1212 | $data[$i] += $this->getElement($i,$j) * $v1->get($j); 1213 | } 1214 | } 1215 | $obj = new Math_Vector($data); 1216 | return $obj; 1217 | }/*}}}*/ 1218 | 1219 | // Static operations 1220 | 1221 | /**@+ 1222 | * @static 1223 | * @access public 1224 | */ 1225 | 1226 | /** 1227 | * Create a matrix from a file, using data stored in the given format 1228 | */ 1229 | function &readFromFile ($filename, $format='serialized') {/*{{{*/ 1230 | if (!file_exists($filename) || !is_readable($filename)) { 1231 | return PEAR::raiseError('File cannot be opened for reading'); 1232 | } 1233 | if (filesize($filename) == 0) { 1234 | return PEAR::raiseError('File is empty'); 1235 | } 1236 | if ($format == 'serialized') { 1237 | if (function_exists("file_get_contents")) { 1238 | $objser = file_get_contents($filename); 1239 | } else { 1240 | $objser = implode("",file($filename)); 1241 | } 1242 | $obj = unserialize($objser); 1243 | if (Math_Matrix::isMatrix($obj)) { 1244 | return $obj; 1245 | } else { 1246 | return PEAR::raiseError('File did not contain a Math_Matrix object'); 1247 | } 1248 | } else { // assume CSV data 1249 | $data = array(); 1250 | $lines = file($filename); 1251 | foreach ($lines as $line) { 1252 | if (preg_match('/^#/', $line)) { 1253 | continue; 1254 | } else { 1255 | $data[] = explode(',',trim($line)); 1256 | } 1257 | } 1258 | $m =& new Math_Matrix(); 1259 | $e = $m->setData($data); 1260 | if (PEAR::isError($e)) { 1261 | return $e; 1262 | } else { 1263 | return $m; 1264 | } 1265 | } 1266 | }/*}}}*/ 1267 | 1268 | /** 1269 | * Writes matrix object to a file using the given format 1270 | * 1271 | * @param object Math_Matrix $matrix the matrix object to store 1272 | * @param string $filename name of file to contain the matrix data 1273 | * @param optional string $format one of 'serialized' (default) or 'csv' 1274 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 1275 | */ 1276 | function writeToFile($matrix, $filename, $format='serialized') {/*{{{*/ 1277 | if (!Math_Matrix::isMatrix($matrix)) { 1278 | return PEAR::raiseError("Parameter must be a Math_Matrix object"); 1279 | } 1280 | if ($matrix->isEmpty()) { 1281 | return PEAR::raiseError("Math_Matrix object is empty"); 1282 | } 1283 | if ($format == 'serialized') { 1284 | $data = serialize($matrix); 1285 | } else { 1286 | $data = ''; 1287 | list($nr, $nc) = $matrix->getSize(); 1288 | for ($i=0; $i<$nr; $i++) { 1289 | $row = $matrix->getRow($i); 1290 | if (PEAR::isError($row)) { 1291 | return $row; 1292 | } 1293 | $data .= implode(',', $row)."\n"; 1294 | } 1295 | } 1296 | $fp = fopen($filename, "w"); 1297 | if (!$fp) { 1298 | return PEAR::raiseError("Cannot write matrix to file $filename"); 1299 | } 1300 | fwrite($fp, $data); 1301 | fclose($fp); 1302 | return true; 1303 | }/*}}}*/ 1304 | 1305 | /** 1306 | * Checks if the object is a Math_Matrix instance 1307 | * 1308 | * @param object Math_Matrix $matrix 1309 | * @return boolean TRUE on success, FALSE otherwise 1310 | */ 1311 | function isMatrix (&$matrix) {/*{{{*/ 1312 | if (function_exists("is_a")) { 1313 | return is_object($matrix) && is_a($matrix, "Math_Matrix"); 1314 | } else { 1315 | return is_object($matrix) && (strtolower(get_class($matrix)) == "math_matrix"); 1316 | } 1317 | }/*}}}*/ 1318 | 1319 | /** 1320 | * Returns a Math_Matrix object of size (nrows, ncols) filled with a value 1321 | * 1322 | * @param integer $nrows number of rows in the generated matrix 1323 | * @param integer $ncols number of columns in the generated matrix 1324 | * @param numeric $value the fill value 1325 | * @return object Math_Matrix|PEAR_Error Math_Matrix instance on success, PEAR_Error otherwise 1326 | */ 1327 | function &makeMatrix ($nrows, $ncols, $value) {/*{{{*/ 1328 | if (!is_int($nrows) && is_int($ncols) && !is_numeric($value)) { 1329 | return PEAR::raiseError('Number of rows, columns, and a numeric fill value expected'); 1330 | } 1331 | $row = explode(":",substr(str_repeat($value.":",$ncols),0,-1)); 1332 | for ($i=0; $i<$nrows; $i++) { 1333 | $m[$i] = $row; 1334 | } 1335 | $obj = new Math_Matrix($m); 1336 | return $obj; 1337 | 1338 | }/*}}}*/ 1339 | 1340 | /** 1341 | * Returns the Math_Matrix object of size (nrows, ncols), filled with the value 1 (one) 1342 | * 1343 | * @param integer $nrows number of rows in the generated matrix 1344 | * @param integer $ncols number of columns in the generated matrix 1345 | * @return object Math_Matrix|PEAR_Error Math_Matrix instance on success, PEAR_Error otherwise 1346 | * @see Math_Matrix::makeMatrix() 1347 | */ 1348 | function &makeOne ($nrows, $ncols) {/*{{{*/ 1349 | return Math_Matrix::makeMatrix ($nrows, $ncols, 1); 1350 | }/*}}}*/ 1351 | 1352 | /** 1353 | * Returns the Math_Matrix object of size (nrows, ncols), filled with the value 0 (zero) 1354 | * 1355 | * @param integer $nrows number of rows in the generated matrix 1356 | * @param integer $ncols number of columns in the generated matrix 1357 | * @return object Math_Matrix|PEAR_Error Math_Matrix instance on success, PEAR_Error otherwise 1358 | * @see Math_Matrix::makeMatrix() 1359 | */ 1360 | function &makeZero ($nrows, $ncols) {/*{{{*/ 1361 | return Math_Matrix::makeMatrix ($nrows, $ncols, 0); 1362 | }/*}}}*/ 1363 | 1364 | /** 1365 | * Returns a square unit Math_Matrix object of the given size 1366 | * 1367 | * A unit matrix is one in which the elements follow the rules: 1368 | * e[i][j] = 1, if i == j 1369 | * e[i][j] = 0, if i != j 1370 | * Such a matrix is also called an 'identity matrix' 1371 | * 1372 | * @param integer $size number of rows and columns in the generated matrix 1373 | * @return object Math_Matrix|PEAR_Error a square unit Math_Matrix instance on success, PEAR_Error otherwise 1374 | * @see Math_Matrix::makeIdentity() 1375 | */ 1376 | function &makeUnit ($size) {/*{{{*/ 1377 | if (!is_integer($size)) { 1378 | return PEAR::raiseError('An integer expected for the size of the Identity matrix'); 1379 | } 1380 | for ($i=0; $i<$size; $i++) { 1381 | for ($j=0; $j<$size; $j++) { 1382 | if ($i == $j) { 1383 | $data[$i][$j] = (float) 1.0; 1384 | } else { 1385 | $data[$i][$j] = (float) 0.0; 1386 | } 1387 | } 1388 | } 1389 | 1390 | $obj = new Math_Matrix($data); 1391 | return $obj; 1392 | }/*}}}*/ 1393 | 1394 | /** 1395 | * Returns the identity matrix of the given size. An alias of Math_Matrix::makeUnit() 1396 | * 1397 | * @param integer $size number of rows and columns in the generated matrix 1398 | * @return object Math_Matrix|PEAR_Error a square unit Math_Matrix instance on success, PEAR_Error otherwise 1399 | * @see Math_Matrix::makeUnit() 1400 | */ 1401 | function &makeIdentity($size) {/*{{{*/ 1402 | return Math_Matrix::makeUnit($size); 1403 | }/*}}}*/ 1404 | 1405 | // famous matrices 1406 | 1407 | /** 1408 | * Returns a Hilbert matrix of the given size: H(i,j) = 1 / (i + j - 1) where {i,j = 1..n} 1409 | * 1410 | * @param integer $size number of rows and columns in the Hilbert matrix 1411 | * @return object Math_Matrix|PEAR_Error a Hilber matrix on success, a PEAR_Error otherwise 1412 | */ 1413 | function &makeHilbert($size) {/*{{{*/ 1414 | if (!is_integer($size)) { 1415 | return PEAR::raiseError('An integer expected for the size of the Hilbert matrix'); 1416 | } 1417 | $data = array(); 1418 | for ($i=1; $i <= $size; $i++) { 1419 | for ($j=1; $j <= $size; $j++) { 1420 | $data[$i - 1][$j - 1] = 1 / ($i + $j - 1); 1421 | } 1422 | } 1423 | $obj = new Math_Matrix($data); 1424 | return $obj; 1425 | }/*}}}*/ 1426 | 1427 | /** 1428 | * Returns a Hankel matrix from a array of size m (C), and (optionally) of 1429 | * an array if size n (R). C will define the first column and R the last 1430 | * row. If R is not defined, C will be used. Also, if the last element of C 1431 | * is not the same to the first element of R, the last element of C is 1432 | * used. 1433 | * 1434 | * H(i,j) = C(i+j-1), i+j-1 <= m 1435 | * H(i,j) = R(i+j-m), otherwise 1436 | * where: 1437 | * i = 1..m 1438 | * j = 1..n 1439 | * 1440 | * @param array $c first column of Hankel matrix 1441 | * @param optional array $r last row of Hankel matrix 1442 | * @return object Math_Matrix|PEAR_Error a Hankel matrix on success, a PEAR_Error otherwise 1443 | */ 1444 | function &makeHankel($c, $r=null) {/*{{{*/ 1445 | if (!is_array($c)) { 1446 | return PEAR::raiseError('Expecting an array of values for the first column of the Hankel matrix'); 1447 | } 1448 | 1449 | if (is_null($r)) { 1450 | $r == $c; 1451 | } 1452 | 1453 | if (!is_array($r)) { 1454 | return PEAR::raiseError('Expecting an array of values for the last row of the Hankel matrix'); 1455 | } 1456 | 1457 | $nc = count($c); 1458 | $nr = count($r); 1459 | 1460 | // make sure that the first element of r is the same as the last element of c 1461 | $r[0] = $c[$nc - 1]; 1462 | 1463 | $data = array(); 1464 | for ($i=1; $i <= $nc; $i++) { 1465 | for ($j=1; $j <= $nr; $j++) { 1466 | if (($i + $j - 1) <= $nc) { 1467 | $val = $c[($i + $j - 1) - 1]; 1468 | } else { 1469 | $val = $r[($i + $j - $nc) - 1]; 1470 | } 1471 | $data[($i - 1)][($j - 1)] = $val; 1472 | } 1473 | } 1474 | $obj = new Math_Matrix($data); 1475 | return $obj; 1476 | 1477 | }/*}}}*/ 1478 | 1479 | 1480 | // methods for solving linear equations 1481 | 1482 | /** 1483 | * Solves a system of linear equations: Ax = b 1484 | * 1485 | * A system such as: 1486 | *
1487 |      *     a11*x1 + a12*x2 + ... + a1n*xn = b1
1488 |      *     a21*x1 + a22*x2 + ... + a2n*xn = b2
1489 |      *     ...
1490 |      *     ak1*x1 + ak2*x2 + ... + akn*xn = bk
1491 |      * 
1492 | * can be rewritten as: 1493 | *
1494 |      *     Ax = b
1495 |      * 
1496 | * where: 1497 | * - A is matrix of coefficients (aij, i=1..k, j=1..n), 1498 | * - b a vector of values (bi, i=1..k), 1499 | * - x the vector of unkowns (xi, i=1..n) 1500 | * Using: x = (Ainv)*b 1501 | * where: 1502 | * - Ainv is the inverse of A 1503 | * 1504 | * @param object Math_Matrix $a the matrix of coefficients 1505 | * @param object Math_Vector $b the vector of values 1506 | * @return object Math_Vector|PEAR_Error a Math_Vector object on succcess, PEAR_Error otherwise 1507 | * @see vectorMultiply() 1508 | */ 1509 | function solve($a, $b) {/*{{{*/ 1510 | // check that the vector classes are defined 1511 | if (!Math_Matrix::isMatrix($a) && !Math_VectorOp::isVector($b)) { 1512 | return PEAR::raiseError('Incorrect parameters, expecting a Math_Matrix and a Math_Vector'); 1513 | } 1514 | $e = $a->invert(); 1515 | if (PEAR::isError($e)) { 1516 | return $e; 1517 | } 1518 | return $a->vectorMultiply($b); 1519 | }/*}}}*/ 1520 | 1521 | /** 1522 | * Solves a system of linear equations: Ax = b, using an iterative error correction algorithm 1523 | * 1524 | * A system such as: 1525 | *
1526 |      *     a11*x1 + a12*x2 + ... + a1n*xn = b1
1527 |      *     a21*x1 + a22*x2 + ... + a2n*xn = b2
1528 |      *     ...
1529 |      *     ak1*x1 + ak2*x2 + ... + akn*xn = bk
1530 |      * 
1531 | * can be rewritten as: 1532 | *
1533 |      *     Ax = b
1534 |      * 
1535 | * where: 1536 | * - A is matrix of coefficients (aij, i=1..k, j=1..n), 1537 | * - b a vector of values (bi, i=1..k), 1538 | * - x the vector of unkowns (xi, i=1..n) 1539 | * Using: x = (Ainv)*b 1540 | * where: 1541 | * - Ainv is the inverse of A 1542 | * 1543 | * The error correction algorithm uses the approach that if: 1544 | * - xp is the approximate solution 1545 | * - bp the values obtained from pluging xp into the original equation 1546 | * We obtain 1547 | *
1548 |      *     A(x - xp) = (b - bp),
1549 |      *     or
1550 |      *     A*xadj = (b - bp)
1551 |      * 
1552 |      * where:
1553 |      * - xadj is the adjusted value (= Ainv*(b - bp))
1554 |      * therefore, we calculate iteratively new values of x using the estimated
1555 |      * xadj and testing to check if we have decreased the error.
1556 |      *
1557 |      * @param object Math_Matrix $a the matrix of coefficients
1558 |      * @param object Math_Vector $b the vector of values
1559 |      * @return object Math_Vector|PEAR_Error a Math_Vector object on succcess, PEAR_Error otherwise
1560 |      * @see vectorMultiply()
1561 |      * @see invert()
1562 |      * @see Math_VectorOp::add()
1563 |      * @see Math_VectorOp::substract()
1564 |      * @see Math_VectorOp::length()
1565 |      */
1566 |     function solveEC($a, $b) {/*{{{*/
1567 |         $ainv = $a->cloneMatrix();
1568 |         $e = $ainv->invert();
1569 |         if (PEAR::isError($e)) {
1570 |             return $e;
1571 |         }
1572 |         $x = $ainv->vectorMultiply($b);
1573 |         if (PEAR::isError($x)) {
1574 |             return $x;
1575 |         }
1576 |         // initial guesses
1577 |         $bprime = $a->vectorMultiply($x);
1578 |         if (PEAR::isError($bprime)) {
1579 |             return $bprime;
1580 |         }
1581 |         $err = Math_VectorOp::substract($b, $bprime);
1582 |         $adj = $ainv->vectorMultiply($err);
1583 |         if (PEAR::isError($adj)) {
1584 |             return $adj;
1585 |         }
1586 |         $adjnorm = $adj->length();
1587 |         $xnew = $x;
1588 | 
1589 |         // compute new solutions and test for accuracy
1590 |         // iterate no more than 10 times
1591 |         for ($i=0; $i<10; $i++) {
1592 |             $xnew = Math_VectorOp::add($x, $adj);
1593 |             $bprime = $a->vectorMultiply($xnew);
1594 |             $err = Math_VectorOp::substract($b, $bprime);
1595 |             $newadj = $ainv->vectorMultiply($err);
1596 |             $newadjnorm = $newadj->length();
1597 |             // did we improve the accuracy?
1598 |             if ($newadjnorm < $adjnorm) {
1599 |                 $adjnorm = $newadjnorm;
1600 |                 $x = $xnew;
1601 |                 $adj = $newadj;
1602 |             } else { // we did improve the accuracy, so break;
1603 |                 break;
1604 |             }
1605 |         }
1606 |         return $x;
1607 |     }/*}}}*/
1608 | 
1609 | } // end of Math_Matrix class /*}}}*/
1610 | 
1611 | // vim: ts=4:sw=4:et:
1612 | // vim6: fdl=1:
1613 | 
1614 | ?>
1615 | 


--------------------------------------------------------------------------------
/lib/PEAR/Math/Tuple.php:
--------------------------------------------------------------------------------
  1 |                 |
 17 | // +----------------------------------------------------------------------+
 18 | //
 19 | // $Id: Tuple.php 304045 2010-10-05 00:16:53Z clockwerx $
 20 | //
 21 | 
 22 | /**
 23 |  * General Tuple class
 24 |  * A Tuple represents a general unidimensional list of n numeric elements
 25 |  * Originally this class was part of NumPHP (Numeric PHP package)
 26 |  *
 27 |  * @author	Jesus M. Castagnetto 
 28 |  * @version	1.0
 29 |  * @access	public
 30 |  * @package	Math_Vector
 31 |  */
 32 | class Math_Tuple {
 33 | 
 34 | 	/**
 35 | 	 * array of numeric elements
 36 | 	 *
 37 | 	 * @var		array
 38 | 	 * @access	private
 39 | 	 */
 40 | 	var $data = null;
 41 | 	
 42 | 	/**
 43 | 	 * Constructor of Math_Tuple
 44 | 	 *
 45 | 	 * @param	array	$data	array of numbers
 46 | 	 * @access	public
 47 | 	 * @return	object	Math_Tuple (or PEAR_Error on error)
 48 | 	 */
 49 | 	function Math_Tuple ($data) /*{{{*/
 50 | 	{
 51 | 		if (is_array($data) || !is_array($data[0])) {
 52 | 			$this->data = $data;
 53 | 		} else {
 54 | 			new PEAR_Error("An unidimensional array is needed to initialize a Tuple",
 55 | 								null, PEAR_ERROR_DIE);
 56 | 		}
 57 | 	}/*}}}*/
 58 | 
 59 | 	/**
 60 | 	 * Squeezes out holes in the tuple sequence
 61 | 	 *
 62 | 	 * @access	public
 63 | 	 * @return	void
 64 | 	 */
 65 | 	function squeezeHoles ()/*{{{*/
 66 | 	{
 67 | 		$this->data = explode(":", implode(":",$this->data));
 68 | 	}/*}}}*/
 69 | 
 70 | 	/**
 71 | 	 * Returns the size (number of elements) in the tuple
 72 | 	 *
 73 | 	 * @access	public
 74 | 	 * @return	integer
 75 | 	 */
 76 | 	function getSize () /*{{{*/
 77 | 	{
 78 | 		return count($this->data);
 79 | 	}/*}}}*/
 80 | 
 81 | 	/**
 82 | 	 * Sets the value of an element
 83 | 	 *
 84 | 	 * @access	public
 85 | 	 * @param	integer	$elindex	element index
 86 | 	 * @param	numeric	$elvalue	element value
 87 | 	 * @return	mixed	true if successful, PEAR_Error object otherwise
 88 | 	 */
 89 | 	function setElement ($elindex, $elvalue) /*{{{*/
 90 | 	{
 91 | 		if ($elindex >= $this->getSize()) {
 92 | 			return PEAR::raiseError("Wrong index: $elindex for element: $elvalue");
 93 | 		}
 94 | 		$this->data[$elindex] = $elvalue;
 95 | 		return true;
 96 | 	}/*}}}*/
 97 | 
 98 | 	/**
 99 | 	 * Appends an element to the tuple
100 | 	 *
101 | 	 * @access	public
102 | 	 * @param	numeric	$elvalue	element value
103 | 	 * @return	mixed	index of appended element on success, PEAR_Error object otherwise
104 | 	 */
105 | 	function addElement ($elvalue) /*{{{*/
106 | 	{
107 | 		if (!is_numeric($elvalue)) {
108 | 			return PEAR::raiseError("Error, a numeric value is needed. You used: $elvalue");
109 | 		}
110 | 		$this->data[$this->getSize()] = $elvalue;
111 | 		return ($this->getSize() - 1);
112 | 	}/*}}}*/
113 | 
114 | 	/**
115 | 	 * Remove an element from the tuple
116 | 	 *
117 | 	 * @access public
118 | 	 * @param	integer $elindex	element index
119 | 	 * @return	mixed	true on success, PEAR_Error object otherwise
120 | 	 */
121 | 	function delElement ($elindex) /*{{{*/
122 | 	{
123 | 		if ($elindex >= $this->getSize()) {
124 | 			return PEAR::raiseError("Wrong index: $elindex, element not deleted");
125 | 		}
126 | 		unset($this->data[$elindex]);
127 | 		$this->squeezeHoles();
128 | 		return true;
129 | 	}/*}}}*/
130 | 
131 | 	/**
132 | 	 * Returns the value of an element in the tuple
133 | 	 *
134 | 	 * @access	public
135 | 	 * @param	integer	$elindex	element index
136 | 	 * @return	mixed	numeric on success, PEAR_Error otherwise
137 | 	 */
138 | 	function getElement($elindex) /*{{{*/
139 | 	{
140 | 		if ($elindex >= $this->getSize()) {
141 | 			return PEAR::raiseError("Wrong index: $elindex, Tuple size is: ".$this->getSize());
142 | 		}
143 | 		return $this->data[$elindex];
144 | 	}/*}}}*/
145 | 
146 | 	/**
147 | 	 * Returns an array with all the elements of the tuple
148 | 	 *
149 | 	 * @access	public
150 | 	 * @return	$array
151 | 	 */
152 | 	function getData () /*{{{*/
153 | 	{
154 | 		$this->squeezeHoles();
155 | 		return $this->data;
156 | 	}/*}}}*/
157 | 	
158 | 	/**
159 | 	 * Returns the minimum value of the tuple
160 | 	 *
161 | 	 * @access	public
162 | 	 * @return	numeric
163 | 	 */
164 | 	function getMin () /*{{{*/
165 | 	{
166 | 		return min($this->getData());
167 | 	}/*}}}*/
168 | 	
169 | 	/**
170 | 	 * Returns the maximum value of the tuple
171 | 	 *
172 | 	 * @access	public
173 | 	 * @return	numeric
174 | 	 */
175 | 	function getMax () /*{{{*/
176 | 	{
177 | 		return max($this->getData());
178 | 	}/*}}}*/
179 | 
180 | 	/**
181 | 	 * Returns an array of the minimum and maximum values of the tuple
182 | 	 *
183 | 	 * @access	public
184 | 	 * @return	array of the minimum and maximum values
185 | 	 */
186 | 	function getMinMax () /*{{{*/
187 | 	{
188 | 		return array ($this->getMin(), $this->getMax());
189 | 	}/*}}}*/
190 | 	
191 | 	/**
192 | 	 * Gets the position of the given value in the tuple
193 | 	 *
194 | 	 * @access	public
195 | 	 * @param	numeric	$val	value for which the index is requested
196 | 	 * @return	integer
197 | 	 */
198 | 	function getValueIndex ($val) /*{{{*/
199 | 	{
200 | 		for ($i=0; $i < $this->getSize(); $i++)
201 | 			if ($this->data[$i] == $val)
202 | 				return $i;
203 | 		return false;
204 | 	}/*}}}*/
205 | 
206 | 	/**
207 | 	 * Gets the position of the minimum value in the tuple
208 | 	 *
209 | 	 * @access	public
210 | 	 * @return	integer
211 | 	 */
212 | 	function getMinIndex () /*{{{*/
213 | 	{
214 | 		return $this->getValueIndex($this->getMin());
215 | 	}/*}}}*/
216 | 
217 | 	/**
218 | 	 * Gets the position of the maximum value in the tuple
219 | 	 *
220 | 	 * @access	public
221 | 	 * @return	integer
222 | 	 */
223 | 	function getMaxIndex () /*{{{*/
224 | 	{
225 | 		return $this->getValueIndex($this->getMax());
226 | 	}/*}}}*/
227 | 
228 | 	/**
229 | 	 * Gets an array of the positions of the minimum and maximum values in the tuple
230 | 	 *
231 | 	 * @access	public
232 | 	 * @return	array of integers indexes
233 | 	 */
234 | 	function getMinMaxIndex () /*{{{*/
235 | 	{
236 | 		return array($this->getMinIndex(), $this->getMaxIndex());
237 | 	}/*}}}*/
238 | 
239 | 	/**
240 | 	 * Checks if the tuple is a a Zero tuple
241 | 	 *
242 | 	 * @access	public
243 | 	 * @return	boolean
244 | 	 */
245 | 	function isZero () /*{{{*/
246 | 	{
247 | 		for ($i=0; $i < $this->getSize(); $i++)
248 | 			if ($this->data[$i] != 0)
249 | 				return false;
250 | 		return true;
251 | 	}/*}}}*/
252 | 	
253 | 	/**
254 | 	 * Returns an string representation of the tuple
255 | 	 *
256 | 	 * @access	public
257 | 	 * @return	string
258 | 	 */
259 | 	function toString () /*{{{*/
260 | 	{
261 | 		return "{ ".implode(", ",$this->data)." }";
262 | 	}/*}}}*/
263 | 
264 | 	/**
265 | 	 * Returns an HTML representation of the tuple
266 | 	 *
267 | 	 * @access	public
268 | 	 * @return	string
269 | 	 */
270 | 	function toHTML() /*{{{*/
271 | 	{
272 | 		$out = "\n\t\n";
273 | 		$out .= "\t\n\t\t\n\t\n";
274 | 		for ($i=0; $i < $this->getSize(); $i++) {
275 | 			$out .= "\t\n\t\t";
276 | 			$out .= "\n\t\n";
277 | 		}
278 | 		return $out."\n
Vector
ivalue
".$i."".$this->data[$i]."
\n"; 279 | }/*}}}*/ 280 | 281 | } /* end of Tuple class */ 282 | 283 | 284 | ?> 285 | -------------------------------------------------------------------------------- /lib/PEAR/Math/Vector.php: -------------------------------------------------------------------------------- 1 | | 17 | // +----------------------------------------------------------------------+ 18 | // 19 | // $Id: Vector.php 304047 2010-10-05 00:25:33Z clockwerx $ 20 | // 21 | 22 | require_once "Tuple.php"; 23 | require_once "VectorOp.php"; 24 | 25 | /** 26 | * General Vector class 27 | * Originally this class was part of NumPHP (Numeric PHP package) 28 | * 29 | * @author Jesus M. Castagnetto 30 | * @version 1.0 31 | * @access public 32 | * @package Math_Vector 33 | */ 34 | 35 | class Math_Vector { 36 | 37 | /** 38 | * Math_Tuple object 39 | * 40 | * @var object Math_Tuple 41 | * @access private 42 | */ 43 | var $_tuple = null; 44 | 45 | /** 46 | * Constructor for Math_Vector 47 | * 48 | * @param optional array|Math_Tuple|Math_Vector $data a Math_Tuple object, a Math_Vetctor object, or an array of numeric data 49 | * @access public 50 | * @return object Math_Vector (or PEAR_Error on error) 51 | * @see setData() 52 | */ 53 | function Math_Vector($data=null) /*{{{*/ 54 | { 55 | if (!is_null($data)) { 56 | $this->setData($data); 57 | } 58 | }/*}}}*/ 59 | 60 | /** 61 | * Initializes the vector 62 | * 63 | * @param array|Math_Tuple|Math_Vector $data a Math_Tuple object, a Math_Vetctor object, or an array of numeric data 64 | * @access public 65 | * @return boolean|PEAR_Error TRUE on success, a PEAR_Error otherwise 66 | */ 67 | function setData($data) /*{{{*/ 68 | { 69 | if (is_array($data)) { 70 | $tuple = new Math_Tuple($data); 71 | } elseif (is_object($data) && strtolower(get_class($data)) == "math_tuple") { 72 | $tuple = $data; 73 | } else if (is_object($data) && strtolower(get_class($data)) == "math_vector") { 74 | $tuple = $data->getTuple(); 75 | } else { 76 | return PEAR::raiseError('Cannot initialize, expecting an array, tuple or vector'); 77 | } 78 | $this->_tuple = $tuple; 79 | return true; 80 | }/*}}}*/ 81 | 82 | 83 | /** 84 | * Returns an array of numbers 85 | * 86 | * @access public 87 | * @return array|PEAR_Error a numeric array on success, a PEAR_Error otherwise 88 | */ 89 | function getData()/*{{{*/ 90 | { 91 | if ($this->isValid()) { 92 | return $this->_tuple->getData(); 93 | } else { 94 | return PEAR::raiseError('Vector has not been initialized'); 95 | } 96 | }/*}}}*/ 97 | 98 | /** 99 | * Checks if the vector has been correctly initialized 100 | * 101 | * @access public 102 | * @return boolean 103 | */ 104 | function isValid() /*{{{*/ 105 | { 106 | return (!is_null($this->_tuple) && is_object($this->_tuple) && 107 | strtolower(get_class($this->_tuple)) == "math_tuple"); 108 | }/*}}}*/ 109 | 110 | /** 111 | * Returns the square of the vector's length 112 | * 113 | * @access public 114 | * @return float 115 | */ 116 | function lengthSquared() /*{{{*/ 117 | { 118 | $n = $this->size(); 119 | $sum = 0; 120 | for ($i=0; $i < $n; $i++) 121 | $sum += pow($this->_tuple->getElement($i), 2); 122 | return $sum; 123 | }/*}}}*/ 124 | 125 | /** 126 | * Returns the length of the vector 127 | * 128 | * @access public 129 | * @return float 130 | */ 131 | function length() /*{{{*/ 132 | { 133 | return sqrt($this->lengthSquared()); 134 | }/*}}}*/ 135 | 136 | /** 137 | * Returns the magnitude of the vector. Alias of length 138 | * 139 | * @access public 140 | * @return float 141 | */ 142 | function magnitude()/*{{{*/ 143 | { 144 | return $this->length(); 145 | }/*}}}*/ 146 | 147 | /** 148 | * Normalizes the vector, converting it to a unit vector 149 | * 150 | * @access public 151 | * @return void 152 | */ 153 | function normalize() /*{{{*/ 154 | { 155 | $n = $this->size(); 156 | $length = $this->length(); 157 | for ($i=0; $i < $n; $i++) { 158 | $this->_tuple->setElement($i, $this->_tuple->getElement($i)/$length); 159 | } 160 | }/*}}}*/ 161 | 162 | /** 163 | * returns the Math_Tuple object corresponding to the vector 164 | * 165 | * @access public 166 | * @return object Math_Tuple 167 | */ 168 | function getTuple() /*{{{*/ 169 | { 170 | return $this->_tuple; 171 | }/*}}}*/ 172 | 173 | /** 174 | * Returns the number of elements (dimensions) of the vector 175 | * 176 | * @access public 177 | * @return float 178 | */ 179 | function size() /*{{{*/ 180 | { 181 | return $this->_tuple->getSize(); 182 | }/*}}}*/ 183 | 184 | /** 185 | * Reverses the direction of the vector negating each element 186 | * 187 | * @access public 188 | * @return void 189 | */ 190 | function reverse() /*{{{*/ 191 | { 192 | $n = $this->size(); 193 | for ($i=0; $i < $n; $i++) 194 | $this->_tuple->setElement($i, -1 * $this->_tuple->getElement($i)); 195 | }/*}}}*/ 196 | 197 | /** 198 | * Conjugates the vector. Alias of reverse. 199 | * 200 | * @access public 201 | * @return void 202 | * 203 | * @see reverse() 204 | */ 205 | function conjugate()/*{{{*/ 206 | { 207 | $this->reverse(); 208 | }/*}}}*/ 209 | 210 | /** 211 | * Scales the vector elements by the given factor 212 | * 213 | * @access public 214 | * @param float $f scaling factor 215 | * @return mixed void on success, a PEAR_Error object otherwise 216 | */ 217 | function scale($f) /*{{{*/ 218 | { 219 | if (is_numeric($f)) { 220 | $n = $this->size(); 221 | $t = $this->getTuple(); 222 | for ($i=0; $i < $n; $i++) 223 | $this->set($i, $this->get($i) * $f); 224 | } else { 225 | return PEAR::raiseError("Requires a numeric factor and a Math_Vector object"); 226 | } 227 | }/*}}}*/ 228 | 229 | /** 230 | * Sets the value of a element 231 | * 232 | * @access public 233 | * @param integer $i the index of the element 234 | * @param numeric $value the value to assign to the element 235 | * @return mixed true on success, a PEAR_Error object otherwise 236 | */ 237 | function set($i, $value) /*{{{*/ 238 | { 239 | $res = $this->_tuple->setElement($i, $value); 240 | if (PEAR::isError($res)) 241 | return $res; 242 | else 243 | return true; 244 | }/*}}}*/ 245 | 246 | /** 247 | * Gets the value of a element 248 | * 249 | * @access public 250 | * @param integer $i the index of the element 251 | * @return mixed the element value (numeric) on success, a PEAR_Error object otherwise 252 | */ 253 | function get($i) {/*{{{*/ 254 | $res = $this->_tuple->getElement($i); 255 | return $res; 256 | }/*}}}*/ 257 | 258 | /** 259 | * Returns the distance to another vector 260 | * 261 | * @access public 262 | * @param object $vector Math_Vector object 263 | * @param string $type distance type: cartesian (default), manhattan or chessboard 264 | * @return float on success, a PEAR_Error object on failure 265 | */ 266 | function distance($vector, $type='cartesian')/*{{{*/ 267 | { 268 | switch ($type) { 269 | case 'manhattan' : 270 | case 'city' : 271 | return $this->manhattanDistance($vector); 272 | break; 273 | case 'chessboard' : 274 | return $this->chessboardDistance($vector); 275 | break; 276 | case 'cartesian' : 277 | default : 278 | return $this->cartesianDistance($vector); 279 | } 280 | }/*}}}*/ 281 | 282 | /** 283 | * Returns the cartesian distance to another vector 284 | * 285 | * @access public 286 | * @param object $vector Math_Vector object 287 | * @return float on success, a PEAR_Error object on failure 288 | */ 289 | function cartesianDistance($vector) /*{{{*/ 290 | { 291 | $n = $this->size(); 292 | $sum = 0; 293 | if (Math_VectorOp::isVector($vector)) 294 | if ($vector->size() == $n) { 295 | for($i=0; $i < $n; $i++) 296 | $sum += pow(($this->_tuple->getElement($i) - $vector->_tuple->getElement($i)), 2); 297 | return sqrt($sum); 298 | } else { 299 | return PEAR::raiseError("Vector has to be of the same size"); 300 | } 301 | else 302 | return PEAR::raiseError("Wrong parameter type, expecting a Math_Vector object"); 303 | }/*}}}*/ 304 | 305 | /** 306 | * Returns the Manhattan (aka City) distance to another vector 307 | * Definition: manhattan dist. = |x1 - x2| + |y1 - y2| + ... 308 | * 309 | * @access public 310 | * @param object $vector Math_Vector object 311 | * @return float on success, a PEAR_Error object on failure 312 | */ 313 | function manhattanDistance($vector) /*{{{*/ 314 | { 315 | $n = $this->size(); 316 | $sum = 0; 317 | if (Math_VectorOp::isVector($vector)) 318 | if ($vector->size() == $n) { 319 | for($i=0; $i < $n; $i++) 320 | $sum += abs($this->_tuple->getElement($i) - $vector->_tuple->getElement($i)); 321 | return $sum; 322 | } else { 323 | return PEAR::raiseError("Vector has to be of the same size"); 324 | } 325 | else 326 | return PEAR::raiseError("Wrong parameter type, expecting a Math_Vector object"); 327 | }/*}}}*/ 328 | 329 | /** 330 | * Returns the Chessboard distance to another vector 331 | * Definition: chessboard dist. = max(|x1 - x2|, |y1 - y2|, ...) 332 | * 333 | * @access public 334 | * @param object $vector Math_Vector object 335 | * @return float on success, a PEAR_Error object on failure 336 | */ 337 | function chessboardDistance($vector) /*{{{*/ 338 | { 339 | $n = $this->size(); 340 | $sum = 0; 341 | if (Math_VectorOp::isVector($vector)) 342 | if ($vector->size() == $n) { 343 | $cdist = array(); 344 | for($i=0; $i < $n; $i++) 345 | $cdist[] = abs($this->_tuple->getElement($i) - $vector->_tuple->getElement($i)); 346 | return max($cdist); 347 | } else { 348 | return PEAR::raiseError("Vector has to be of the same size"); 349 | } 350 | else 351 | return PEAR::raiseError("Wrong parameter type, expecting a Math_Vector object"); 352 | }/*}}}*/ 353 | 354 | /** 355 | * Returns a simple string representation of the vector 356 | * 357 | * @access public 358 | * @return string 359 | */ 360 | function toString() /*{{{*/ 361 | { 362 | return "Vector: < ".implode(", ",$this->_tuple->getData())." >"; 363 | }/*}}}*/ 364 | } 365 | 366 | ?> 367 | -------------------------------------------------------------------------------- /lib/PEAR/Math/Vector2.php: -------------------------------------------------------------------------------- 1 | | 17 | // +----------------------------------------------------------------------+ 18 | // 19 | // $Id: Vector2.php 304047 2010-10-05 00:25:33Z clockwerx $ 20 | // 21 | 22 | require_once "Vector.php"; 23 | 24 | /** 25 | * 2D Vector class 26 | * Originally this class was part of NumPHP (Numeric PHP package) 27 | * 28 | * @author Jesus M. Castagnetto 29 | * @version 1.0 30 | * @access public 31 | * @package Math_Vector 32 | */ 33 | class Math_Vector2 extends Math_Vector { 34 | 35 | /** 36 | * Constructor for Math_Vector2 37 | * 38 | * @access public 39 | * @param mixed $arg an array of values, a Math_Tuple object or a Math_Vector2 object 40 | */ 41 | function Math_Vector2($arg) /*{{{*/ 42 | { 43 | if ( is_array($arg) && count($arg) != 2 ) 44 | $this->tuple = null; 45 | elseif ( is_object($arg) && (strtolower(get_class($arg)) != "math_vector2" 46 | && strtolower(get_class($arg)) != "math_tuple") ) 47 | $this->tuple = null; 48 | elseif ( is_object($arg) && strtolower(get_class($arg)) == "math_tuple" 49 | && $arg->getSize() != 2 ) 50 | $this->tuple = null; 51 | else 52 | $this->Math_Vector($arg); 53 | }/*}}}*/ 54 | 55 | /** 56 | * Returns the X component of the vector 57 | * 58 | * @access public 59 | * @return numeric 60 | */ 61 | function getX()/*{{{*/ 62 | { 63 | return $this->get(0); 64 | }/*}}}*/ 65 | 66 | /** 67 | * Sets the X component of the vector 68 | * 69 | * @access public 70 | * @param numeric $val the value for the Y component 71 | * @return mixed true on success, PEAR_Error object otherwise 72 | */ 73 | function setX($val)/*{{{*/ 74 | { 75 | return $this->set(0, $val); 76 | }/*}}}*/ 77 | 78 | /** 79 | * Returns the Y component of the vector 80 | * 81 | * @access public 82 | * @return numeric 83 | */ 84 | function getY()/*{{{*/ 85 | { 86 | return $this->get(1); 87 | }/*}}}*/ 88 | 89 | /** 90 | * Sets the Y component of the vector 91 | * 92 | * @access public 93 | * @param numeric $val the value for the Y component 94 | * @return mixed true on success, PEAR_Error object otherwise 95 | */ 96 | function setY($val)/*{{{*/ 97 | { 98 | return $this->set(1, $val); 99 | }/*}}}*/ 100 | 101 | } 102 | 103 | ?> 104 | -------------------------------------------------------------------------------- /lib/PEAR/Math/Vector3.php: -------------------------------------------------------------------------------- 1 | | 17 | // +----------------------------------------------------------------------+ 18 | // 19 | // $Id: Vector3.php 304047 2010-10-05 00:25:33Z clockwerx $ 20 | // 21 | 22 | 23 | require_once "Vector.php"; 24 | 25 | /** 26 | * 3D vector class 27 | * Originally this class was part of NumPHP (Numeric PHP package) 28 | * 29 | * @author Jesus M. Castagnetto 30 | * @version 1.0 31 | * @access public 32 | * @package Math_Vector 33 | */ 34 | class Math_Vector3 extends Math_Vector { 35 | 36 | /** 37 | * Constructor for Math_Vector3 38 | * 39 | * @access public 40 | * @param mixed $arg an array of values, a Math_Tuple object or a Math_Vector3 object 41 | */ 42 | function Math_Vector3($arg) { 43 | if ( is_array($arg) && count($arg) != 3 ) 44 | $this->tuple = null; 45 | elseif ( is_object($arg) && (strtolower(get_class($arg)) != "math_vector3" 46 | && strtolower(get_class($arg)) != "math_tuple") ) 47 | $this->tuple = null; 48 | elseif ( is_object($arg) && strtolower(get_class($arg)) == "math_tuple" 49 | && $arg->getSize() != 3 ) 50 | $this->tuple = null; 51 | else 52 | $this->Math_Vector($arg); 53 | } 54 | 55 | /** 56 | * Returns the X component of the vector 57 | * 58 | * @access public 59 | * @return numeric 60 | */ 61 | function getX()/*{{{*/ 62 | { 63 | return $this->get(0); 64 | }/*}}}*/ 65 | 66 | /** 67 | * Sets the X component of the vector 68 | * 69 | * @access public 70 | * @param numeric $val the value for the Y component 71 | * @return mixed true on success, PEAR_Error object otherwise 72 | */ 73 | function setX($val)/*{{{*/ 74 | { 75 | return $this->set(0, $val); 76 | }/*}}}*/ 77 | 78 | /** 79 | * Returns the Y component of the vector 80 | * 81 | * @access public 82 | * @return numeric 83 | */ 84 | function getY()/*{{{*/ 85 | { 86 | return $this->get(1); 87 | }/*}}}*/ 88 | 89 | /** 90 | * Sets the Y component of the vector 91 | * 92 | * @access public 93 | * @param numeric $val the value for the Y component 94 | * @return mixed true on success, PEAR_Error object otherwise 95 | */ 96 | function setY($val)/*{{{*/ 97 | { 98 | return $this->set(1, $val); 99 | }/*}}}*/ 100 | 101 | /** 102 | * Returns the Z component of the vector 103 | * 104 | * @access public 105 | * @return numeric 106 | */ 107 | function getZ()/*{{{*/ 108 | { 109 | return $this->get(2); 110 | }/*}}}*/ 111 | 112 | /** 113 | * Sets the Z component of the vector 114 | * 115 | * @access public 116 | * @param numeric $val the value for the Y component 117 | * @return mixed true on success, PEAR_Error object otherwise 118 | */ 119 | function setZ($val)/*{{{*/ 120 | { 121 | return $this->set(2, $val); 122 | }/*}}}*/ 123 | 124 | } 125 | 126 | ?> 127 | -------------------------------------------------------------------------------- /lib/PEAR/Math/VectorOp.php: -------------------------------------------------------------------------------- 1 | | 17 | // +----------------------------------------------------------------------+ 18 | // 19 | // $Id: VectorOp.php 304045 2010-10-05 00:16:53Z clockwerx $ 20 | // 21 | 22 | /** 23 | * Vector operation class. 24 | * A static class implementing methods to operate on Vector objects. 25 | * Originally this class was part of NumPHP (Numeric PHP package) 26 | * 27 | * @author Jesus M. Castagnetto 28 | * @version 1.0 29 | * @access public 30 | * @package Math_Vector 31 | */ 32 | class Math_VectorOp { 33 | 34 | /** 35 | * Checks if object is of Math_Vector class (or a subclass of Math_Vector) 36 | * 37 | * @access public 38 | * @param object $obj 39 | * @return boolean true on success 40 | */ 41 | function isVector($obj) /*{{{*/ 42 | { 43 | if (function_exists("is_a")) 44 | return (is_object($obj) && is_a($obj, "Math_Vector")); 45 | else 46 | return (is_object($obj) && (strtolower(get_class($obj)) == "math_vector" || 47 | is_subclass_of($obj, "Math_Vector"))); 48 | }/*}}}*/ 49 | 50 | /** 51 | * Checks if object is of Math_Vector2 class (or a subclass of Math_Vector2) 52 | * 53 | * @access public 54 | * @param object $obj 55 | * @return boolean true on success 56 | */ 57 | function isVector2($obj) /*{{{*/ 58 | { 59 | if (function_exists("is_a")) 60 | return (is_object($obj) && is_a($obj, "Math_Vector2")); 61 | else 62 | return (is_object($obj) && (strtolower(get_class($obj)) == "math_vector2" || 63 | is_subclass_of($obj, "Math_Vector2"))); 64 | }/*}}}*/ 65 | 66 | /** 67 | * Checks if object is of Math_Vector3 class (or a subclass of Math_Vector3) 68 | * 69 | * @access public 70 | * @param object $obj 71 | * @return boolean true on success 72 | */ 73 | function isVector3($obj) /*{{{*/ 74 | { 75 | if (function_exists("is_a")) 76 | return (is_object($obj) && is_a($obj, "Math_Vector3")); 77 | else 78 | return (is_object($obj) && (strtolower(get_class($obj)) == "math_vector3" || 79 | is_subclass_of($obj, "Math_Vector3")) ); 80 | }/*}}}*/ 81 | 82 | /** 83 | * Creates a vector of a given size in which all elements have the same value 84 | * 85 | * @access public 86 | * @param int $size vector size 87 | * @param numeric $value value to assign to the elements 88 | * @return object if ($size == 2) Math_Vector2 elseif ($size == 3) Math_Vector3 else Math_Vector 89 | */ 90 | function create ($size, $value) /*{{{*/ 91 | { 92 | if ($size == 2) 93 | $VClass = "Math_Vector2"; 94 | elseif ($size == 3) 95 | $VClass = "Math_Vector3"; 96 | else 97 | $VClass = "Math_Vector"; 98 | return new $VClass(Math_VectorOp::_fill(0, $size, $value)); 99 | }/*}}}*/ 100 | 101 | /** 102 | * Creates a zero-filled vector of the given size 103 | * 104 | * @access public 105 | * @param int $size vector size 106 | * @return object if ($size == 2) Math_Vector2 elseif ($size == 3) Math_Vector3 else Math_Vector 107 | * 108 | * @see create() 109 | */ 110 | function createZero ($size) 111 | { 112 | return Math_VectorOp::create ($size, 0); 113 | } 114 | 115 | /** 116 | * Creates a one-filled vector of the given size 117 | * 118 | * @access public 119 | * @param int $size vector size 120 | * @return object if ($size == 2) Math_Vector2 elseif ($size == 3) Math_Vector3 else Math_Vector 121 | * 122 | * @see create() 123 | */ 124 | function createOne ($size) /*{{{*/ 125 | { 126 | return Math_VectorOp::create ($size, 1); 127 | }/*}}}*/ 128 | 129 | 130 | /** 131 | * Creates a basis vector of the given size 132 | * A basis vector of size n, has n - 1 elements equal to 0 133 | * and one element equal to 1 134 | * 135 | * @access public 136 | * @param int $size vector size 137 | * @param int $index element to be set at 1 138 | * @return object if ($size == 2) Math_Vector2 elseif ($size == 3) Math_Vector3 else Math_Vector, on error PEAR_Error 139 | * 140 | * @see createZero() 141 | */ 142 | function createBasis ($size, $index) /*{{{*/ 143 | { 144 | if ($index >= $size) 145 | return PEAR::raiseError("Incorrect index for size: $index >= $size"); 146 | $v = Math_VectorOp::createZero($size); 147 | $res =$v->set($index, 1); 148 | if (PEAR::isError($res)) 149 | return $res; 150 | else 151 | return $v; 152 | }/*}}}*/ 153 | 154 | /** 155 | * Vector addition 156 | * v + w = 157 | * 158 | * @access public 159 | * @param object Math_Vector (or subclass) $v1 160 | * @param object Math_Vector (or subclass) $v2 161 | * @return object Math_Vector (or subclass) on success, PEAR_Error otherwise 162 | * 163 | * @see isVector() 164 | */ 165 | function add ($v1, $v2) /*{{{*/ 166 | { 167 | if (Math_VectorOp::isVector($v1) && Math_VectorOp::isVector($v2)) { 168 | $n = $v1->size(); 169 | if ($v2->size() != $n) 170 | return PEAR::raiseError("Vectors must of the same size"); 171 | for ($i=0; $i < $n; $i++) 172 | $arr[$i] = $v1->get($i) + $v2->get($i); 173 | return new Math_Vector($arr); 174 | } else { 175 | return PEAR::raiseError("V1 and V2 must be Math_Vector objects"); 176 | } 177 | }/*}}}*/ 178 | 179 | /** 180 | * Vector substraction 181 | * v - w = 182 | * 183 | * @access public 184 | * @param object Math_Vector (or subclass) $v1 185 | * @param object Math_Vector (or subclass) $v2 186 | * @return object Math_Vector (or subclass) on success, PEAR_Error otherwise 187 | * 188 | * @see isVector() 189 | */ 190 | function substract ($v1, $v2) /*{{{*/ 191 | { 192 | if (Math_VectorOp::isVector($v1) && Math_VectorOp::isVector($v2)) { 193 | $n = $v1->size(); 194 | if ($v2->size() != $n) 195 | return PEAR::raiseError("Vectors must of the same size"); 196 | for ($i=0; $i < $n; $i++) 197 | $arr[$i] = $v1->get($i) - $v2->get($i); 198 | return new Math_Vector($arr); 199 | } else { 200 | return PEAR::raiseError("V1 and V2 must be Math_Vector objects"); 201 | } 202 | }/*}}}*/ 203 | 204 | /** 205 | * Vector multiplication 206 | * v * w = 207 | * 208 | * @access public 209 | * @param object Math_Vector (or subclass) $v1 210 | * @param object Math_Vector (or subclass) $v2 211 | * @return object Math_Vector (or subclass) on success, PEAR_Error otherwise 212 | * 213 | * @see isVector() 214 | */ 215 | function multiply ($v1, $v2) /*{{{*/ 216 | { 217 | if (Math_VectorOp::isVector($v1) && Math_VectorOp::isVector($v2)) { 218 | $n = $v1->size(); 219 | if ($v2->size() != $n) 220 | return PEAR::raiseError("Vectors must of the same size"); 221 | for ($i=0; $i < $n; $i++) 222 | $arr[$i] = $v1->get($i) * $v2->get($i); 223 | return new Math_Vector($arr); 224 | } else { 225 | return PEAR::raiseError("V1 and V2 must be Math_Vector objects"); 226 | } 227 | }/*}}}*/ 228 | 229 | /** 230 | * Vector scaling 231 | * f * w = 232 | * 233 | * @access public 234 | * @param numeric $f scaling factor 235 | * @param object Math_Vector (or subclass) $v 236 | * @return object Math_Vector (or subclass) on success, PEAR_Error otherwise 237 | * 238 | * @see isVector() 239 | */ 240 | function scale ($f, $v) /*{{{*/ 241 | { 242 | if (is_numeric($f) && Math_VectorOp::isVector($v)) { 243 | $n = $v->size(); 244 | for ($i=0; $i < $n; $i++) 245 | $arr[$i] = $v->get($i) * $f; 246 | return new Math_Vector($arr); 247 | } else { 248 | return PEAR::raiseError("Requires a numeric factor and a Math_Vector object"); 249 | } 250 | }/*}}}*/ 251 | 252 | /** 253 | * Vector division 254 | * v / w = 255 | * 256 | * @access public 257 | * @param object Math_Vector (or subclass) $v1 258 | * @param object Math_Vector (or subclass) $v2 259 | * @return object Math_Vector (or subclass) on success, PEAR_Error otherwise 260 | * 261 | * @see isVector() 262 | */ 263 | function divide ($v1, $v2) /*{{{*/ 264 | { 265 | if (Math_VectorOp::isVector($v1) && Math_VectorOp::isVector($v2)) { 266 | $n = $v1->size(); 267 | if ($v2->size() != $n) 268 | return PEAR::raiseError("Vectors must of the same size"); 269 | for ($i=0; $i < $n; $i++) { 270 | $d = $v2->get($i); 271 | if ($d == 0) 272 | return PEAR::raiseError("Division by zero: Element $i in V2 is zero"); 273 | $arr[$i] = $v1->get($i) / $d; 274 | } 275 | return new Math_Vector($arr); 276 | } else { 277 | return PEAR::raiseError("V1 and V2 must be Math_Vector objects"); 278 | } 279 | }/*}}}*/ 280 | 281 | /** 282 | * Vector dot product = v . w = |v| |w| cos(theta) 283 | * 284 | * @access public 285 | * @param object Math_Vector2 or MathVector3 (or subclass) $v1 286 | * @param object Math_Vector2 or MathVector3 (or subclass) $v2 287 | * @return mixed the dot product (float) on success, a PEAR_Error object otherwise 288 | * 289 | * @see isVector2() 290 | * @see isVector3() 291 | */ 292 | function dotProduct ($v1, $v2)/*{{{*/ 293 | { 294 | if (Math_VectorOp::isVector2($v1) && Math_VectorOp::isVector2($v2)) 295 | return ( $v1->getX() * $v2->getX() + 296 | $v1->getY() * $v2->getY() ); 297 | elseif (Math_VectorOp::isVector3($v1) && Math_VectorOp::isVector3($v2)) 298 | return ( $v1->getX() * $v2->getX() + 299 | $v1->getY() * $v2->getY() + 300 | $v1->getZ() * $v2->getZ() ); 301 | else 302 | return PEAR::raiseError("Vectors must be both of the same type"); 303 | }/*}}}*/ 304 | 305 | /** 306 | * Vector cross product = v x w 307 | * 308 | * @access public 309 | * @param object Math_Vector3 (or subclass) $v1 310 | * @param object Math_Vector3 (or subclass) $v2 311 | * @return object the cross product vector (Math_Vector3) on success, a PEAR_Error object otherwise 312 | * 313 | * @see isVector3() 314 | */ 315 | function crossProduct ($v1, $v2) 316 | { 317 | if (Math_VectorOp::isVector3($v1) && Math_VectorOp::isVector3($v2)) { 318 | $arr[0] = $v1->getY() * $v2->getZ() - $v1->getZ() * $v2->getY(); 319 | $arr[1] = $v1->getZ() * $v2->getX() - $v1->getX() * $v2->getZ(); 320 | $arr[2] = $v1->getX() * $v2->getY() - $v1->getY() * $v2->getX(); 321 | return new Math_Vector3($arr); 322 | } else { 323 | return PEAR::raiseError("Vectors must be both of the same type"); 324 | } 325 | } 326 | 327 | /** 328 | * Vector triple scalar product = v1 . (v2 x v3) 329 | * 330 | * @access public 331 | * @param object Math_Vector3 (or subclass) $v1 332 | * @param object Math_Vector3 (or subclass) $v2 333 | * @param object Math_Vector3 (or subclass) $v3 334 | * @return mixed the triple scalar product (float) on success, a PEAR_Error object otherwise 335 | * 336 | * @see isVector3() 337 | * @see dotProduct() 338 | * @see crossProduct() 339 | */ 340 | function tripleScalarProduct ($v1, $v2, $v3) /*{{{*/ 341 | { 342 | if (Math_VectorOp::isVector3($v1) 343 | && Math_VectorOp::isVector3($v2) 344 | && Math_VectorOp::isVector3($v3)) 345 | return Math_VectorOp::dotProduct($v1,Math_VectorOp::crossProduct($v2, $v3)); 346 | else 347 | return PEAR_Error("All three vectors must be of the same type"); 348 | }/*}}}*/ 349 | 350 | /** 351 | * Angle between vectors, using the equation: v . w = |v| |w| cos(theta) 352 | * 353 | * @access public 354 | * @param object Math_Vector2 or MathVector3 (or subclass) $v1 355 | * @param object Math_Vector2 or MathVector3 (or subclass) $v2 356 | * @return mixed the angle between vectors (float, in radians) on success, a PEAR_Error object otherwise 357 | * 358 | * @see isVector2() 359 | * @see isVector3() 360 | * @see dotProduct() 361 | */ 362 | function angleBetween($v1, $v2) /*{{{*/ 363 | { 364 | if ( (Math_VectorOp::isVector2($v1) && Math_VectorOp::isVector2($v2)) || 365 | (Math_VectorOp::isVector3($v1) && Math_VectorOp::isVector3($v2)) ) { 366 | $v1->normalize(); 367 | $v2->normalize(); 368 | return acos( Math_VectorOp::dotProduct($v1,$v2) ); 369 | } else { 370 | return PEAR::raiseError("Vectors must be both of the same type"); 371 | } 372 | }/*}}}*/ 373 | 374 | /** 375 | * To generate an array of a given size filled with a single value 376 | * If available uses array_fill() 377 | * 378 | * @access private 379 | * @param int $index starting index 380 | * @param int $size size of the array 381 | * @param numeric $value value to use for filling the array 382 | * @return array 383 | */ 384 | function _fill($index, $size, $value)/*{{{*/ 385 | { 386 | if (function_exists("array_fill")) 387 | return array_fill($index, $size, $value); 388 | 389 | for ($i=$index; $i < ($index + $size); $i++) 390 | $arr[$i] = $value; 391 | return $arr; 392 | }/*}}}*/ 393 | } 394 | 395 | 396 | ?> 397 | -------------------------------------------------------------------------------- /lib/PEAR/PEAR.php: -------------------------------------------------------------------------------- 1 |