├── .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 |
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\tMatrix ";
965 | $out .= " \n\t\n\t\t";
966 | $out .= $this->_num_rows."x".$this->_num_cols." ";
967 | for ($i=0; $i < $this->_num_cols; $i++) {
968 | $out .= "".$i." ";
969 | }
970 | $out .= "\n\t \n";
971 | for ($i=0; $i < $this->_num_rows; $i++) {
972 | $out .= "\t\n\t\t".$i." ";
973 | for ($j=0; $j < $this->_num_cols; $j++) {
974 | $out .= "".$this->_data[$i][$j]." ";
975 | }
976 | $out .= "\n\t ";
977 | }
978 | return $out."\n
\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\tVector \n";
273 | $out .= "\t\n\t\ti value \n\t \n";
274 | for ($i=0; $i < $this->getSize(); $i++) {
275 | $out .= "\t\n\t\t".$i." ";
276 | $out .= "".$this->data[$i]." \n\t \n";
277 | }
278 | return $out."\n
\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 |