Skip to content

Commit 92a1467

Browse files
committed
Add MatrixFunction implementation and documentation
1 parent 5de04dd commit 92a1467

10 files changed

Lines changed: 190 additions & 32 deletions

File tree

symja_android_library/doc/99-function-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ Functions in alphabetical order:
699699
* [MatrixD](functions/MatrixD.md)
700700
* ✅ [MatrixExp](functions/MatrixExp.md)
701701
* [MatrixForm](functions/MatrixForm.md)
702+
* ✅ [MatrixFunction](functions/MatrixFunction.md)
702703
* ☑ [MatrixLog](functions/MatrixLog.md)
703704
* ✅ [MatrixMinimalPolynomial](functions/MatrixMinimalPolynomial.md)
704705
* 🧪 [MatrixPlot](functions/MatrixPlot.md)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## MatrixFunction
2+
3+
```
4+
MatrixFunction(function-head, matrix)
5+
```
6+
7+
> computes the matrix function of the `matrix`.
8+
9+
See
10+
* [Wikipedia - Analytic function of a matrix](https://en.wikipedia.org/wiki/Analytic_function_of_a_matrix)
11+
* [Wikipedia - Matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential)
12+
13+
### Examples
14+
15+
An example from Wikipedia:
16+
17+
```
18+
>> mf=MatrixFunction(Gamma, {{1,3},{2,1}})
19+
{{1/2*(Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6))),1/2*Sqrt(3/2)*(-Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6)))},{(-Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6)))/Sqrt(6),1/2*(Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6)))}}
20+
21+
>> N(mf)
22+
{{2.81144,0.407999},{0.271999,2.81144}}
23+
```

symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/basic/Config.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,8 +331,10 @@ public static Cache<IExpr, Object> getExprCache() {
331331
*/
332332
public static double SPECIAL_FUNCTIONS_TOLERANCE = 1.0e-10;
333333

334+
public static double DEFAULT_EQUALS_TOLERANCE = 1.0e-14;
335+
334336
/**
335-
* Return {@link True} for {@link Apfloat} integers in <code>Element(apfloat, Integers)</code>
337+
* Return {@link S#True} for {@link Apfloat} integers in <code>Element(apfloat, Integers)</code>
336338
*/
337339
public static boolean ACCEPT_NUMERIC_INTEGER_IN_INTEGERS_DOMAIN = false;
338340

symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/FunctionDefinitions.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ private static void init() {
126126
S.LinearProgramming
127127
.setEvaluator(new org.matheclipse.core.reflection.system.LinearProgramming());
128128
S.LogBarnesG.setEvaluator(new org.matheclipse.core.reflection.system.LogBarnesG());
129+
S.MatrixFunction.setEvaluator(new org.matheclipse.core.reflection.system.MatrixFunction());
129130
S.MaximalBy.setEvaluator(new org.matheclipse.core.reflection.system.MaximalBy());
130131
S.MeijerG.setEvaluator(new org.matheclipse.core.reflection.system.MeijerG());
131132
S.MeijerGReduce.setEvaluator(new org.matheclipse.core.reflection.system.MeijerGReduce());

symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/builtin/LinearAlgebra.java

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2872,7 +2872,6 @@ private static void init() {
28722872
S.LUDecomposition.setEvaluator(new LUDecomposition());
28732873
S.MatrixMinimalPolynomial.setEvaluator(new MatrixMinimalPolynomial());
28742874
S.MatrixExp.setEvaluator(new MatrixExp());
2875-
S.MatrixFunction.setEvaluator(new MatrixFunction());
28762875
S.MatrixLog.setEvaluator(new MatrixLog());
28772876
S.MatrixPower.setEvaluator(new MatrixPower());
28782877
S.MatrixRank.setEvaluator(new MatrixRank());
@@ -4039,34 +4038,6 @@ public int[] expectedArgSize(IAST ast) {
40394038

40404039
}
40414040

4042-
4043-
private static class MatrixFunction extends AbstractEvaluator {
4044-
4045-
@Override
4046-
public IExpr evaluate(final IAST ast, EvalEngine engine) {
4047-
int[] dimensions = ast.arg2().isMatrix();
4048-
if (dimensions != null) {
4049-
if (dimensions[0] == 0 || dimensions[1] == 0) {
4050-
return ast.arg2();
4051-
}
4052-
// TODO
4053-
// final IExpr function = ast.arg1();
4054-
// FieldMatrix<IExpr> fieldMatrix = Convert.list2Matrix(ast.arg2());
4055-
// return Convert.matrix2Expr(fieldMatrix);
4056-
}
4057-
return F.NIL;
4058-
}
4059-
4060-
@Override
4061-
public int[] expectedArgSize(IAST ast) {
4062-
return ARGS_2_2;
4063-
}
4064-
4065-
@Override
4066-
public void setUp(final ISymbol newSymbol) {}
4067-
}
4068-
4069-
40704041
private static class MatrixLog extends MatrixExp {
40714042

40724043
@Override
@@ -6652,7 +6623,7 @@ public IExpr evaluate(final IAST ast, EvalEngine engine) {
66526623
// }
66536624
// };
66546625
final IndexTableGenerator generator = new IndexTableGenerator(indexArray, S.List, //
6655-
indx -> Power(lst.get(indx[0] + 1), F.ZZ(indx[1])));
6626+
indx -> F.Power(lst.get(indx[0] + 1), F.ZZ(indx[1])));
66566627
final IAST matrix = (IAST) generator.table();
66576628
// because the rows can contain sub lists the IAST.IS_MATRIX flag cannot be set directly.
66586629
// isMatrix()

symja_android_library/matheclipse-core/src/main/java/org/matheclipse/core/interfaces/IExpr.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1352,7 +1352,7 @@ default IExpr.COMPARE_TERNARY equalTernary(IExpr that, EvalEngine engine) {
13521352

13531353
IExpr difference = engine.evaluate(F.Subtract(arg1, arg2));
13541354
if (difference.isNumber()) {
1355-
if (difference.isZero()) {
1355+
if (((INumber) difference).isZero(Config.DEFAULT_EQUALS_TOLERANCE)) {// Config.SPECIAL_FUNCTIONS_TOLERANCE))
13561356
return IExpr.COMPARE_TERNARY.TRUE;
13571357
}
13581358
return IExpr.COMPARE_TERNARY.FALSE;
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package org.matheclipse.core.reflection.system;
2+
3+
import org.matheclipse.core.eval.EvalEngine;
4+
import org.matheclipse.core.eval.interfaces.AbstractFunctionEvaluator;
5+
import org.matheclipse.core.expression.F;
6+
import org.matheclipse.core.interfaces.IAST;
7+
import org.matheclipse.core.interfaces.IASTAppendable;
8+
import org.matheclipse.core.interfaces.IExpr;
9+
10+
/**
11+
* <pre>
12+
* MatrixFunction(f, m)
13+
* </pre>
14+
*
15+
* <blockquote>
16+
* <p>
17+
* gives the matrix generated by the scalar function f at the matrix argument m.
18+
* </p>
19+
* </blockquote>
20+
*/
21+
public class MatrixFunction extends AbstractFunctionEvaluator {
22+
23+
@Override
24+
public IExpr evaluate(final IAST ast, EvalEngine engine) {
25+
IExpr f = ast.arg1();
26+
IExpr m = ast.arg2();
27+
28+
// MatrixFunction works only on square matrices
29+
int[] dims = m.isMatrix();
30+
if (dims == null || dims[0] != dims[1]) {
31+
return F.NIL;
32+
}
33+
34+
// Diagonalization approach using Eigensystem(m) -> {eigenvalues, eigenvectors}
35+
IExpr eigenResult = engine.evaluate(F.Eigensystem(m));
36+
37+
if (eigenResult.isList() && eigenResult.argSize() == 2) {
38+
IExpr eigenvalues = ((IAST) eigenResult).arg1();
39+
IExpr eigenvectors = ((IAST) eigenResult).arg2();
40+
41+
if (eigenvalues.isList() && eigenvectors.isList()) {
42+
IAST valsList = (IAST) eigenvalues;
43+
IAST vecsList = (IAST) eigenvectors;
44+
int n = dims[0];
45+
46+
// Evaluate f at each eigenvalue to construct the diagonal matrix D
47+
IASTAppendable fVals = F.ListAlloc(n);
48+
for (int i = 1; i <= n; i++) {
49+
fVals.append(engine.evaluate(F.unaryAST1(f, valsList.get(i))));
50+
}
51+
52+
IAST diagonalMatrix = F.DiagonalMatrix(fVals);
53+
54+
// The eigenvectors are the rows, so we transpose to get the transformation matrix S
55+
IAST sMatrix = (IAST) engine.evaluate(F.Transpose(vecsList));
56+
57+
// Compute Inverse(S)
58+
IExpr sInverse = engine.evaluate(F.Inverse(sMatrix));
59+
60+
// Check if the eigenvectors matrix is invertible (i.e., matrix is diagonalizable)
61+
if (sInverse.isList()) {
62+
// Compute f(M) = S . f(D) . S^(-1)
63+
return engine.evaluate(F.Dot(sMatrix, F.Dot(diagonalMatrix, sInverse)));
64+
}
65+
}
66+
}
67+
68+
// TODO Fallback strategy:
69+
// If Eigensystem fails or the matrix is defective (non-diagonalizable),
70+
// a fallback to JordanDecomposition(m) or SchurDecomposition(m) should be executed here.
71+
return F.NIL;
72+
}
73+
74+
@Override
75+
public int[] expectedArgSize(IAST ast) {
76+
return ARGS_2_2;
77+
}
78+
}

symja_android_library/matheclipse-core/src/main/resources/doc/99-function-reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -699,6 +699,7 @@ Functions in alphabetical order:
699699
* [MatrixD](functions/MatrixD.md)
700700
* &#x2705; [MatrixExp](functions/MatrixExp.md)
701701
* [MatrixForm](functions/MatrixForm.md)
702+
* &#x2705; [MatrixFunction](functions/MatrixFunction.md)
702703
* &#x2611; [MatrixLog](functions/MatrixLog.md)
703704
* &#x2705; [MatrixMinimalPolynomial](functions/MatrixMinimalPolynomial.md)
704705
* &#x1F9EA; [MatrixPlot](functions/MatrixPlot.md)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## MatrixFunction
2+
3+
```
4+
MatrixFunction(function-head, matrix)
5+
```
6+
7+
> computes the matrix function of the `matrix`.
8+
9+
See
10+
* [Wikipedia - Analytic function of a matrix](https://en.wikipedia.org/wiki/Analytic_function_of_a_matrix)
11+
* [Wikipedia - Matrix exponential](https://en.wikipedia.org/wiki/Matrix_exponential)
12+
13+
### Examples
14+
15+
An example from Wikipedia:
16+
17+
```
18+
>> mf=MatrixFunction(Gamma, {{1,3},{2,1}})
19+
{{1/2*(Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6))),1/2*Sqrt(3/2)*(-Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6)))},{(-Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6)))/Sqrt(6),1/2*(Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6)))}}
20+
21+
>> N(mf)
22+
{{2.81144,0.407999},{0.271999,2.81144}}
23+
```
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.matheclipse.core.reflection.system;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.matheclipse.core.system.ExprEvaluatorTestCase;
5+
6+
public class MatrixFunctionTest extends ExprEvaluatorTestCase {
7+
8+
@Test
9+
public void testMatrixFunctionGamma() {
10+
// example from https://en.wikipedia.org/wiki/Analytic_function_of_a_matrix
11+
check("mf=MatrixFunction(Gamma, {{1,3},{2,1}})", //
12+
"{{1/2*(Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6))),1/2*Sqrt(3/2)*(-Gamma(1-Sqrt(6))+Gamma(\n" //
13+
+ "1+Sqrt(6)))},\n" //
14+
+ " {(-Gamma(1-Sqrt(6))+Gamma(1+Sqrt(6)))/Sqrt(6),1/2*(Gamma(1-Sqrt(6))+Gamma(1+Sqrt(\n"//
15+
+ "6)))}}");
16+
check("N(mf)", //
17+
"{{2.81144,0.407999},{0.271999,2.81144}}");
18+
}
19+
20+
@Test
21+
public void testMatrixFunctionSqrt() {
22+
check("m=MatrixFunction(Sqrt, {{a, 1}, {0, b}})", //
23+
"{{Sqrt(a),1/(Sqrt(a)+Sqrt(b))},\n" + " {0,Sqrt(b)}}");
24+
check("m.m", //
25+
"{{a,1},\n" //
26+
+ " {0,b}}");
27+
}
28+
29+
@Test
30+
public void testMatrixFunctionLog() {
31+
check("{{2.,2.,1.},{4.,6.,4.},{3.,6.,8.}}=={{2.000000000000001,2.0,0.9999999999999991},\n"
32+
+ " {4.000000000000002,5.999999999999997,3.9999999999999964},\n"
33+
+ " {3.0000000000000093,6.000000000000002,7.999999999999997}}", "True");
34+
35+
checkNumeric("m ={{2.,2.,1.},{4.,6.,4.},{3.,6.,8.}}", //
36+
"{{2.0,2.0,1.0},{4.0,6.0,4.0},{3.0,6.0,8.0}}");
37+
check("l=MatrixFunction(Log, m)", //
38+
"{{-0.118314,0.804385,0.0234838},\n" //
39+
+ " {1.60877,1.00354,0.74315},\n" //
40+
+ " {0.0704514,1.11472,1.75383}}");
41+
checkNumeric("me=MatrixExp(l)", //
42+
"{{2.000000000000001,2.0,0.9999999999999991},\n" //
43+
+ " {4.000000000000002,5.999999999999997,3.9999999999999964},\n" //
44+
+ " {3.0000000000000093,6.000000000000002,7.999999999999997}}");
45+
check("me==m", //
46+
"True");
47+
}
48+
49+
@Test
50+
public void testMatrixFunction001() {
51+
check("MatrixFunction(x |-> x^5 + 2 x^2 + 1, {{a, 0}, {1, b}})", //
52+
"{{1+2*a^2+a^5,0},\n"//
53+
+ " {2*a+a^4+2*b+a^3*b+a^2*b^2+a*b^3+b^4,1+2*b^2+b^5}}");
54+
check("MatrixFunction(1 &, {{1, 2}, {3, 4}})", //
55+
"{{1,0},\n" //
56+
+ " {0,1}}");
57+
}
58+
}

0 commit comments

Comments
 (0)