Failed Conditions
Push — master ( 42761f...d02352 )
by Adrien
16:26 queued 08:32
created

Matrix   F

Complexity

Total Complexity 215

Size/Duplication

Total Lines 1169
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 543
dl 0
loc 1169
rs 2
c 0
b 0
f 0
wmc 215

30 Methods

Rating   Name   Duplication   Size   Complexity  
A getRowDimension() 0 3 1
A get() 0 3 1
A getArray() 0 3 1
A getColumnDimension() 0 3 1
B __construct() 0 53 10
A getMatrixByRow() 0 11 3
A getMatrixByCol() 0 11 3
A checkMatrixDimensions() 0 11 4
A trace() 0 9 2
A diagonal() 0 8 2
A identity() 0 3 1
A set() 0 4 1
A transpose() 0 10 3
D getMatrix() 0 128 35
B arrayLeftDivide() 0 34 7
A det() 0 5 1
B arrayRightDivideEquals() 0 34 7
C plusEquals() 0 48 14
C arrayRightDivide() 0 53 15
B arrayLeftDivideEquals() 0 34 7
B minus() 0 34 7
C minusEquals() 0 48 14
B plus() 0 34 7
A inverse() 0 3 1
D times() 0 83 23
C power() 0 48 14
B concat() 0 34 7
B arrayTimes() 0 34 7
C arrayTimesEquals() 0 48 14
A solve() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like Matrix often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Matrix, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace PhpOffice\PhpSpreadsheet\Shared\JAMA;
4
5
use PhpOffice\PhpSpreadsheet\Calculation\Exception as CalculationException;
6
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
7
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
8
9
/**
10
 * Matrix class.
11
 *
12
 * @author Paul Meagher
13
 * @author Michael Bommarito
14
 * @author Lukasz Karapuda
15
 * @author Bartek Matosiuk
16
 *
17
 * @version 1.8
18
 *
19
 * @see https://math.nist.gov/javanumerics/jama/
20
 */
21
class Matrix
22
{
23
    const POLYMORPHIC_ARGUMENT_EXCEPTION = 'Invalid argument pattern for polymorphic function.';
24
    const ARGUMENT_TYPE_EXCEPTION = 'Invalid argument type.';
25
    const ARGUMENT_BOUNDS_EXCEPTION = 'Invalid argument range.';
26
    const MATRIX_DIMENSION_EXCEPTION = 'Matrix dimensions are not equal.';
27
    const ARRAY_LENGTH_EXCEPTION = 'Array length must be a multiple of m.';
28
    const MATRIX_SPD_EXCEPTION = 'Can only perform operation on symmetric positive definite matrix.';
29
30
    /**
31
     * Matrix storage.
32
     *
33
     * @var array
34
     */
35
    public $A = [];
36
37
    /**
38
     * Matrix row dimension.
39
     *
40
     * @var int
41
     */
42
    private $m;
43
44
    /**
45
     * Matrix column dimension.
46
     *
47
     * @var int
48
     */
49
    private $n;
50
51
    /**
52
     * Polymorphic constructor.
53
     *
54
     * 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.
55
     */
56
    public function __construct(...$args)
57
    {
58
        if (count($args) > 0) {
59
            $match = implode(',', array_map('gettype', $args));
60
61
            switch ($match) {
62
                //Rectangular matrix - m x n initialized from 2D array
63
                case 'array':
64
                    $this->m = count($args[0]);
65
                    $this->n = count($args[0][0]);
66
                    $this->A = $args[0];
67
68
                    break;
69
                //Square matrix - n x n
70
                case 'integer':
71
                    $this->m = $args[0];
72
                    $this->n = $args[0];
73
                    $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
74
75
                    break;
76
                //Rectangular matrix - m x n
77
                case 'integer,integer':
78
                    $this->m = $args[0];
79
                    $this->n = $args[1];
80
                    $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
81
82
                    break;
83
                //Rectangular matrix - m x n initialized from packed array
84
                case 'array,integer':
85
                    $this->m = $args[1];
86
                    if ($this->m != 0) {
87
                        $this->n = count($args[0]) / $this->m;
88
                    } else {
89
                        $this->n = 0;
90
                    }
91
                    if (($this->m * $this->n) == count($args[0])) {
92
                        for ($i = 0; $i < $this->m; ++$i) {
93
                            for ($j = 0; $j < $this->n; ++$j) {
94
                                $this->A[$i][$j] = $args[0][$i + $j * $this->m];
95
                            }
96
                        }
97
                    } else {
98
                        throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION);
99
                    }
100
101
                    break;
102
                default:
103
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
104
105
                    break;
106
            }
107
        } else {
108
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
109
        }
110
    }
111
112
    /**
113
     * getArray.
114
     *
115
     * @return array Matrix array
116
     */
117
    public function getArray()
118
    {
119
        return $this->A;
120
    }
121
122
    /**
123
     * getRowDimension.
124
     *
125
     * @return int Row dimension
126
     */
127
    public function getRowDimension()
128
    {
129
        return $this->m;
130
    }
131
132
    /**
133
     * getColumnDimension.
134
     *
135
     * @return int Column dimension
136
     */
137
    public function getColumnDimension()
138
    {
139
        return $this->n;
140
    }
141
142
    /**
143
     * get.
144
     *
145
     * Get the i,j-th element of the matrix.
146
     *
147
     * @param int $i Row position
148
     * @param int $j Column position
149
     *
150
     * @return mixed Element (int/float/double)
151
     */
152
    public function get($i = null, $j = null)
153
    {
154
        return $this->A[$i][$j];
155
    }
156
157
    /**
158
     * getMatrix.
159
     *
160
     *    Get a submatrix
161
     *
162
     * @return Matrix Submatrix
163
     */
164
    public function getMatrix(...$args)
165
    {
166
        if (count($args) > 0) {
167
            $match = implode(',', array_map('gettype', $args));
168
169
            switch ($match) {
170
                //A($i0...; $j0...)
171
                case 'integer,integer':
172
                    [$i0, $j0] = $args;
173
                    if ($i0 >= 0) {
174
                        $m = $this->m - $i0;
175
                    } else {
176
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
177
                    }
178
                    if ($j0 >= 0) {
179
                        $n = $this->n - $j0;
180
                    } else {
181
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
182
                    }
183
                    $R = new self($m, $n);
184
                    for ($i = $i0; $i < $this->m; ++$i) {
185
                        for ($j = $j0; $j < $this->n; ++$j) {
186
                            $R->set($i, $j, $this->A[$i][$j]);
187
                        }
188
                    }
189
190
                    return $R;
191
192
                    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...
193
                //A($i0...$iF; $j0...$jF)
194
                case 'integer,integer,integer,integer':
195
                    [$i0, $iF, $j0, $jF] = $args;
196
                    if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
197
                        $m = $iF - $i0;
198
                    } else {
199
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
200
                    }
201
                    if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
202
                        $n = $jF - $j0;
203
                    } else {
204
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
205
                    }
206
                    $R = new self($m + 1, $n + 1);
207
                    for ($i = $i0; $i <= $iF; ++$i) {
208
                        for ($j = $j0; $j <= $jF; ++$j) {
209
                            $R->set($i - $i0, $j - $j0, $this->A[$i][$j]);
210
                        }
211
                    }
212
213
                    return $R;
214
215
                    break;
216
                //$R = array of row indices; $C = array of column indices
217
                case 'array,array':
218
                    [$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, $j, $this->A[$RL[$i]][$CL[$j]]);
233
                        }
234
                    }
235
236
                    return $R;
237
238
                    break;
239
                //A($i0...$iF); $CL = array of column indices
240
                case 'integer,integer,array':
241
                    [$i0, $iF, $CL] = $args;
242
                    if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
243
                        $m = $iF - $i0;
244
                    } else {
245
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
246
                    }
247
                    if (count($CL) > 0) {
248
                        $n = count($CL);
249
                    } else {
250
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
251
                    }
252
                    $R = new self($m, $n);
253
                    for ($i = $i0; $i < $iF; ++$i) {
254
                        for ($j = 0; $j < $n; ++$j) {
255
                            $R->set($i - $i0, $j, $this->A[$i][$CL[$j]]);
256
                        }
257
                    }
258
259
                    return $R;
260
261
                    break;
262
                //$RL = array of row indices
263
                case 'array,integer,integer':
264
                    [$RL, $j0, $jF] = $args;
265
                    if (count($RL) > 0) {
266
                        $m = count($RL);
267
                    } else {
268
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
269
                    }
270
                    if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
271
                        $n = $jF - $j0;
272
                    } else {
273
                        throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
274
                    }
275
                    $R = new self($m, $n + 1);
276
                    for ($i = 0; $i < $m; ++$i) {
277
                        for ($j = $j0; $j <= $jF; ++$j) {
278
                            $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]);
279
                        }
280
                    }
281
282
                    return $R;
283
284
                    break;
285
                default:
286
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
287
288
                    break;
289
            }
290
        } else {
291
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
292
        }
293
    }
294
295
    /**
296
     * checkMatrixDimensions.
297
     *
298
     *    Is matrix B the same size?
299
     *
300
     * @param Matrix $B Matrix B
301
     *
302
     * @return bool
303
     */
304
    public function checkMatrixDimensions($B = null)
305
    {
306
        if ($B instanceof self) {
307
            if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) {
308
                return true;
309
            }
310
311
            throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
312
        }
313
314
        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
315
    }
316
317
    //    function checkMatrixDimensions()
318
319
    /**
320
     * set.
321
     *
322
     * Set the i,j-th element of the matrix.
323
     *
324
     * @param int $i Row position
325
     * @param int $j Column position
326
     * @param mixed $c Int/float/double value
327
     *
328
     * @return mixed Element (int/float/double)
329
     */
330
    public function set($i = null, $j = null, $c = null)
331
    {
332
        // Optimized set version just has this
333
        $this->A[$i][$j] = $c;
334
    }
335
336
    //    function set()
337
338
    /**
339
     * identity.
340
     *
341
     * Generate an identity matrix.
342
     *
343
     * @param int $m Row dimension
344
     * @param int $n Column dimension
345
     *
346
     * @return Matrix Identity matrix
347
     */
348
    public function identity($m = null, $n = null)
349
    {
350
        return $this->diagonal($m, $n, 1);
351
    }
352
353
    /**
354
     * diagonal.
355
     *
356
     *    Generate a diagonal matrix
357
     *
358
     * @param int $m Row dimension
359
     * @param int $n Column dimension
360
     * @param mixed $c Diagonal value
361
     *
362
     * @return Matrix Diagonal matrix
363
     */
364
    public function diagonal($m = null, $n = null, $c = 1)
365
    {
366
        $R = new self($m, $n);
367
        for ($i = 0; $i < $m; ++$i) {
368
            $R->set($i, $i, $c);
369
        }
370
371
        return $R;
372
    }
373
374
    /**
375
     * getMatrixByRow.
376
     *
377
     *    Get a submatrix by row index/range
378
     *
379
     * @param int $i0 Initial row index
380
     * @param int $iF Final row index
381
     *
382
     * @return Matrix Submatrix
383
     */
384
    public function getMatrixByRow($i0 = null, $iF = null)
385
    {
386
        if (is_int($i0)) {
387
            if (is_int($iF)) {
388
                return $this->getMatrix($i0, 0, $iF + 1, $this->n);
389
            }
390
391
            return $this->getMatrix($i0, 0, $i0 + 1, $this->n);
392
        }
393
394
        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
395
    }
396
397
    /**
398
     * getMatrixByCol.
399
     *
400
     *    Get a submatrix by column index/range
401
     *
402
     * @param int $j0 Initial column index
403
     * @param int $jF Final column index
404
     *
405
     * @return Matrix Submatrix
406
     */
407
    public function getMatrixByCol($j0 = null, $jF = null)
408
    {
409
        if (is_int($j0)) {
410
            if (is_int($jF)) {
411
                return $this->getMatrix(0, $j0, $this->m, $jF + 1);
412
            }
413
414
            return $this->getMatrix(0, $j0, $this->m, $j0 + 1);
415
        }
416
417
        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
418
    }
419
420
    /**
421
     * transpose.
422
     *
423
     *    Tranpose matrix
424
     *
425
     * @return Matrix Transposed matrix
426
     */
427
    public function transpose()
428
    {
429
        $R = new self($this->n, $this->m);
430
        for ($i = 0; $i < $this->m; ++$i) {
431
            for ($j = 0; $j < $this->n; ++$j) {
432
                $R->set($j, $i, $this->A[$i][$j]);
433
            }
434
        }
435
436
        return $R;
437
    }
438
439
    //    function transpose()
440
441
    /**
442
     * trace.
443
     *
444
     *    Sum of diagonal elements
445
     *
446
     * @return float Sum of diagonal elements
447
     */
448
    public function trace()
449
    {
450
        $s = 0;
451
        $n = min($this->m, $this->n);
452
        for ($i = 0; $i < $n; ++$i) {
453
            $s += $this->A[$i][$i];
454
        }
455
456
        return $s;
457
    }
458
459
    /**
460
     * plus.
461
     *
462
     *    A + B
463
     *
464
     * @return Matrix Sum
465
     */
466
    public function plus(...$args)
467
    {
468
        if (count($args) > 0) {
469
            $match = implode(',', array_map('gettype', $args));
470
471
            switch ($match) {
472
                case 'object':
473
                    if ($args[0] instanceof self) {
474
                        $M = $args[0];
475
                    } else {
476
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
477
                    }
478
479
                    break;
480
                case 'array':
481
                    $M = new self($args[0]);
482
483
                    break;
484
                default:
485
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
486
487
                    break;
488
            }
489
            $this->checkMatrixDimensions($M);
490
            for ($i = 0; $i < $this->m; ++$i) {
491
                for ($j = 0; $j < $this->n; ++$j) {
492
                    $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]);
493
                }
494
            }
495
496
            return $M;
497
        }
498
499
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
500
    }
501
502
    /**
503
     * plusEquals.
504
     *
505
     *    A = A + B
506
     *
507
     * @return $this
508
     */
509
    public function plusEquals(...$args)
510
    {
511
        if (count($args) > 0) {
512
            $match = implode(',', array_map('gettype', $args));
513
514
            switch ($match) {
515
                case 'object':
516
                    if ($args[0] instanceof self) {
517
                        $M = $args[0];
518
                    } else {
519
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
520
                    }
521
522
                    break;
523
                case 'array':
524
                    $M = new self($args[0]);
525
526
                    break;
527
                default:
528
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
529
530
                    break;
531
            }
532
            $this->checkMatrixDimensions($M);
533
            for ($i = 0; $i < $this->m; ++$i) {
534
                for ($j = 0; $j < $this->n; ++$j) {
535
                    $validValues = true;
536
                    $value = $M->get($i, $j);
537
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
538
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
539
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
540
                    }
541
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
542
                        $value = trim($value, '"');
543
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
544
                    }
545
                    if ($validValues) {
546
                        $this->A[$i][$j] += $value;
547
                    } else {
548
                        $this->A[$i][$j] = Functions::NAN();
549
                    }
550
                }
551
            }
552
553
            return $this;
554
        }
555
556
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
557
    }
558
559
    /**
560
     * minus.
561
     *
562
     *    A - B
563
     *
564
     * @return Matrix Sum
565
     */
566
    public function minus(...$args)
567
    {
568
        if (count($args) > 0) {
569
            $match = implode(',', array_map('gettype', $args));
570
571
            switch ($match) {
572
                case 'object':
573
                    if ($args[0] instanceof self) {
574
                        $M = $args[0];
575
                    } else {
576
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
577
                    }
578
579
                    break;
580
                case 'array':
581
                    $M = new self($args[0]);
582
583
                    break;
584
                default:
585
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
586
587
                    break;
588
            }
589
            $this->checkMatrixDimensions($M);
590
            for ($i = 0; $i < $this->m; ++$i) {
591
                for ($j = 0; $j < $this->n; ++$j) {
592
                    $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]);
593
                }
594
            }
595
596
            return $M;
597
        }
598
599
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
600
    }
601
602
    /**
603
     * minusEquals.
604
     *
605
     *    A = A - B
606
     *
607
     * @return $this
608
     */
609
    public function minusEquals(...$args)
610
    {
611
        if (count($args) > 0) {
612
            $match = implode(',', array_map('gettype', $args));
613
614
            switch ($match) {
615
                case 'object':
616
                    if ($args[0] instanceof self) {
617
                        $M = $args[0];
618
                    } else {
619
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
620
                    }
621
622
                    break;
623
                case 'array':
624
                    $M = new self($args[0]);
625
626
                    break;
627
                default:
628
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
629
630
                    break;
631
            }
632
            $this->checkMatrixDimensions($M);
633
            for ($i = 0; $i < $this->m; ++$i) {
634
                for ($j = 0; $j < $this->n; ++$j) {
635
                    $validValues = true;
636
                    $value = $M->get($i, $j);
637
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
638
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
639
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
640
                    }
641
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
642
                        $value = trim($value, '"');
643
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
644
                    }
645
                    if ($validValues) {
646
                        $this->A[$i][$j] -= $value;
647
                    } else {
648
                        $this->A[$i][$j] = Functions::NAN();
649
                    }
650
                }
651
            }
652
653
            return $this;
654
        }
655
656
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
657
    }
658
659
    /**
660
     * arrayTimes.
661
     *
662
     *    Element-by-element multiplication
663
     *    Cij = Aij * Bij
664
     *
665
     * @return Matrix Matrix Cij
666
     */
667
    public function arrayTimes(...$args)
668
    {
669
        if (count($args) > 0) {
670
            $match = implode(',', array_map('gettype', $args));
671
672
            switch ($match) {
673
                case 'object':
674
                    if ($args[0] instanceof self) {
675
                        $M = $args[0];
676
                    } else {
677
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
678
                    }
679
680
                    break;
681
                case 'array':
682
                    $M = new self($args[0]);
683
684
                    break;
685
                default:
686
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
687
688
                    break;
689
            }
690
            $this->checkMatrixDimensions($M);
691
            for ($i = 0; $i < $this->m; ++$i) {
692
                for ($j = 0; $j < $this->n; ++$j) {
693
                    $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]);
694
                }
695
            }
696
697
            return $M;
698
        }
699
700
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
701
    }
702
703
    /**
704
     * arrayTimesEquals.
705
     *
706
     *    Element-by-element multiplication
707
     *    Aij = Aij * Bij
708
     *
709
     * @return $this
710
     */
711
    public function arrayTimesEquals(...$args)
712
    {
713
        if (count($args) > 0) {
714
            $match = implode(',', array_map('gettype', $args));
715
716
            switch ($match) {
717
                case 'object':
718
                    if ($args[0] instanceof self) {
719
                        $M = $args[0];
720
                    } else {
721
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
722
                    }
723
724
                    break;
725
                case 'array':
726
                    $M = new self($args[0]);
727
728
                    break;
729
                default:
730
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
731
732
                    break;
733
            }
734
            $this->checkMatrixDimensions($M);
735
            for ($i = 0; $i < $this->m; ++$i) {
736
                for ($j = 0; $j < $this->n; ++$j) {
737
                    $validValues = true;
738
                    $value = $M->get($i, $j);
739
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
740
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
741
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
742
                    }
743
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
744
                        $value = trim($value, '"');
745
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
746
                    }
747
                    if ($validValues) {
748
                        $this->A[$i][$j] *= $value;
749
                    } else {
750
                        $this->A[$i][$j] = Functions::NAN();
751
                    }
752
                }
753
            }
754
755
            return $this;
756
        }
757
758
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
759
    }
760
761
    /**
762
     * arrayRightDivide.
763
     *
764
     *    Element-by-element right division
765
     *    A / B
766
     *
767
     * @return Matrix Division result
768
     */
769
    public function arrayRightDivide(...$args)
770
    {
771
        if (count($args) > 0) {
772
            $match = implode(',', array_map('gettype', $args));
773
774
            switch ($match) {
775
                case 'object':
776
                    if ($args[0] instanceof self) {
777
                        $M = $args[0];
778
                    } else {
779
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
780
                    }
781
782
                    break;
783
                case 'array':
784
                    $M = new self($args[0]);
785
786
                    break;
787
                default:
788
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
789
790
                    break;
791
            }
792
            $this->checkMatrixDimensions($M);
793
            for ($i = 0; $i < $this->m; ++$i) {
794
                for ($j = 0; $j < $this->n; ++$j) {
795
                    $validValues = true;
796
                    $value = $M->get($i, $j);
797
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
798
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
799
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
800
                    }
801
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
802
                        $value = trim($value, '"');
803
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
804
                    }
805
                    if ($validValues) {
806
                        if ($value == 0) {
807
                            //    Trap for Divide by Zero error
808
                            $M->set($i, $j, '#DIV/0!');
809
                        } else {
810
                            $M->set($i, $j, $this->A[$i][$j] / $value);
811
                        }
812
                    } else {
813
                        $M->set($i, $j, Functions::NAN());
814
                    }
815
                }
816
            }
817
818
            return $M;
819
        }
820
821
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
822
    }
823
824
    /**
825
     * arrayRightDivideEquals.
826
     *
827
     *    Element-by-element right division
828
     *    Aij = Aij / Bij
829
     *
830
     * @return Matrix Matrix Aij
831
     */
832
    public function arrayRightDivideEquals(...$args)
833
    {
834
        if (count($args) > 0) {
835
            $match = implode(',', array_map('gettype', $args));
836
837
            switch ($match) {
838
                case 'object':
839
                    if ($args[0] instanceof self) {
840
                        $M = $args[0];
841
                    } else {
842
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
843
                    }
844
845
                    break;
846
                case 'array':
847
                    $M = new self($args[0]);
848
849
                    break;
850
                default:
851
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
852
853
                    break;
854
            }
855
            $this->checkMatrixDimensions($M);
856
            for ($i = 0; $i < $this->m; ++$i) {
857
                for ($j = 0; $j < $this->n; ++$j) {
858
                    $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j);
859
                }
860
            }
861
862
            return $M;
863
        }
864
865
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
866
    }
867
868
    /**
869
     * arrayLeftDivide.
870
     *
871
     *    Element-by-element Left division
872
     *    A / B
873
     *
874
     * @return Matrix Division result
875
     */
876
    public function arrayLeftDivide(...$args)
877
    {
878
        if (count($args) > 0) {
879
            $match = implode(',', array_map('gettype', $args));
880
881
            switch ($match) {
882
                case 'object':
883
                    if ($args[0] instanceof self) {
884
                        $M = $args[0];
885
                    } else {
886
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
887
                    }
888
889
                    break;
890
                case 'array':
891
                    $M = new self($args[0]);
892
893
                    break;
894
                default:
895
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
896
897
                    break;
898
            }
899
            $this->checkMatrixDimensions($M);
900
            for ($i = 0; $i < $this->m; ++$i) {
901
                for ($j = 0; $j < $this->n; ++$j) {
902
                    $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]);
903
                }
904
            }
905
906
            return $M;
907
        }
908
909
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
910
    }
911
912
    /**
913
     * arrayLeftDivideEquals.
914
     *
915
     *    Element-by-element Left division
916
     *    Aij = Aij / Bij
917
     *
918
     * @return Matrix Matrix Aij
919
     */
920
    public function arrayLeftDivideEquals(...$args)
921
    {
922
        if (count($args) > 0) {
923
            $match = implode(',', array_map('gettype', $args));
924
925
            switch ($match) {
926
                case 'object':
927
                    if ($args[0] instanceof self) {
928
                        $M = $args[0];
929
                    } else {
930
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
931
                    }
932
933
                    break;
934
                case 'array':
935
                    $M = new self($args[0]);
936
937
                    break;
938
                default:
939
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
940
941
                    break;
942
            }
943
            $this->checkMatrixDimensions($M);
944
            for ($i = 0; $i < $this->m; ++$i) {
945
                for ($j = 0; $j < $this->n; ++$j) {
946
                    $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j];
947
                }
948
            }
949
950
            return $M;
951
        }
952
953
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
954
    }
955
956
    /**
957
     * times.
958
     *
959
     *    Matrix multiplication
960
     *
961
     * @return Matrix Product
962
     */
963
    public function times(...$args)
964
    {
965
        if (count($args) > 0) {
966
            $match = implode(',', array_map('gettype', $args));
967
968
            switch ($match) {
969
                case 'object':
970
                    if ($args[0] instanceof self) {
971
                        $B = $args[0];
972
                    } else {
973
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
974
                    }
975
                    if ($this->n == $B->m) {
976
                        $C = new self($this->m, $B->n);
977
                        for ($j = 0; $j < $B->n; ++$j) {
978
                            $Bcolj = [];
979
                            for ($k = 0; $k < $this->n; ++$k) {
980
                                $Bcolj[$k] = $B->A[$k][$j];
981
                            }
982
                            for ($i = 0; $i < $this->m; ++$i) {
983
                                $Arowi = $this->A[$i];
984
                                $s = 0;
985
                                for ($k = 0; $k < $this->n; ++$k) {
986
                                    $s += $Arowi[$k] * $Bcolj[$k];
987
                                }
988
                                $C->A[$i][$j] = $s;
989
                            }
990
                        }
991
992
                        return $C;
993
                    }
994
995
                    throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
996
                case 'array':
997
                    $B = new self($args[0]);
998
                    if ($this->n == $B->m) {
999
                        $C = new self($this->m, $B->n);
1000
                        for ($i = 0; $i < $C->m; ++$i) {
1001
                            for ($j = 0; $j < $C->n; ++$j) {
1002
                                $s = '0';
1003
                                for ($k = 0; $k < $C->n; ++$k) {
1004
                                    $s += $this->A[$i][$k] * $B->A[$k][$j];
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 'integer':
1015
                    $C = new self($this->A);
1016
                    for ($i = 0; $i < $C->m; ++$i) {
1017
                        for ($j = 0; $j < $C->n; ++$j) {
1018
                            $C->A[$i][$j] *= $args[0];
1019
                        }
1020
                    }
1021
1022
                    return $C;
1023
                case 'double':
1024
                    $C = new self($this->m, $this->n);
1025
                    for ($i = 0; $i < $C->m; ++$i) {
1026
                        for ($j = 0; $j < $C->n; ++$j) {
1027
                            $C->A[$i][$j] = $args[0] * $this->A[$i][$j];
1028
                        }
1029
                    }
1030
1031
                    return $C;
1032
                case 'float':
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
                default:
1042
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1043
            }
1044
        } else {
1045
            throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1046
        }
1047
    }
1048
1049
    /**
1050
     * power.
1051
     *
1052
     *    A = A ^ B
1053
     *
1054
     * @return $this
1055
     */
1056
    public function power(...$args)
1057
    {
1058
        if (count($args) > 0) {
1059
            $match = implode(',', array_map('gettype', $args));
1060
1061
            switch ($match) {
1062
                case 'object':
1063
                    if ($args[0] instanceof self) {
1064
                        $M = $args[0];
1065
                    } else {
1066
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
1067
                    }
1068
1069
                    break;
1070
                case 'array':
1071
                    $M = new self($args[0]);
1072
1073
                    break;
1074
                default:
1075
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1076
1077
                    break;
1078
            }
1079
            $this->checkMatrixDimensions($M);
1080
            for ($i = 0; $i < $this->m; ++$i) {
1081
                for ($j = 0; $j < $this->n; ++$j) {
1082
                    $validValues = true;
1083
                    $value = $M->get($i, $j);
1084
                    if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
1085
                        $this->A[$i][$j] = trim($this->A[$i][$j], '"');
1086
                        $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
1087
                    }
1088
                    if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
1089
                        $value = trim($value, '"');
1090
                        $validValues &= StringHelper::convertToNumberIfFraction($value);
1091
                    }
1092
                    if ($validValues) {
1093
                        $this->A[$i][$j] = $this->A[$i][$j] ** $value;
1094
                    } else {
1095
                        $this->A[$i][$j] = Functions::NAN();
1096
                    }
1097
                }
1098
            }
1099
1100
            return $this;
1101
        }
1102
1103
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1104
    }
1105
1106
    /**
1107
     * concat.
1108
     *
1109
     *    A = A & B
1110
     *
1111
     * @return $this
1112
     */
1113
    public function concat(...$args)
1114
    {
1115
        if (count($args) > 0) {
1116
            $match = implode(',', array_map('gettype', $args));
1117
1118
            switch ($match) {
1119
                case 'object':
1120
                    if ($args[0] instanceof self) {
1121
                        $M = $args[0];
1122
                    } else {
1123
                        throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
1124
                    }
1125
1126
                    break;
1127
                case 'array':
1128
                    $M = new self($args[0]);
1129
1130
                    break;
1131
                default:
1132
                    throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1133
1134
                    break;
1135
            }
1136
            $this->checkMatrixDimensions($M);
1137
            for ($i = 0; $i < $this->m; ++$i) {
1138
                for ($j = 0; $j < $this->n; ++$j) {
1139
                    $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"');
1140
                }
1141
            }
1142
1143
            return $this;
1144
        }
1145
1146
        throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
1147
    }
1148
1149
    /**
1150
     * Solve A*X = B.
1151
     *
1152
     * @param Matrix $B Right hand side
1153
     *
1154
     * @return Matrix ... Solution if A is square, least squares solution otherwise
1155
     */
1156
    public function solve(self $B)
1157
    {
1158
        if ($this->m == $this->n) {
1159
            $LU = new LUDecomposition($this);
1160
1161
            return $LU->solve($B);
1162
        }
1163
        $QR = new QRDecomposition($this);
1164
1165
        return $QR->solve($B);
1166
    }
1167
1168
    /**
1169
     * Matrix inverse or pseudoinverse.
1170
     *
1171
     * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise.
1172
     */
1173
    public function inverse()
1174
    {
1175
        return $this->solve($this->identity($this->m, $this->m));
1176
    }
1177
1178
    /**
1179
     * det.
1180
     *
1181
     *    Calculate determinant
1182
     *
1183
     * @return float Determinant
1184
     */
1185
    public function det()
1186
    {
1187
        $L = new LUDecomposition($this);
1188
1189
        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...
1190
    }
1191
}
1192