Speeding up computation of symbolic determinant in SymPy

前端 未结 2 1700
暖寄归人
暖寄归人 2021-01-18 15:27

I have a 4x4 matrix A with rather long but simple symbolic expressions in each of its entries. About 30 different symbols are involved. By \"simple\" I mean tha

2条回答
  •  轻奢々
    轻奢々 (楼主)
    2021-01-18 16:05

    What you describe as a ratio of polynomials is what is known as a rational function: https://en.wikipedia.org/wiki/Rational_function

    SymPy's polys module does have ways of representing rational functions although they can be slow especially with lots of variables.

    There is a new matrix implementation in sympy 1.7 which is still somewhat experimental but is based on the polys module and can handle rational functions. We can test it here by quickly creating a random matrix:

    In [35]: import random                                                                                                            
    
    In [36]: from sympy import random_poly, symbols, Matrix                                                                           
    
    In [37]: randpoly = lambda : random_poly(random.choice(symbols('x:z')), 2, 0, 2)                                                  
    
    In [38]: randfunc = lambda : randpoly() / randpoly()                                                                              
    
    In [39]: M = Matrix([randfunc() for _ in range(16)]).reshape(4, 4)                                                                
    
    In [40]: M                                                                                                                        
    Out[40]: 
    ⎡     2              2            2              2       ⎤
    ⎢  2⋅z  + 1       2⋅z  + z     2⋅z  + z + 2     x  + 2   ⎥
    ⎢  ────────     ────────────   ────────────   ────────── ⎥
    ⎢   2            2                 2             2       ⎥
    ⎢  y  + 2⋅y     y  + 2⋅y + 1      x  + 1      2⋅z  + 2⋅z ⎥
    ⎢                                                        ⎥
    ⎢  2              2                  2        2          ⎥
    ⎢ y  + y + 1   2⋅x  + 2⋅x + 1       z        z  + 2⋅z + 1⎥
    ⎢ ──────────   ──────────────     ──────     ────────────⎥
    ⎢     2            2               2           2         ⎥
    ⎢  2⋅y  + 2       y  + 2⋅y        y  + 1      x  + x + 2 ⎥
    ⎢                                                        ⎥
    ⎢     2           2                2            2        ⎥
    ⎢  2⋅z  + 2    2⋅z  + 2⋅z + 2     y  + 1     2⋅y  + y + 2⎥
    ⎢────────────  ──────────────   ──────────   ────────────⎥
    ⎢   2             2                2             2       ⎥
    ⎢2⋅z  + z + 1  2⋅x  + 2⋅x + 2   2⋅y  + 2⋅y      x  + 2   ⎥
    ⎢                                                        ⎥
    ⎢    2               2            2             2        ⎥
    ⎢ 2⋅y  + 2⋅y      2⋅y  + y     2⋅x  + x + 1  2⋅x  + x + 1⎥
    ⎢ ──────────      ────────     ────────────  ────────────⎥
    ⎢    2              2                 2          2       ⎥
    ⎣   z  + 2         x  + 2          2⋅y          x  + 2   ⎦
    

    If we convert that to the new matrix implementation then we can compute the determinant using the charpoly method:

    In [41]: from sympy.polys.domainmatrix import DomainMatrix                                                                        
    
    In [42]: dM = DomainMatrix.from_list_sympy(*M.shape, M.tolist())                                                                  
    
    In [43]: dM.domain                                                                                                                
    Out[43]: ZZ(x,y,z)
    
    In [44]: dM.domain.field                                                                                                          
    Out[44]: Rational function field in x, y, z over ZZ with lex order
    
    In [45]: %time det = dM.charpoly()[-1] * (-1)**M.shape[0]                                                                         
    CPU times: user 22 s, sys: 231 ms, total: 22.3 s
    Wall time: 23 s
    

    This is slower than the approach suggested by @asmeurer above but it produces output in a canonical form as a ratio of expanded polynomials. In particular this means that you can immediately tell if the determinant is zero (for all x, y, z) or not. The time is also taken by the equivalent of cancel but the implementation is more efficient than Matrix.det.

    How long this takes is largely a function of how complicated the final output is and you can get some sense of that from the length of its string representation (I won't show the whole thing!):

    In [46]: len(str(det))                                                                                                            
    Out[46]: 54458
    
    In [47]: str(det)[:80]                                                                                                            
    Out[47]: '(16*x**16*y**7*z**4 + 48*x**16*y**7*z**2 + 32*x**16*y**7 + 80*x**16*y**6*z**4 + '
    

    At some point it should be possible to integrate this into the main Matrix class or otherwise to make the DomainMatrix class more publicly accessible.

提交回复
热议问题