Matrix::subtract()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 24
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 5

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
dl 0
loc 24
ccs 9
cts 9
cp 1
crap 5
rs 9.6111
c 1
b 0
f 0
eloc 11
nc 4
nop 1
1
<?php
2
3
namespace Samsara\Fermat\LinearAlgebra\Types;
4
5
use ReflectionException;
6
use Samsara\Exceptions\SystemError\LogicalError\IncompatibleObjectState;
7
use Samsara\Exceptions\UsageError\IntegrityConstraint;
8
use Samsara\Fermat\Core\Types\NumberCollection;
9
use Samsara\Fermat\LinearAlgebra\Matrices;
10
use Samsara\Fermat\Core\Numbers;
11
use Samsara\Fermat\Core\Provider\SequenceProvider;
12
use Samsara\Fermat\LinearAlgebra\Types\Base\Interfaces\Groups\MatrixInterface;
13
use Samsara\Fermat\Core\Types\Base\Interfaces\Numbers\NumberInterface;
0 ignored issues
show
Bug introduced by
The type Samsara\Fermat\Core\Type...Numbers\NumberInterface was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
14
use Samsara\Fermat\LinearAlgebra\Types\Traits\Matrix\DirectAccessTrait;
15
use Samsara\Fermat\LinearAlgebra\Types\Traits\Matrix\ShapeTrait;
16
use Samsara\Fermat\Core\Values\ImmutableFraction;
17
use Samsara\Fermat\LinearAlgebra\Values\ImmutableMatrix;
18
use Samsara\Fermat\Core\Values\ImmutableDecimal;
19
20
abstract class Matrix implements MatrixInterface
21
{
22
    public const MODE_ROWS_INPUT = 'rows';
23
    public const MODE_COLUMNS_INPUT = 'columns';
24
25
    use ShapeTrait;
26
    use DirectAccessTrait;
27
28
    /**
29
     * Matrix constructor. The array of number collections can be an array of rows, or an array of columns. Default is rows.
30
     *
31
     * @param NumberCollection[] $data
32
     * @param string $mode
33
     */
34 46
    public function __construct(array $data, string $mode = Matrix::MODE_ROWS_INPUT)
35
    {
36
37 46
        $this->normalizeInputData($data, $mode);
38
39
    }
40
41
    /**
42
     * @param array $data
43
     * @param string $mode
44
     */
45 46
    protected function normalizeInputData(array $data, string $mode): void
46
    {
47 46
        if ($mode === self::MODE_ROWS_INPUT) {
48 44
            $this->rows = $data;
49 44
            $this->columns = self::swapArrayHierarchy($data);
50 44
            $this->numRows = count($this->rows);
51 44
            $this->numColumns = count($this->columns);
52 4
        } elseif ($mode === self::MODE_COLUMNS_INPUT) {
53 4
            $this->columns = $data;
54 4
            $this->rows = self::swapArrayHierarchy($data);
55 4
            $this->numRows = count($this->rows);
56 4
            $this->numColumns = count($this->columns);
57
        }
58
    }
59
60
    /**
61
     * @return ImmutableDecimal
62
     * @throws IncompatibleObjectState
63
     * @throws IntegrityConstraint
64
     */
65 2
    public function getDeterminant(): ImmutableDecimal
66
    {
67 2
        if (!$this->isSquare()) {
68
            throw new IncompatibleObjectState(
0 ignored issues
show
Bug introduced by
The call to Samsara\Exceptions\Syste...ectState::__construct() has too few arguments starting with suggestedSolution. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

68
            throw /** @scrutinizer ignore-call */ new IncompatibleObjectState(

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
69
                'Only matrices which are square have determinants.'
70
            );
71
        }
72
73 2
        if ($this->numRows > 2) {
74 2
            $determinant = Numbers::makeZero();
75
76 2
            foreach ($this->rows[0]->toArray() as $key => $value) {
77 2
                $childMatrix = $this->childMatrix(0, $key);
78
79 2
                $determinant = $determinant->add($value->multiply($childMatrix->getDeterminant())->multiply(SequenceProvider::nthPowerNegativeOne($key)));
80
            }
81
        } else {
82
            /** @var ImmutableDecimal $value */
83 2
            $determinant = $this->rows[0]->get(0)->multiply($this->rows[1]->get(1))->subtract($this->rows[1]->get(0)->multiply($this->rows[0]->get(1)));
84
        }
85
86 2
        return $determinant;
87
    }
88
89
    /**
90
     * @param MatrixInterface $value
91
     * @return MatrixInterface
92
     * @throws IntegrityConstraint
93
     */
94 12
    public function add(MatrixInterface $value): MatrixInterface
95
    {
96 12
        if ($this->getRowCount() !== $value->getRowCount() || $this->getColumnCount() !== $value->getColumnCount()) {
97 2
            throw new IntegrityConstraint(
98
                'Matrices must be the same size in order to be added.',
99
                'Only add two matrices if they are the same size.',
100
                'Attempted addition on matrices of different sizes.'
101
            );
102
        }
103
104 12
        $resultArray = [];
105
106 12
        foreach ($this->rows as $rowKey => $row) {
107 12
            $resultArray[$rowKey] = new NumberCollection();
108
            /**
109
             * @var int $columnKey
110
             * @var NumberInterface $num
111
             */
112 12
            foreach ($row->toArray() as $columnKey => $num) {
113 12
                $resultArray[$rowKey]->push($num->add($value->getRow($rowKey)->get($columnKey)));
114
            }
115
        }
116
117 12
        return $this->setValue($resultArray, self::MODE_ROWS_INPUT);
118
    }
119
120
    /**
121
     * This function takes an input scalar value and multiplies an identity matrix by that scalar, then does matrix
122
     * addition with the resulting matrix.
123
     *
124
     * @param NumberInterface $value
125
     * @return MatrixInterface
126
     * @throws IntegrityConstraint
127
     */
128 4
    public function addScalarAsI(NumberInterface $value): MatrixInterface
129
    {
130 4
        if (!$this->isSquare()) {
131
            throw new IntegrityConstraint(
132
                'Can only add scalar as scaled identity matrix if the matrix is square.',
133
                'Add the scalar as scaled ones matrix, or construct a matrix manually to add.',
134
                'Cannot add a scalar as scaled identity matrix when the original matrix is not square.'
135
            );
136
        }
137
138 4
        $I = Matrices::identityMatrix(Matrices::IMMUTABLE_MATRIX, $this->getRowCount());
139 4
        $I = $I->multiply($value);
140
141 4
        return $this->add($I);
142
    }
143
144
    /**
145
     * This function takes a scalar input value and adds that value to each position in the matrix directly.
146
     *
147
     * @param NumberInterface $value
148
     * @return MatrixInterface
149
     * @throws IntegrityConstraint
150
     */
151 6
    public function addScalarAsJ(NumberInterface $value): MatrixInterface
152
    {
153 6
        $J = Matrices::onesMatrix(Matrices::IMMUTABLE_MATRIX, $this->getRowCount(), $this->getColumnCount());
154 6
        $J = $J->multiply($value);
155
156 6
        return $this->add($J);
157
    }
158
159
    /**
160
     * @param MatrixInterface $value
161
     * @return MatrixInterface
162
     * @throws IntegrityConstraint
163
     */
164 2
    public function subtract(MatrixInterface $value): MatrixInterface
165
    {
166 2
        if ($this->getRowCount() !== $value->getRowCount() || $this->getColumnCount() !== $value->getColumnCount()) {
167 2
            throw new IntegrityConstraint(
168
                'Matrices must be the same size in order to be subtracted.',
169
                'Only subtract two matrices if they are the same size.',
170
                'Attempted subtraction on matrices of different sizes.'
171
            );
172
        }
173
174 2
        $resultArray = [];
175
176 2
        foreach ($this->rows as $rowKey => $row) {
177 2
            $resultArray[$rowKey] = new NumberCollection();
178
            /**
179
             * @var int $columnKey
180
             * @var NumberInterface $num
181
             */
182 2
            foreach ($row->toArray() as $columnKey => $num) {
183 2
                $resultArray[$rowKey]->push($num->subtract($value->getRow($rowKey)->get($columnKey)));
184
            }
185
        }
186
187 2
        return $this->setValue($resultArray, self::MODE_ROWS_INPUT);
188
    }
189
190
    /**
191
     * @param NumberInterface $value
192
     *
193
     * @return MatrixInterface
194
     * @throws IntegrityConstraint
195
     */
196 2
    public function subtractScalarAsI(NumberInterface $value): MatrixInterface
197
    {
198 2
        $value = $value->multiply(-1);
199
200 2
        return $this->addScalarAsI($value);
201
    }
202
203
    /**
204
     * @param NumberInterface $value
205
     *
206
     * @return MatrixInterface
207
     * @throws IntegrityConstraint
208
     */
209 4
    public function subtractScalarAsJ(NumberInterface $value): MatrixInterface
210
    {
211 4
        $value = $value->multiply(-1);
212
213 4
        return $this->addScalarAsJ($value);
214
    }
215
216
    /**
217
     * @param $value
218
     *
219
     * @return MatrixInterface
220
     * @throws IntegrityConstraint
221
     */
222 12
    public function multiply($value): MatrixInterface
223
    {
224 12
        if ($value instanceof MatrixInterface) {
225 2
            if ($this->getColumnCount() !== $value->getRowCount()) {
226 2
                throw new IntegrityConstraint(
227
                    'The columns of matrix A must equal the columns of matrix B.',
228
                    'Ensure that compatible matrices are multiplied.',
229
                    'Attempted to multiply two matrices that do not have the needed row and column correspondence.'
230
                );
231
            }
232
233 2
            $resultArray = [];
234
235 2
            foreach ($this->rows as $rowKey => $row) {
236 2
                $resultArray[$rowKey] = new NumberCollection();
237 2
                for ($i = 0;$i < $value->getColumnCount();$i++) {
238 2
                    $cellVal = Numbers::makeZero();
239
                    /** @var NumberInterface $num */
240 2
                    foreach ($row->toArray() as $index => $num) {
241 2
                        $cellVal = $cellVal->add($num->multiply($value->getColumn($i)->get($index)));
242
                    }
243
244 2
                    $resultArray[$rowKey]->push($cellVal);
245
                }
246
            }
247
        } else {
248 12
            $value = Numbers::makeOrDont(Numbers::IMMUTABLE, $value);
249
250 12
            $resultArray = [];
251
252 12
            foreach ($this->rows as $row) {
253 12
                $newRow = clone $row;
254 12
                $newRow->multiply($value);
255
256 12
                $resultArray[] = $newRow;
257
            }
258
        }
259
260 12
        return $this->setValue($resultArray, self::MODE_ROWS_INPUT);
261
    }
262
263
    /**
264
     * @return MatrixInterface
265
     * @throws IncompatibleObjectState
266
     * @throws IntegrityConstraint
267
     */
268
    public function inverseMatrix(): MatrixInterface
269
    {
270
        $determinant = $this->getDeterminant();
271
        $inverseDeterminant = new ImmutableFraction(Numbers::makeOne(), $determinant);
272
273
        $data = $this->rows;
0 ignored issues
show
Unused Code introduced by
The assignment to $data is dead and can be removed.
Loading history...
274
        $columnCount = $this->getColumnCount();
275
276
        $newMatrixData = [];
0 ignored issues
show
Unused Code introduced by
The assignment to $newMatrixData is dead and can be removed.
Loading history...
277
278
        // TODO: Implement minors & cofactors method https://www.mathsisfun.com/algebra/matrix-inverse-minors-cofactors-adjugate.html
279
        for ($i = 0;$i < $columnCount;$i++) {
280
            for ($r = 0;$r < $columnCount;$r++) {
281
282
            }
283
        }
284
285
        return $this->multiply($inverseDeterminant);
286
    }
287
288
    public function minors()
289
    {
290
291
292
293
    }
294
295
    /**
296
     * This function returns a subset of the current matrix as a new matrix with one row and one column removed
297
     * from the dataset.
298
     *
299
     * @param int $excludeRow
300
     * @param int $excludeColumn
301
     * @param bool $forceNewMatrix
302
     *
303
     * @return MatrixInterface
304
     */
305 2
    protected function childMatrix(int $excludeRow, int $excludeColumn, bool $forceNewMatrix = false)
306
    {
307
308 2
        $newRows = [];
309
310 2
        for ($i = 0;$i < $this->getRowCount();$i++) {
311 2
            if ($i === $excludeRow) {
312 2
                continue;
313
            }
314
315 2
            $newRows[] = $this->getRow($i)->filterByKeys([$excludeColumn]);
316
        }
317
318 2
        return $forceNewMatrix ? new ImmutableMatrix($newRows) : $this->setValue($newRows);
319
320
    }
321
322
    /**
323
     * This function takes an input of rows or columns and returns the dataset formatted in the opposite type
324
     * of input.
325
     *
326
     * @param NumberCollection[] $data
327
     *
328
     * @return NumberCollection[]
329
     */
330 46
    protected static function swapArrayHierarchy(array $data): array
331
    {
332 46
        $swappedArray = [];
333
334 46
        foreach ($data as $value) {
335 46
            foreach ($value->toArray() as $subKey => $subValue) {
336 46
                if (!isset($swappedArray[$subKey])) {
337 46
                    $swappedArray[$subKey] = new NumberCollection();
338
                }
339
340 46
                $swappedArray[$subKey]->push($subValue);
341
            }
342
        }
343
344 46
        return $swappedArray;
345
    }
346
347
    abstract protected function setValue(array $data, $mode = Matrix::MODE_ROWS_INPUT): MatrixInterface;
348
349
}