mechanics - 3 member linkage with linear actuator, no connecting path found

113 views
Skip to first unread message

Aaron Shatters

unread,
Nov 30, 2017, 1:36:42 PM11/30/17
to sympy
I have a simple 3 member actuator where one of the members is a linear actuator.  I have a reference frame for each member (ground, arm, linear actuator).  I cannot seem to get sympy to use the point locations within multiple frames to serve as constraints that will determine the relative frame orientations.  For example, if I have a triangle where each side of the triangle has its own reference frame, the point positions alone should be able to determine the frame orientations, but I cannot figure out how to let sympy know this.  How would I do this? (this generic pattern should apply to any complex linkage with any number of closed geometries.)

I have included code:

from sympy import *
from sympy.physics.mechanics import dynamicsymbols, ReferenceFrame, Point


#
# I would like to develop the model for the below picture...
#
# There is an 'arm' and a linear 'actuator'.
#   - the a_pt of the arm is connected to the ground
#   - the y_pt of the actuator is connected to the ground
#   - the k_pt is on the arm and the actuator is connected to it
#
# The desire is to solve the relationship between the length of the actuator (a dynamic symbol)
# and the angle from vector AY to AB, as well as the magnitude of the Y-B vector
#
# Since a closed path triangle is created between A, K, and Y, we should be able to 'automagically'
# orient the frames with respect to each other, but I don't know how to do this without introducing
# a bunch of dummy dynamic symbols to represent the orientations between the frames.
#
#                            O <-- b_pt
#                          //
#                         //
#                  --->  //
#                 /     //
#              Arm     //
#              /       O <-- k_pt
#              --->  // \
#                   //   \
#                  //     \  <-- Actuator (extends/retracts)
#                 //       \
#                //         \
#      a_pt --> O============O <-- y_pt
#                 /  /  /  /
#                   Ground
#


# Symbols for the Ground Points
ground_coord_a
= symbols(('ground_coord_a_x', 'ground_coord_a_y'))
ground_coord_y
= symbols(('ground_coord_y_x', 'ground_coord_y_y'))


# Symbols for the Arm Points
arm_coord_a
= symbols(('arm_coord_a_x', 'arm_coord_a_y'))
arm_coord_k
= symbols(('arm_coord_k_x', 'arm_coord_k_y'))
arm_coord_b
= symbols(('arm_coord_b_x', 'arm_coord_b_y'))


# Symbol for the Actuator Cylinder Length
actuator_len
= dynamicsymbols('actuator_len')


# Create the reference frame for each member
ground_frame
= ReferenceFrame('ground_frame')
ground_frame_origin_pt
= Point('ground_frame_origin_pt')


arm_frame
= ReferenceFrame('arm_frame')
arm_frame_origin_pt
= Point('arm_frame_origin_pt')


actuator_frame
= ReferenceFrame('actuator_frame')


# Define all of the Points on the Linkage
a_pt
= Point('a_pt')
b_pt
= Point('b_pt')
k_pt
= Point('k_pt')
y_pt
= Point('y_pt')


# Place the points on the Ground
a_pt
.set_pos(ground_frame_origin_pt, ground_coord_a[0] * ground_frame.x + ground_coord_a[1] * ground_frame.y)
y_pt
.set_pos(ground_frame_origin_pt, ground_coord_y[0] * ground_frame.x + ground_coord_y[1] * ground_frame.y)


# Place the points on the Arm
a_pt
.set_pos(arm_frame_origin_pt, arm_coord_a[0] * arm_frame.x + arm_coord_a[1] * arm_frame.y)
b_pt
.set_pos(arm_frame_origin_pt, arm_coord_b[0] * arm_frame.x + arm_coord_b[1] * arm_frame.y)
k_pt
.set_pos(arm_frame_origin_pt, arm_coord_k[0] * arm_frame.x + arm_coord_k[1] * arm_frame.y)


# Place the points on the Actuator
k_pt
.set_pos(y_pt, actuator_len * actuator_frame.x + 0.0 * actuator_frame.y)


# Of course things work within the same frame
length_ab
= b_pt.pos_from(a_pt).magnitude()
print(length_ab)


# How about length Y to K?
length_yk
= k_pt.pos_from(y_pt).magnitude()
print(length_yk)


# But not Y to B?
try:
    length_yb
= b_pt.pos_from(y_pt).magnitude()
   
print(length_yb)
except ValueError as e:
   
print(e)


# Ok, I can add a redundant symbol to orient the ground frame to the arm frame...


# Dynamic Symbols (time variant)
angle_ground_to_arm
= dynamicsymbols('angle_ground_to_arm')
arm_frame
.orient(ground_frame, 'Axis', (angle_ground_to_arm, ground_frame.z))


# Now can I get Y to B?  still no
try:
    length_yb
= b_pt.pos_from(y_pt).magnitude()
   
print(length_yb)
except ValueError as e:
   
print(e)


# Ok, do I need to orient the actuator to the ground as well?
angle_ground_to_actuator
= dynamicsymbols('angle_ground_to_actuator')
actuator_frame
.orient(ground_frame, 'Axis', (angle_ground_to_actuator, ground_frame.z))


# Now can I get Y to B?
# Yes, but now I have an expression that involves 'angle_ground_to_actuator(t)' as well as 'actuator_len(t)'
#   which is redundant.  I don't actually have to degrees of freedom or actuators.
try:
    length_yb
= b_pt.pos_from(y_pt).magnitude()
   
print(length_yb)
except ValueError as e:
   
print(e)


# How do I make sympy know about the closed path so that it can 'infer'
# the frame orientations based on that closed path?  (Without adding redundant dummy dynamic symbols)

Jason Moore

unread,
Dec 1, 2017, 11:05:31 AM12/1/17
to sy...@googlegroups.com
You will need to define an equation that defines the kinematic loop constraint (holonomic constraint). This will look like 0 = f(coordinates, lengths). If you can then solve this equation for the dependent coordinate, then you can construct all of your position vectors in terms of only the independent coordinates, using the solution to the constraint equation to substitute in for the dep coordinate. If you can't analytically solve the loop constraint (often the case), then you need to define all your position vectors in terms of both the independent and dependent coordinates. You can then take the time derivative of f to get a pseudo velocity constraint which you can include as an extra kinematic differential equation. Depending on what you want to do (forward sim, initial value problem, linearization, etc) there are different things to do with this equation.

--
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+unsubscribe@googlegroups.com.
To post to this group, send email to sy...@googlegroups.com.
Visit this group at https://groups.google.com/group/sympy.
To view this discussion on the web visit https://groups.google.com/d/msgid/sympy/639fdd0f-a35c-44ee-b57a-405f622bc205%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Aaron Shatters

unread,
Dec 7, 2017, 4:58:56 PM12/7/17
to sympy
I have added a constraint in the form of law of cosines as follows:
# Build the kinematic loop constraint (Law of Cosines)
ak_vec = k_pt.pos_from(a_pt)
ak_len
= ak_vec.magnitude()
ay_vec
= y_pt.pos_from(a_pt)
ay_len
= ay_vec.magnitude()
kay_angle
= acos((ak_len**2 + ay_len**2 - actuator_len**2)/(2*ak_len*ay_len))

# Orient the arm frame to the ground frame
arm_frame.orient(ground_frame, 'Axis', (kay_angle, ground_frame.z))

This got be a bit farther, however, when I try to calculate the length from y to b, it throws (No Connecting Path found between actuator_frame and arm_frame):
length_yb = b_pt.pos_from(y_pt).magnitude()

I gave an orientation between ground_frame and arm_frame... why can it not go through that path instead since the y_pt exists in both the ground_frame and the actuator_frame?

Thanks for the help,
Aaron


To unsubscribe from this group and stop receiving emails from it, send an email to sympy+un...@googlegroups.com.

Aaron Shatters

unread,
Dec 11, 2017, 1:15:39 PM12/11/17
to sympy
The pos_from Point method selects the shortest number of points to traverse to get from self to other even is there is no connecting 'dcm' path to get from beginning to end.  In my case, there is another 'dcm connected' path, but it has more points, so it is discarded in _pdict_list.

Should the pos_from (and underlying _pdict_list) method only return a Vector that can be expressed in any single frame?  (Instead of a vector that cannot be expressed in any single frame)

Thanks,
Aaron

Aaron Shatters

unread,
Dec 11, 2017, 5:03:27 PM12/11/17
to sympy
How about this (edit if vector/point.py):

def _pdict_lists(self, other, num):
   
"""Creates a list of lists from self to other using _pos_dict. """
    outlist = [[self]]
    oldlist
= [[]]
   
while outlist != oldlist:
        oldlist
= outlist[:]
       
for i, v in enumerate(outlist):
            templist
= v[-1]._pdlist[num].keys()
           
for i2, v2 in enumerate(templist):
               
if not v.__contains__(v2):
                    littletemplist
= v + [v2]
                   
if not outlist.__contains__(littletemplist):
                        outlist
.append(littletemplist)
   
for i, v in enumerate(oldlist):
       
if v[-1] != other:
            outlist
.remove(v)
    outlist
.sort(key=len)
   
if len(outlist) != 0:
       
return outlist
   
raise ValueError('No Connecting Path found between ' + other.name +
                     
' and ' + self.name)

def _pdict_list(self, other, num):
   
"""Creates a list from self to other using _pos_dict. """
    return self._pdict_lists(other, num)[0]

def pos_from(self, otherpoint, singleframe=False, frame=None, variables=False):
   
"""Returns a Vector distance between this Point and the other Point.

    Parameters
    ==========

    otherpoint : Point
        The otherpoint we are locating this one relative to
    singleframe : boolean
        Expresses the resulting vector in a single frame
    frame : ReferenceFrame
        Expresses the resulting vector in this frame
    variables : boolean
        If True, the coordinate symbols(if present) in the resulting Vector
        are re-expressed in terms the resulting frame (if singleframe is True
        or frame is given)

    Examples
    ========

    >>> from sympy.physics.vector import Point, ReferenceFrame
    >>> N = ReferenceFrame('N')
    >>> p1 = Point('p1')
    >>> p2 = Point('p2')
    >>> p1.set_pos(p2, 10 * N.x)
    >>> p1.pos_from(p2)
    10*N.x

    """
    if frame is not None:
        _check_frame
(frame)

    last_error
= None

    plists = self._pdict_lists(otherpoint, 0)

   
for plist in plists:
        outvec
= Vector(0)
       
for i in range(len(plist) - 1):
            outvec
+= plist[i]._pos_dict[plist[i + 1]]

       
# 0 Vector
        if len(outvec.args) == 0:
           
return outvec

       
try:
           
if frame is not None:
               
# Express in the given frame
                outvec = outvec.express(frame, variables=variables)
           
elif singleframe:
               
# Express in a single frame
                outvec = outvec.express(outvec.args[0][1], variables=variables)
           
return outvec

       
except ValueError as e:
            last_error
= e
           
continue

    raise last_error


Aaron Meurer

unread,
Dec 11, 2017, 5:31:29 PM12/11/17
to sy...@googlegroups.com
Can you submit this as a pull request? Even if it's just an inprogress
patch it's much easier to comment on a change in a pull request
because GitHub shows the diff, and Travis runs the test suite on the
change.

Aaron Meurer
> --
> 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+un...@googlegroups.com.
> To post to this group, send email to sy...@googlegroups.com.
> Visit this group at https://groups.google.com/group/sympy.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/sympy/24c366d0-6386-4603-abe3-47a74be36a32%40googlegroups.com.

Aaron Shatters

unread,
Dec 12, 2017, 3:34:54 PM12/12/17
to sympy
Reply all
Reply to author
Forward
0 new messages