On Fri, 25 Aug 2023 at 14:08, Ralf Schlatterbeck <r...@runtux.com> wrote: > > Hi all, > > [This is quite long, tl;dr: I cannot solve a matrix in reasonable time > that can be solved with Maxima in 8s.] > > I'm using Lcapy (which in turn uses sympy) for computing a transfer > function of a simple circuit (see below for an ascii-art drawing). > > I was referred here by the author of Lcapy. > > As many of you may be aware one can compute the parameters of a circuit > by setting up a matrix and solving for the voltages (or currents). See > at the end for an ascii-art of the circuit. > > Lcapy comes up with the following matrix: > > from sympy import Matrix, symbols > R1, R2, C1, C2, C3, C4, C5, C6, C7, L1, L2, L3 = symbols( > 'R1, R2, C1, C2, C3, C4, C5, C6, C7, L1, L2, L3', positive=True, > real=True) > s = symbols('s') > b = Matrix([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]) > A = Matrix([ > [1/R1, -1/R1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1], > [-1/R1, C1*s + 1/R1, -C1*s, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], > [0, -C1*s, C1*s + C2*s + C3*s, -C2*s, -C3*s, 0, 0, 0, 0, 0, 0, 0, 0], > [0, 0, -C2*s, C2*s, 0, 0, 0, 0, 0, 1, 0, 0, 0], > [0, 0, -C3*s, 0, C3*s + C4*s + C5*s, -C4*s, -C5*s, 0, 0, 0, 0, 0, 0], > [0, 0, 0, 0, -C4*s, C4*s, 0, 0, 0, 0, 1, 0, 0], > [0, 0, 0, 0, -C5*s, 0, C5*s + C6*s + C7*s, -C7*s, -C6*s, 0, 0, 0, 0], > [0, 0, 0, 0, 0, 0, -C7*s, C7*s, 0, 0, 0, 1, 0], > [0, 0, 0, 0, 0, 0, -C6*s, 0, C6*s + 1/R2, 0, 0, 0, 0], > [0, 0, 0, 1, 0, 0, 0, 0, 0, -L1*s, 0, 0, 0], > [0, 0, 0, 0, 0, 1, 0, 0, 0, 0, -L2*s, 0, 0], > [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, -L3*s, 0], > [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]) > > x = A.solve(b, method='LU') > > Trying to solve this with the 'LU' solver produces a really huge > solution (but the solution is quite fast): > > def exsize (expr): > n = 0 > if not expr.args: > return 1 > for arg in expr.args: > n += exsize (arg) > return n > > >>> print ([exsize (k) for k in x]) > [52298, 26148, 884884, 128156, 730566, 65218, 510990, 35741, 255579, > 128154, 65216, 35739, 26146] > > [Is there a better way to determine the size of an expression?] > > Simplifying the first expression (x [0]) should yield 1 (probably using > scipy.cancel). But I'm too impatient to wait for it, I've killed it when > it took 1.2GB resident memory after an hour or so. > > Other solvers are *very* slow. Trying to solve with default 'GJ' method > wasn't completed in several hours, so I left it running overnight and > had terminated in the morning, unfortunately I had not computed the > sizes as above. > > The size of the result created by 'LU' prevents further computations to > happen in reasonable time.
These problems are both well known (to me at least). Some solvers like LU produce large unsimplified output that potentially includes divide by zero within the large expressions. Others like GJ would produce correct and simplified output but are extremely slow. See also the discussion here: https://github.com/sympy/sympy/issues/24679 I have been working on improving this and will write about this in more detail soon. For now if you use the current SymPy master branch then it is possible to get a result like the GJ one but a lot faster if you use DomainMatrix instead of Matrix. With the current master branch Matrix has a .to_DM() method for converting a Matrix to a DomainMatrix: In [7]: %time sol_num, den = A.to_DM().solve_den(b.to_DM()) CPU times: user 20.6 s, sys: 236 ms, total: 20.8 s Wall time: 22.2 s So currently that takes 20 seconds on this (not very powerful) laptop. This computes a matrix numerator and scalar denominator that when divided represent the solution to your matrix equation. We can perform the division either with or without cancellation: In [8]: %time sol1 = (sol_num.to_field() / den).to_Matrix() CPU times: user 369 ms, sys: 2.67 ms, total: 372 ms Wall time: 372 ms In [9]: %time sol2 = sol_num.to_Matrix() / den.as_expr() CPU times: user 104 ms, sys: 1.88 ms, total: 106 ms Wall time: 105 ms I won't show the full output because it is complicated but sol1 is in the canonical form of a ratio of expanded polynomials with gcd cancelled. Both solutions have a 1 in the leading entry as expected: In [14]: sol1[0] Out[14]: 1 In [15]: sol2[0] Out[15]: 1 The two matrices are not otherwise equal because the expressions in sol2 are not fully cancelled: In [16]: sol1 == sol2 Out[16]: False In [17]: sol1 == sol2.applyfunc(cancel) Out[17]: True The usual method for measuring expression size in SymPy is with "operation count". There is nothing particularly special about this as a measure but it is at least easy to compute because a method is ready made for computing it: In [19]: print(sol1.applyfunc(lambda e: e.count_ops())) Matrix([[0], [2472], [2030], [1823], [1795], [1683], [1672], [1611], [1610], [1783], [1664], [1602], [2059]]) In [20]: print(sol2.applyfunc(lambda e: e.count_ops())) Matrix([[0], [2595], [2119], [1891], [1871], [1751], [1743], [1679], [1672], [1859], [1735], [1671], [2140]]) In [22]: print(A.solve(b, method='LU').applyfunc(lambda e: e.count_ops())) Matrix([[45301], [22650], [766394], [110995], [632734], [56484], [442554], [30946], [221347], [110993], [56482], [30944], [22648]]) So both sol2 and sol1 are a lot simpler than what LU gives (on this measure) and they both compute much more quickly than GJ. Installing gmpy2 can help with timings for DomainMatrix in some cases (although probably does not make much difference in this example). In future it will be possible to speed this up a lot using python-flint. Specifically python-flint will provide fast multiplication and gcd for sparse multivariate polynomials which are the bottlenecks in the two steps shown above. -- Oscar -- You received this message because you are subscribed to the Google Groups "sympy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sympy+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/CAHVvXxQqwsfnmKn%2BvA3%2BLZ3AHytGnuC_to4%2BrFnnu6gg6WSqeg%40mail.gmail.com.