1<?php
2
3namespace Matrix;
4
5class Functions
6{
7    /**
8     * Calculate the adjoint of the matrix
9     *
10     * @param Matrix $matrix The matrix whose adjoint we wish to calculate
11     * @return Matrix
12     *
13     * @throws Exception
14     */
15    private static function getAdjoint(Matrix $matrix)
16    {
17        return self::transpose(
18            self::getCofactors($matrix)
19        );
20    }
21
22    /**
23     * Return the adjoint of this matrix
24     * The adjugate, classical adjoint, or adjunct of a square matrix is the transpose of its cofactor matrix.
25     * The adjugate has sometimes been called the "adjoint", but today the "adjoint" of a matrix normally refers
26     *     to its corresponding adjoint operator, which is its conjugate transpose.
27     *
28     * @param Matrix $matrix The matrix whose adjoint we wish to calculate
29     * @return Matrix
30     * @throws Exception
31     **/
32    public static function adjoint(Matrix $matrix)
33    {
34        if (!$matrix->isSquare()) {
35            throw new Exception('Adjoint can only be calculated for a square matrix');
36        }
37
38        return self::getAdjoint($matrix);
39    }
40
41    /**
42     * Calculate the cofactors of the matrix
43     *
44     * @param Matrix $matrix The matrix whose cofactors we wish to calculate
45     * @return Matrix
46     *
47     * @throws Exception
48     */
49    private static function getCofactors(Matrix $matrix)
50    {
51        $cofactors = self::getMinors($matrix);
52        $dimensions = $matrix->rows;
53
54        $cof = 1;
55        for ($i = 0; $i < $dimensions; ++$i) {
56            $cofs = $cof;
57            for ($j = 0; $j < $dimensions; ++$j) {
58                $cofactors[$i][$j] *= $cofs;
59                $cofs = -$cofs;
60            }
61            $cof = -$cof;
62        }
63
64        return new Matrix($cofactors);
65    }
66
67    /**
68     * Return the cofactors of this matrix
69     *
70     * @param Matrix $matrix The matrix whose cofactors we wish to calculate
71     * @return Matrix
72     *
73     * @throws Exception
74     */
75    public static function cofactors(Matrix $matrix)
76    {
77        if (!$matrix->isSquare()) {
78            throw new Exception('Cofactors can only be calculated for a square matrix');
79        }
80
81        return self::getCofactors($matrix);
82    }
83
84    /**
85     * @param Matrix $matrix
86     * @param int $row
87     * @param int $column
88     * @return float
89     * @throws Exception
90     */
91    private static function getDeterminantSegment(Matrix $matrix, $row, $column)
92    {
93        $tmpMatrix = $matrix->toArray();
94        unset($tmpMatrix[$row]);
95        array_walk(
96            $tmpMatrix,
97            function (&$row) use ($column) {
98                unset($row[$column]);
99            }
100        );
101
102        return self::getDeterminant(new Matrix($tmpMatrix));
103    }
104
105    /**
106     * Calculate the determinant of the matrix
107     *
108     * @param Matrix $matrix The matrix whose determinant we wish to calculate
109     * @return float
110     *
111     * @throws Exception
112     */
113    private static function getDeterminant(Matrix $matrix)
114    {
115        $dimensions = $matrix->rows;
116        $determinant = 0;
117
118        switch ($dimensions) {
119            case 1:
120                $determinant = $matrix->getValue(1, 1);
121                break;
122            case 2:
123                $determinant = $matrix->getValue(1, 1) * $matrix->getValue(2, 2) -
124                    $matrix->getValue(1, 2) * $matrix->getValue(2, 1);
125                break;
126            default:
127                for ($i = 1; $i <= $dimensions; ++$i) {
128                    $det = $matrix->getValue(1, $i) * self::getDeterminantSegment($matrix, 0, $i - 1);
129                    if (($i % 2) == 0) {
130                        $determinant -= $det;
131                    } else {
132                        $determinant += $det;
133                    }
134                }
135                break;
136        }
137
138        return $determinant;
139    }
140
141    /**
142     * Return the determinant of this matrix
143     *
144     * @param Matrix $matrix The matrix whose determinant we wish to calculate
145     * @return float
146     * @throws Exception
147     **/
148    public static function determinant(Matrix $matrix)
149    {
150        if (!$matrix->isSquare()) {
151            throw new Exception('Determinant can only be calculated for a square matrix');
152        }
153
154        return self::getDeterminant($matrix);
155    }
156
157    /**
158     * Return the diagonal of this matrix
159     *
160     * @param Matrix $matrix The matrix whose diagonal we wish to calculate
161     * @return Matrix
162     * @throws Exception
163     **/
164    public static function diagonal(Matrix $matrix)
165    {
166        if (!$matrix->isSquare()) {
167            throw new Exception('Diagonal can only be extracted from a square matrix');
168        }
169
170        $dimensions = $matrix->rows;
171        $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions)
172            ->toArray();
173
174        for ($i = 0; $i < $dimensions; ++$i) {
175            $grid[$i][$i] = $matrix->getValue($i + 1, $i + 1);
176        }
177
178        return new Matrix($grid);
179    }
180
181    /**
182     * Return the antidiagonal of this matrix
183     *
184     * @param Matrix $matrix The matrix whose antidiagonal we wish to calculate
185     * @return Matrix
186     * @throws Exception
187     **/
188    public static function antidiagonal(Matrix $matrix)
189    {
190        if (!$matrix->isSquare()) {
191            throw new Exception('Anti-Diagonal can only be extracted from a square matrix');
192        }
193
194        $dimensions = $matrix->rows;
195        $grid = Builder::createFilledMatrix(0, $dimensions, $dimensions)
196            ->toArray();
197
198        for ($i = 0; $i < $dimensions; ++$i) {
199            $grid[$i][$dimensions - $i - 1] = $matrix->getValue($i + 1, $dimensions - $i);
200        }
201
202        return new Matrix($grid);
203    }
204
205    /**
206     * Return the identity matrix
207     * The identity matrix, or sometimes ambiguously called a unit matrix, of size n is the n × n square matrix
208     *   with ones on the main diagonal and zeros elsewhere
209     *
210     * @param Matrix $matrix The matrix whose identity we wish to calculate
211     * @return Matrix
212     * @throws Exception
213     **/
214    public static function identity(Matrix $matrix)
215    {
216        if (!$matrix->isSquare()) {
217            throw new Exception('Identity can only be created for a square matrix');
218        }
219
220        $dimensions = $matrix->rows;
221
222        return Builder::createIdentityMatrix($dimensions);
223    }
224
225    /**
226     * Return the inverse of this matrix
227     *
228     * @param Matrix $matrix The matrix whose inverse we wish to calculate
229     * @return Matrix
230     * @throws Exception
231     **/
232    public static function inverse(Matrix $matrix, string $type = 'inverse')
233    {
234        if (!$matrix->isSquare()) {
235            throw new Exception(ucfirst($type) . ' can only be calculated for a square matrix');
236        }
237
238        $determinant = self::getDeterminant($matrix);
239        if ($determinant == 0.0) {
240            throw new Div0Exception(ucfirst($type) . ' can only be calculated for a matrix with a non-zero determinant');
241        }
242
243        if ($matrix->rows == 1) {
244            return new Matrix([[1 / $matrix->getValue(1, 1)]]);
245        }
246
247        return self::getAdjoint($matrix)
248            ->multiply(1 / $determinant);
249    }
250
251    /**
252     * Calculate the minors of the matrix
253     *
254     * @param Matrix $matrix The matrix whose minors we wish to calculate
255     * @return array[]
256     *
257     * @throws Exception
258     */
259    protected static function getMinors(Matrix $matrix)
260    {
261        $minors = $matrix->toArray();
262        $dimensions = $matrix->rows;
263        if ($dimensions == 1) {
264            return $minors;
265        }
266
267        for ($i = 0; $i < $dimensions; ++$i) {
268            for ($j = 0; $j < $dimensions; ++$j) {
269                $minors[$i][$j] = self::getDeterminantSegment($matrix, $i, $j);
270            }
271        }
272
273        return $minors;
274    }
275
276    /**
277     * Return the minors of the matrix
278     * The minor of a matrix A is the determinant of some smaller square matrix, cut down from A by removing one or
279     *     more of its rows or columns.
280     * Minors obtained by removing just one row and one column from square matrices (first minors) are required for
281     *     calculating matrix cofactors, which in turn are useful for computing both the determinant and inverse of
282     *     square matrices.
283     *
284     * @param Matrix $matrix The matrix whose minors we wish to calculate
285     * @return Matrix
286     * @throws Exception
287     **/
288    public static function minors(Matrix $matrix)
289    {
290        if (!$matrix->isSquare()) {
291            throw new Exception('Minors can only be calculated for a square matrix');
292        }
293
294        return new Matrix(self::getMinors($matrix));
295    }
296
297    /**
298     * Return the trace of this matrix
299     * The trace is defined as the sum of the elements on the main diagonal (the diagonal from the upper left to the lower right)
300     *     of the matrix
301     *
302     * @param Matrix $matrix The matrix whose trace we wish to calculate
303     * @return float
304     * @throws Exception
305     **/
306    public static function trace(Matrix $matrix)
307    {
308        if (!$matrix->isSquare()) {
309            throw new Exception('Trace can only be extracted from a square matrix');
310        }
311
312        $dimensions = $matrix->rows;
313        $result = 0;
314        for ($i = 1; $i <= $dimensions; ++$i) {
315            $result += $matrix->getValue($i, $i);
316        }
317
318        return $result;
319    }
320
321    /**
322     * Return the transpose of this matrix
323     *
324     * @param Matrix $matrix The matrix whose transpose we wish to calculate
325     * @return Matrix
326     **/
327    public static function transpose(Matrix $matrix)
328    {
329        $array = array_values(array_merge([null], $matrix->toArray()));
330        $grid = call_user_func_array(
331            'array_map',
332            $array
333        );
334
335        return new Matrix($grid);
336    }
337}
338