> 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?
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.
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?
> 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.
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