Test Failed
Push — develop ( 90366f...812a46 )
by Adrien
28:16
created

Matrix::identity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 2
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
4
5
/*
6
 *    Matrix class
7
 *
8
 *    @author Paul Meagher
9
 *    @author Michael Bommarito
10
 *    @author Lukasz Karapuda
11
 *    @author Bartek Matosiuk
12
 *    @version 1.8
13
 *    @see http://math.nist.gov/javanumerics/jama/
14
 */
15
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
16
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
17
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
18
19
class Matrix
20
{
21
    const POLYMORPHIC_ARGUMENT_EXCEPTION = 'Invalid argument pattern for polymorphic function.';
22
    const ARGUMENT_TYPE_EXCEPTION = 'Invalid argument type.';
23
    const ARGUMENT_BOUNDS_EXCEPTION = 'Invalid argument range.';
24
    const MATRIX_DIMENSION_EXCEPTION = 'Matrix dimensions are not equal.';
25
    const ARRAY_LENGTH_EXCEPTION = 'Array length must be a multiple of m.';
26
    const MATRIX_SPD_EXCEPTION = 'Can only perform operation on symmetric positive definite matrix.';
27
28
    /**
29
     * Matrix storage.
30
     *
31
     * @var array
32
     */
33
    public $A = [];
34
35
    /**
36
     * Matrix row dimension.
37
     *
38
     * @var int
39
     */
40
    private $m;
41
42
    /**
43
     * Matrix column dimension.
44
     *
45
     * @var int
46
     */
47
    private $n;
48
49
    /**
50
     * Polymorphic constructor.
51
     *
52
     * As PHP has no support for polymorphic constructors, we use tricks to make our own sort of polymorphism using func_num_args, func_get_arg, and gettype. In essence, we're just implementing a simple RTTI filter and calling the appropriate constructor.
53
     */
54
    public function __construct(...$args)
55
    {
56
        if (count($args) > 0) {
57
            $match = implode(',', array_map('gettype', $args));
58
59
            switch ($match) {
60
                //Rectangular matrix - m x n initialized from 2D array
61
                case 'array':
62
                    $this->m = count($args[0]);
63
                    $this->n = count($args[0][0]);
64
                    $this->A = $args[0];
65
66
                    break;
67
                //Square matrix - n x n
68 View Code Duplication
                case 'integer':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
69
                    $this->m = $args[0];
70
                    $this->n = $args[0];
71
                    $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
72
73
                    break;
74
                //Rectangular matrix - m x n
75 View Code Duplication
                case 'integer,integer':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
76
                    $this->m = $args[0];
77
                    $this->n = $args[1];
78
                    $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
79
80
                    break;
81
                //Rectangular matrix - m x n initialized from packed array
82
                case 'array,integer':
83
                    $this->m = $args[1];
84
                    if ($this->m != 0) {
85
                        $this->n = count($args[0]) / $this->m;
86
                    } else {
87
                        $this->n = 0;
88
                    }
89
                    if (($this->m * $this->n) == count($args[0])) {
90
                        for ($i = 0; $i < $this->m; ++$i) {
91
                            for ($j = 0; $j < $this->n; ++$j) {
92
                                $this->A[$i][$j] = $args[0][$i + $j * $this->m];
93
                            }
94
                        }
95
                    } else {
96
                        throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION);
97
                    }
98
99
                    break;
100
                default:
101
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
102
                    break;
103
            }
104
        } else {
105
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
106
        }
107
    }
108
109
    /**
110
     * getArray.
111
     *
112
     * @return array Matrix array
113
     */
114
    public function getArray()
115
    {
116
        return $this->A;
117
    }
118
119
    /**
120
     * getRowDimension.
121
     *
122
     * @return int Row dimension
123
     */
124
    public function getRowDimension()
125
    {
126
        return $this->m;
127
    }
128
129
    /**
130
     * getColumnDimension.
131
     *
132
     * @return int Column dimension
133
     */
134
    public function getColumnDimension()
135
    {
136
        return $this->n;
137
    }
138
139
    /**
140
     * get.
141
     *
142
     * Get the i,j-th element of the matrix.
143
     *
144
     * @param int $i Row position
145
     * @param int $j Column position
146
     *
147
     * @return mixed Element (int/float/double)
148
     */
149
    public function get($i = null, $j = null)
150
    {
151
        return $this->A[$i][$j];
152
    }
153
154
    /**
155
     * getMatrix.
156
     *
157
     *    Get a submatrix
158
     *
159
     * @param int $i0 Initial row index
160
     * @param int $iF Final row index
161
     * @param int $j0 Initial column index
162
     * @param int $jF Final column index
163
     *
164
     * @return Matrix Submatrix
165
     */
166
    public function getMatrix(...$args)
167
    {
168
        if (count($args) > 0) {
169
            $match = implode(',', array_map('gettype', $args));
170
171
            switch ($match) {
172
                //A($i0...; $j0...)
173
                case 'integer,integer':
174
                    list($i0, $j0) = $args;
175
                    if ($i0 >= 0) {
176
                        $m = $this->m - $i0;
177
                    } else {
178
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
179
                    }
180
                    if ($j0 >= 0) {
181
                        $n = $this->n - $j0;
182
                    } else {
183
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
184
                    }
185
                    $R = new self($m, $n);
186
                    for ($i = $i0; $i < $this->m; ++$i) {
187
                        for ($j = $j0; $j < $this->n; ++$j) {
188
                            $R->set($i, $j, $this->A[$i][$j]);
189
                        }
190
                    }
191
192
                    return $R;
193
                    break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
194
                //A($i0...$iF; $j0...$jF)
195
                case 'integer,integer,integer,integer':
196
                    list($i0, $iF, $j0, $jF) = $args;
197
                    if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
198
                        $m = $iF - $i0;
199
                    } else {
200
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
201
                    }
202
                    if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
203
                        $n = $jF - $j0;
204
                    } else {
205
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
206
                    }
207
                    $R = new self($m + 1, $n + 1);
208
                    for ($i = $i0; $i <= $iF; ++$i) {
209
                        for ($j = $j0; $j <= $jF; ++$j) {
210
                            $R->set($i - $i0, $j - $j0, $this->A[$i][$j]);
211
                        }
212
                    }
213
214
                    return $R;
215
                    break;
216
                //$R = array of row indices; $C = array of column indices
217
                case 'array,array':
218
                    list($RL, $CL) = $args;
219
                    if (count($RL) > 0) {
220
                        $m = count($RL);
221
                    } else {
222
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
223
                    }
224
                    if (count($CL) > 0) {
225
                        $n = count($CL);
226
                    } else {
227
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
228
                    }
229
                    $R = new self($m, $n);
230
                    for ($i = 0; $i < $m; ++$i) {
231
                        for ($j = 0; $j < $n; ++$j) {
232
                            $R->set($i - $i0, $j - $j0, $this->A[$RL[$i]][$CL[$j]]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i0 seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $j0 seems to be never defined.
Loading history...
233
                        }
234
                    }
235
236
                    return $R;
237
                    break;
238
                //A($i0...$iF); $CL = array of column indices
239 View Code Duplication
                case 'integer,integer,array':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
240
                    list($i0, $iF, $CL) = $args;
241
                    if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
242
                        $m = $iF - $i0;
243
                    } else {
244
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
245
                    }
246
                    if (count($CL) > 0) {
247
                        $n = count($CL);
248
                    } else {
249
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
250
                    }
251
                    $R = new self($m, $n);
252
                    for ($i = $i0; $i < $iF; ++$i) {
253
                        for ($j = 0; $j < $n; ++$j) {
254
                            $R->set($i - $i0, $j, $this->A[$RL[$i]][$j]);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $RL seems to be never defined.
Loading history...
255
                        }
256
                    }
257
258
                    return $R;
259
                    break;
260
                //$RL = array of row indices
261 View Code Duplication
                case 'array,integer,integer':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
262
                    list($RL, $j0, $jF) = $args;
263
                    if (count($RL) > 0) {
264
                        $m = count($RL);
265
                    } else {
266
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
267
                    }
268
                    if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
269
                        $n = $jF - $j0;
270
                    } else {
271
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
272
                    }
273
                    $R = new self($m, $n + 1);
274
                    for ($i = 0; $i < $m; ++$i) {
275
                        for ($j = $j0; $j <= $jF; ++$j) {
276
                            $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]);
277
                        }
278
                    }
279
280
                    return $R;
281
                    break;
282
                default:
283
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
284
                    break;
285
            }
286
        } else {
287
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
288
        }
289
    }
290
291
    /**
292
     * checkMatrixDimensions.
293
     *
294
     *    Is matrix B the same size?
295
     *
296
     * @param Matrix $B Matrix B
297
     *
298
     * @return bool
299
     */
300
    public function checkMatrixDimensions($B = null)
301
    {
302
        if ($B instanceof self) {
303
            if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) {
304
                return true;
305
            }
306
307
            throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
308
        }
309
310
        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
311
    }
312
313
    //    function checkMatrixDimensions()
314
315
    /**
316
     * set.
317
     *
318
     * Set the i,j-th element of the matrix.
319
     *
320
     * @param int $i Row position
321
     * @param int $j Column position
322
     * @param mixed $c Int/float/double value
323
     *
324
     * @return mixed Element (int/float/double)
325
     */
326
    public function set($i = null, $j = null, $c = null)
327
    {
328
        // Optimized set version just has this
329
        $this->A[$i][$j] = $c;
330
    }
331
332
    //    function set()
333
334
    /**
335
     * identity.
336
     *
337
     * Generate an identity matrix.
338
     *
339
     * @param int $m Row dimension
340
     * @param int $n Column dimension
341
     *
342
     * @return Matrix Identity matrix
343
     */
344
    public function identity($m = null, $n = null)
345
    {
346
        return $this->diagonal($m, $n, 1);
347
    }
348
349
    /**
350
     * diagonal.
351
     *
352
     *    Generate a diagonal matrix
353
     *
354
     * @param int $m Row dimension
355
     * @param int $n Column dimension
356
     * @param mixed $c Diagonal value
357
     *
358
     * @return Matrix Diagonal matrix
359
     */
360
    public function diagonal($m = null, $n = null, $c = 1)
361
    {
362
        $R = new self($m, $n);
363
        for ($i = 0; $i < $m; ++$i) {
364
            $R->set($i, $i, $c);
365
        }
366
367
        return $R;
368
    }
369
370
    /**
371
     * getMatrixByRow.
372
     *
373
     *    Get a submatrix by row index/range
374
     *
375
     * @param int $i0 Initial row index
376
     * @param int $iF Final row index
377
     *
378
     * @return Matrix Submatrix
379
     */
380 View Code Duplication
    public function getMatrixByRow($i0 = null, $iF = null)
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...
381
    {
382
        if (is_int($i0)) {
383
            if (is_int($iF)) {
384
                return $this->getMatrix($i0, 0, $iF + 1, $this->n);
385
            }
386
387
            return $this->getMatrix($i0, 0, $i0 + 1, $this->n);
388
        }
389
390
        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
391
    }
392
393
    /**
394
     * getMatrixByCol.
395
     *
396
     *    Get a submatrix by column index/range
397
     *
398
     * @param int $j0 Initial column index
399
     * @param int $jF Final column index
400
     *
401
     * @return Matrix Submatrix
402
     */
403 View Code Duplication
    public function getMatrixByCol($j0 = null, $jF = null)
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...
404
    {
405
        if (is_int($j0)) {
406
            if (is_int($jF)) {
407
                return $this->getMatrix(0, $j0, $this->m, $jF + 1);
408
            }
409
410
            return $this->getMatrix(0, $j0, $this->m, $j0 + 1);
411
        }
412
413
        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
414
    }
415
416
    /**
417
     * transpose.
418
     *
419
     *    Tranpose matrix
420
     *
421
     * @return Matrix Transposed matrix
422
     */
423
    public function transpose()
424
    {
425
        $R = new self($this->n, $this->m);
426
        for ($i = 0; $i < $this->m; ++$i) {
427
            for ($j = 0; $j < $this->n; ++$j) {
428
                $R->set($j, $i, $this->A[$i][$j]);
429
            }
430
        }
431
432
        return $R;
433
    }
434
435
    //    function transpose()
436
437
    /**
438
     * trace.
439
     *
440
     *    Sum of diagonal elements
441
     *
442
     * @return float Sum of diagonal elements
443
     */
444
    public function trace()
445
    {
446
        $s = 0;
447
        $n = min($this->m, $this->n);
448
        for ($i = 0; $i < $n; ++$i) {
449
            $s += $this->A[$i][$i];
450
        }
451
452
        return $s;
453
    }
454
455
    /**
456
     * uminus.
457
     *
458
     *    Unary minus matrix -A
459
     *
460
     * @return Matrix Unary minus matrix
461
     */
462
    public function uminus()
463
    {
464
    }
465
466
    /**
467
     * plus.
468
     *
469
     *    A + B
470
     *
471
     * @param mixed $B Matrix/Array
472
     *
473
     * @return Matrix Sum
474
     */
475
    public function plus(...$args)
476
    {
477
        if (count($args) > 0) {
478
            $match = implode(',', array_map('gettype', $args));
479
480
            switch ($match) {
481
                case 'object':
482
                    if ($args[0] instanceof self) {
483
                        $M = $args[0];
484
                    } else {
485
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
486
                    }
487
488
                    break;
489
                case 'array':
490
                    $M = new self($args[0]);
491
492
                    break;
493
                default:
494
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
495
                    break;
496
            }
497
            $this->checkMatrixDimensions($M);
498
            for ($i = 0; $i < $this->m; ++$i) {
499
                for ($j = 0; $j < $this->n; ++$j) {
500
                    $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]);
501
                }
502
            }
503
504
            return $M;
505
        }
506
507
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
508
    }
509
510
    /**
511
     * plusEquals.
512
     *
513
     *    A = A + B
514
     *
515
     * @param mixed $B Matrix/Array
516
     *
517
     * @return Matrix Sum
518
     */
519 View Code Duplication
    public function plusEquals(...$args)
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...
520
    {
521
        if (count($args) > 0) {
522
            $match = implode(',', array_map('gettype', $args));
523
524
            switch ($match) {
525
                case 'object':
526
                    if ($args[0] instanceof self) {
527
                        $M = $args[0];
528
                    } else {
529
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
530
                    }
531
532
                    break;
533
                case 'array':
534
                    $M = new self($args[0]);
535
536
                    break;
537
                default:
538
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
539
                    break;
540
            }
541
            $this->checkMatrixDimensions($M);
542
            for ($i = 0; $i < $this->m; ++$i) {
543
                for ($j = 0; $j < $this->n; ++$j) {
544
                    $validValues = true;
545
                    $value = $M->get($i, $j);
546
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
547
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
548
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
549
                    }
550
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
551
                        $value = trim($value, '"');
552
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
553
                    }
554
                    if ($validValues) {
555
                        $this->A[$i][$j] += $value;
556
                    } else {
557
                        $this->A[$i][$j] = Functions::NAN();
558
                    }
559
                }
560
            }
561
562
            return $this;
563
        }
564
565
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
566
    }
567
568
    /**
569
     * minus.
570
     *
571
     *    A - B
572
     *
573
     * @param mixed $B Matrix/Array
574
     *
575
     * @return Matrix Sum
576
     */
577
    public function minus(...$args)
578
    {
579
        if (count($args) > 0) {
580
            $match = implode(',', array_map('gettype', $args));
581
582
            switch ($match) {
583
                case 'object':
584
                    if ($args[0] instanceof self) {
585
                        $M = $args[0];
586
                    } else {
587
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
588
                    }
589
590
                    break;
591
                case 'array':
592
                    $M = new self($args[0]);
593
594
                    break;
595
                default:
596
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
597
                    break;
598
            }
599
            $this->checkMatrixDimensions($M);
600
            for ($i = 0; $i < $this->m; ++$i) {
601
                for ($j = 0; $j < $this->n; ++$j) {
602
                    $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]);
603
                }
604
            }
605
606
            return $M;
607
        }
608
609
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
610
    }
611
612
    /**
613
     * minusEquals.
614
     *
615
     *    A = A - B
616
     *
617
     * @param mixed $B Matrix/Array
618
     *
619
     * @return Matrix Sum
620
     */
621 View Code Duplication
    public function minusEquals(...$args)
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...
622
    {
623
        if (count($args) > 0) {
624
            $match = implode(',', array_map('gettype', $args));
625
626
            switch ($match) {
627
                case 'object':
628
                    if ($args[0] instanceof self) {
629
                        $M = $args[0];
630
                    } else {
631
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
632
                    }
633
634
                    break;
635
                case 'array':
636
                    $M = new self($args[0]);
637
638
                    break;
639
                default:
640
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
641
                    break;
642
            }
643
            $this->checkMatrixDimensions($M);
644
            for ($i = 0; $i < $this->m; ++$i) {
645
                for ($j = 0; $j < $this->n; ++$j) {
646
                    $validValues = true;
647
                    $value = $M->get($i, $j);
648
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
649
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
650
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
651
                    }
652
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
653
                        $value = trim($value, '"');
654
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
655
                    }
656
                    if ($validValues) {
657
                        $this->A[$i][$j] -= $value;
658
                    } else {
659
                        $this->A[$i][$j] = Functions::NAN();
660
                    }
661
                }
662
            }
663
664
            return $this;
665
        }
666
667
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
668
    }
669
670
    /**
671
     * arrayTimes.
672
     *
673
     *    Element-by-element multiplication
674
     *    Cij = Aij * Bij
675
     *
676
     * @param mixed $B Matrix/Array
677
     *
678
     * @return Matrix Matrix Cij
679
     */
680
    public function arrayTimes(...$args)
681
    {
682
        if (count($args) > 0) {
683
            $match = implode(',', array_map('gettype', $args));
684
685
            switch ($match) {
686
                case 'object':
687
                    if ($args[0] instanceof self) {
688
                        $M = $args[0];
689
                    } else {
690
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
691
                    }
692
693
                    break;
694
                case 'array':
695
                    $M = new self($args[0]);
696
697
                    break;
698
                default:
699
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
700
                    break;
701
            }
702
            $this->checkMatrixDimensions($M);
703
            for ($i = 0; $i < $this->m; ++$i) {
704
                for ($j = 0; $j < $this->n; ++$j) {
705
                    $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]);
706
                }
707
            }
708
709
            return $M;
710
        }
711
712
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
713
    }
714
715
    /**
716
     * arrayTimesEquals.
717
     *
718
     *    Element-by-element multiplication
719
     *    Aij = Aij * Bij
720
     *
721
     * @param mixed $B Matrix/Array
722
     *
723
     * @return Matrix Matrix Aij
724
     */
725 View Code Duplication
    public function arrayTimesEquals(...$args)
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...
726
    {
727
        if (count($args) > 0) {
728
            $match = implode(',', array_map('gettype', $args));
729
730
            switch ($match) {
731
                case 'object':
732
                    if ($args[0] instanceof self) {
733
                        $M = $args[0];
734
                    } else {
735
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
736
                    }
737
738
                    break;
739
                case 'array':
740
                    $M = new self($args[0]);
741
742
                    break;
743
                default:
744
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
745
                    break;
746
            }
747
            $this->checkMatrixDimensions($M);
748
            for ($i = 0; $i < $this->m; ++$i) {
749
                for ($j = 0; $j < $this->n; ++$j) {
750
                    $validValues = true;
751
                    $value = $M->get($i, $j);
752
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
753
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
754
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
755
                    }
756
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
757
                        $value = trim($value, '"');
758
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
759
                    }
760
                    if ($validValues) {
761
                        $this->A[$i][$j] *= $value;
762
                    } else {
763
                        $this->A[$i][$j] = Functions::NAN();
764
                    }
765
                }
766
            }
767
768
            return $this;
769
        }
770
771
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
772
    }
773
774
    /**
775
     * arrayRightDivide.
776
     *
777
     *    Element-by-element right division
778
     *    A / B
779
     *
780
     * @param Matrix $B Matrix B
781
     *
782
     * @return Matrix Division result
783
     */
784
    public function arrayRightDivide(...$args)
785
    {
786
        if (count($args) > 0) {
787
            $match = implode(',', array_map('gettype', $args));
788
789
            switch ($match) {
790
                case 'object':
791
                    if ($args[0] instanceof self) {
792
                        $M = $args[0];
793
                    } else {
794
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
795
                    }
796
797
                    break;
798
                case 'array':
799
                    $M = new self($args[0]);
800
801
                    break;
802
                default:
803
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
804
                    break;
805
            }
806
            $this->checkMatrixDimensions($M);
807
            for ($i = 0; $i < $this->m; ++$i) {
808
                for ($j = 0; $j < $this->n; ++$j) {
809
                    $validValues = true;
810
                    $value = $M->get($i, $j);
811
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
812
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
813
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
814
                    }
815
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
816
                        $value = trim($value, '"');
817
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
818
                    }
819
                    if ($validValues) {
820
                        if ($value == 0) {
821
                            //    Trap for Divide by Zero error
822
                            $M->set($i, $j, '#DIV/0!');
823
                        } else {
824
                            $M->set($i, $j, $this->A[$i][$j] / $value);
825
                        }
826
                    } else {
827
                        $M->set($i, $j, Functions::NAN());
828
                    }
829
                }
830
            }
831
832
            return $M;
833
        }
834
835
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
836
    }
837
838
    /**
839
     * arrayRightDivideEquals.
840
     *
841
     *    Element-by-element right division
842
     *    Aij = Aij / Bij
843
     *
844
     * @param mixed $B Matrix/Array
845
     *
846
     * @return Matrix Matrix Aij
847
     */
848 View Code Duplication
    public function arrayRightDivideEquals(...$args)
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...
849
    {
850
        if (count($args) > 0) {
851
            $match = implode(',', array_map('gettype', $args));
852
853
            switch ($match) {
854
                case 'object':
855
                    if ($args[0] instanceof self) {
856
                        $M = $args[0];
857
                    } else {
858
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
859
                    }
860
861
                    break;
862
                case 'array':
863
                    $M = new self($args[0]);
864
865
                    break;
866
                default:
867
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
868
                    break;
869
            }
870
            $this->checkMatrixDimensions($M);
871
            for ($i = 0; $i < $this->m; ++$i) {
872
                for ($j = 0; $j < $this->n; ++$j) {
873
                    $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j);
874
                }
875
            }
876
877
            return $M;
878
        }
879
880
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
881
    }
882
883
    /**
884
     * arrayLeftDivide.
885
     *
886
     *    Element-by-element Left division
887
     *    A / B
888
     *
889
     * @param Matrix $B Matrix B
890
     *
891
     * @return Matrix Division result
892
     */
893 View Code Duplication
    public function arrayLeftDivide(...$args)
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...
894
    {
895
        if (count($args) > 0) {
896
            $match = implode(',', array_map('gettype', $args));
897
898
            switch ($match) {
899
                case 'object':
900
                    if ($args[0] instanceof self) {
901
                        $M = $args[0];
902
                    } else {
903
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
904
                    }
905
906
                    break;
907
                case 'array':
908
                    $M = new self($args[0]);
909
910
                    break;
911
                default:
912
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
913
                    break;
914
            }
915
            $this->checkMatrixDimensions($M);
916
            for ($i = 0; $i < $this->m; ++$i) {
917
                for ($j = 0; $j < $this->n; ++$j) {
918
                    $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]);
919
                }
920
            }
921
922
            return $M;
923
        }
924
925
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
926
    }
927
928
    /**
929
     * arrayLeftDivideEquals.
930
     *
931
     *    Element-by-element Left division
932
     *    Aij = Aij / Bij
933
     *
934
     * @param mixed $B Matrix/Array
935
     *
936
     * @return Matrix Matrix Aij
937
     */
938 View Code Duplication
    public function arrayLeftDivideEquals(...$args)
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...
939
    {
940
        if (count($args) > 0) {
941
            $match = implode(',', array_map('gettype', $args));
942
943
            switch ($match) {
944
                case 'object':
945
                    if ($args[0] instanceof self) {
946
                        $M = $args[0];
947
                    } else {
948
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
949
                    }
950
951
                    break;
952
                case 'array':
953
                    $M = new self($args[0]);
954
955
                    break;
956
                default:
957
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
958
                    break;
959
            }
960
            $this->checkMatrixDimensions($M);
961
            for ($i = 0; $i < $this->m; ++$i) {
962
                for ($j = 0; $j < $this->n; ++$j) {
963
                    $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j];
964
                }
965
            }
966
967
            return $M;
968
        }
969
970
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
971
    }
972
973
    /**
974
     * times.
975
     *
976
     *    Matrix multiplication
977
     *
978
     * @param mixed $n Matrix/Array/Scalar
979
     *
980
     * @return Matrix Product
981
     */
982
    public function times(...$args)
983
    {
984
        if (count() > 0) {
0 ignored issues
show
Bug introduced by
The call to count() has too few arguments starting with var. ( Ignorable by Annotation )

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

984
        if (/** @scrutinizer ignore-call */ count() > 0) {

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...
985
            $match = implode(',', array_map('gettype', $args));
986
987
            switch ($match) {
988
                case 'object':
989
                    if ($args[0] instanceof self) {
990
                        $B = $args[0];
991
                    } else {
992
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
993
                    }
994
                    if ($this->n == $B->m) {
995
                        $C = new self($this->m, $B->n);
996
                        for ($j = 0; $j < $B->n; ++$j) {
997
                            for ($k = 0; $k < $this->n; ++$k) {
998
                                $Bcolj[$k] = $B->A[$k][$j];
999
                            }
1000 View Code Duplication
                            for ($i = 0; $i < $this->m; ++$i) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1001
                                $Arowi = $this->A[$i];
1002
                                $s = 0;
1003
                                for ($k = 0; $k < $this->n; ++$k) {
1004
                                    $s += $Arowi[$k] * $Bcolj[$k];
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $Bcolj does not seem to be defined for all execution paths leading up to this point.
Loading history...
1005
                                }
1006
                                $C->A[$i][$j] = $s;
1007
                            }
1008
                        }
1009
1010
                        return $C;
1011
                    }
1012
1013
                    throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
1014
                case 'array':
1015
                    $B = new self($args[0]);
1016
                    if ($this->n == $B->m) {
1017
                        $C = new self($this->m, $B->n);
1018
                        for ($i = 0; $i < $C->m; ++$i) {
1019 View Code Duplication
                            for ($j = 0; $j < $C->n; ++$j) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1020
                                $s = '0';
1021
                                for ($k = 0; $k < $C->n; ++$k) {
1022
                                    $s += $this->A[$i][$k] * $B->A[$k][$j];
1023
                                }
1024
                                $C->A[$i][$j] = $s;
1025
                            }
1026
                        }
1027
1028
                        return $C;
1029
                    }
1030
1031
                    throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
1032 View Code Duplication
                case 'integer':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1033
                    $C = new self($this->A);
1034
                    for ($i = 0; $i < $C->m; ++$i) {
1035
                        for ($j = 0; $j < $C->n; ++$j) {
1036
                            $C->A[$i][$j] *= $args[0];
1037
                        }
1038
                    }
1039
1040
                    return $C;
1041
                case 'double':
1042
                    $C = new self($this->m, $this->n);
1043
                    for ($i = 0; $i < $C->m; ++$i) {
1044
                        for ($j = 0; $j < $C->n; ++$j) {
1045
                            $C->A[$i][$j] = $args[0] * $this->A[$i][$j];
1046
                        }
1047
                    }
1048
1049
                    return $C;
1050 View Code Duplication
                case 'float':
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
1051
                    $C = new self($this->A);
1052
                    for ($i = 0; $i < $C->m; ++$i) {
1053
                        for ($j = 0; $j < $C->n; ++$j) {
1054
                            $C->A[$i][$j] *= $args[0];
1055
                        }
1056
                    }
1057
1058
                    return $C;
1059
                default:
1060
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1061
            }
1062
        } else {
1063
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1064
        }
1065
    }
1066
1067
    /**
1068
     * power.
1069
     *
1070
     *    A = A ^ B
1071
     *
1072
     * @param mixed $B Matrix/Array
1073
     *
1074
     * @return Matrix Sum
1075
     */
1076
    public function power(...$args)
1077
    {
1078
        if (count() > 0) {
0 ignored issues
show
Bug introduced by
The call to count() has too few arguments starting with var. ( Ignorable by Annotation )

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

1078
        if (/** @scrutinizer ignore-call */ count() > 0) {

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...
1079
            $match = implode(',', array_map('gettype', $args));
1080
1081
            switch ($match) {
1082
                case 'object':
1083
                    if ($args[0] instanceof self) {
1084
                        $M = $args[0];
1085
                    } else {
1086
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
1087
                    }
1088
1089
                    break;
1090
                case 'array':
1091
                    $M = new self($args[0]);
1092
1093
                    break;
1094
                default:
1095
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1096
                    break;
1097
            }
1098
            $this->checkMatrixDimensions($M);
1099
            for ($i = 0; $i < $this->m; ++$i) {
1100
                for ($j = 0; $j < $this->n; ++$j) {
1101
                    $validValues = true;
1102
                    $value = $M->get($i, $j);
1103
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
1104
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
1105
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
1106
                    }
1107
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
1108
                        $value = trim($value, '"');
1109
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
1110
                    }
1111
                    if ($validValues) {
1112
                        $this->A[$i][$j] = pow($this->A[$i][$j], $value);
1 ignored issue
show
Bug introduced by
It seems like $value can also be of type string; however, parameter $exp of pow() does only seem to accept integer|double, maybe add an additional type check? ( Ignorable by Annotation )

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

1112
                        $this->A[$i][$j] = pow($this->A[$i][$j], /** @scrutinizer ignore-type */ $value);
Loading history...
1113
                    } else {
1114
                        $this->A[$i][$j] = Functions::NAN();
1115
                    }
1116
                }
1117
            }
1118
1119
            return $this;
1120
        }
1121
1122
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1123
    }
1124
1125
    /**
1126
     * concat.
1127
     *
1128
     *    A = A & B
1129
     *
1130
     * @param mixed $B Matrix/Array
1131
     *
1132
     * @return Matrix Sum
1133
     */
1134
    public function concat(...$args)
1135
    {
1136
        if (count($args) > 0) {
1137
            $match = implode(',', array_map('gettype', $args));
1138
1139
            switch ($match) {
1140
                case 'object':
1141
                    if ($args[0] instanceof self) {
1142
                        $M = $args[0];
1143
                    } else {
1144
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
1145
                    }
1146
1147
                    break;
1148
                case 'array':
1149
                    $M = new self($args[0]);
1150
1151
                    break;
1152
                default:
1153
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1154
                    break;
1155
            }
1156
            $this->checkMatrixDimensions($M);
1157
            for ($i = 0; $i < $this->m; ++$i) {
1158
                for ($j = 0; $j < $this->n; ++$j) {
1159
                    $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"');
1160
                }
1161
            }
1162
1163
            return $this;
1164
        }
1165
1166
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1167
    }
1168
1169
    /**
1170
     * Solve A*X = B.
1171
     *
1172
     * @param Matrix $B Right hand side
1173
     *
1174
     * @return Matrix ... Solution if A is square, least squares solution otherwise
1175
     */
1176
    public function solve($B)
1177
    {
1178
        if ($this->m == $this->n) {
1179
            $LU = new LUDecomposition($this);
1180
1181
            return $LU->solve($B);
1182
        }
1183
        $QR = new QRDecomposition($this);
1184
1185
        return $QR->solve($B);
1186
    }
1187
1188
    /**
1189
     * Matrix inverse or pseudoinverse.
1190
     *
1191
     * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise.
1192
     */
1193
    public function inverse()
1194
    {
1195
        return $this->solve($this->identity($this->m, $this->m));
1196
    }
1197
1198
    /**
1199
     * det.
1200
     *
1201
     *    Calculate determinant
1202
     *
1203
     * @return float Determinant
1204
     */
1205
    public function det()
1206
    {
1207
        $L = new LUDecomposition($this);
1208
1209
        return $L->det();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $L->det() returns the type array which is incompatible with the documented return type double.
Loading history...
1210
    }
1211
}
1212