KmolYuan / Pyslvs-UI

An open source planar linkage mechanism simulation and mechanical synthesis system.
https://pyslvs-ui.readthedocs.io
GNU Affero General Public License v3.0
194 stars 37 forks source link

[ENH] Extract calculation functions from GUI #35

Closed juanma9613 closed 4 years ago

juanma9613 commented 4 years ago

Hi! First of all I would like to say that this system is amazing, it works great!

I would like to use this in my thesis with Reinforcement learning in which I am creating mechanism (1-DOF, derived from a fourbar linkage) from python and I need to get the trajectories of those mechanism as fast as possible (3trajectories /second). Is there any way of defining the mechanism in a script and get the trajectories there without using the GUI interface ?

This is how I define the linkages in python currently image

Thanks in advance for your help!

KmolYuan commented 4 years ago

Pyslvs has an expression phase and a backend calculator. Use calculator will be more efficient instead of building expression.

A four-bar linkage can be formed as

The calculation functions:

from math import radians
from pyslvs import plap, pllp, Coordinate

# p0x, p0y, p4x, p4y, input_theta, len0, len1, len2, len3 and gamma are known,
# where gamma is between len1 and len3
# All angles are in radians

p0 = Coordinate(p0x, p0y)
p4 = Coordinate(p4x, p4y)
p3_path = []
for i in range(360):
    input_theta = radians(i)
    p1 = plap(p0, len0, input_theta)
    p2 = pllp(p1, len1, len2, p4)
    p3 = plap(p1, len3, gamma, p2)
    p3_path.append((p3.x, p3.y))
KmolYuan commented 4 years ago

Pyslvs 20.07.0 will generate a script when selected "Export as Python script".

# Generate by Pyslvs 20.07.0.dev0
# Project "Crank rocker"

from pyslvs import parse_vpoints, t_config, data_collecting, expr_solving

if __name__ == '__main__':
    vpoints = parse_vpoints(
        "M["
        "J[R, color[Green], P[0, 0], L[ground, L1]], "
        "J[R, color[Green], P[12.92, 32.53], L[L1, L2]], "
        "J[R, color[Green], P[73.28, 67.97], L[L2, L3]], "
        "J[R, color[Green], P[33.3, 66.95], L[L2]], "
        "J[R, color[Green], P[90, 0], L[ground, L3]], "
        "]")
    exprs = t_config(vpoints, ((0, 1),))
    mapping = {n: f'P{n}' for n in range(len(vpoints))}
    data_dict, dof = data_collecting(exprs, mapping, vpoints)
    pos = expr_solving(exprs, mapping, vpoints, [0.])
    print(data_dict)
    print(f"DOF:{dof}")
    print(pos)

###

from pyslvs import plap, pllp

# a0, i0, l0, l1, l2, l3, p0, p4 are known

if __name__ == '__main__':
    p1 = plap(p0, l0, i0)
    p2 = pllp(p1, l1, l2, p4)
    p3 = plap(p1, l3, a0, p2)
juanma9613 commented 4 years ago

gif_singularities

Thank you for your quick reply! In the code I wrote based on this I'm facing the problem that when two bars are aligned (a singularity) the mechanism "jumps" to a different solution. That's why I was asking for a solver for trajectory specifically. Don't you have this problem when solving for different inputs angles whitout taking into account the current trajectory?

KmolYuan commented 4 years ago

The PLLP function has two solutions which are the intersections between two circles. ("inverse" argument can switch them) I'm recommend to use PLAP function to fix this problem with an angular constrain. (counter-clockwise is default)

That's why the target point 3 in previous case is solved by PLAP instead of PLLP function.

p3 = plap(p1, l3, a0, p2)  # CCW
p3 = plap(p1, l3, a0, p2, inverse=True)  # CW
# has two solutions (same as changing the order of p1 & p2)
p3 = pllp(p1, l3, l4, p2, inverse=False)
p3 = pllp(p1, l3, l4, p2, inverse=True)
juanma9613 commented 4 years ago

Thank you for all your help! I will change the way I'm defining my mechanisms to be compatible with pyslvs (pllp and plap) and I will let you know the results.

juanma9613 commented 4 years ago

Hi Yuan, at the end I decided to use the expr_solving() in order to get automatically the plap and pllp operations. But I am facing the same problem, some bars jump to other configuration:

jumping_bar

Below is the code I used to generate the animation, if you had any idea how to avoid this behavior I would really appreciate it.

from matplotlib import pyplot as plt
import numpy as np
from collections import defaultdict
from pyslvs import example_list, parse_vpoints, t_config, expr_solving

expr = "M[J[R, color[Green], P[0.0, 0.0], L[ground, L1, L3]],\
        J[R, color[Green], P[-0.30901699437494734, 0.9510565162951536], L[L1, L2, L4]],\
        J[R, color[Green], P[0.5656740963008081, 1.4357450529687716], L[L2, L8, L12, L14]],\
        J[R, color[Green], P[1.0, 0.0], L[ground, L5, L6, L10, L11]],\
        J[R, color[Green], P[2.2185821011191758, 1.1521984301835777], L[L5, L7, L9]],\
        J[R, color[Green], P[2.7614894807431654, -0.6424828860273869], L[L6, L7]],\
        J[R, color[Green], P[1.675674721495186, 2.946879746394542], L[L4, L8, L9]],\
        J[R, color[Green], P[-0.6529080048183675, 0.28354662278519394], L[L3, L11, L13, L15]],\
        J[R, color[Green], P[-1.1958153844423574, 2.0782279389961587], L[L12, L13]],\
        J[R, color[Green], P[1.108581475924798, -0.35893626324219297], L[L10, L14, L15]],]"
inputs  = ((0, 1),)
vpoints = parse_vpoints(expr)
exprs = t_config(vpoints, inputs)
mapping = {n: f'P{n}' for n in range(len(vpoints))}
results =[]
for i in np.linspace(0,360, 360, endpoint = False):
    try:
        results.append(expr_solving(exprs, mapping, vpoints, angles =[i]))
    except:
        pass
trajectory = np.array(results)

bars = defaultdict(list)
for idx, links in enumerate(vpoints):
    for link in links.links:
        bars[link].append(idx)

fig=plt.figure()
ax = fig.add_subplot(111)

ax.axis('equal')
ax.set_xlim([-10, 10])
ax.set_ylim([-10, 10]) 

for i in range(0,trajectory.shape[0], 4):
    ax.clear()
    x = trajectory[i,:,:]
    for k,v in list(bars.items())[::-1]:
        if k != 'ground':
            n1 = v[0]
            n2 = v[1]
            plt.plot(x[[n1, n2],0], x[[n1, n2],1])
            plt.axis('equal')
            ax.axis('equal')
    plt.xlim([-10, 10])
    plt.ylim([-10, 10]) 
    ax.set_title(str(i))
    fig.canvas.draw()
    plt.pause(0.001)
plt.pause(0.3)
KmolYuan commented 4 years ago

The above case can be simplified as following expression:

M[
    J[R, color[Green], P[0, 0], L[ground, L1]],
    J[R, color[Green], P[-30.9017, 95.1057], L[L1, L2]],
    J[R, color[Green], P[56.5674, 143.5745], L[L2, L3, L4]],
    J[R, color[Green], P[100, 0], L[ground, L5]],
    J[R, color[Green], P[221.8582, 115.2198], L[L5, L6]],
    J[R, color[Green], P[276.1489, -64.2483], L[L5]],
    J[R, color[Green], P[167.5675, 294.688], L[L6, L2]],
    J[R, color[Green], P[-65.2908, 28.3547], L[L7, ground]],
    J[R, color[Green], P[-119.5815, 207.8228], L[L3, L7]],
    J[R, color[Green], P[110.8581, -35.8936], L[L4, ground]]
]

The problematic loop is represented as following expression: (with 3->0 input link)

M[
    J[R, color[Green], P[56.5674, 143.5745], L[L3, L4]],
    J[R, color[Green], P[-65.2908, 28.3547], L[L7, ground]],
    J[R, color[Green], P[-119.5815, 207.8228], L[L3, L7]],
    J[R, color[Green], P[110.8581, -35.8936], L[L4, ground]]
]

Well, that truly is a bug of Pyslvs for handling parallel linkage. I'm forward this to #36.

KmolYuan commented 4 years ago

@juanma9613 Parallel linkage is a special case of the simulation. If your link lengths are not variables, please use direct function to solve. If not (just like dimensional synthesis), try to avoid this case.

(in nightly version) https://pyslvs-ui.readthedocs.io/en/latest/pyslvs-api/#ppp Here is the source code: https://github.com/KmolYuan/pyslvs/blob/master/pyslvs/tinycadlib.pyx#L38

juanma9613 commented 4 years ago

Thanks! I have one last question.

By direct function you mean that I can't use expr_solving, if that's the case how can I solve the position for different mechanism automatically?

What I do is to generate a lot of mechanisms "randomly" from a script in python, which gives me the bar lengths and the initial configurations for each mechanism, and after that I want to use your library just to simulate their trajectories.

@KmolYuan Thank you for all the help.

KmolYuan commented 4 years ago

The develop version functions can support this feature. (in pyslvs master branch) It includes a length condition to add the special solution.