Completed
Push — master ( 6296e4...a87859 )
by Arkadiusz
18:50
created

Matrix::__construct()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 22
rs 8.6737
c 0
b 0
f 0
cc 5
eloc 13
nc 6
nop 2
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Phpml\Math;
6
7
use Phpml\Exception\InvalidArgumentException;
8
use Phpml\Exception\MatrixException;
9
10
class Matrix
11
{
12
    /**
13
     * @var array
14
     */
15
    private $matrix;
16
17
    /**
18
     * @var int
19
     */
20
    private $rows;
21
22
    /**
23
     * @var int
24
     */
25
    private $columns;
26
27
    /**
28
     * @var float
29
     */
30
    private $determinant;
31
32
    /**
33
     * @param array $matrix
34
     * @param bool  $validate
35
     *
36
     * @throws InvalidArgumentException
37
     */
38
    public function __construct(array $matrix, bool $validate = true)
39
    {
40
        // When a row vector is given
41
        if (!is_array($matrix[0])) {
42
            $this->rows = 1;
43
            $this->columns = count($matrix);
44
            $matrix = [$matrix];
45
        } else {
46
            $this->rows = count($matrix);
47
            $this->columns = count($matrix[0]);
48
        }
49
50
        if ($validate) {
51
            for ($i = 0; $i < $this->rows; ++$i) {
52
                if (count($matrix[$i]) !== $this->columns) {
53
                    throw InvalidArgumentException::matrixDimensionsDidNotMatch();
54
                }
55
            }
56
        }
57
58
        $this->matrix = $matrix;
59
    }
60
61
    /**
62
     * @param array $array
63
     *
64
     * @return Matrix
65
     */
66
    public static function fromFlatArray(array $array)
67
    {
68
        $matrix = [];
69
        foreach ($array as $value) {
70
            $matrix[] = [$value];
71
        }
72
73
        return new self($matrix);
74
    }
75
76
    /**
77
     * @return array
78
     */
79
    public function toArray()
80
    {
81
        return $this->matrix;
82
    }
83
84
    /**
85
     * @return float
86
     */
87
    public function toScalar()
88
    {
89
        return $this->matrix[0][0];
90
    }
91
92
    /**
93
     * @return int
94
     */
95
    public function getRows()
96
    {
97
        return $this->rows;
98
    }
99
100
    /**
101
     * @return int
102
     */
103
    public function getColumns()
104
    {
105
        return $this->columns;
106
    }
107
108
    /**
109
     * @param $column
110
     *
111
     * @return array
112
     *
113
     * @throws MatrixException
114
     */
115
    public function getColumnValues($column)
116
    {
117
        if ($column >= $this->columns) {
118
            throw MatrixException::columnOutOfRange();
119
        }
120
121
        return array_column($this->matrix, $column);
122
    }
123
124
125
    /**
126
     * @return float|int
127
     *
128
     * @throws MatrixException
129
     */
130
    public function getDeterminant()
131
    {
132
        if ($this->determinant) {
133
            return $this->determinant;
134
        }
135
136
        if (!$this->isSquare()) {
137
            throw MatrixException::notSquareMatrix();
138
        }
139
140
        return $this->determinant = $this->calculateDeterminant();
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->calculateDeterminant() can also be of type integer. However, the property $determinant is declared as type double. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
141
    }
142
143
    /**
144
     * @return float|int
145
     *
146
     * @throws MatrixException
147
     */
148
    private function calculateDeterminant()
149
    {
150
        $determinant = 0;
151
        if ($this->rows == 1 && $this->columns == 1) {
152
            $determinant = $this->matrix[0][0];
153
        } elseif ($this->rows == 2 && $this->columns == 2) {
154
            $determinant =
155
                $this->matrix[0][0] * $this->matrix[1][1] -
156
                $this->matrix[0][1] * $this->matrix[1][0];
157
        } else {
158
            for ($j = 0; $j < $this->columns; ++$j) {
159
                $subMatrix = $this->crossOut(0, $j);
160
                $minor = $this->matrix[0][$j] * $subMatrix->getDeterminant();
161
                $determinant += fmod((float) $j, 2.0) == 0 ? $minor : -$minor;
162
            }
163
        }
164
165
        return $determinant;
166
    }
167
168
    /**
169
     * @return bool
170
     */
171
    public function isSquare()
172
    {
173
        return $this->columns === $this->rows;
174
    }
175
176
    /**
177
     * @return Matrix
178
     */
179
    public function transpose()
180
    {
181
        if ($this->rows == 1) {
182
            $matrix = array_map(function ($el) {
183
                return [$el];
184
            }, $this->matrix[0]);
185
        } else {
186
            $matrix = array_map(null, ...$this->matrix);
187
        }
188
189
        return new self($matrix, false);
190
    }
191
192
    /**
193
     * @param Matrix $matrix
194
     *
195
     * @return Matrix
196
     *
197
     * @throws InvalidArgumentException
198
     */
199
    public function multiply(Matrix $matrix)
200
    {
201
        if ($this->columns != $matrix->getRows()) {
202
            throw InvalidArgumentException::inconsistentMatrixSupplied();
203
        }
204
205
        $product = [];
206
        $multiplier = $matrix->toArray();
207
        for ($i = 0; $i < $this->rows; ++$i) {
208
            $columns = $matrix->getColumns();
209
            for ($j = 0; $j < $columns; ++$j) {
210
                $product[$i][$j] = 0;
211
                for ($k = 0; $k < $this->columns; ++$k) {
212
                    $product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j];
213
                }
214
            }
215
        }
216
217
        return new self($product, false);
218
    }
219
220
    /**
221
     * @param $value
222
     *
223
     * @return Matrix
224
     */
225
    public function divideByScalar($value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
226
    {
227
        $newMatrix = [];
228
        for ($i = 0; $i < $this->rows; ++$i) {
229
            for ($j = 0; $j < $this->columns; ++$j) {
230
                $newMatrix[$i][$j] = $this->matrix[$i][$j] / $value;
231
            }
232
        }
233
234
        return new self($newMatrix, false);
235
    }
236
237
    /**
238
     * @param $value
239
     *
240
     * @return Matrix
241
     */
242
    public function multiplyByScalar($value)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
243
    {
244
        $newMatrix = [];
245
        for ($i = 0; $i < $this->rows; ++$i) {
246
            for ($j = 0; $j < $this->columns; ++$j) {
247
                $newMatrix[$i][$j] = $this->matrix[$i][$j] * $value;
248
            }
249
        }
250
251
        return new self($newMatrix, false);
252
    }
253
254
    /**
255
     * Element-wise addition of the matrix with another one
256
     *
257
     * @param Matrix $other
258
     */
259
    public function add(Matrix $other)
260
    {
261
        return $this->_add($other);
262
    }
263
264
    /**
265
     * Element-wise subtracting of another matrix from this one
266
     *
267
     * @param Matrix $other
268
     */
269
    public function subtract(Matrix $other)
270
    {
271
        return $this->_add($other, -1);
272
    }
273
274
    /**
275
     * Element-wise addition or substraction depending on the given sign parameter
276
     *
277
     * @param Matrix $other
278
     * @param type $sign
279
     */
280
    protected function _add(Matrix $other, $sign = 1)
281
    {
282
        $a1 = $this->toArray();
283
        $a2 = $other->toArray();
284
285
        $newMatrix = [];
286
        for ($i=0; $i < $this->rows; $i++) {
287
            for ($k=0; $k < $this->columns; $k++) {
288
                $newMatrix[$i][$k] = $a1[$i][$k] + $sign * $a2[$i][$k];
289
            }
290
        }
291
292
        return new Matrix($newMatrix, false);
293
    }
294
295
    /**
296
     * @return Matrix
297
     *
298
     * @throws MatrixException
299
     */
300
    public function inverse()
301
    {
302
        if (!$this->isSquare()) {
303
            throw MatrixException::notSquareMatrix();
304
        }
305
306
        if ($this->isSingular()) {
307
            throw MatrixException::singularMatrix();
308
        }
309
310
        $newMatrix = [];
311
        for ($i = 0; $i < $this->rows; ++$i) {
312
            for ($j = 0; $j < $this->columns; ++$j) {
313
                $minor = $this->crossOut($i, $j)->getDeterminant();
314
                $newMatrix[$i][$j] = fmod((float) ($i + $j), 2.0) == 0 ? $minor : -$minor;
315
            }
316
        }
317
318
        $cofactorMatrix = new self($newMatrix, false);
319
320
        return $cofactorMatrix->transpose()->divideByScalar($this->getDeterminant());
321
    }
322
323
    /**
324
     * @param int $row
325
     * @param int $column
326
     *
327
     * @return Matrix
328
     */
329
    public function crossOut(int $row, int $column)
330
    {
331
        $newMatrix = [];
332
        $r = 0;
333
        for ($i = 0; $i < $this->rows; ++$i) {
334
            $c = 0;
335
            if ($row != $i) {
336
                for ($j = 0; $j < $this->columns; ++$j) {
337
                    if ($column != $j) {
338
                        $newMatrix[$r][$c] = $this->matrix[$i][$j];
339
                        ++$c;
340
                    }
341
                }
342
                ++$r;
343
            }
344
        }
345
346
        return new self($newMatrix, false);
347
    }
348
349
    /**
350
     * @return bool
351
     */
352
    public function isSingular() : bool
353
    {
354
        return 0 == $this->getDeterminant();
355
    }
356
357
    /**
358
     * Returns the transpose of given array
359
     *
360
     * @param array $array
361
     *
362
     * @return array
363
     */
364
    public static function transposeArray(array $array)
365
    {
366
        return (new Matrix($array, false))->transpose()->toArray();
367
    }
368
369
    /**
370
     * Returns the dot product of two arrays<br>
371
     * Matrix::dot(x, y) ==> x.y'
372
     *
373
     * @param array $array1
374
     * @param array $array2
375
     *
376
     * @return array
377
     */
378
    public static function dot(array $array1, array $array2)
379
    {
380
        $m1 = new Matrix($array1, false);
381
        $m2 = new Matrix($array2, false);
382
383
        return $m1->multiply($m2->transpose())->toArray()[0];
384
    }
385
}
386