Code Coverage for /src/SciPhp/NumPhp/DiagonalTrait.php

 
Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.92% covered (success)
94.92%
56 / 59
62.50% covered (warning)
62.50%
5 / 8
CRAP
0.00% covered (danger)
0.00%
0 / 1
DiagonalTrait
94.92% covered (success)
94.92%
56 / 59
62.50% covered (warning)
62.50%
5 / 8
22.06
0.00% covered (danger)
0.00%
0 / 1
 trace
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 identity
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 eye
93.33% covered (success)
93.33%
14 / 15
0.00% covered (danger)
0.00%
0 / 1
4.00
 diag
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 diagonal
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
4.01
 diagflat
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 fromDiagonal
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
3.02
 itemFromDiagonal
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
1 <?php
2
3 declare(strict_types=1);
4
5 namespace SciPhp\NumPhp;
6
7 use SciPhp\Exception\Message;
8 use SciPhp\NdArray;
9 use Webmozart\Assert\Assert;
10
11 /**
12  * Diagonal methods for NumPhp
13  */
14 trait DiagonalTrait
15 {
16     /**
17      * Sum along diagonals
18      *
19      * @param  \SciPhp\NdArray|array $m
20      * @param  int $k offset
21      * @return int|float|array
22      * @throws \InvalidArgumentException
23      * @link http://sciphp.org/numphp.trace Documentation
24      * @todo Implement axis supports
25      * @api
26      */
27     final public static function trace($m, int $k = 0)
28     {
29         static::transform($m, true);
30
31         return array_sum(
32             static::diagonal($m, $k)->data
33         );
34     }
35
36     /**
37      * Construct an identity array
38      *
39      * @throws \InvalidArgumentException
40      * @link http://sciphp.org/numphp.identity Documentation
41      * @todo implement Assert::natural()
42      * @api
43      */
44     final public static function identity(int $n): NdArray
45     {
46         Assert::integer($n, Message::ONLY_POSITIVE_INT);
47         Assert::greaterThan($n, 0, Message::ONLY_POSITIVE_INT);
48
49         return self::eye($n, $n);
50     }
51
52     /**
53      * Construct a diagonal array
54      *
55      * @param  int $rows Number of rows
56      * @param  int $cols Number of columns
57      * @param  int $k    Offset
58      * @link http://sciphp.org/numphp.eye Documentation
59      * @api
60      */
61     final public static function eye(int $rows, int $cols = 0, int $k = 0): NdArray
62     {
63         Assert::integer($rows);
64         Assert::integer($cols);
65         Assert::integer($k);
66         Assert::greaterThan($rows, 0);
67         Assert::greaterThanEq($cols, 0);
68
69         if ($cols === 0) {
70             $cols = $rows;
71         }
72
73         $diag = $rows > $cols
74             ? array_fill(0, $cols, 1)
75             : array_fill(0, $rows, 1);
76
77         $col = $k > 0 ? $k : 0;
78
79         return static::ar(
80             array_map(
81                 self::itemFromDiagonal($col, $diag, $k),
82                 static::zeros($rows, $cols)->data
83             )
84         );
85     }
86
87     /**
88      * Extract a diagonal or construct a diagonal array
89      *
90      * @param  array|\SciPhp\NdArray $m
91      * @param  int $k Diagonal
92      * @throws \InvalidArgumentException
93      * @link http://sciphp.org/numphp.diag Documentation
94      * @api
95      */
96     final public static function diag($m, int $k = 0): NdArray
97     {
98         static::transform($m, true);
99
100         Assert::oneOf($m->ndim, [1, 2], 'Dimension must be 1 or 2. Given %s');
101
102         if ($m->ndim === 1) {
103             return self::fromDiagonal($m->data, $k);
104         }
105
106         return self::diagonal($m, $k);
107     }
108
109     /**
110      * Extract a diagonal
111      *
112      * @param  \SciPhp\NdArray|array $m
113      * @param  int $k Offset
114      * @link http://sciphp.org/numphp.diagonal Documentation
115      * @api
116      */
117     final public static function diagonal($m, int $k = 0): NdArray
118     {
119         Assert::integer($k, 'Offset must be an integer. Given %s.');
120
121         static::transform($m, true);
122
123         Assert::oneOf($m->ndim, [1, 2]);
124
125         $col = $k > 0 ? $k : 0;
126         $line = $k < 0 ? -$k : 0;
127
128         return static::ar(
129             array_reduce(
130                 $m->data,
131                 static function($diag) use (&$line, &$col, $m): array {
132                     if (isset($m->data[$line], $m->data[$line][$col])) {
133                         $diag[] = $m->data[$line++][$col++];
134                     }
135                     return $diag;
136                 },
137                 []
138             )
139         );
140     }
141
142     /**
143      * Create a two-dimensional array with the flattened input
144      * as a diagonal.
145      *
146      * @param  mixed $m An array to flatten
147      * @link http://sciphp.org/numphp.diagflat Documentation
148      * @api
149      */
150     final public static function diagflat($m, int $k = 0): NdArray
151     {
152         Assert::integer($k);
153
154         static::transform($m, true);
155
156         return self::fromDiagonal($m->copy()->ravel()->data, $k);
157     }
158
159     /**
160      * Construct a diagonal array
161      *
162      * @param  array $diagonal
163      */
164     final protected static function fromDiagonal(array $diagonal, int $k): NdArray
165     {
166         $col = $k > 0 ? $k : 0;
167
168         $height = $width = count($diagonal);
169         $height += $k < 0 ? -$k : 0;
170         $width += $col;
171
172         return static::ar(
173             array_map(
174                 self::itemFromDiagonal($col, $diagonal, $k),
175                 static::zeros($height, $width)->data
176             )
177         );
178     }
179
180     /**
181      * Fill a line among diagonal, offset and indexes
182      *
183      * @param  int   $col  Diagonal column index
184      * @param  array $diagonal
185      * @param  int   $k    Offset
186      */
187     final protected static function itemFromDiagonal($col, array $diagonal, int $k, int $line = 1): callable
188     {
189         return static function($item) use (&$line, &$col, $diagonal, $k) {
190             if ($k >= 0 && isset($item[$col], $diagonal[$col - $k])) {
191                 $item[$col] = $diagonal[$col - $k];
192                 $col++;
193             } elseif ($k < 0) {
194                 if ($line++ > -$k && isset($item[$col], $diagonal[$col])) {
195                     $item[$col] = $diagonal[$col];
196                     $col++;
197                 }
198             }
199
200             return $item;
201         };
202     }
203 }