Lagrangian Mechanics

274 views
Skip to first unread message

gadha007

unread,
Aug 1, 2012, 8:21:13 AM8/1/12
to sy...@googlegroups.com, Gilbert, Jason Moore, Luke Peterson
I have been working on the addition of lagrangian mechanics to sympy.physics.mechanics and I was hoping to get some input, suggestions or recommendations on it. Equations of motion are generated using Lagrange's equations of the second kind and this is what the procedure looks like-

1.) Upon having done the kinematics and subequently developing a lagrangian, the user initializes the lagrange object where the lagrangian, L, is the argument.

l = Lagrange(L)

2.) The user then supplies a list of generalized coordinates using the coords method.

l.coords(list_of_gen_coords)

3.) This is followed by a constraints method where the user can supply nonholonomic equations (i.e. velocity constraints) and/or differentiated holonomic equations (i.e. differentiated configuration constraints) if there are any. By default the constraint equations are set to none.

l.constraints(list_of_coneqs)

4.) With this done, the user can then call the method 'lagranges_equations' where (s)he can supply the non-conservative forces (or moments due to non-conservative), if any, along with a reference frame. In this way the effect of non-conservative forces such as friction can be accounted for. This generates a column vector of the dynamical equations using lagrange's method.

l.lagranges_equations(forcelist, frame)

5.) a.) Following this the user also has the option to either look at the mass matrix (i.e. coeffecients of the acceleration terms in the dynamical equations) and the forcing terms (i.e. all the other terms in the ) from the dynamical equations separately using the 'mass_matrix' and 'forcing' methods.

l.mass_matrix

l.forcing

5.)b)The user also has the option to look at the mass matrix and forcing matrix augmented with the appropriate parts of the differentiated constraint equations respectively. These methods are called 'mass_matrix_full' and 'forcing_full'.

l.mass_matrix_full

l.forcing_full

5.)c) And finally the user can call the 'rhs' method which essentially solves the lagrange equations by multiplying the inverse of mass_matrix_full and the forcing_full.

l.rhs

let me know if you have any recommendations with respect to names of methods or anything else at all.

Thanks
Angadh



krastano...@gmail.com

unread,
Aug 1, 2012, 8:35:20 AM8/1/12
to sy...@googlegroups.com, Gilbert, Jason Moore, Luke Peterson
> 1.) Upon having done the kinematics and subequently developing a lagrangian,
> the user initializes the lagrange object where the lagrangian, L, is the
> argument.
>
> l = Lagrange(L)
What is L? Just an expression?

>
> 2.) The user then supplies a list of generalized coordinates using the
> coords method.
>
> l.coords(list_of_gen_coords)
Why supply them? The opposite makes more sense. You can use some
dummies generated by `Lagrange` itself. Otherwise there can be clashes
in the names of the symbols. What happens when you supply to many (or
not enough) symbols?

Does L already contain these expressions. If yes, it seems like a bad
solutions to supply them separately.

>
> 3.) This is followed by a constraints method where the user can supply
> nonholonomic equations (i.e. velocity constraints) and/or differentiated
> holonomic equations (i.e. differentiated configuration constraints) if there
> are any. By default the constraint equations are set to none.
>
> l.constraints(list_of_coneqs)
>

All that is below sound nice.
You do understand that your object is quite mutable, right? Is it a
Basic subclass? Does it make sense to be a Basic subclass? If yes,
does it work correctly with all the machinery expected from Basic?
subs, hash, comparison, atoms, etc - do they work?

I am not sure why you supply each part separately in some setter
method. It seems much better to supply them once together during the
creation of the objects. The call signature will be longer, but
otherwise you just have all these methods that are actually
**required** before using the object.

Does the order in which the methods are called play any role? The user
should not be forced to learn such implementation specific details.

Jason Moore

unread,
Aug 1, 2012, 10:31:52 AM8/1/12
to sy...@googlegroups.com
I'm of the opinion that you should supply all the necessary information in the __init__ method of the Lagrange versus having all the setter methods. Stefan points this out too. The __init__ method should require the bare minimum arguments (lagrangian, generlized coords) and all the others be optional, probably as keyword arguments. It can be handled with something like __init__(lagragian, coordinates, **kwargs). This way the user is forced to supply all necessary items to the class on it's creation and it is immediately useful. I've always found it odd that the current Kane formulation calls for you to create and empty useless object and it only becomes useful after calling all the extra setter methods.

Following that, the user should call the .lagrange_equations() method with no argmuments to do the computation (arguments to this method should reflect options for the computation like with or without simplification etc), which has the potential to take a long time. Once these equations are stored in the class, seems like mass_matrix(), forcing() etc should be methods not attributes, as they are doing some computing too, i.e. picking off coefficients and parsing Lagrange's equations.

What methods do you plan to use for the rhs method? I could imagine this taking arguments for different types of solving methods.

Here is an example of how I imagine the class generation:

# build the object
lag = Lagrange(lagrangian, coordinates, constraints=con, forces=forces, frame=N)

# compute the equations
lag.lagrange_equations(simplify=True)

# extract the mass matrix
m = lag.mass_matrix()

# find the right hand side
rhs = lag.rhs(method=guass)

The preceding seems cleaner to me and more intuitive for the user following typical standards from other popular python software.

Jason
--
Personal Website
Sports Biomechanics Lab, UC Davis
Davis Bike Collective Minister, Davis, CA
BikeDavis.info
Google Voice: +01 530-601-9791
Home: +01 530-753-0794
Office: +01 530-752-2163
Lab: +01 530-752-2235



Dale "Luke" Peterson

unread,
Aug 1, 2012, 12:59:19 PM8/1/12
to sy...@googlegroups.com
I like the approach Jason outlined though I'm not 100% sure whether
mass_matrix & forcing should be properties or methods. If these
matrices are being formed when you call:

lag.lagrange_equations(simplify=True)

then it seems like they should be properties rather than methods.
However, this opens up the possibility that the user asks for these
properties before lagrange_equations() has been called, in which case
I'm not sure what the best thing to do would be.

How about just returning mass_matrix and forcing matrices as list when
you call lagrange_equations, and getting rid of the mass_matrix and
forcing methods/properties entirely? So it would look like:

M, f = lag.lagrange_equations(simplify=True)

It might even make sense to *always* return the full mass matrix and
forcing vector, and then the user can do whatever they want by simply
slicing away the part they aren't interested in. This would make it
so there is only 1 way to use the class and would make it simpler for
the user to learn and for you to test/maintain/implement.

This would essentially enforce the proper usage.

Also, with regards to Stefan's comment about L being an expression and
having to supply the coordinates as a list, I think it is fine the way
you have it. They typical use case might be something like:

q = dynamicsymbols('q:8')
N = ReferenceFrame('N')
A = N.orientnew('A', 'Axis', [N.z, q[0]])
# more kinematics goes here, making use of q to build the lagrangian expression
lag = Lagrange(lagrangian, coordinates, constraints=con, forces=forces, frame=N)
M, f = lag.lagrange_equations()

Also, to be sure, you should not make any calls to things like
simplify() or trigsimp() in this class unless the user specifically
asks for it like Jason did. In my experience the results vary (both
execution time and the final expression obtained) as
simplify()/trigsimp() evolve and how you formulated the problem. For
toy problems it is usually fine but for anything non-trivial these
function calls simply become too expensive and slow the derivation
down to a crawl and therefore should not be on by default. Having an
optional keyword argument like Jason wrote would be fine though.

Otherwise, I think it looks great! I'm looking forward to seeing it
solve the rolling disc problem -- will that be your unit test case?

~Luke
> --
> You received this message because you are subscribed to the Google Groups
> "sympy" group.
> To post to this group, send email to sy...@googlegroups.com.
> To unsubscribe from this group, send email to
> sympy+un...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/sympy?hl=en.



--
“People call me a perfectionist, but I'm not. I'm a rightist. I do
something until it's right, and then I move on to the next thing.”
― James Cameron

Joachim Durchholz

unread,
Aug 1, 2012, 2:09:37 PM8/1/12
to sy...@googlegroups.com
Am 01.08.2012 16:31, schrieb Jason Moore:
> The __init__ method should require the bare
> minimum arguments (lagrangian, generlized coords) and all the others be
> optional, probably as keyword arguments.

I'd insist on keyword arguments. At about three positional arguments, it
starts to become easy to miscount argument positions so that values are
getting passed to some other parameter than expected.

> It can be handled with something
> like __init__(lagragian, coordinates, **kwargs).

There's an alternative: Split the thing in two, a "builder" object
that's mutable. You can then write stuff like
Builder(required1, required2).setOptional1(5).setOptional47(Blah)
and still have something with 47 optional parameters manageable.
That chaining style is particularly useful for adding to a list (e.g.
you can add series of boundary conditions without having to set them up
as a list).
Once you're done with the Builder object, you use a toEquation() (or
build() or whatever) method and get an immutable object, useful for
equation munging and other fun transformations.
These are all ideas from the Java world, where such stuff is
indispensable because Java has no keyword arguments, no hash literals,
and clumsy list pseudoliterals. The need is far less pressing in Python,
but I think it's good to be aware of alternative approaches just in case
the standard approach doesn't work well enough.

Jason Moore

unread,
Aug 1, 2012, 6:07:47 PM8/1/12
to sy...@googlegroups.com
Joachim's idea is used in scipy's ode class: http://docs.scipy.org/doc/scipy/reference/generated/scipy.integrate.ode.html#scipy.integrate.ode

The Kane class is basically like that and you can write it like:

fr, frstar = Kane(N).coords(qi, qdep=qd, coneqs=qc).speeds(ui, udep=ud, coneqs=uc).kindiff(kindiff).kanes_equations(bodies, forces)

and Angadh's Lagrange idea is similar. You can directly compare it to something like:

fr, frstar = Kane(N, qi, ui, bodies, forces, kindiff, qdep=qd, conc=qc, udep=ud, conu=uc).kanes_equations()

As far as I know the order doesn't matter in the method calls in the first version but they have to be called.

Jason

Gilbert Gede

unread,
Aug 1, 2012, 6:23:52 PM8/1/12
to sy...@googlegroups.com
A number of points here...

What is returned from the class: I setup the Kane class to return just the differential equations that it calculates, and not do any rearranging. In order to get rearranged quantities, I used (@property) attributes, to return things which had negligible computational costs - just concatenating existing expressions. I would argue for the same approach in Lagrange: return just the bare differential equations first, then rearranged quantities as property attributes. If the user wants the differential equations in a form which can be easily sent to an integration routine, they could call a method which does that matrix inversion for them (as others have suggested).

One of the reasons I set up the Kane class to be the way it is now, is that a lot of the operations involved in getting to the equations of motion could be relatively expensive. When there are nonholonomic (motion) constraints, some matrix inversion needs to happen, and I chose to separate that operation from the other steps involved in setting up the class before generating the equations of motion. Looking at it now, the methods I wrote could probably be kept, and just called by __init__() upon initialization of the Kane class using keyword args - and any tracebacks would show which function it was possibly hung up in.

That is the reason the Lagrange class has been proposed in its current fashion - to match the Kane class. That does bring up a point though: should we change Kane to be mostly formed on initialization? I think it would be best if they both used as similar an interface as possible. The way I approached the Kane class, the following quantities were needed:

Kane - 
  •     An inertial ReferenceFrame
  •     Coordinates (and possibly configuration constraints & identification of dependent coordinates)
  •     Speeds (and potentially velocity constraints & identification of dependent speeds, and possible identification of auxiliary speeds) *possibly expensive
  •     Kinematic Differential equations *possibly expensive, but not likely
  •     A list of Forces, and a list of Bodies/Particles *probably expensive

So, there are between 6 and 13 things that need to be supplied.

With the Lagrange class, there aren't as many things.

Lagrange - 
  • A Lagrangian
  • Coordinates (possibly configuration and/or velocity constraints)
  • (possibly nonconservative forces & an inertial ReferenceFrame for them)

We have between 2 and 6 things that need to be supplied.

Both of these classes are fairly narrow in scope - take in the necessary quantities, perform some math, give the basic results, and possibly do some more manipulation of those results. I'm not sure if they should be defined in a way which allows them to generate equations of motion (which will only happen once) in fewer setup calls, or in a way that is easier to follow, or if these two things are the same. I would argue for the following priorities: user written code would be easy for others to read -> user can write code easily -> as few setup calls as possible. I'm not sure what others' opinions would be on what is easiest to read though.

I don't like the approach:

>  Builder(required1, required2).setOptional1(5).setOptional47(Blah) 

as the methods are order dependent (some things need to be supplied before others, or at least simultaneously), and I don't see it as being much of an improvement over the current implementation in Kane in terms of readability - the kwargs approach is preferable if a change is going to be made. 


As for the need to supply coordinates - if someone has coordinates in their system which are specified functions of time (but not degrees of freedom), they will be "dynamicssymbols" (undefined functions of time), just as actual coordinates would be. But you would not want them to be treated as coordinates, as that would give incorrect equations of motion. That's why the user needs to specify which ones are coordinates (and every other dynamicsymbol will be assumed to not be a degree of freedom).


-Gilbert

Gilbert Gede

unread,
Aug 1, 2012, 6:24:48 PM8/1/12
to sy...@googlegroups.com
Jason,
I don't think you can do that with Kane right now, as a lot of those methods have no return (and thus return None).

Aaron Meurer

unread,
Aug 1, 2012, 6:58:55 PM8/1/12
to sy...@googlegroups.com
On Wed, Aug 1, 2012 at 12:09 PM, Joachim Durchholz <j...@durchholz.org> wrote:
> Am 01.08.2012 16:31, schrieb Jason Moore:
>
>> The __init__ method should require the bare
>> minimum arguments (lagrangian, generlized coords) and all the others be
>> optional, probably as keyword arguments.
>
>
> I'd insist on keyword arguments. At about three positional arguments, it
> starts to become easy to miscount argument positions so that values are
> getting passed to some other parameter than expected.

Note that in Python you can give positional arguments as keyword
arguments. For example, if you have

def f(a, b, **kwargs):
...

you can do f(1, 2 c=3) or f(a=1, b=2, c=3) or f(b=2, a=1, c=2) or f(1,
c=3, b=2). They are all the same.

>
>
>> It can be handled with something
>> like __init__(lagragian, coordinates, **kwargs).
>
> There's an alternative: Split the thing in two, a "builder" object that's
> mutable. You can then write stuff like
> Builder(required1, required2).setOptional1(5).setOptional47(Blah)
> and still have something with 47 optional parameters manageable.
> That chaining style is particularly useful for adding to a list (e.g. you
> can add series of boundary conditions without having to set them up as a
> list).
> Once you're done with the Builder object, you use a toEquation() (or build()
> or whatever) method and get an immutable object, useful for equation munging
> and other fun transformations.
> These are all ideas from the Java world, where such stuff is indispensable
> because Java has no keyword arguments, no hash literals, and clumsy list
> pseudoliterals. The need is far less pressing in Python, but I think it's
> good to be aware of alternative approaches just in case the standard
> approach doesn't work well enough.

I think a more Pythonic way if you don't want to have a function call
that is ten lines long is to first build up the dictionary and then
just call func(**kwargs), where kwargs is the dictionary. So the
approach will look more like

opts = {}
opt['opt1'] = 1
opt['opt2'] = False
...

func(arg1, arg2, **opt)

And as I noted above, you can even include arg1 and arg2 in opt if you want.

Aaron Meurer

gadha007

unread,
Aug 1, 2012, 7:03:26 PM8/1/12
to sy...@googlegroups.com, Gilbert, Jason Moore, Luke Peterson


On Wednesday, August 1, 2012 7:35:20 AM UTC-5, Stefan Krastanov wrote:
> 1.) Upon having done the kinematics and subequently developing a lagrangian,
> the user initializes the lagrange object where the lagrangian, L, is the
> argument.
>
> l = Lagrange(L)
What is L? Just an expression?

Yes. L is an expression for the lagrangian that the user can feed in.
 

>
> 2.) The user then supplies a list of generalized coordinates using the
> coords method.
>
> l.coords(list_of_gen_coords)
Why supply them? The opposite makes more sense. You can use some
dummies generated by `Lagrange` itself. Otherwise there can be clashes
in the names of the symbols. What happens when you supply to many (or
not enough) symbols?

The gen. coords. are supplied in a list from which the simple genralized speeds and accelerations are determind. (i.e. the first and second derivatives of the gen coords) which are used later. If you were to not supply all the symbols, you would have an insufficient number of equations describing the system.
I am not familiar with what you say about the dummies being generated by Lagrange. Could you tell me how exactly that is done so I can look into it?
Well as of right now it does. But it is flexible. The only reason it is structured how it is is to maintain uniformity in the procedure between Kane and Lagrange. Having seen multiple suggestions that suggest doing things differently i.e. in the __init__ method, I will work on making that change.

Aaron Meurer

unread,
Aug 1, 2012, 7:05:58 PM8/1/12
to sy...@googlegroups.com
I agree with this. You (the Kane class) know much better than the
user what order things should happen in. So unless there really are
situations where the user would know best what order things ought to
be done in, I would hide this information from the user using keyword
arguments, or via some other inherently unordered mechanism. Even if
the order does matter in one case and only the user would know, it
might be easier to just convert that to some keyword argument (like
a_before_b=True or something) (sorry for being a little generic, but I
don't know enough mechanics to know what case actually applies here).

Aaron Meurer
> --
> You received this message because you are subscribed to the Google Groups
> "sympy" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/sympy/-/zZ4nLEWSLy8J.

gadha007

unread,
Aug 1, 2012, 7:16:03 PM8/1/12
to sy...@googlegroups.com


On Wednesday, August 1, 2012 9:31:52 AM UTC-5, Jason Moore wrote:
I'm of the opinion that you should supply all the necessary information in the __init__ method of the Lagrange versus having all the setter methods. Stefan points this out too. The __init__ method should require the bare minimum arguments (lagrangian, generlized coords) and all the others be optional, probably as keyword arguments. It can be handled with something like __init__(lagragian, coordinates, **kwargs). This way the user is forced to supply all necessary items to the class on it's creation and it is immediately useful. I've always found it odd that the current Kane formulation calls for you to create and empty useless object and it only becomes useful after calling all the extra setter methods. 

Following that, the user should call the .lagrange_equations() method with no argmuments to do the computation (arguments to this method should reflect options for the computation like with or without simplification etc), which has the potential to take a long time. Once these equations are stored in the class, seems like mass_matrix(), forcing() etc should be methods not attributes, as they are doing some computing too, i.e. picking off coefficients and parsing Lagrange's equations. 

What methods do you plan to use for the rhs method? I could imagine this taking arguments for different types of solving methods.

I didn't really think about using different methods actually. I should explore this a little more.


Here is an example of how I imagine the class generation:

# build the object
lag = Lagrange(lagrangian, coordinates, constraints=con, forces=forces, frame=N)

# compute the equations
lag.lagrange_equations(simplify=True)

# extract the mass matrix
m = lag.mass_matrix()

# find the right hand side
rhs = lag.rhs(method=guass)

The preceding seems cleaner to me and more intuitive for the user following typical standards from other popular python software.

Yup. I agree it is simpler and the changes you suggest are reasonable and sensible to me too. Apologies for repeating myself from above but the only reason I do this is for the sake of uniformity.

gadha007

unread,
Aug 1, 2012, 7:31:08 PM8/1/12
to sy...@googlegroups.com


On Wednesday, August 1, 2012 11:59:19 AM UTC-5, Luke wrote:
I like the approach Jason outlined though I'm not 100% sure whether
mass_matrix & forcing should be properties or methods.  If these
matrices are being formed when you call:

lag.lagrange_equations(simplify=True)

then it seems like they should be properties rather than methods.
However, this opens up the possibility that the user asks for these
properties before lagrange_equations() has been called, in which case
I'm not sure what the best thing to do would be.

Apologies for the confusion from my post. These are indeed properties. Atleast for now.  
The matrices are indeed formed when lagrange_equations is called.


How about just returning mass_matrix and forcing matrices as list when
you call lagrange_equations, and getting rid of the mass_matrix and
forcing methods/properties entirely?  So it would look like:

M, f = lag.lagrange_equations(simplify=True) 

It might even make sense to *always* return the full mass matrix and
forcing vector, and then the user can do whatever they want by simply
slicing away the part they aren't interested in.  This would make it
so there is only 1 way to use the class and would make it simpler for
the user to learn and for you to test/maintain/implement. 

This would essentially enforce the proper usage. 

Well, the reason I prefer having it spit out the a vector of eoms right now is because there is the outside chance of there being people who might be interested in seeing the 'unmeddled' lagrange equations as is. What do you think?
 

Also, with regards to Stefan's comment about L being an expression and
having to supply the coordinates as a list, I think it is fine the way
you have it.  They typical use case might be something like:

q = dynamicsymbols('q:8')
N = ReferenceFrame('N')
A = N.orientnew('A', 'Axis', [N.z, q[0]])
# more kinematics goes here, making use of q to build the lagrangian expression
lag = Lagrange(lagrangian, coordinates, constraints=con, forces=forces, frame=N)
M, f = lag.lagrange_equations()

Also, to be sure, you should not make any calls to things like
simplify() or trigsimp() in this class unless the user specifically
asks for it like Jason did.  In my experience the results vary (both
execution time and the final expression obtained) as
simplify()/trigsimp() evolve and how you formulated the problem.  For
toy problems it is usually fine but for anything non-trivial these
function calls simply become too expensive and slow the derivation
down to a crawl and therefore should not be on by default.  Having an
optional keyword argument like Jason wrote would be fine though.

Otherwise, I think it looks great!  I'm looking forward to seeing it
solve the rolling disc problem -- will that be your unit test case?

I was thinking of starting with a disc in a plane (or maybe a disc rolling down an inclined plane) and then moving onto the rolling disc for my tests.

 
> sympy+unsubscribe@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/sympy?hl=en.



--
“People call me a perfectionist, but I'm not. I'm a rightist. I do
something until it's right, and then I move on to the next thing.”
― James Cameron
> sympy+unsubscribe@googlegroups.com.

gadha007

unread,
Aug 1, 2012, 9:30:32 PM8/1/12
to sy...@googlegroups.com


On Wednesday, August 1, 2012 5:23:52 PM UTC-5, Gilbert Gede wrote:
A number of points here...

What is returned from the class: I setup the Kane class to return just the differential equations that it calculates, and not do any rearranging. In order to get rearranged quantities, I used (@property) attributes, to return things which had negligible computational costs - just concatenating existing expressions. I would argue for the same approach in Lagrange: return just the bare differential equations first, then rearranged quantities as property attributes. If the user wants the differential equations in a form which can be easily sent to an integration routine, they could call a method which does that matrix inversion for them (as others have suggested).

One of the reasons I set up the Kane class to be the way it is now, is that a lot of the operations involved in getting to the equations of motion could be relatively expensive. When there are nonholonomic (motion) constraints, some matrix inversion needs to happen, and I chose to separate that operation from the other steps involved in setting up the class before generating the equations of motion. Looking at it now, the methods I wrote could probably be kept, and just called by __init__() upon initialization of the Kane class using keyword args - and any tracebacks would show which function it was possibly hung up in.

That is the reason the Lagrange class has been proposed in its current fashion - to match the Kane class. That does bring up a point though: should we change Kane to be mostly formed on initialization? I think it would be best if they both used as similar an interface as possible. The way I approached the Kane class, the following quantities were needed:

Kane - 
  •     An inertial ReferenceFrame
  •     Coordinates (and possibly configuration constraints & identification of dependent coordinates)
  •     Speeds (and potentially velocity constraints & identification of dependent speeds, and possible identification of auxiliary speeds) *possibly expensive
  •     Kinematic Differential equations *possibly expensive, but not likely
  •     A list of Forces, and a list of Bodies/Particles *probably expensive

So, there are between 6 and 13 things that need to be supplied.

With the Lagrange class, there aren't as many things.

Lagrange - 
  • A Lagrangian
  • Coordinates (possibly configuration and/or velocity constraints)
  • (possibly nonconservative forces & an inertial ReferenceFrame for them)

We have between 2 and 6 things that need to be supplied.

Both of these classes are fairly narrow in scope - take in the necessary quantities, perform some math, give the basic results, and possibly do some more manipulation of those results. I'm not sure if they should be defined in a way which allows them to generate equations of motion (which will only happen once) in fewer setup calls, or in a way that is easier to follow, or if these two things are the same. I would argue for the following priorities: user written code would be easy for others to read -> user can write code easily -> as few setup calls as possible. I'm not sure what others' opinions would be on what is easiest to read though.

I don't like the approach:

>  Builder(required1, required2).setOptional1(5).setOptional47(Blah) 

as the methods are order dependent (some things need to be supplied before others, or at least simultaneously), and I don't see it as being much of an improvement over the current implementation in Kane in terms of readability - the kwargs approach is preferable if a change is going to be made. 


As for the need to supply coordinates - if someone has coordinates in their system which are specified functions of time (but not degrees of freedom), they will be "dynamicssymbols" (undefined functions of time), just as actual coordinates would be. But you would not want them to be treated as coordinates, as that would give incorrect equations of motion. That's why the user needs to specify which ones are coordinates (and every other dynamicsymbol will be assumed to not be a degree of freedom).


-Gilbert

So when I began learning physics.mechanics, I liked the flow of things in the Kane class. At that point, I knew no Python and was using it as purely a dynamicist with no Python knowledge (or any programming language) would. I actually found the parts where it was being explicit about what I was doing to my liking i.e. with it's 'coords' and 'speeds' method. This was purely because it was comparable to how I did things on paper if I were using Kane's method. In fact, at that time I was always suspicious when certain steps weren't explicitly shown. E.g. The formation of the generalized active and inertia forces isn't shown to the user. Just directly obtaining Kane's equations was in my mind a little anti-climactic at the time. Being able to create each term separately would just give me the illusion of being in control even though I wouldn't be doing a lot of the 'dirty' work myself. But that was when I was learning and I accepted that things were happening like they should be. And I feel like that's the procedure that Gilbert and I have tried to incorporate even with Lagrange where things shouldn't be ambiguous to a dynamicist who knows nothing else but dynamics.

I don't speak for the majority and I'm not trying to be partisan either but I just feel that clarity trumps brevity. But I can also see that it can defeat the whole point of computation in the first place. :)

Joachim Durchholz

unread,
Aug 2, 2012, 3:40:20 AM8/2/12
to sy...@googlegroups.com
Am 02.08.2012 00:58, schrieb Aaron Meurer:
> I think a more Pythonic way if you don't want to have a function call
> that is ten lines long is to first build up the dictionary and then
> just call func(**kwargs), where kwargs is the dictionary. So the
> approach will look more like
>
> opts = {}
> opt['opt1'] = 1
> opt['opt2'] = False
> ...
>
> func(arg1, arg2, **opt)

As I said elsewhere, "more Pythonic" isn't a very helpful criterion.
Real questions, in ascending order of importance, are:
- What's easier to write?
- What's easier to read?
- What's easier to adapt to changing requirements?
- What's less prone to errors?
"More Pythonic" in itself doesn't help deciding what's better since it's
unclear which of these aspects it means. Mostly the "easier to read" one
since more idiomatic code is easier to read, but that's really not the
most important one. Plus, SymPy seems to be in somewhat widespread use,
to SymPy is in fact one of the projects that *define* what's "Pythonic" ;-)

Now... how do the alternatives look, and how do they fare wrt. to the
four questions above?

*Positional arguments*
func(arg1, arg2, opt1, null, opt3, ...)
-> Prone to errors. We all agree that's bad design.

*Keyword arguments*
func(arg1, arg2, opt1=..., opt3=..., ...)
-> Typos in the option names aren't detected at compile time, so there's
a slight problem in "easier to write" here.

*Dictionaries*
opts = {}
opts['opt1'] = ...
opts['opt3'] = ...
func(arg1, arg2, opts)
-> Better, but mistyped option names will go unnoticed unless every
opts-consuming function does a check whether it knows all names. I.e.
we're somewhat hampered on the accounts of "prone to errors" and "ease
of adaptation to changing requirements".
-> The nice thing is that it interoperates well with the keyword
arguments approach.

*DSL / Builder*
Builder(arg1, arg2)
.opt1(...)
.opt3(...)
. ...
.func()
-> Compared to the keyword argument approach, typos in the option names
are now caught by the runtime instead of via user code, so we get a
slight improvement in the "prone to errors" area.
-> Users can write a stdBuild(arg1,arg2) function that returns
Builder(arg1,arg2).opt1(...). That's an object with the standard
settings already predetermined, and remaining settings left as blanks to
fill in.
-> Remember that the Builder class members are essentially just a
dictionary. The final call could still be in kwargs form.

Now a small advantage on an important question can always be overridden
by a huge advantage on a less important questions.
And I'm a bit out of my league determining how small or large the
aspects outlined above are.

When I weigh the aspects I have seen, from my perspective, I'm seeing
kwargs and DSL both at the top (can't decide which one is first place),
with dictionaries considerably behind and positional arguments far behind.
YMMV :-)

Regards,
Jo

P.S.: Sorry for wall of text...
Message has been deleted

gadha007

unread,
Aug 4, 2012, 9:23:06 AM8/4/12
to sy...@googlegroups.com
All

As per the recommendations, all arguments are now passed in the initialization of the method. It has definitely made for a cleaner interface.

Thanks a lot for the input.

Angadh
Reply all
Reply to author
Forward
0 new messages