Home > Enterprise >  Sympy: possible to make the solver return as soon as a solution is found (before all solutions are f
Sympy: possible to make the solver return as soon as a solution is found (before all solutions are f

Time:08-24

I'm working on a project whereby I am coding a virtual robot arm. Within the script I generate roughly 1350 targets [x, y, z] for the robot's end-effector and then I use the coordinates to calculate 3 angles for the robot's joints. I'm using sympy.solve:

def solve_robot(coordinate_x, coordinate_y):

    try:
        w, z = sym.symbols('w, z')

        # Sympy solver cannot handle trig functions that contain a symbolic and a float.
        # Have to round to integer to work around. (Introduces rounding error to calculation).
        angle = round(radians(90))

        eq1 = sym.Eq(60 * sym.sin(w)   80 * sym.cos(z - angle), coordinate_x)
        eq2 = sym.Eq(37.03   60 * sym.cos(w) - 80 * sym.sin(z - angle) - 20, coordinate_y)

        result = sym.solve([eq1, eq2], (w, z))

        if len(result) > 0:                     #
            omega, beta = result[0]             #
            omega = round(degrees(omega), 2)    # If boom 1 angle smaller than -26 degrees then a collision
            if omega < -26.0:                   # with the robot body will occur. Thus, output invalid result.
                result = []                     #

        return result
    except:  # (To handle problems) If solver is unable to solve, return empty.
        return []

It takes rougly 7 minutes to complete all calculations. I've tried the flags such as manual=True, simplify=False, but it still takes a long time. Almost all my solutions have 2 solutions, is there a way to force sympy to stop after finding one solution? So theoretically the code will be twice as fast?

Edit: I needed to round the angle otherwise this error occurs: TypeError: can't convert -oo to int

CodePudding user response:

I'm not sure why you are rounding radians(90). If you want the exact angle 90 degrees in radians then use SymPy's pi/2. Here are your equations:

from sympy import *

x, y, w, z = symbols('x, y, w, z')

angle = pi/2

eq1 = Eq(60 * sin(w)   80 * cos(z - angle), x)
eq2 = Eq(37.03   60 * cos(w) - 80 * sin(z - angle) - 20, y)

eqs = [eq1, eq2]

The pi/2 naturally simplifies so the equations become:

In [17]: eq1
Out[17]: 60⋅sin(w)   80⋅sin(z) = x

In [18]: eq2
Out[18]: 60⋅cos(w)   80⋅cos(z)   17.03 = y

We can solve this kind of system using Groebner bases if we convert to polynomials with the substitutions sw = sin(w), cw = cos(w) etc.:

In [23]: sw, cw, sz, cz = symbols('sw, cw, sz, cz')

In [24]: rep = {sin(w): sw, cos(w): cw, sin(z): sz, cos(z): cz}

In [25]: eqs2 = [eq.subs(rep) for eq in eqs]

In [26]: eqs2
Out[26]: [60⋅sw   80⋅sz = x, 60⋅cw   80⋅cz   17.03 = y]

We now have two equations for four unknowns but we know that we can make new polynomial equations because sin(x)**2 cos(x)**2 = 1:

In [27]: eqs3 = eqs2   [Eq(sz**2   cz**2, 1), Eq(sw**2   cw**2, 1)]

In [28]: for eq in eqs3: pprint(eq)
60⋅sw   80⋅sz = x
60⋅cw   80⋅cz   17.03 = y
  2     2    
cz    sz  = 1
  2     2    
cw    sw  = 1

We are going to compute a Groebner basis but for that it is much better to have exact rational numbers rather than floats:

In [30]: eqs4 = [nsimplify(eq) for eq in eqs3]

In [31]: eqs4
Out[31]: 
⎡                                   1703        2     2        2     2    ⎤
⎢60⋅sw   80⋅sz = x, 60⋅cw   80⋅cz   ──── = y, cz    sz  = 1, cw    sw  = 1⎥
⎣                                   100                                   ⎦

Now we can compute a Groebner basis for these polynomials

In [35]: gb = groebner(eqs4, [sw, sz, cw, cz])

In [36]: for eq in gb: pprint(eq)
                           2     2                    
   ⎛1703   4⋅y⎞           x     y    1703⋅y   30900209
cz⋅⎜──── - ───⎟   sw⋅x - ───   ─── - ──────   ────────
   ⎝ 75     3 ⎠          120   120    6000    1200000 
                         2     2                    
   ⎛    1703⎞           x     y    1703⋅y   30900209
cz⋅⎜y - ────⎟   sz⋅x - ─── - ───   ────── - ────────
   ⎝    100 ⎠          160   160    8000    1600000 
     4⋅cz   y    1703
cw   ──── - ──   ────
      3     60   6000
                                      ⎛   2           2    3         2                           ⎞ 
  2 ⎛ 2    2   1703⋅y   2900209⎞      ⎜  x ⋅y   1703⋅x    y    5109⋅y    36700627⋅y   52623055927⎟ 
cz ⋅⎜x    y  - ──────   ───────⎟   cz⋅⎜- ────   ─────── - ──   ─────── - ──────────   ───────────⎟ 
    ⎝            50      10000 ⎠      ⎝   80      8000    80     8000      800000       80000000 ⎠ 

     4     2  2         2               2      4          3             2                          
    x     x ⋅y    1703⋅x ⋅y   97099791⋅x      y     1703⋅y    36700627⋅y    52623055927⋅y   9548229
  ─────   ───── - ───────── - ───────────   ───── - ───────   ─────────── - ─────────────   ───────
  25600   12800     640000     128000000    25600    640000    128000000      6400000000     256000

        
16243681
────────
0000000 

The final equation is a quadratic in cz. The first 3 are linear in sw, sz and cw (although singular at x=0 which would need to be handled specially). We can therefore compute the solutions like this:

In [40]: gb = groebner(eqs4, [sw, sz, cw, cz])

In [41]: [lsol] = linsolve(gb[:-1], [sw, sz, cw])

In [42]: cz1, cz2 = roots(gb[-1], cz)

In [43]: sol1 = lsol.subs(cz, cz1)   (cz1,)

In [44]: sol2 = lsol.subs(cz, cz2)   (cz2,)

These two expressions sol1 and sol2 are the general form of the solution in terms of the parameters x and y. You can substitute some particular values for those to get numeric answers:

In [49]: sol1.subs({x:100, y:100})
Out[49]: 
⎛704201045    8297⋅√4477025624836319  8297⋅√4477025624836319   984201045   5⋅√4477025624836319   11
⎜────────── - ──────────────────────, ──────────────────────   ──────────, ───────────────────   ──
⎝1013041254       2026082508000           2701443344000        1350721672       1013041254       20

68551214073  1633183214073   5⋅√4477025624836319⎞
───────────, ───────────── - ───────────────────⎟
26082508000  2701443344000        1350721672    ⎠

In [50]: [s.n(3) for s in sol1.subs({x:100, y:100})]
Out[50]: [0.421, 0.934, 0.907, 0.357]

In [51]: [s.n(3) for s in sol2.subs({x:100, y:100})]
Out[51]: [0.969, 0.523, 0.247, 0.852]

Of course these are the answers for sz etc but you want z itself. We can recover z and w using atan2 and then use lambdify for faster evaluation:

In [52]: swsol, szsol, cwsol, czsol = sol1

In [54]: zsol = atan2(szsol, czsol)

In [55]: wsol = atan2(swsol, cwsol)

In [56]: f = lambdify((x, y), (zsol, wsol))

In [57]: f(100, 100)
Out[57]: (1.2058759278150635, 0.4346913079154993)

In [58]: %time f(100, 100)
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 239 µs
Out[58]: (1.2058759278150635, 0.4346913079154993)

In [59]: %time f(102, 95)
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 256 µs
Out[59]: (1.2700124590995348, 0.4406497868037883)

Now you can see that the answer for any given x and y can be computed in less than a millisecond.

In fact you can do all 1350 points in a few milliseconds:

In [60]: x = y = np.linspace(50, 100, 1350)

In [61]: %time f(x, y)
CPU times: user 4 ms, sys: 0 ns, total: 4 ms
Wall time: 3.41 ms
Out[61]: 
(array([1.82911048, 1.82883992, 1.82856907, ..., 1.20763722, 1.20675767,
        1.20587593]),
 array([-0.47322886, -0.47263842, -0.47204816, ...,  0.43240847,
         0.43354848,  0.43469131]))
  • Related