99"""
1010import warnings
1111
12+ import numpy as np
13+ from numpy .linalg import LinAlgError
14+
1215from sympy import MutableSparseMatrix
1316from sympy .matrices .exceptions import NonInvertibleMatrixError
1417
1518def block_matrix_inverse (P , blocks_extent , simplify = True ):
16- """Function to invert a symbolic matrix :math:`P` divided by blocks.
19+ """Function to invert a symbolic or numeric matrix :math:`P` divided by blocks.
1720
1821 Parameters
1922 ----------
20- P: ~sympy.matrices.immutable.ImmutableSparseMatrix or ~sympy.matrices.mutable.MutableSparseMatrix
23+ P: ~sympy.matrices.immutable.ImmutableSparseMatrix or ~sympy.matrices.mutable.MutableSparseMatrix or ~numpy.ndarray
2124 The block matrix to invert.
2225 blocks_extent: list(tuple)
2326 The extent of each block, as a list of 2-tuple.
2427 simplify: bool, optional
2528 Try to simplify the inverse.
29+ Only applies to symbolic matrices.
2630 Default to `True`.
2731
2832 Warnings
2933 --------
30- If the `simplify` argument is set to `False`, this function will not check if the
34+ In the symbolic case, if the `simplify` argument is set to `False`, this function will not check if the
3135 matrix :math:`P` is invertible !
3236
3337 """
34- if simplify :
35- if P .det ().simplify () == 0 :
36- raise NonInvertibleMatrixError ('block_matrix_inverse: The provided matrix is not invertible.' )
37- else :
38- warnings .warn (f'Inverting a symbolic matrix without checking that it is invertible. '
39- 'Be cautious about the result.' )
40- be = blocks_extent .copy ()
41- PP = P
42- B_list = list ()
43- C_list = list ()
44- Am1_list = list ()
45- while len (be ) >= 2 :
46- rest_block_extent = (be [1 ][0 ], be [- 1 ][1 ])
47- Ainv , B , C , D , PPP = _block_matrix_inverse_2x2 (PP , (be [0 ], rest_block_extent ))
48-
49- B_list .append (B )
50- C_list .append (C )
51- Am1_list .append (Ainv )
52-
53- PP = PPP
54- ret = be [0 ][1 ]
55- be = [(p [0 ]- ret , p [1 ]- ret ) for p in be [1 :]]
56-
57- PP = PP .adjugate () / PP .det ()
58- for Am1 , B , C in zip (Am1_list [::- 1 ], B_list [::- 1 ], C_list [::- 1 ]):
59- Ashape = Am1 .shape [0 ]
60- Pshape = PP .shape [0 ]
61- mat_shape = Ashape + Pshape
62- Pp1 = MutableSparseMatrix (mat_shape , mat_shape , {})
63- Pp1 [slice (0 , Ashape ), slice (0 , Ashape )] = Am1 + Am1 @ B @ PP @ C @ Am1
64- Pp1 [slice (0 , Ashape ), slice (Ashape , mat_shape )] = - Am1 @ B @ PP
65- Pp1 [slice (Ashape , mat_shape ), slice (0 , Ashape )] = - PP @ C @ Am1
66- Pp1 [slice (Ashape , mat_shape ), slice (Ashape , mat_shape )] = PP
67- PP = Pp1
68-
69- if simplify :
70- return PP .simplify ()
71- else :
38+ if isinstance (P , np .ndarray ):
39+ if np .linalg .det (P ) == 0 :
40+ raise LinAlgError ('block_matrix_inverse: The provided matrix is not invertible.' )
41+ be = blocks_extent .copy ()
42+ PP = P
43+ B_list = list ()
44+ C_list = list ()
45+ Am1_list = list ()
46+ while len (be ) >= 2 :
47+ rest_block_extent = (be [1 ][0 ], be [- 1 ][1 ])
48+ Ainv , B , C , D , PPP = _block_matrix_inverse_2x2_num (PP , (be [0 ], rest_block_extent ))
49+
50+ B_list .append (B )
51+ C_list .append (C )
52+ Am1_list .append (Ainv )
53+
54+ PP = PPP
55+ ret = be [0 ][1 ]
56+ be = [(p [0 ] - ret , p [1 ] - ret ) for p in be [1 :]]
57+
58+ PP = np .linalg .inv (PP )
59+ for Am1 , B , C in zip (Am1_list [::- 1 ], B_list [::- 1 ], C_list [::- 1 ]):
60+ Ashape = Am1 .shape [0 ]
61+ Pshape = PP .shape [0 ]
62+ mat_shape = Ashape + Pshape
63+ Pp1 = np .zeros ((mat_shape , mat_shape ))
64+ Pp1 [slice (0 , Ashape ), slice (0 , Ashape )] = Am1 + Am1 @ B @ PP @ C @ Am1
65+ Pp1 [slice (0 , Ashape ), slice (Ashape , mat_shape )] = - Am1 @ B @ PP
66+ Pp1 [slice (Ashape , mat_shape ), slice (0 , Ashape )] = - PP @ C @ Am1
67+ Pp1 [slice (Ashape , mat_shape ), slice (Ashape , mat_shape )] = PP
68+ PP = Pp1
69+
7270 return PP
71+ else :
72+ if simplify :
73+ if P .det ().simplify () == 0 :
74+ raise NonInvertibleMatrixError ('block_matrix_inverse: The provided matrix is not invertible.' )
75+ else :
76+ warnings .warn (f'Inverting a symbolic matrix without checking that it is invertible. '
77+ 'Be cautious about the result.' )
78+ be = blocks_extent .copy ()
79+ PP = P
80+ B_list = list ()
81+ C_list = list ()
82+ Am1_list = list ()
83+ while len (be ) >= 2 :
84+ rest_block_extent = (be [1 ][0 ], be [- 1 ][1 ])
85+ Ainv , B , C , D , PPP = _block_matrix_inverse_2x2 (PP , (be [0 ], rest_block_extent ))
86+
87+ B_list .append (B )
88+ C_list .append (C )
89+ Am1_list .append (Ainv )
90+
91+ PP = PPP
92+ ret = be [0 ][1 ]
93+ be = [(p [0 ]- ret , p [1 ]- ret ) for p in be [1 :]]
94+
95+ PP = PP .adjugate () / PP .det ()
96+ for Am1 , B , C in zip (Am1_list [::- 1 ], B_list [::- 1 ], C_list [::- 1 ]):
97+ Ashape = Am1 .shape [0 ]
98+ Pshape = PP .shape [0 ]
99+ mat_shape = Ashape + Pshape
100+ Pp1 = MutableSparseMatrix (mat_shape , mat_shape , {})
101+ Pp1 [slice (0 , Ashape ), slice (0 , Ashape )] = Am1 + Am1 @ B @ PP @ C @ Am1
102+ Pp1 [slice (0 , Ashape ), slice (Ashape , mat_shape )] = - Am1 @ B @ PP
103+ Pp1 [slice (Ashape , mat_shape ), slice (0 , Ashape )] = - PP @ C @ Am1
104+ Pp1 [slice (Ashape , mat_shape ), slice (Ashape , mat_shape )] = PP
105+ PP = Pp1
106+
107+ if simplify :
108+ return PP .simplify ()
109+ else :
110+ return PP
73111
74112def _block_matrix_inverse_2x2 (P , blocks_extent ):
75113 be = blocks_extent
@@ -81,6 +119,16 @@ def _block_matrix_inverse_2x2(P, blocks_extent):
81119
82120 return Ainv , B , C , D , D - C @ Ainv @ B
83121
122+ def _block_matrix_inverse_2x2_num (P , blocks_extent ):
123+ be = blocks_extent
124+ A = P [slice (* be [0 ]), slice (* be [0 ])]
125+ B = P [slice (* be [0 ]), slice (* be [1 ])]
126+ C = P [slice (* be [1 ]), slice (* be [0 ])]
127+ D = P [slice (* be [1 ]), slice (* be [1 ])]
128+ Ainv = np .linalg .inv (A )
129+
130+ return Ainv , B , C , D , D - C @ Ainv @ B
131+
84132
85133if __name__ == '__main__' :
86134 A = MutableSparseMatrix (3 , 3 , {})
@@ -102,3 +150,22 @@ def _block_matrix_inverse_2x2(P, blocks_extent):
102150 bl = [(0 , 3 ), (3 , 6 ), (6 , 9 )]
103151 Ginv = block_matrix_inverse (G , bl )
104152
153+ A = np .zeros ((3 , 3 ))
154+ A [0 ,0 ] = 1
155+ A [1 ,1 ] = 3
156+ A [2 ,2 ] = 2
157+ C = A / 2
158+ D = 3 * A
159+ G = np .zeros ((9 , 9 ))
160+ G [:3 , :3 ] = A
161+ G [:3 , 3 :6 ] = - C
162+ G [:3 , 6 :] = D
163+ G [3 :6 , :3 ] = C
164+ G [3 :6 , 3 :6 ] = C
165+ G [3 :6 , 6 :] = - D
166+ G [6 :, :3 ] = - C
167+ G [6 :, 3 :6 ] = A
168+ G [6 :, 6 :] = D
169+ bl = [(0 , 3 ), (3 , 6 ), (6 , 9 )]
170+ Ginv_num = block_matrix_inverse (G , bl )
171+
0 commit comments