Skip to content
This repository

LagrangesMethod class for sympy.physics.mechanics. #1460

Merged
merged 6 commits into from over 1 year ago

9 participants

Angadh Nanjangud Don't Add Me To Your Organization a.k.a The Travis Bot Stefan Krastanov Ondřej Čertík Jason K. Moore Gilbert Gede Dale Lukas Peterson Aaron Meurer Christopher Smith
Angadh Nanjangud

The docstrings will likely need to be revamped.

Christopher Smith smichr commented on the diff August 03, 2012
sympy/physics/mechanics/lagrange.py
((13 lines not shown))
  13
+    arguments. The Lagrangian multipliers are automatically generated and are
  14
+    equal in number to the constraint equations.Similarly if there are any
  15
+    non-conservative forces, they can be supplied in a list along with a
  16
+    ReferenceFrame. This is discussed further in the __init__ method.
  17
+
  18
+    Attributes
  19
+    ==========
  20
+
  21
+    mass_matrix : Matrix
  22
+        The system's mass matrix
  23
+
  24
+    forcing : Matrix
  25
+        The system's forcing vector
  26
+
  27
+    mass_matrix_full : Matrix
  28
+        The "mass matrix" for the qdot's, qdoubledot's, and the
2
Christopher Smith Collaborator
smichr added a note August 03, 2012

in my experience, mixing single and double quotes in a docstring generates sphinx errors...let's see if this happens with your request.

Angadh Nanjangud
angadhn added a note August 03, 2012

This hasn't been an issue in the "Kane" class, so I presumed it wouldn't be a problem here but that could've been an erroneous assumption.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((64 lines not shown))
  64
+
  65
+        self._L = sympify(Lagrangian)
  66
+        self.eom = None #initializing the eom Matrix
  67
+        self._m_cd = Matrix([]) #Mass Matrix of differentiated coneqs
  68
+        self._m_d = Matrix([]) #Mass Matrix of dynamic equations
  69
+        self._f_cd = Matrix([]) #Forcing part of the diff coneqs
  70
+        self._f_d = Matrix([]) #Forcing part of the dynamic equations
  71
+        self.lam_coeffs = Matrix([]) #Initializing the coeffecients matrix of lams
  72
+
  73
+        self.forcelist = forcelist
  74
+        self.inertial = frame
  75
+
  76
+        self.lam_vec = Matrix([])
  77
+
  78
+
  79
+        #What used to be the coords method
1
Christopher Smith Collaborator
smichr added a note August 03, 2012

if it's not there anymore then this comment will have no meaning for anyone looking at this in the future, so it can either be deleted or changed to indicate what you are doing, perhaps?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((73 lines not shown))
  73
+        self.forcelist = forcelist
  74
+        self.inertial = frame
  75
+
  76
+        self.lam_vec = Matrix([])
  77
+
  78
+
  79
+        #What used to be the coords method
  80
+
  81
+        q_list = list(q_list)
  82
+        if not isinstance(q_list, list):
  83
+            raise TypeError('Generalized coords. must be supplied in a list')
  84
+        self._q = q_list
  85
+        self._qdots = [diff(i, dynamicsymbols._t) for i in self._q]
  86
+        self._qdoubledots = [diff(i, dynamicsymbols._t) for i in self._qdots]
  87
+
  88
+        #What used to be the constraints method
2
Christopher Smith Collaborator
smichr added a note August 03, 2012

ditto about what used to be

Angadh Nanjangud
angadhn added a note August 03, 2012

Thanks! I forgot to edit those lines. I had left those in as notes for myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((169 lines not shown))
  169
+                        raise TypeError('First entry in force pair is a point'
  170
+                                        ' or frame')
  171
+
  172
+        else:
  173
+            term4 = zeros(n,1)
  174
+
  175
+        self.eom = term1 - term2 -term3 -term4
  176
+
  177
+        #The mass matrix is generated by the following
  178
+        self._m_d = (self.eom).jacobian(qdd)
  179
+
  180
+        return self.eom
  181
+
  182
+    @property
  183
+    def mass_matrix(self):
  184
+        # Returns the mass matrix, which is augmented by the Lagrange
2
Christopher Smith Collaborator
smichr added a note August 03, 2012

convert to docstring instead of comments

Angadh Nanjangud
angadhn added a note August 03, 2012

Once again, I was replicating the presentation of "Kane". I have changed this now though. Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((187 lines not shown))
  187
+        # an n X n matrix is returned.
  188
+        # If there are 'n' generalized coordinates and 'm' constraint equations
  189
+        # have been supplied during initialization then an n X (n+m) matrix is
  190
+        # returned. The (n + m - 1)th and (n + m)th columns contain the
  191
+        # coeffecients of the lagrange multipliers.
  192
+
  193
+        if self.eom == None:
  194
+            raise ValueError('Need to compute the equations of motion first')
  195
+        if len(self.lam_coeffs) != 0:
  196
+            return (self._m_d).row_join((self.lam_coeffs).transpose())
  197
+        else:
  198
+            return self._m_d
  199
+
  200
+    @property
  201
+    def mass_matrix_full(self):
  202
+        n = len(self._q)
2
Christopher Smith Collaborator
smichr added a note August 03, 2012

docstring?

Angadh Nanjangud
angadhn added a note August 03, 2012

The docstring I had written was a little contorted so I decided to leave it blank. I have added a docstring now though. Just waiting for the decision on the '*'/PEP 8 comments to push again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christopher Smith smichr commented on the diff August 03, 2012
sympy/physics/mechanics/lagrange.py
((205 lines not shown))
  205
+        #THE FIRST TWO ROWS OF THE MATRIX
  206
+        row1 = eye(n).row_join(zeros(n,n))
  207
+        row2 = zeros(n,n).row_join(self.mass_matrix)
  208
+        if self.coneqs != None:
  209
+            m = len(self.coneqs)
  210
+            I = eye(n).row_join(zeros(n,n+m))
  211
+            below_eye = zeros(n+m,n)
  212
+            A = (self.mass_matrix).col_join((self._m_cd).row_join(zeros(m,m)))
  213
+            below_I = below_eye.row_join(A)
  214
+            return I.col_join(below_I)
  215
+        else:
  216
+           A = row1.col_join(row2)
  217
+           return A
  218
+
  219
+    @property
  220
+    def forcing(self):
2
Christopher Smith Collaborator
smichr added a note August 03, 2012

docstring?

@angadhn Check the formatting for single line docstrings that shows up elsewhere and try to match that:
"""Some descriptive sentence."""

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christopher Smith smichr commented on the diff August 03, 2012
sympy/physics/mechanics/lagrange.py
((227 lines not shown))
  227
+
  228
+        if self.coneqs != None:
  229
+            lam = self.lam_vec
  230
+            lamzero = dict(zip(lam, [0] * len(lam)))
  231
+
  232
+            #The forcing terms from the eoms
  233
+            self._f_d = -((self.eom).subs(qddzero)).subs(lamzero)
  234
+
  235
+        else:
  236
+            #The forcing terms from the eoms
  237
+            self._f_d = -(self.eom).subs(qddzero)
  238
+
  239
+        return self._f_d
  240
+
  241
+    @property
  242
+    def forcing_full(self):
1
Christopher Smith Collaborator
smichr added a note August 03, 2012

docstring?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((235 lines not shown))
  235
+        else:
  236
+            #The forcing terms from the eoms
  237
+            self._f_d = -(self.eom).subs(qddzero)
  238
+
  239
+        return self._f_d
  240
+
  241
+    @property
  242
+    def forcing_full(self):
  243
+        if self.eom == None:
  244
+            raise ValueError('Need to compute the equations of motion first')
  245
+        if self.coneqs != None:
  246
+            return (Matrix(self._qdots)).col_join((self.forcing).col_join(self._f_cd))
  247
+        else:
  248
+            return (Matrix(self._qdots)).col_join(self.forcing)
  249
+
  250
+    def rhs(self, method):
1
Christopher Smith Collaborator
smichr added a note August 03, 2012

convert comments to docstring

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Christopher Smith smichr commented on the diff August 03, 2012
sympy/physics/mechanics/lagrange.py
((211 lines not shown))
  211
+            below_eye = zeros(n+m,n)
  212
+            A = (self.mass_matrix).col_join((self._m_cd).row_join(zeros(m,m)))
  213
+            below_I = below_eye.row_join(A)
  214
+            return I.col_join(below_I)
  215
+        else:
  216
+           A = row1.col_join(row2)
  217
+           return A
  218
+
  219
+    @property
  220
+    def forcing(self):
  221
+        # Returns the forcing vector
  222
+        if self.eom == None:
  223
+            raise ValueError('Need to compute the equations of motion first')
  224
+
  225
+        qdd = self._qdoubledots
  226
+        qddzero = dict(zip(qdd, [0] * len(qdd)))
5
Christopher Smith Collaborator
smichr added a note August 03, 2012

PEP8, I believe, would suggest removing spaces around *

Jason K. Moore Collaborator

Are you sure?

http://www.python.org/dev/peps/pep-0008/#other-recommendations

Seems like spaces around these operators is preferred.

Ondřej Čertík Owner
certik added a note August 07, 2012

Yes, it seems to me as well, that PEP8 says you should have spaces around "*". In either case, I would use whatever looks more readable on the case by case basis, in this PR it seems that having spaces is more readable.

Aaron Meurer Owner
asmeurer added a note August 07, 2012

We usually differ from PEP 8 in SymPy for * and ** because it makes reading expressions like 3*x**2 + 2*x**4 easier. Here I would say it's justified, though really either way looks fine.

Christopher Smith Collaborator
smichr added a note August 10, 2012

I reread PEP8 and see that I was wrong about the * -- I was running under the sympy-modifed version (that Aaron cites). So although we are doing exactly what PEP8 says not to do I think it looks better, too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((158 lines not shown))
  158
+                        ' or tuples')
  159
+            term4 = zeros(len(qd), 1)
  160
+            for i,v in enumerate(qd):
  161
+                for j,w in enumerate(forcelist):
  162
+                    if isinstance(w[0], ReferenceFrame):
  163
+                        speed = w[0].ang_vel_in(N)
  164
+                        term4[i] += speed.diff(v, N) & w[1]
  165
+                    if isinstance(w[0], Point):
  166
+                        speed = w[0].vel(N)
  167
+                        term4[i] += speed.diff(v, N) & w[1]
  168
+                    else:
  169
+                        raise TypeError('First entry in force pair is a point'
  170
+                                        ' or frame')
  171
+
  172
+        else:
  173
+            term4 = zeros(n,1)
1
Christopher Smith Collaborator
smichr added a note August 03, 2012

space afater comma

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((119 lines not shown))
  119
+            if not isinstance(coneqs, list):
  120
+                raise TypeError('Enter the constraint equations in a list')
  121
+
  122
+            o = len(coneqs)
  123
+
  124
+            #Creating the multipliers
  125
+            self.lam_vec = Matrix(dynamicsymbols('lam1:' + str(o+1)))
  126
+
  127
+            #Extracting the coeffecients of the multipliers
  128
+            coneqs_mat = Matrix(coneqs)
  129
+            qd = self._qdots
  130
+            self.lam_coeffs = -coneqs_mat.jacobian(qd)
  131
+
  132
+            #Determining the third term in Lagrange's EOM
  133
+            #term3 = ((self.lam_vec).transpose() * self.lam_coeffs).transpose()
  134
+            term3 = self.lam_coeffs.transpose() * self.lam_vec
1
Christopher Smith Collaborator
smichr added a note August 03, 2012

remove here (and throughout) spaces around * as per PEP8

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((110 lines not shown))
  110
+        #Determining the second term in Lagrange's EOM
  111
+        term2 = (L.jacobian(q)).transpose()
  112
+
  113
+        #term1 and term2 will be there no matter what so leave them as they are
  114
+
  115
+        if self.coneqs != None:
  116
+            coneqs = self.coneqs
  117
+            #If there are coneqs supplied then the following will be created
  118
+            coneqs = list(coneqs)
  119
+            if not isinstance(coneqs, list):
  120
+                raise TypeError('Enter the constraint equations in a list')
  121
+
  122
+            o = len(coneqs)
  123
+
  124
+            #Creating the multipliers
  125
+            self.lam_vec = Matrix(dynamicsymbols('lam1:' + str(o+1)))
1
Christopher Smith Collaborator
smichr added a note August 03, 2012

add space around + in o+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged b3a58b0a into 9625918).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: 9625918
branch hash: b3a58b0

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYtLYiDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYxqMjDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYs7YiDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYq48iDA

Automatic review by SymPy Bot.

Ondřej Čertík
Owner

Thanks for opening the pull request. So the first priority is regular tests. Either add them to some file in sympy/physics/mechanics/tests, or create a new one. Then some doctests showing how to use it in the docstring of the various methods and also what Chris mentioned above.

The regular tests are the most important.

Jason K. Moore moorepants commented on the diff August 03, 2012
sympy/physics/mechanics/lagrange.py
... ...
@@ -0,0 +1,253 @@
  1
+__all__ = ['LagrangesMethod']
  2
+
  3
+from sympy import diff, zeros, Matrix, eye, sympify
  4
+from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point)
  5
+
  6
+class LagrangesMethod(object):
4
Jason K. Moore Collaborator

the Kane method class isn't called KanesMethod, so why do that here? It's just more to type. There are no other naming conflicts in this module are there?

Angadh Nanjangud
angadhn added a note August 03, 2012

So, @hazelnusse, @gilbertgede and I talked about this in the lab yesterday. We talked about how other methods of deriving EOMs are referred to i.e. 'Kane's method', 'Hamilton's method', etc and how maybe the "Kane" class could be renamed to "KanesMethod" possibly. Also, just calling it "Lagrange" might be ambiguous.

Jason K. Moore Collaborator

Ok great, we should change the name of the Kane Class too then.

@moorepants I will take care of that in another PR that I am going to open soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 8c26b504 into 9625918).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@angadhn: Please fix the test failures.

Test command: setup.py test
master hash: 7d92368
branch hash: 8c26b50

Interpreter 1: :red_circle: There were test failures.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYsKsjDA

Interpreter 2: :red_circle: There were test failures.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYsd0iDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYr6sjDA

Build HTML Docs: :red_circle: There were test failures.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY580iDA

Automatic review by SymPy Bot.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 3092f5e1 into 7d92368).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@angadhn: Please fix the test failures.

Test command: setup.py test
master hash: 1627b32
branch hash: 3092f5e

Interpreter 1: :red_circle: There were test failures.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYz6MjDA

Interpreter 2: :red_circle: There were test failures.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYle0iDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYoIwjDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYjvAhDA

Automatic review by SymPy Bot.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 619dcc6d into 65b6582).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 84a5ea6f into 65b6582).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@angadhn: Please fix the test failures.

Test command: setup.py test
master hash: 98cc80f
branch hash: 84a5ea6

Interpreter 1: :red_circle: There were test failures.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY8P8hDA

Interpreter 2: :red_circle: There were test failures.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYjbMjDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY7_8hDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYu6sjDA

Automatic review by SymPy Bot.

sympy/physics/mechanics/tests/test_lagrange.py
((102 lines not shown))
  102
+    # of the string fixed frame is redefined as 'LagrangesMethod' doesn't require
  103
+    # generalized speeds, per se. (Lagrangian mechanics requires 'simple'
  104
+    # generalized speeds)
  105
+    A.orientnew('A', 'Axis', [q, N.z])
  106
+    A.set_ang_vel(N, qd *A.z)
  107
+    P.v2pt_theory(O,N,A)
  108
+    T = 1/2.0 * m * P.vel(N) & P.vel(N) # T is the kinetic energy of the system
  109
+    V = - m * g * l * cos(q) # V is the potential energy of the system
  110
+    L = T - V # L is the Lagrangian
  111
+
  112
+    # The 'LagrangesMethod' class is invoked and the equations of motion are generated.
  113
+    l = LagrangesMethod(L, [q])
  114
+    l.lagranges_equations()
  115
+    l.rhs("GE")
  116
+
  117
+    # Finally, we the results of both methods and it's seen that they are
2
Ondřej Čertík Owner
certik added a note August 07, 2012

the word "compare" is missing?

Angadh Nanjangud
angadhn added a note August 07, 2012

thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((22 lines not shown))
  22
+        The system's mass matrix
  23
+
  24
+    forcing : Matrix
  25
+        The system's forcing vector
  26
+
  27
+    mass_matrix_full : Matrix
  28
+        The "mass matrix" for the qdot's, qdoubledot's, and the
  29
+        lagrange multipliers (lam)
  30
+
  31
+    forcing_full : Matrix
  32
+        The forcing vector for the qdot's, qdoubledot's and
  33
+        lagrange multipliers (lam)
  34
+
  35
+    rhs : Matrix
  36
+        Solves for the states (i.e. q's, qdot's, and multipliers)
  37
+
2
Ondřej Čertík Owner
certik added a note August 07, 2012

Can you put a simple doctest here showing how to use the class?

Angadh Nanjangud
angadhn added a note August 07, 2012

I've added it now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Ondřej Čertík
Owner

You seem to have some trailing whitespace problems there: http://travis-ci.org/#!/sympy/sympy/jobs/2056371

sympy/physics/mechanics/lagrange.py
((75 lines not shown))
  75
+
  76
+        self.lam_vec = Matrix([])
  77
+
  78
+
  79
+        # Creating the qs, qdots and qdoubledots
  80
+
  81
+        q_list = list(q_list)
  82
+        if not isinstance(q_list, list):
  83
+            raise TypeError('Generalized coords. must be supplied in a list')
  84
+        self._q = q_list
  85
+        self._qdots = [diff(i, dynamicsymbols._t) for i in self._q]
  86
+        self._qdoubledots = [diff(i, dynamicsymbols._t) for i in self._qdots]
  87
+
  88
+        self.coneqs = coneqs
  89
+
  90
+    def lagranges_equations(self):
1

Maybe form_lagranges_equations instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
sympy/physics/mechanics/lagrange.py
((130 lines not shown))
  130
+            self.lam_coeffs = -coneqs_mat.jacobian(qd)
  131
+
  132
+            #Determining the third term in Lagrange's EOM
  133
+            #term3 = ((self.lam_vec).transpose() * self.lam_coeffs).transpose()
  134
+            term3 = self.lam_coeffs.transpose() * self.lam_vec
  135
+
  136
+            #Taking the time derivative of the constraint equations
  137
+            diffconeqs = [diff(i, dynamicsymbols._t) for i in coneqs]
  138
+
  139
+            #Extracting the coeffecients of the qdds from the diff coneqs
  140
+            diffconeqs_mat = Matrix(diffconeqs)
  141
+            qdd = self._qdoubledots
  142
+            self._m_cd = diffconeqs_mat.jacobian(qdd)
  143
+
  144
+            #The remaining terms i.e. the 'forcing' terms in diff coneqs
  145
+            qddzero = dict(zip(qdd, [0] * len(qdd)))
1

Isn't len(qdd) equal to n, which you use elsewhere?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Angadh Nanjangud

So it appears that I have mistakenly rebased a couple of my branches and so my commits (including the most recent one on this page) aren't showing up correctly. Any way I can fix this?

Angadh Nanjangud

Actually, it's fine here. Never mind.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 758c380b into 0d88b25).

Ondřej Čertík
Owner

You still have a trailing whitespace in lagrange.py line 49, see the Travis test results:

AssertionError: File contains trailing whitespace: /home/vagrant/virtualenv/python2.6/lib/python2.6/site-packages/sympy/physics/mechanics/lagrange.py, line 49.
Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@angadhn: Please fix the test failures.

Test command: setup.py test
master hash: 0d88b25
branch hash: 758c380

Interpreter 1: :red_circle: There were test failures.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYwqsjDA

Interpreter 2: :red_circle: There were test failures.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYou0iDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY-JsjDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYkZQjDA

Automatic review by SymPy Bot.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 9d7d8b3b into 0d88b25).

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged e4d9026f into 0d88b25).

Jason K. Moore
Collaborator

@certik @Krastanov

So this pull request may take a while to get in. It implements a complicated method and the testing is still weak. @angadhn pulled earlier than usual so that we can comment on the code easily and discuss some of the issues. Is there anyway not to get bombarded by the bots constantly? I personally find it stressful. We probably only need the bot testing when the PR is closer to being mergable.

Stefan Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: 0d88b25
branch hash: e4d9026

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYoPAhDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYjcYiDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY3KMjDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY5tUiDA

Automatic review by SymPy Bot.

Angadh Nanjangud

So the docs commit came into Lagrange because I thought that the Lagrange class was complete w.r.t. the tests; they are identical in number to those in Kane (apart from that I will also be adding the rolling disc). The two degree of freedom test in my case is the double pendulum. Like Kane, I also have the simple pendulum. Lagrange's equations don't need the test for auxiliary equations and ditto for the parallel axis theorem.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged e5d912a2 into 15c2c89).

Jason K. Moore
Collaborator

Angadh, your current tests don't really cover the use cases of the class. An ideal set of tests would probably include a test for each problem case individually and at least one test case problem that covers a problem that has all dynamics features. This way you know that your class plays well with whatever the user may throw at it. It will also prove that your class gives the correct answer for a robust dynamics problem and if it does that then we would all be convinced that it can probably handle any lesser problems. You'd hate to send out a class into the world that incorrectly derives equations of certain types of problems.

I think you need at least a 3D problem with multiple degrees of freedom, both holonomic and non-holonomic constraints, and analytically unsolvable kinematic loop.

The nice thing is that Gilbert already has problems like this in his Kane test suite. The only thing you have to do is to write out the Lagrangian for those problems and then see if your Lagrange class gives the same answer as Gilbert's. In fact, both Kane and Lagrange classes should be tested by the same problem test suite as they are both just different ways to crack the coconut and should give the exact same answer.

sympy/physics/mechanics/tests/test_lagrange.py
((68 lines not shown))
  68
+    N = ReferenceFrame('N')
  69
+    A = N.orientnew('A', 'Axis', [q, N.z])
  70
+    A.set_ang_vel(N, qd * N.z)
  71
+
  72
+    # Next, we create the point O and fix it in the inertial frame. We then
  73
+    # locate the point P to which the bob is attached. Its corresponding
  74
+    # velocity is then determined by the 'two point formula'.
  75
+    O = Point('O')
  76
+    O.set_vel(N, 0)
  77
+    P = O.locatenew('P', l * A.x)
  78
+    P.v2pt_theory(O, N, A)
  79
+
  80
+    # The 'Particle' which represents the bob is then created.
  81
+    Pa = Particle('Pa', P, m)
  82
+
  83
+    T = 1/2.0 * m * P.vel(N) & P.vel(N) # T is the kinetic energy of the system
1
Jason K. Moore Collaborator

Whenever you use the & and ^ for the dot and cross products it is probably good practice to always enclose the operation in parentheses because these two operators have special order of operation rules that don't necessarily act like you think they do. In this case it is fine, but I think all example code that a user may possibly look at to figure out how to write their own problems should have this explicit. New users may spend forever trying to figure out the bug if they type something like

A & B ^ C ^ D

and expect the order of operations to match they way they think about it on paper.

Look back on the sympy mailing list, the online docs, or old pull requests to see how these operators work (or you may already know). http://docs.sympy.org/dev/modules/physics/mechanics/vectors.html#vector-algebra-in-mechanics only has a small warning about this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 85ac0417 into 15c2c89).

sympy/physics/mechanics/lagrange.py
((21 lines not shown))
  21
+
  22
+    mass_matrix : Matrix
  23
+        The system's mass matrix
  24
+
  25
+    forcing : Matrix
  26
+        The system's forcing vector
  27
+
  28
+    mass_matrix_full : Matrix
  29
+        The "mass matrix" for the qdot's, qdoubledot's, and the
  30
+        lagrange multipliers (lam)
  31
+
  32
+    forcing_full : Matrix
  33
+        The forcing vector for the qdot's, qdoubledot's and
  34
+        lagrange multipliers (lam)
  35
+
  36
+    rhs : Matrix
3
Jason K. Moore Collaborator

This is a method, so you should have a Methods section to accompany your Attributes section. Also, don't forget the form_equations method.

Jason, I don't think that Methods are usually listed in the class docstring, although rhs should be taken out of here.

Jason K. Moore Collaborator

Ok, here is some potential reference material for that:

https://github.com/numpy/numpy/blob/master/doc/HOWTO_DOCUMENT.rst.txt#documenting-classes

Although, I don't know the standard in SymPy is.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged cdaf29ba into 15c2c89).

Gilbert Gede

@moorepants I have to disagree with you on some of these points Jason.

We just realized that the Kane class actually returns a negative mass matrix and a negative forcing vector. In all calculations that have been done, these cancel out and it goes unnoticed. Angadh and I discovered it today though. I suggested that he ensure that his results check out against hand/reference calculations rather than what the Kane class gives (also, if any other changes ever break Kane, Lagrange won't fail it's tests).

Not all of the test cases go between the 2 classes either - if the generalized speeds are not just the derivatives of the coordinates, things get messy. That being said, storing some of these reference results that could be used for both cases in 1 place seems like a good idea.

Also, Angadh is working to get reference results for a few more tests. Note that the Kane class does not have a test within SymPy for holonomic and nonholonomic constraints.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request fails (merged 0fc013fe into 15c2c89).

Jason K. Moore
Collaborator

I'm not sure what you are disagreeing with, seems more like agreeing. I'm glad you found the bug in the Kane class.

Yes, results should always check against well known results that are in literature as the de facto test. I'm suggesting that both classes can test to the same benchmark problems from literature. i.e. setup up a benchmark problem with known canonical results and then run the two classes to see if you get the same answers as literature. There seems to be little reason to make different test problems for both of the classes. These test cases certainly need to be designed such that the resulting equations of motion use the same coordinates and such, but they certainly should both be able to solve the same problems and give the same EoMs.

Secondly, maintaining different sets of benchmark tests for many automated EoM derivation methods will ultimately be a nightmare. I can imagine that all methods test to the same benchmarks, but then there may be some special test cases for each Method class that test the particularities and advantages of one method or an another.

I thought you have the Bicycle example as a test for a "complete" dynamics problem. This test could probably be run in the test suite even though it is slower, especially since the tests are mostly run by the bots in the background.

Also a simple four bar linkage or a 3D version of one could be a nice holonomic/kinematic loop test. I think the problem in Kane's online dynamics in Chapter Zero is a good one for that.

Lastly, we should find a problem that is "complete" but which is simpler than the bicycle for computation time reasons. We've talked about this in the past and I vaguely remember suggesting one. I'll dig around for that.

Stefan Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: 15c2c89
branch hash: 0fc013f

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYxt0iDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYgpwjDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY_P8hDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYgZwjDA

Automatic review by SymPy Bot.

Jason K. Moore
Collaborator

I asked Mont if he knew of a simpler problem than the bicycle which was full featured and he suggested the unicycle with the riders leg(s). The leg and crank form a four bar linkage. He doubted than anyone had the solution to this problem written down, but it may be possible. We could compute the EoM's using Autolev and then compare the results to what the classes in SymPy give if writing the solution by hand is a mess.

I was think of something similar today too that added a four bar linkage to the the rolling disc, but it doesn't have as nice of a physical interpretation.

I'm willing to make this test up, but can't do it till after he 21st (my dissertation due date).

Dale Lukas Peterson
Ondřej Čertík
Owner

@gilbertgede, @moorepants, are you ok with pusing this in? It looks good to me.

@angadhn, for the future, see here how to write good commit messages: https://plus.google.com/u/0/104039945248245758823/posts/GcJmpxCVDfg
So for example instead of "More edits." it's better to explain what exactly you did and why. See the blog post, it can be just one line, or more paragraphs if needed.

Jason K. Moore
Collaborator
Angadh Nanjangud

@moorepants Yeah. I'm redoing the rolling disc.

Gilbert Gede

I agree with @moorepants , we should add at least 1 test which is more complex, but I am not sure how complex we should make it. @angadhn is working on a test right now though, so something should be up soon.

Ondřej Čertík
Owner

Excellent. Please ping me when it's ready for review.

Angadh Nanjangud

Everyone, I just wanted to give a heads up that I will be rebasing this branch; I need to have access to the energy functions that just got merged into master. as I will need to use this for the 'LagrangesMethod' tests. And I won't amend the commits this time around ;)

added some commits August 03, 2012
Angadh Nanjangud Revised6: LagrangesMethod class for sympy.physics.mechanics. a054386
Angadh Nanjangud Minor edits. 9f5999f
Angadh Nanjangud ADDED ROLLING DISC TEST AND MINOR EDITS.
The rolling disc test has been included.

Two minor edits have been included:
- addition of the energy function to determine the Lagrangian.
- slight modification of the 'rhs' method in the LagrangesMethod class.
1171a1c
Angadh Nanjangud

@certik I have added the test for the rolling disc.

@hazelnusse @moorepants I tried to run the rolling disc with the non-minimal coordinates and so did @gilbertgede. It took forever to run. 12 mins into running the test, there was still no output so we decided to stick with the three generalized coordinates. I guess having trivial generalized (speeds as defined by Lagrange) slows things down drastically when we have 6 generalized coordinates AND three constraint equations.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged 1171a1c into 2797590).

sympy/physics/mechanics/tests/test_lagrange.py
((178 lines not shown))
  178
+    C.set_vel(N, 0)
  179
+    Dmc = C.locatenew('Dmc', r * L.z)
  180
+    Dmc.v2pt_theory(C, N, R)
  181
+
  182
+    # Forming the inertia dyadic.
  183
+    I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
  184
+    BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
  185
+
  186
+    # Finally we form the equations of motion, using the same steps we did
  187
+    # before. Supply the Lagrangian, the generalized speeds.
  188
+    T = kinetic_energy(N, BodyD)
  189
+    BodyD.set_potential_energy(- m * g * r * cos(q2))
  190
+    V = potential_energy(BodyD)
  191
+    Lag = T - V
  192
+    q = [q1, q2, q3]
  193
+    l = LagrangesMethod(Lag, q)
1
Jason K. Moore Collaborator

I'm wondering if this would flow better like:

BodyD.set_potential_energy(- m * g * r * cos(q2))
q = [q1, q2, q3]
l = LagrangesMethod(BodyD, (q1, q2, q3))

For 99% of problems the Lagrangian is T - V. I'm not sure why it would be otherwise. So why force all this extra typing? The above will mean that the kinetic energy and potential energy calls happen inside the Lagrange class.

So for that 1% where you might want to define a different Lagrangian, why not just have either an optional keyword argument or check the first object to see if it is a Body, Particle, or an expression.

So you can either type:

LagrangesMethod(body1, body2, particle1, particle2, (q1, q2, q3))

or

LagrangesMethod(L, (q1, q2, q3))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Jason K. Moore
Collaborator

I fine with the tests as is. Nice job on this!

I've only the one comment above that I'm curious about the design of the code and why the Lagrangian can't be formed in the class.

Angadh Nanjangud

How about adding a 'Lagrangian' function in the functions.py instead. That way users can feed in a Lagrangian however they desire.
I'm thinking of something like this in 'functions.py'-

def Lagrangian(frame, *body):
    "compute Lagrangian and return it here"

The frame would still be needed to determine the kinetic energy. What do you think?

Angadh Nanjangud

Also a git question-
After having rebased and committed once (without amending), will it be okay to 'git commit --amend'?

Stefan Krastanov
Collaborator

SymPy Bot Summary: :red_circle: There were test failures.

@angadhn: Please fix the test failures.

Test command: setup.py test
master hash: 2797590
branch hash: 1171a1c

Interpreter 1: :red_circle: There were test failures.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYjvUiDA

Interpreter 2: :red_circle: There were test failures.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY1Y8iDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY2bYiDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYr_AhDA

Automatic review by SymPy Bot.

Dale Lukas Peterson

@angadhn @gilbertgede @moorepants @certik

I'm concerned about the test taking 12 minutes. Where was the majority of the time spent? Changing back to the minimal coordinates and speeds seems to imply you won't be testing functionality that is really important to this class. Maybe I'm missing something but to me, 12 minutes seems to be an indication that your algorithm needs improvement and/or the way you are using core sympy functionality is less than optimal. Can you explain why it was taking so long?

Aaron Meurer
Owner

git commit --amend is essentially the same as git rebase HEAD~1, as far as changing the history goes.

Angadh Nanjangud

@hazelnusse Things get slow at the mass matrix inversion. The 'form_lagranges_equations' method doesn't stall so I'm wondering if maybe the issue lies in inverting a huge mass matrix, in this case, it was a 15 by 15 matrix.

Christopher Smith
Collaborator

If you use git commit -a (a=all, not ammend) it will do what you are thinking, I believe: just add the changed files and put you in the editor to add a message. If you do git commit -a -m "a title" for a title-only commit and it will bypass the editing step.

Dale Lukas Peterson

@angadhn
Is the mass matrix inversion for solving for the q double dot's and the lagrange multipliers? Is this done by the class, or in user code after the mass matrix and forcing function has been obtained? If the former, think this is a pretty clear example of why coupling your class with a linear system solver is dicey. Also, what technique were you using to do the matrix inverse? I think in my experience, doing something like M.adjugate() / M.det(method='...') was pretty fast, even for relatively big matrices.

Angadh Nanjangud

@hazelnusse yes. and also solves for the qdots (which is of course trivial.) .
And yes, this is done within clsas in the 'rhs' method. The inversion of the mass matrix MM is done by

MM.inv("GE", try_block_diag = True)

I will try it out the way you have suggested and see how it works.

Angadh Nanjangud

@hazelnusse no dice. it seems to be taking forever to compute the determinant as well as the adjoint of the mass matrix.

Angadh Nanjangud

I stand corrected. so the issue seems to be with printing the adjugate and not calculating it. As for just using the the method buil in to the class, that does take forever.

Dale Lukas Peterson
Angadh Nanjangud

the method == rhs method to solve for the states.

Angadh Nanjangud ADDED LAGRANGIAN AND SOME OTHER CORRECTIONS
A Lagrangian function has been added to 'functions.py' and appropriately
used now in the tests.

Corrected an error in the docstring of 'kinetic_energy'.

Modified the 'potential_energy' methods for both Particle and RigidBody;
the default value is now zero thus preventing the user from having to
set potential energy when there is none.
ba38ebe
Angadh Nanjangud

@smichr @asmeurer Thanks for the information. Very helpful.

@hazelnusse Thanks for showing the path to faster inversions. Very insightful. Nonetheless, I have spent a good few hours since we met trying to decipher the gargantuan 'rhs' terms and I didn't find a feasible way to make those terms any simpler. It's quite horrendous as you may have seen.

@hazelnusse @moorepants @gilbertgede I will try to find a "simpler" test case for nonholnomicity later if you are okay with it as I personally feel this doesn't fit the bill in the case of using Lagrange's method.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged ba38ebe into 2797590).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: 3b77c94
branch hash: ba38ebe

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY148iDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY2rYiDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYiYAiDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY7qMjDA

Automatic review by SymPy Bot.

Ondřej Čertík
Owner

It's definitely ok with me to merge this now, if you want. Or should I wait?

Angadh Nanjangud ADDED WHITESPACE AFTER A COMMA.
I had forgotten to address one of Ondrej's comments on PR 1407 so I have
taken care of that here.
ce17e7b
Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged ce17e7b into 2797590).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: b0a9866
branch hash: ce17e7b

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYvowjDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYocYiDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY8aMjDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY0t0iDA

Automatic review by SymPy Bot.

Gilbert Gede

@certik I'm OK with this going in - the tests aren't as complex as they could be, but I believe that all the functionality is covered.

Angadh Nanjangud

@certik With that last commit I'm OK with this going in too.

Don't Add Me To Your Organization a.k.a The Travis Bot

This pull request passes (merged d04c599 into 2797590).

Stefan Krastanov
Collaborator

SymPy Bot Summary: :eight_spoked_asterisk: All tests have passed.

Test command: setup.py test
master hash: b0a9866
branch hash: d04c599

Interpreter 1: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/local/bin/python2.5 (2.5.6-final-0)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY_dUiDA

Interpreter 2: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python2.7 (2.7.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYwYwjDA

Interpreter 3: :eight_spoked_asterisk: All tests have passed.

Interpreter: /usr/bin/python3.2 (3.2.3-candidate-2)
Architecture: Linux (64-bit)
Cache: yes

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sY_NUiDA

Build HTML Docs: :eight_spoked_asterisk: All tests have passed.

Docs build command: make html-errors
Sphinx version: 1.1.3

Test results html report: http://reviews.sympy.org/report/agZzeW1weTNyDAsSBFRhc2sYrbMjDA

Automatic review by SymPy Bot.

Ondřej Čertík certik merged commit 86696a5 into from August 12, 2012
Ondřej Čertík certik closed this August 12, 2012
Ondřej Čertík
Owner

Excellent, it's in!

Angadh Nanjangud

Everyone- thanks a lot for all the input.

Aaron Meurer
Owner

This has led to some test failures in master. See #1370 (comment).

Aaron Meurer
Owner

Actually, I can't reproduce those. They might be related to the copy_py3k_sympy feature of sympy-bot.

Gilbert Gede

Yeah, it appears to only be with the python3 tests.

Is this just a random issue, or will this code keep popping up as an issue?

Aaron Meurer
Owner

Hopefully it will go away. I think it was an issue of py3k-sympy not being updated correctly.

Ondřej Čertík
Owner

The Python 3.2 tests are well tested by travis:

http://travis-ci.org/#!/sympy/sympy/builds

you can see that there are no failures in master, except some bugs in Travis itself.

Angadh Nanjangud

That's a relief! Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Showing 6 unique commits by 1 author.

Aug 10, 2012
Angadh Nanjangud Revised6: LagrangesMethod class for sympy.physics.mechanics. a054386
Angadh Nanjangud Minor edits. 9f5999f
Angadh Nanjangud ADDED ROLLING DISC TEST AND MINOR EDITS.
The rolling disc test has been included.

Two minor edits have been included:
- addition of the energy function to determine the Lagrangian.
- slight modification of the 'rhs' method in the LagrangesMethod class.
1171a1c
Aug 11, 2012
Angadh Nanjangud ADDED LAGRANGIAN AND SOME OTHER CORRECTIONS
A Lagrangian function has been added to 'functions.py' and appropriately
used now in the tests.

Corrected an error in the docstring of 'kinetic_energy'.

Modified the 'potential_energy' methods for both Particle and RigidBody;
the default value is now zero thus preventing the user from having to
set potential energy when there is none.
ba38ebe
Angadh Nanjangud ADDED WHITESPACE AFTER A COMMA.
I had forgotten to address one of Ondrej's comments on PR 1407 so I have
taken care of that here.
ce17e7b
Angadh Nanjangud ADDED USAGE OF LAGRANGIAN IN DOCSTRING EXAMPLE. d04c599
This page is out of date. Refresh to see the latest.
4  doc/src/modules/physics/mechanics/examples.rst
Source Rendered
@@ -68,7 +68,7 @@ equations in the end. ::
68 68
 Kinematic differential equations; how the generalized coordinate time
69 69
 derivatives relate to generalized speeds. Here these were computed by hand. ::
70 70
 
71  
-  >>> kd = [q1d - u3/cos(q3), q2d - u1, q3d - u2 + u3 * tan(q2)]
  71
+  >>> kd = [q1d - u3/cos(q2), q2d - u1, q3d - u2 + u3 * tan(q2)]
72 72
 
73 73
 Creation of the force list; it is the gravitational force at the center of mass of
74 74
 the disc. Then we create the disc by assigning a Point to the center of mass
@@ -145,7 +145,7 @@ and along the ground in an perpendicular direction. ::
145 145
   >>> vel = Dmc.v2pt_theory(C, N, R)
146 146
   >>> acc = Dmc.a2pt_theory(C, N, R)
147 147
   >>> I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
148  
-  >>> kd = [q1d - u3/cos(q3), q2d - u1, q3d - u2 + u3 * tan(q2)]
  148
+  >>> kd = [q1d - u3/cos(q2), q2d - u1, q3d - u2 + u3 * tan(q2)]
149 149
 
150 150
 Just as we previously introduced three speeds as part of this process, we also
151 151
 introduce three forces; they are in the same direction as the speeds, and
4  sympy/physics/mechanics/__init__.py
@@ -34,3 +34,7 @@
34 34
 import essential
35 35
 from essential import *
36 36
 __all__.extend(essential.__all__)
  37
+
  38
+import lagrange
  39
+from lagrange import *
  40
+__all__.extend(lagrange.__all__)
63  sympy/physics/mechanics/functions.py
@@ -13,7 +13,8 @@
13 13
            'linear_momentum',
14 14
            'angular_momentum',
15 15
            'kinetic_energy',
16  
-           'potential_energy']
  16
+           'potential_energy',
  17
+           'Lagrangian']
17 18
 
18 19
 from sympy.physics.mechanics.essential import (Vector, Dyadic, ReferenceFrame,
19 20
                                                MechanicsStrPrinter,
@@ -577,7 +578,8 @@ def kinetic_energy(frame, *body):
577 578
     ==========
578 579
 
579 580
     frame : ReferenceFrame
580  
-        The frame in which angular momentum is desired.
  581
+        The frame in which the velocity or angular velocity of the body is
  582
+        defined.
581 583
 
582 584
     body1, body2, body3... : Particle and/or RigidBody
583 585
         The body (or bodies) whose kinetic energy is required.
@@ -650,7 +652,6 @@ def potential_energy(*body):
650 652
     >>> a = ReferenceFrame('a')
651 653
     >>> I = outer(N.z, N.z)
652 654
     >>> A = RigidBody('A', Ac, a, M, (I, Ac))
653  
-    >>> BL = [Pa, A]
654 655
     >>> Pa.set_potential_energy(m * g * h)
655 656
     >>> A.set_potential_energy(M * g * h)
656 657
     >>> potential_energy(Pa, A)
@@ -665,3 +666,59 @@ def potential_energy(*body):
665 666
         else:
666 667
             raise TypeError('*body must have only Particle or RigidBody')
667 668
     return pe_sys
  669
+
  670
+def Lagrangian(frame, *body):
  671
+    """Lagrangian of a multibody system.
  672
+
  673
+    This function returns the Lagrangian of a system of Particle's and/or
  674
+    RigidBody's. The Lagrangian of such a system is equal to the difference
  675
+    between the kinetic energies and potential energies of its constituents. If
  676
+    T and V are the kinetic and potential energies of a system then it's
  677
+    Lagrangian, L, is defined as
  678
+
  679
+    L = T - V
  680
+
  681
+    The Lagrangian is a scalar.
  682
+
  683
+    Parameters
  684
+    ==========
  685
+
  686
+    frame : ReferenceFrame
  687
+        The frame in which the velocity or angular velocity of the body is
  688
+        defined to determine the kinetic energy.
  689
+
  690
+    body1, body2, body3... : Particle and/or RigidBody
  691
+        The body (or bodies) whose kinetic energy is required.
  692
+
  693
+    Examples
  694
+    ========
  695
+
  696
+    >>> from sympy.physics.mechanics import Point, Particle, ReferenceFrame
  697
+    >>> from sympy.physics.mechanics import RigidBody, outer, Lagrangian
  698
+    >>> from sympy import symbols
  699
+    >>> M, m, g, h = symbols('M m g h')
  700
+    >>> N = ReferenceFrame('N')
  701
+    >>> O = Point('O')
  702
+    >>> O.set_vel(N, 0 * N.x)
  703
+    >>> P = O.locatenew('P', 1 * N.x)
  704
+    >>> P.set_vel(N, 10 * N.x)
  705
+    >>> Pa = Particle('Pa', P, 1)
  706
+    >>> Ac = O.locatenew('Ac', 2 * N.y)
  707
+    >>> Ac.set_vel(N, 5 * N.y)
  708
+    >>> a = ReferenceFrame('a')
  709
+    >>> a.set_ang_vel(N, 10 * N.z)
  710
+    >>> I = outer(N.z, N.z)
  711
+    >>> A = RigidBody('A', Ac, a, 20, (I, Ac))
  712
+    >>> Pa.set_potential_energy(m * g * h)
  713
+    >>> A.set_potential_energy(M * g * h)
  714
+    >>> Lagrangian(N, Pa, A)
  715
+    -M*g*h - g*h*m + 350
  716
+
  717
+    """
  718
+
  719
+    if not isinstance(frame, ReferenceFrame):
  720
+        raise TypeError('Please supply a valid ReferenceFrame')
  721
+    for e in body:
  722
+        if not isinstance(e, (RigidBody, Particle)):
  723
+            raise TypeError('*body must have only Particle or RigidBody')
  724
+    return kinetic_energy(frame, *body) - potential_energy(*body)
332  sympy/physics/mechanics/lagrange.py
... ...
@@ -0,0 +1,332 @@
  1
+__all__ = ['LagrangesMethod']
  2
+
  3
+from sympy import diff, zeros, Matrix, eye, sympify
  4
+from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point)
  5
+
  6
+class LagrangesMethod(object):
  7
+    """Lagrange's method object.
  8
+
  9
+    This object generates the equations of motion in a two step procedure. The
  10
+    first step involves the initialization of LagrangesMethod by supplying the
  11
+    Lagrangian and a list of the generalized coordinates, at the bare minimum.
  12
+    If there are any constraint equations, they can be supplied as keyword
  13
+    arguments. The Lagrangian multipliers are automatically generated and are
  14
+    equal in number to the constraint equations.Similarly any non-conservative
  15
+    forces can be supplied in a list (as described below and also shown in the
  16
+    example) along with a ReferenceFrame. This is also discussed further in the
  17
+    __init__ method.
  18
+
  19
+    Attributes
  20
+    ==========
  21
+
  22
+    mass_matrix : Matrix
  23
+        The system's mass matrix
  24
+
  25
+    forcing : Matrix
  26
+        The system's forcing vector
  27
+
  28
+    mass_matrix_full : Matrix
  29
+        The "mass matrix" for the qdot's, qdoubledot's, and the
  30
+        lagrange multipliers (lam)
  31
+
  32
+    forcing_full : Matrix
  33
+        The forcing vector for the qdot's, qdoubledot's and
  34
+        lagrange multipliers (lam)
  35
+
  36
+    Examples
  37
+    ========
  38
+
  39
+    This is a simple example for a one degree of freedom translational
  40
+    spring-mass-damper.
  41
+
  42
+    In this example, we first need to do the kinematics.$
  43
+    This involves creating generalized coordinates and its derivative.
  44
+    Then we create a point and set its velocity in a frame::
  45
+
  46
+        >>> from sympy.physics.mechanics import LagrangesMethod, Lagrangian
  47
+        >>> from sympy.physics.mechanics import ReferenceFrame, Particle, Point
  48
+        >>> from sympy.physics.mechanics import dynamicsymbols, kinetic_energy
  49
+        >>> from sympy import symbols
  50
+        >>> q = dynamicsymbols('q')
  51
+        >>> qd = dynamicsymbols('q', 1)
  52
+        >>> m, k, b = symbols('m k b')
  53
+        >>> N = ReferenceFrame('N')
  54
+        >>> P = Point('P')
  55
+        >>> P.set_vel(N, qd * N.x)
  56
+
  57
+    We need to then prepare the information as required by LagrangesMethod to
  58
+    generate equations of motion.
  59
+    First we create the Particle, which has a point attached to it.
  60
+    Following this the lagrangian is created from the kinetic and potential
  61
+    energies.
  62
+    Then, a list of nonconservative forces/torques must be constructed, where
  63
+    each entry in is a (Point, Vector) or (ReferenceFrame, Vector) tuple, where
  64
+    the Vectors represent the nonconservative force or torque.
  65
+
  66
+        >>> Pa = Particle('Pa', P, m)
  67
+        >>> Pa.set_potential_energy(k * q**2 / 2.0)
  68
+        >>> L = Lagrangian(N, Pa)
  69
+        >>> fl = [(P, -b * qd * N.x)]
  70
+
  71
+     Finally we can generate the equations of motion.
  72
+     First we create the LagrangesMethod object.To do this one must supply an
  73
+     the Lagrangian, the list of generalized coordinates. Also supplied are the
  74
+     constraint equations, the forcelist and the inertial frame, if relevant.
  75
+     Next we generate Lagrange's equations of motion, such that:
  76
+     Lagrange's equations of motion = 0.
  77
+     We have the equations of motion at this point.
  78
+
  79
+        >>> l = LagrangesMethod(L, [q], forcelist = fl, frame = N)
  80
+        >>> print l.form_lagranges_equations()
  81
+        [b*Derivative(q(t), t) + 1.0*k*q(t) + m*Derivative(q(t), t, t)]
  82
+
  83
+    We can also solve for the states using the 'rhs' method.
  84
+
  85
+        >>> print l.rhs()
  86
+        [                    Derivative(q(t), t)]
  87
+        [(-b*Derivative(q(t), t) - 1.0*k*q(t))/m]
  88
+
  89
+    Please refer to the docstrings on each method for more details.
  90
+
  91
+    """
  92
+
  93
+    def __init__(self, Lagrangian, q_list, coneqs = None, forcelist = None, frame = None):
  94
+        """Supply the following for the initialization of LagrangesMethod
  95
+
  96
+        Lagrangian : Sympifyable
  97
+
  98
+        q_list : list
  99
+            A list of the generalized coordinates
  100
+
  101
+        coneqs : list
  102
+            A list of the holonomic and non-holonomic constraint equations.
  103
+            VERY IMPORTANT NOTE- The holonomic constraints must be
  104
+            differentiated with respect to time and then included in coneqs.
  105
+
  106
+        forcelist : list
  107
+            Takes a list of (Point, Vector) or (ReferenceFrame, Vector) tuples
  108
+            which represent the force at a point or torque on a frame. This
  109
+            feature is primarily to account for the nonconservative forces
  110
+            amd/or moments.
  111
+
  112
+        frame : ReferenceFrame
  113
+            Supply the inertial frame. This is used to determine the
  114
+            generalized forces due to non-sonservative forces.
  115
+
  116
+        """
  117
+
  118
+        self._L = sympify(Lagrangian)
  119
+        self.eom = None #initializing the eom Matrix
  120
+        self._m_cd = Matrix([]) #Mass Matrix of differentiated coneqs
  121
+        self._m_d = Matrix([]) #Mass Matrix of dynamic equations
  122
+        self._f_cd = Matrix([]) #Forcing part of the diff coneqs
  123
+        self._f_d = Matrix([]) #Forcing part of the dynamic equations
  124
+        self.lam_coeffs = Matrix([]) #Initializing the coeffecients of lams
  125
+
  126
+        self.forcelist = forcelist
  127
+        self.inertial = frame
  128
+
  129
+        self.lam_vec = Matrix([])
  130
+
  131
+        self._term1 = Matrix([])
  132
+        self._term2 = Matrix([])
  133
+        self._term3 = Matrix([])
  134
+        self._term4 = Matrix([])
  135
+
  136
+        # Creating the qs, qdots and qdoubledots
  137
+
  138
+        q_list = list(q_list)
  139
+        if not isinstance(q_list, list):
  140
+            raise TypeError('Generalized coords. must be supplied in a list')
  141
+        self._q = q_list
  142
+        self._qdots = [diff(i, dynamicsymbols._t) for i in self._q]
  143
+        self._qdoubledots = [diff(i, dynamicsymbols._t) for i in self._qdots]
  144
+
  145
+        self.coneqs = coneqs
  146
+
  147
+    def form_lagranges_equations(self):
  148
+        """Method to form Lagrange's equations of motion.
  149
+
  150
+        Returns a vector of equations of motion using Lagrange's equations of
  151
+        the second kind.
  152
+
  153
+        """
  154
+
  155
+        q = self._q
  156
+        qd = self._qdots
  157
+        qdd = self._qdoubledots
  158
+        n = len(q)
  159
+
  160
+        #Putting the Lagrangian in a Matrix
  161
+        L = Matrix([self._L])
  162
+
  163
+        #Determining the first term in Lagrange's EOM
  164
+        self._term1 = L.jacobian(qd)
  165
+        self._term1 = ((self._term1).diff(dynamicsymbols._t)).transpose()
  166
+
  167
+        #Determining the second term in Lagrange's EOM
  168
+        self._term2 = (L.jacobian(q)).transpose()
  169
+
  170
+        #term1 and term2 will be there no matter what so leave them as they are
  171
+
  172
+        if self.coneqs != None:
  173
+            coneqs = self.coneqs
  174
+            #If there are coneqs supplied then the following will be created
  175
+            coneqs = list(coneqs)
  176
+            if not isinstance(coneqs, list):
  177
+                raise TypeError('Enter the constraint equations in a list')
  178
+
  179
+            o = len(coneqs)
  180
+
  181
+            #Creating the multipliers
  182
+            self.lam_vec = Matrix(dynamicsymbols('lam1:' + str(o + 1)))
  183
+
  184
+            #Extracting the coeffecients of the multipliers
  185
+            coneqs_mat = Matrix(coneqs)
  186
+            qd = self._qdots
  187
+            self.lam_coeffs = -coneqs_mat.jacobian(qd)
  188
+
  189
+            #Determining the third term in Lagrange's EOM
  190
+            #term3 = ((self.lam_vec).transpose() * self.lam_coeffs).transpose()
  191
+            self._term3 = self.lam_coeffs.transpose() * self.lam_vec
  192
+
  193
+            #Taking the time derivative of the constraint equations
  194
+            diffconeqs = [diff(i, dynamicsymbols._t) for i in coneqs]
  195
+
  196
+            #Extracting the coeffecients of the qdds from the diff coneqs
  197
+            diffconeqs_mat = Matrix(diffconeqs)
  198
+            qdd = self._qdoubledots
  199
+            self._m_cd = diffconeqs_mat.jacobian(qdd)
  200
+
  201
+            #The remaining terms i.e. the 'forcing' terms in diff coneqs
  202
+            qddzero = dict(zip(qdd, [0] * n))
  203
+            self._f_cd = -diffconeqs_mat.subs(qddzero)
  204
+
  205
+        else:
  206
+            self._term3 = zeros(n, 1)
  207
+
  208
+        if self.forcelist != None:
  209
+            forcelist = self.forcelist
  210
+            N = self.inertial
  211
+            if not isinstance(N, ReferenceFrame):
  212
+                raise TypeError('Enter a valid ReferenceFrame')
  213
+            if not isinstance(forcelist, (list, tuple)):
  214
+                raise TypeError('Forces must be supplied in a list of: lists'
  215
+                        ' or tuples')
  216
+            self._term4 = zeros(n, 1)
  217
+            for i,v in enumerate(qd):
  218
+                for j,w in enumerate(forcelist):
  219
+                    if isinstance(w[0], ReferenceFrame):
  220
+                        speed = w[0].ang_vel_in(N)
  221
+                        self._term4[i] += speed.diff(v, N) & w[1]
  222
+                    if isinstance(w[0], Point):
  223
+                        speed = w[0].vel(N)
  224
+                        self._term4[i] += speed.diff(v, N) & w[1]
  225
+                    else:
  226
+                        raise TypeError('First entry in force pair is a point'
  227
+                                        ' or frame')
  228
+
  229
+        else:
  230
+            self._term4 = zeros(n, 1)
  231
+
  232
+        self.eom = self._term1 - self._term2 - self._term3 - self._term4
  233
+
  234
+        return self.eom
  235
+
  236
+    @property
  237
+    def mass_matrix(self):
  238
+        """ Returns the mass matrix, which is augmented by the Lagrange
  239
+        multipliers, if necessary.
  240
+
  241
+        If the system is described by 'n' generalized coordinates and there are
  242
+        no constraint equations then an n X n matrix is returned.
  243
+
  244
+        If there are 'n' generalized coordinates and 'm' constraint equations
  245
+        have been supplied during initialization then an n X (n+m) matrix is
  246
+        returned. The (n + m - 1)th and (n + m)th columns contain the
  247
+        coefficients of the Lagrange multipliers.
  248
+
  249
+        """
  250
+
  251
+        if self.eom == None:
  252
+            raise ValueError('Need to compute the equations of motion first')
  253
+
  254
+        #The 'dynamic' mass matrix is generated by the following
  255
+        self._m_d = (self.eom).jacobian(self._qdoubledots)
  256
+
  257
+        if len(self.lam_coeffs) != 0:
  258
+            return (self._m_d).row_join((self.lam_coeffs).transpose())
  259
+        else:
  260
+            return self._m_d
  261
+
  262
+    @property
  263
+    def mass_matrix_full(self):
  264
+        """ Augments the coefficients of qdots to the mass_matrix. """
  265
+
  266
+        n = len(self._q)
  267
+        if self.eom == None:
  268
+            raise ValueError('Need to compute the equations of motion first')
  269
+        #THE FIRST TWO ROWS OF THE MATRIX
  270
+        row1 = eye(n).row_join(zeros(n,n))
  271
+        row2 = zeros(n,n).row_join(self.mass_matrix)
  272
+        if self.coneqs != None:
  273
+            m = len(self.coneqs)
  274
+            I = eye(n).row_join(zeros(n,n+m))
  275
+            below_eye = zeros(n+m,n)
  276
+            A = (self.mass_matrix).col_join((self._m_cd).row_join(zeros(m,m)))
  277
+            below_I = below_eye.row_join(A)
  278
+            return I.col_join(below_I)
  279
+        else:
  280
+           A = row1.col_join(row2)
  281
+           return A
  282
+
  283
+    @property
  284
+    def forcing(self):
  285
+        """ Returns the forcing vector from 'lagranges_equations' method. """
  286
+
  287
+        if self.eom == None:
  288
+            raise ValueError('Need to compute the equations of motion first')
  289
+
  290
+        qdd = self._qdoubledots
  291
+        qddzero = dict(zip(qdd, [0] * len(qdd)))
  292
+
  293
+        if self.coneqs != None:
  294
+            lam = self.lam_vec
  295
+            lamzero = dict(zip(lam, [0] * len(lam)))
  296
+
  297
+            #The forcing terms from the eoms
  298
+            self._f_d = -((self.eom).subs(qddzero)).subs(lamzero)
  299
+
  300
+        else:
  301
+            #The forcing terms from the eoms
  302
+            self._f_d = -(self.eom).subs(qddzero)
  303
+
  304
+        return self._f_d
  305
+
  306
+    @property
  307
+    def forcing_full(self):
  308
+        """ Augments qdots to the forcing vector above. """
  309
+
  310
+        if self.eom == None:
  311
+            raise ValueError('Need to compute the equations of motion first')
  312
+        if self.coneqs != None:
  313
+            return (Matrix(self._qdots)).col_join((self.forcing).col_join(self._f_cd))
  314
+        else:
  315
+            return (Matrix(self._qdots)).col_join(self.forcing)
  316
+
  317
+    def rhs(self, method = "GE"):
  318
+        """ Returns equations that can be solved numerically
  319
+
  320
+        Parameters
  321
+        ==========
  322
+
  323
+        method : string
  324
+            The method by which matrix inversion of mass_matrix_full must be
  325
+            performed such as Gauss Elimination or LU decomposition.
  326
+
  327
+        """
  328
+
  329
+        # TODO- should probably use the matinvmul method from Kane
  330
+
  331
+        return ((self.mass_matrix_full).inv(method, try_block_diag = True) *
  332
+                self.forcing_full)
9  sympy/physics/mechanics/particle.py
@@ -41,7 +41,7 @@ def __init__(self, name, point, mass):
41 41
         self._name = name
42 42
         self.set_mass(mass)
43 43
         self.set_point(point)
44  
-        self._pe = None
  44
+        self._pe = sympify(0)
45 45
 
46 46
     def __str__(self):
47 47
         return self._name
@@ -144,7 +144,7 @@ def angular_momentum(self, point, frame):
144 144
     def kinetic_energy(self, frame):
145 145
         """Kinetic energy of the particle
146 146
 
147  
-        The kinetic energy, T, of a particle,P, is given by
  147
+        The kinetic energy, T, of a particle, P, is given by
148 148
 
149 149
         'T = 1/2 m v^2'
150 150
 
@@ -218,7 +218,4 @@ def potential_energy(self):
218 218
 
219 219
         """
220 220
 
221  
-        if callable(self._pe) == True:
222  
-            return self._pe
223  
-        else:
224  
-            raise ValueError('Please set the potential energy of the Particle')
  221
+        return self._pe
6  sympy/physics/mechanics/rigidbody.py
@@ -53,6 +53,7 @@ def __init__(self, name, masscenter, frame, mass, inertia):
53 53
         self.set_mass(mass)
54 54
         self.set_frame(frame)
55 55
         self.set_inertia(inertia)
  56
+        self._pe = sympify(0)
56 57
 
57 58
     def __str__(self):
58 59
         return self._name