Completed
Push — master ( a33d5f...f0a798 )
by Arkadiusz
02:51
created

Matrix::isSingular()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
        $this->rows = count($matrix);
41
        $this->columns = count($matrix[0]);
42
43
        if ($validate) {
44
            for ($i = 0; $i < $this->rows; ++$i) {
45
                if (count($matrix[$i]) !== $this->columns) {
46
                    throw InvalidArgumentException::matrixDimensionsDidNotMatch();
47
                }
48
            }
49
        }
50
51
        $this->matrix = $matrix;
52
    }
53
54
    /**
55
     * @param array $array
56
     *
57
     * @return Matrix
58
     */
59
    public static function fromFlatArray(array $array)
60
    {
61
        $matrix = [];
62
        foreach ($array as $value) {
63
            $matrix[] = [$value];
64
        }
65
66
        return new self($matrix);
67
    }
68
69
    /**
70
     * @return array
71
     */
72
    public function toArray()
73
    {
74
        return $this->matrix;
75
    }
76
77
    /**
78
     * @return int
79
     */
80
    public function getRows()
81
    {
82
        return $this->rows;
83
    }
84
85
    /**
86
     * @return int
87
     */
88
    public function getColumns()
89
    {
90
        return $this->columns;
91
    }
92
93
    /**
94
     * @param $column
95
     *
96
     * @return array
97
     *
98
     * @throws MatrixException
99
     */
100
    public function getColumnValues($column)
101
    {
102
        if ($column >= $this->columns) {
103
            throw MatrixException::columnOutOfRange();
104
        }
105
106
        $values = [];
107
        for ($i = 0; $i < $this->rows; ++$i) {
108
            $values[] = $this->matrix[$i][$column];
109
        }
110
111
        return $values;
112
    }
113
114
    /**
115
     * @return float|int
116
     *
117
     * @throws MatrixException
118
     */
119
    public function getDeterminant()
120
    {
121
        if ($this->determinant) {
122
            return $this->determinant;
123
        }
124
125
        if (!$this->isSquare()) {
126
            throw MatrixException::notSquareMatrix();
127
        }
128
129
        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...
130
    }
131
132
    /**
133
     * @return float|int
134
     *
135
     * @throws MatrixException
136
     */
137
    private function calculateDeterminant()
138
    {
139
        $determinant = 0;
140
        if ($this->rows == 1 && $this->columns == 1) {
141
            $determinant = $this->matrix[0][0];
142
        } elseif ($this->rows == 2 && $this->columns == 2) {
143
            $determinant =
144
                $this->matrix[0][0] * $this->matrix[1][1] -
145
                $this->matrix[0][1] * $this->matrix[1][0];
146
        } else {
147
            for ($j = 0; $j < $this->columns; ++$j) {
148
                $subMatrix = $this->crossOut(0, $j);
149
                $minor = $this->matrix[0][$j] * $subMatrix->getDeterminant();
150
                $determinant += fmod((float) $j, 2.0) == 0 ? $minor : -$minor;
151
            }
152
        }
153
154
        return $determinant;
155
    }
156
157
    /**
158
     * @return bool
159
     */
160
    public function isSquare()
161
    {
162
        return $this->columns === $this->rows;
163
    }
164
165
    /**
166
     * @return Matrix
167
     */
168
    public function transpose()
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...
169
    {
170
        $newMatrix = [];
171
        for ($i = 0; $i < $this->rows; ++$i) {
172
            for ($j = 0; $j < $this->columns; ++$j) {
173
                $newMatrix[$j][$i] = $this->matrix[$i][$j];
174
            }
175
        }
176
177
        return new self($newMatrix, false);
178
    }
179
180
    /**
181
     * @param Matrix $matrix
182
     *
183
     * @return Matrix
184
     *
185
     * @throws InvalidArgumentException
186
     */
187
    public function multiply(Matrix $matrix)
188
    {
189
        if ($this->columns != $matrix->getRows()) {
190
            throw InvalidArgumentException::inconsistentMatrixSupplied();
191
        }
192
193
        $product = [];
194
        $multiplier = $matrix->toArray();
195
        for ($i = 0; $i < $this->rows; ++$i) {
196
            $columns = $matrix->getColumns();
197
            for ($j = 0; $j < $columns; ++$j) {
198
                $product[$i][$j] = 0;
199
                for ($k = 0; $k < $this->columns; ++$k) {
200
                    $product[$i][$j] += $this->matrix[$i][$k] * $multiplier[$k][$j];
201
                }
202
            }
203
        }
204
205
        return new self($product, false);
206
    }
207
208
    /**
209
     * @param $value
210
     *
211
     * @return Matrix
212
     */
213
    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...
214
    {
215
        $newMatrix = [];
216
        for ($i = 0; $i < $this->rows; ++$i) {
217
            for ($j = 0; $j < $this->columns; ++$j) {
218
                $newMatrix[$i][$j] = $this->matrix[$i][$j] / $value;
219
            }
220
        }
221
222
        return new self($newMatrix, false);
223
    }
224
225
    /**
226
     * @return Matrix
227
     *
228
     * @throws MatrixException
229
     */
230
    public function inverse()
231
    {
232
        if (!$this->isSquare()) {
233
            throw MatrixException::notSquareMatrix();
234
        }
235
236
        if ($this->isSingular()) {
237
            throw MatrixException::singularMatrix();
238
        }
239
240
        $newMatrix = [];
241
        for ($i = 0; $i < $this->rows; ++$i) {
242
            for ($j = 0; $j < $this->columns; ++$j) {
243
                $minor = $this->crossOut($i, $j)->getDeterminant();
244
                $newMatrix[$i][$j] = fmod((float) ($i + $j), 2.0) == 0 ? $minor : -$minor;
245
            }
246
        }
247
248
        $cofactorMatrix = new self($newMatrix, false);
249
250
        return $cofactorMatrix->transpose()->divideByScalar($this->getDeterminant());
251
    }
252
253
    /**
254
     * @param int $row
255
     * @param int $column
256
     *
257
     * @return Matrix
258
     */
259
    public function crossOut(int $row, int $column)
260
    {
261
        $newMatrix = [];
262
        $r = 0;
263
        for ($i = 0; $i < $this->rows; ++$i) {
264
            $c = 0;
265
            if ($row != $i) {
266
                for ($j = 0; $j < $this->columns; ++$j) {
267
                    if ($column != $j) {
268
                        $newMatrix[$r][$c] = $this->matrix[$i][$j];
269
                        ++$c;
270
                    }
271
                }
272
                ++$r;
273
            }
274
        }
275
276
        return new self($newMatrix, false);
277
    }
278
279
    /**
280
     * @return bool
281
     */
282
    public function isSingular() : bool
283
    {
284
        return 0 == $this->getDeterminant();
285
    }
286
}
287