Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
 
3
/**
4
 *
5
 * Class for the management of Matrices
6
 *
7
 * @copyright  Copyright (c) 2018 Mark Baker (https://github.com/MarkBaker/PHPMatrix)
8
 * @license    https://opensource.org/licenses/MIT    MIT
9
 */
10
 
11
namespace Matrix;
12
 
13
use Generator;
14
use Matrix\Decomposition\LU;
15
use Matrix\Decomposition\QR;
16
 
17
/**
18
 * Matrix object.
19
 *
20
 * @package Matrix
21
 *
22
 * @property-read int $rows The number of rows in the matrix
23
 * @property-read int $columns The number of columns in the matrix
24
 * @method Matrix antidiagonal()
25
 * @method Matrix adjoint()
26
 * @method Matrix cofactors()
27
 * @method float determinant()
28
 * @method Matrix diagonal()
29
 * @method Matrix identity()
30
 * @method Matrix inverse()
31
 * @method Matrix minors()
32
 * @method float trace()
33
 * @method Matrix transpose()
34
 * @method Matrix add(...$matrices)
35
 * @method Matrix subtract(...$matrices)
36
 * @method Matrix multiply(...$matrices)
37
 * @method Matrix divideby(...$matrices)
38
 * @method Matrix divideinto(...$matrices)
39
 * @method Matrix directsum(...$matrices)
40
 */
41
class Matrix
42
{
43
    protected $rows;
44
    protected $columns;
45
    protected $grid = [];
46
 
47
    /*
48
     * Create a new Matrix object from an array of values
49
     *
50
     * @param array $grid
51
     */
52
    final public function __construct(array $grid)
53
    {
54
        $this->buildFromArray(array_values($grid));
55
    }
56
 
57
    /*
58
     * Create a new Matrix object from an array of values
59
     *
60
     * @param array $grid
61
     */
62
    protected function buildFromArray(array $grid): void
63
    {
64
        $this->rows = count($grid);
65
        $columns = array_reduce(
66
            $grid,
67
            function ($carry, $value) {
68
                return max($carry, is_array($value) ? count($value) : 1);
69
            }
70
        );
71
        $this->columns = $columns;
72
 
73
        array_walk(
74
            $grid,
75
            function (&$value) use ($columns) {
76
                if (!is_array($value)) {
77
                    $value = [$value];
78
                }
79
                $value = array_pad(array_values($value), $columns, null);
80
            }
81
        );
82
 
83
        $this->grid = $grid;
84
    }
85
 
86
    /**
87
     * Validate that a row number is a positive integer
88
     *
89
     * @param int $row
90
     * @return int
91
     * @throws Exception
92
     */
93
    public static function validateRow(int $row): int
94
    {
95
        if ((!is_numeric($row)) || (intval($row) < 1)) {
96
            throw new Exception('Invalid Row');
97
        }
98
 
99
        return (int)$row;
100
    }
101
 
102
    /**
103
     * Validate that a column number is a positive integer
104
     *
105
     * @param int $column
106
     * @return int
107
     * @throws Exception
108
     */
109
    public static function validateColumn(int $column): int
110
    {
111
        if ((!is_numeric($column)) || (intval($column) < 1)) {
112
            throw new Exception('Invalid Column');
113
        }
114
 
115
        return (int)$column;
116
    }
117
 
118
    /**
119
     * Validate that a row number falls within the set of rows for this matrix
120
     *
121
     * @param int $row
122
     * @return int
123
     * @throws Exception
124
     */
125
    protected function validateRowInRange(int $row): int
126
    {
127
        $row = static::validateRow($row);
128
        if ($row > $this->rows) {
129
            throw new Exception('Requested Row exceeds matrix size');
130
        }
131
 
132
        return $row;
133
    }
134
 
135
    /**
136
     * Validate that a column number falls within the set of columns for this matrix
137
     *
138
     * @param int $column
139
     * @return int
140
     * @throws Exception
141
     */
142
    protected function validateColumnInRange(int $column): int
143
    {
144
        $column = static::validateColumn($column);
145
        if ($column > $this->columns) {
146
            throw new Exception('Requested Column exceeds matrix size');
147
        }
148
 
149
        return $column;
150
    }
151
 
152
    /**
153
     * Return a new matrix as a subset of rows from this matrix, starting at row number $row, and $rowCount rows
154
     * A $rowCount value of 0 will return all rows of the matrix from $row
155
     * A negative $rowCount value will return rows until that many rows from the end of the matrix
156
     *
157
     * Note that row numbers start from 1, not from 0
158
     *
159
     * @param int $row
160
     * @param int $rowCount
161
     * @return static
162
     * @throws Exception
163
     */
164
    public function getRows(int $row, int $rowCount = 1): Matrix
165
    {
166
        $row = $this->validateRowInRange($row);
167
        if ($rowCount === 0) {
168
            $rowCount = $this->rows - $row + 1;
169
        }
170
 
171
        return new static(array_slice($this->grid, $row - 1, (int)$rowCount));
172
    }
173
 
174
    /**
175
     * Return a new matrix as a subset of columns from this matrix, starting at column number $column, and $columnCount columns
176
     * A $columnCount value of 0 will return all columns of the matrix from $column
177
     * A negative $columnCount value will return columns until that many columns from the end of the matrix
178
     *
179
     * Note that column numbers start from 1, not from 0
180
     *
181
     * @param int $column
182
     * @param int $columnCount
183
     * @return Matrix
184
     * @throws Exception
185
     */
186
    public function getColumns(int $column, int $columnCount = 1): Matrix
187
    {
188
        $column = $this->validateColumnInRange($column);
189
        if ($columnCount < 1) {
190
            $columnCount = $this->columns + $columnCount - $column + 1;
191
        }
192
 
193
        $grid = [];
194
        for ($i = $column - 1; $i < $column + $columnCount - 1; ++$i) {
195
            $grid[] = array_column($this->grid, $i);
196
        }
197
 
198
        return (new static($grid))->transpose();
199
    }
200
 
201
    /**
202
     * Return a new matrix as a subset of rows from this matrix, dropping rows starting at row number $row,
203
     *     and $rowCount rows
204
     * A negative $rowCount value will drop rows until that many rows from the end of the matrix
205
     * A $rowCount value of 0 will remove all rows of the matrix from $row
206
     *
207
     * Note that row numbers start from 1, not from 0
208
     *
209
     * @param int $row
210
     * @param int $rowCount
211
     * @return static
212
     * @throws Exception
213
     */
214
    public function dropRows(int $row, int $rowCount = 1): Matrix
215
    {
216
        $this->validateRowInRange($row);
217
        if ($rowCount === 0) {
218
            $rowCount = $this->rows - $row + 1;
219
        }
220
 
221
        $grid = $this->grid;
222
        array_splice($grid, $row - 1, (int)$rowCount);
223
 
224
        return new static($grid);
225
    }
226
 
227
    /**
228
     * Return a new matrix as a subset of columns from this matrix, dropping columns starting at column number $column,
229
     *     and $columnCount columns
230
     * A negative $columnCount value will drop columns until that many columns from the end of the matrix
231
     * A $columnCount value of 0 will remove all columns of the matrix from $column
232
     *
233
     * Note that column numbers start from 1, not from 0
234
     *
235
     * @param int $column
236
     * @param int $columnCount
237
     * @return static
238
     * @throws Exception
239
     */
240
    public function dropColumns(int $column, int $columnCount = 1): Matrix
241
    {
242
        $this->validateColumnInRange($column);
243
        if ($columnCount < 1) {
244
            $columnCount = $this->columns + $columnCount - $column + 1;
245
        }
246
 
247
        $grid = $this->grid;
248
        array_walk(
249
            $grid,
250
            function (&$row) use ($column, $columnCount) {
251
                array_splice($row, $column - 1, (int)$columnCount);
252
            }
253
        );
254
 
255
        return new static($grid);
256
    }
257
 
258
    /**
259
     * Return a value from this matrix, from the "cell" identified by the row and column numbers
260
     * Note that row and column numbers start from 1, not from 0
261
     *
262
     * @param int $row
263
     * @param int $column
264
     * @return mixed
265
     * @throws Exception
266
     */
267
    public function getValue(int $row, int $column)
268
    {
269
        $row = $this->validateRowInRange($row);
270
        $column = $this->validateColumnInRange($column);
271
 
272
        return $this->grid[$row - 1][$column - 1];
273
    }
274
 
275
    /**
276
     * Returns a Generator that will yield each row of the matrix in turn as a vector matrix
277
     *     or the value of each cell if the matrix is a column vector
278
     *
279
     * @return Generator|Matrix[]|mixed[]
280
     */
281
    public function rows(): Generator
282
    {
283
        foreach ($this->grid as $i => $row) {
284
            yield $i + 1 => ($this->columns == 1)
285
                ? $row[0]
286
                : new static([$row]);
287
        }
288
    }
289
 
290
    /**
291
     * Returns a Generator that will yield each column of the matrix in turn as a vector matrix
292
     *     or the value of each cell if the matrix is a row vector
293
     *
294
     * @return Generator|Matrix[]|mixed[]
295
     */
296
    public function columns(): Generator
297
    {
298
        for ($i = 0; $i < $this->columns; ++$i) {
299
            yield $i + 1 => ($this->rows == 1)
300
                ? $this->grid[0][$i]
301
                : new static(array_column($this->grid, $i));
302
        }
303
    }
304
 
305
    /**
306
     * Identify if the row and column dimensions of this matrix are equal,
307
     *     i.e. if it is a "square" matrix
308
     *
309
     * @return bool
310
     */
311
    public function isSquare(): bool
312
    {
313
        return $this->rows === $this->columns;
314
    }
315
 
316
    /**
317
     * Identify if this matrix is a vector
318
     *     i.e. if it comprises only a single row or a single column
319
     *
320
     * @return bool
321
     */
322
    public function isVector(): bool
323
    {
324
        return $this->rows === 1 || $this->columns === 1;
325
    }
326
 
327
    /**
328
     * Return the matrix as a 2-dimensional array
329
     *
330
     * @return array
331
     */
332
    public function toArray(): array
333
    {
334
        return $this->grid;
335
    }
336
 
337
    /**
338
     * Solve A*X = B.
339
     *
340
     * @param Matrix $B Right hand side
341
     *
342
     * @throws Exception
343
     *
344
     * @return Matrix ... Solution if A is square, least squares solution otherwise
345
     */
346
    public function solve(Matrix $B): Matrix
347
    {
348
        if ($this->columns === $this->rows) {
349
            return (new LU($this))->solve($B);
350
        }
351
 
352
        return (new QR($this))->solve($B);
353
    }
354
 
355
    protected static $getters = [
356
        'rows',
357
        'columns',
358
    ];
359
 
360
    /**
361
     * Access specific properties as read-only (no setters)
362
     *
363
     * @param string $propertyName
364
     * @return mixed
365
     * @throws Exception
366
     */
367
    public function __get(string $propertyName)
368
    {
369
        $propertyName = strtolower($propertyName);
370
 
371
        // Test for function calls
372
        if (in_array($propertyName, self::$getters)) {
373
            return $this->$propertyName;
374
        }
375
 
376
        throw new Exception('Property does not exist');
377
    }
378
 
379
    protected static $functions = [
380
        'adjoint',
381
        'antidiagonal',
382
        'cofactors',
383
        'determinant',
384
        'diagonal',
385
        'identity',
386
        'inverse',
387
        'minors',
388
        'trace',
389
        'transpose',
390
    ];
391
 
392
    protected static $operations = [
393
        'add',
394
        'subtract',
395
        'multiply',
396
        'divideby',
397
        'divideinto',
398
        'directsum',
399
    ];
400
 
401
    /**
402
     * Returns the result of the function call or operation
403
     *
404
     * @param string $functionName
405
     * @param mixed[] $arguments
406
     * @return Matrix|float
407
     * @throws Exception
408
     */
409
    public function __call(string $functionName, $arguments)
410
    {
411
        $functionName = strtolower(str_replace('_', '', $functionName));
412
 
413
        // Test for function calls
414
        if (in_array($functionName, self::$functions, true)) {
415
            return Functions::$functionName($this, ...$arguments);
416
        }
417
        // Test for operation calls
418
        if (in_array($functionName, self::$operations, true)) {
419
            return Operations::$functionName($this, ...$arguments);
420
        }
421
        throw new Exception('Function or Operation does not exist');
422
    }
423
}