-
Notifications
You must be signed in to change notification settings - Fork 114
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Prototype System class. #81
Conversation
- Edited double_pendulum example to use System class. - Started writing the system class. This commit is partial. Maybe we can later squash it with other commits.
@tarzzz this will allow for re-integrating (though, as Jason and I discussed, it's not necessary for re-integrating). |
I added what I started. The assert_raises are bogus, they need to be correctly done. Feel free to run with things. I'll help when I can. |
@@ -9,7 +9,7 @@ | |||
import sympy.physics.mechanics as me | |||
|
|||
|
|||
def generate_mass_spring_damper_equations_of_motion(external_force=True): | |||
def generate_mass_spring_damper_equations_of_motion(external_force=True, kane=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should just have these models output a System in the future.
In sympy/sympy#7790, @jcrist said:
|
@jcrist Look at how we are thinking one would construct a System. Are you instead proposing a constructor that would be called like |
@moorepants and I discussed this for a while last night, about having a class that just held equations. We discussed that then the class would just be a mathematical thing (like EOM). We were discussing that maybe we don't want to lose information about the mechanics (bodies, etc), since those would be useful for setting up visualization. @moorepants how concerned should we be about deprecation? I also want to have a functional version of this class by the end of the week; I don't want this to explode into a big project. We can have bigger plans, but I think we want something functional and simple for now. |
values. If not provided, we get all the specified symbols | ||
from the `method`, and set their numerical value to 0. You can also | ||
provide a subset of symbols for this attribute, in which case the | ||
remaining symbols still have their numerical value set to 0. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between the constants
dictionary and specified_symbols
/specified_values
? If I were to do: dict(zip(specified_symbols, specified_values))
would this be the same as constants
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. Reading further on in the code I can see these are dynamicsymbols
, not symbols
. Perhaps a better name for these parameters? fixed_dynsyms
? I think a dictionary for specifying these would be cleaner as well, as they are relational (can't have one iterable longer than the other). If a user has iterables, could just run dict(zip(*))
to make the dict, and then pass it to the method themselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @jcrist. This is something that @moorepants and I discussed for a bit but we are still not completely satisfied. We agree with your point about how they are relational. However, Jason was saying that the specified_values
could be a function that returns more than one number, to define multiple specified_symbols
. That is, you may have symbols [a, b, c]
and values lambda x, t: np.zeros(3)
to define all 3 of those symbols. If we were to use a dict, we'd have to give each symbol its value separately.
Can you think of a good solution that (1) allows simulatenously defining a numerical value for more than one specified symbol at a time, and (2) enforces the relational (key, value) aspect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And yeah good point, the documentation for distinguishing symbols and specified's could be better. specifieds
is also kinda weird b/c it's an adjective, not a noun.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds like something more complicated that should be done outside the class. Suppose I have an iterable of symbols: [a, b, c, d, e, f]
, and an iterable of functions [f1, f2]
. f1
generates the value for a, b
, and f2
generates for c - f
. What if the code for f1
changed to now return 3 things? Now you have an off by one error all symbols after b
. I'm not sure how you can resolve this in a robust way with multiple returns from a single function. Unless you plan on dynamically calling these functions in a systematic manner during integration/simulation, I don't think this use is necessary. The user can still just as easily call the functions themselves, and generate the dictionary to pass to System
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That may work. I like it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay I want to go that route but it may cause issues when passing self.specifieds.keys()
to pydy.codegen.code.generate_ode_function
. Does anyone have insight as to what'll happen here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@moorepants I don't know enough about how generate_ode_function
parses the list of functions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can update generate_ode_function too with this I guess. I guess generate_ode_function could take a system as an argument now.
Right now the rhs function requires the specified to be in a certain form. This shows what it needs to be: https://github.com/pydy/pydy/blob/master/pydy/codegen/code.py#L441
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah shucks. What I have now does not conform. I'll commit it and maybe we can work on making it conform.
Losing information about the bodies would be an issue. I'm not sure how visualization works now, but some way of relating a rigid body in space to the dynamicsymbols could probably be found that didn't rely on housing inside the |
In the future, I want everything, so we can keep discussing. But for this week, I just want whatever will help out you, @tarzzz , and @moorepants . |
I hadn't thought of *Method returning a System...hmm. I'd be interested in hearing more about that. |
@chrisdembia Deprecating things in SymPy releases needs to be adhered too but here, in PyDy, this is alpha software, so we can change up things. But...this stuff is in the wild now and I just gave 4 tutorials on it so people may use it. I'd like to minimize users getting confused and annoyed if possible. It can have a detrimental effect on retaining users. But like I said, this is classified as alpha software. |
One classic example for specified inputs are controllers. State feedback controllers are computed like f = -k * x, and if x is big then you have a big matrix multiplication. You only want to call this one per integration step to get the n inputs. So it doesn't really work with the dictionary concept. Maybe we need to support both methods of input: dictionary and function. The bottom line is that we don't want to cripple the speed of any computation that must happen at every time step. Computation of speified inputs is one of those. |
Oh, they're the inputs! This was not clear to me above. Yeah, dynamically solving for them is very important in simulation. I think the dictionary concept I outlined above retains flexibility, while still being computationally simple enough to work. |
BTW, after reading Jim's comment on a super class for the *Methods, I think that makes sense to have one. It can be an abstract one for sure, but it would at least make sure we conform on attribute names for the things we need in the system class. |
It'd be nice if |
Agreed. |
|
||
frames_per_sec = 60 | ||
final_time = 5.0 | ||
|
||
t = linspace(0.0, final_time, final_time * frames_per_sec) | ||
x = odeint(xdot_function, x0, t, args=(args,)) | ||
|
||
x = sys.integrate(times) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is what I had been implying for re-integrating Scene visualization stuff.
So that we supply times
and sys
to the Scene class, and is easier to handle than the existing structure.
I am ready to work on integrating this code with Scene
class(after tomorrow).
But also there will be some backwards incompatible changes(in Scene class as well), when this is integrated into Scene.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that is why we are creating this, so that you can have something easier to work with in your viz code. It won't be ready tomorrow, but please help contribute to this so we can get it done more quickly.
When using KanesMethod, the user must call |
Ah I think the answer is yes, and the method is |
I didn't run any tests or check for syntax errors :/ specifieds need a lot of work.
Okay we're getting close. I'd like some help. Please see the initial message to see where you might be able to help. |
km = KanesMethod(...) | ||
km.kanes_equations(force_list, body_list) | ||
sys = System(km) | ||
times = [0, 5.0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we pass in a vector of times instead of a start and stop? Some people may want variable step outputs or to control the time step.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
times
can be either I think; it's whatever odeint
can accept. I was showing the start and stop for simplicity. Maybe the example is misleading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think odeint only accepts a vector of times: t : array
A sequence of time points for which to solve for y. The initial value point should be the first element of this sequence.
Should not system be made into an independent submodule? It is fine to put it here, since it is in development, but later on we can migrate it such that dir. structure looks like: pydy/ |
@@ -51,14 +51,13 @@ | |||
# this, we create a world frame that is rotated +90 degrees about the N frame's | |||
# z direction. | |||
world_frame = N.orientnew('world', 'Axis', [0.5 * pi, N.z]) | |||
scene = Scene(world_frame, O, | |||
scene = Scene(sys, world_frame, O, | |||
linkP_viz_frame, linkR_viz_frame, sphereP_viz_frame, sphereR_viz_frame) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way, that we can use System to eliminate the ReferenceFrame and Point argument supplied here.
Maybe they can be saved in System class too?
so that all we need to do is:
scene = Scene(sys, vframes_as_a_list_or_tuple)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be nice to clean up the Scene constructor. However, I am not sure this is a good idea. Those two arguments are specific to how the visualization looks, and they have been useful for me before. We can consider this again down the line when System becomes bigger and potentially manages the viz info. But for now (this PR), System does not handle viz info.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you have to have the option of providing any frame or point as the base viz frame. You could snatch the base frame from the Methods as a default frame, and maybe you could go through all the points stored in the Methods class and find one that has zero velocity in the base frame as a default.
def _Kane_inlist_insyms(self): | ||
"""TODO temporary.""" | ||
uaux = self.eom_method._uaux | ||
uauxdot = [diff(i, t) for i in uaux] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this should fail as diff isn't imported anywhere.
I just realized that moving the specified parsing out of the rhs isn't that trivial because of the function of x and t. So I guess we will keep this as is and just note the backward incompatibility. |
I just did a speed test on evaluating the rhs in master and this branch. The old version gave me 189 us per rhs eval and the new gave 380 us per eval. That's a significant increase for the dictionary parsing. Here is a profile:
|
This speed decrease is because we want to support this fancy dictionary for specified inputs. I'm not sure it is worth it. It is a really nice convenience, but adds some overhead. Should we worry about it? |
I think that's too much of a speed decrease. I'm not sure what we should On Tue, Aug 5, 2014 at 11:29 AM, Jason K. Moore notifications@github.com
|
My recommendation is to leave as-is now and create a new issue for restoring the previous efficiency by supplying a different value for
|
Ready for final review. @moorepants let me know when this can be merged. I allowed users a fast path (3x faster using 'symbols' and 'values', restoring the previous speed). However, there is a small complication. If users, with the same System instance, switch between formats for specifieds, they'll need to re-call generate_ode_function. That's b/c they are in control of the order of specifieds under 'symbols' and 'values' format. |
One worry here. If we push as is, and Tarun uses it for the Scene stuff, then you will only be able to visualize scenes created from KanesMethod. So we will, at least, need to make an release stopping issue to fix that too. Would a Method abstract base class be relatively simple? We just need to support mass_matrix_full, forcing_vector_full, coordinates, speeds, specified, etc. Maybe that wouldn't be too hard. |
|
||
sys = System(kane, | ||
constants={mass: 1.0, stiffness: 1.0, damping: 0.2, gravity: 9.8}, | ||
specified={'symbols': [force], 'values': lambda x, t: sin(t)}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do you make sure that your list of specifieds is the same order as those found from the kane object?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't. If the user is using the symbols/values format, they are in control of the order of symbols. See https://github.com/pydy/pydy/pull/81/files#diff-105304ef1b8479ef4d5d9e41b786cc34R383
I'm going to do an editing pass here over every thing. So maybe hold off editing. |
Ahh good point!
The thing I don't know how to do is find the list of constants and specifieds for a LagrangesMethod. That's the limitation (for me). |
@jcrist has some new methods in sympys sympy/sympy#7790 that adds generic functions for finding the various symbols for either method. We need to use those. |
So should that get in first? |
He's about to merge those. So we can work with SymPy master for PyDy master or just copy his code to pydy temporarily until sympy makes a new release. |
Both sound messy! I guess the latter makes more sense. Is it okay if the visualizer temporarily does not support Lagrange (until the next sympy release)? Alternatively, the visualizer can for now just have partial support of LagrangesMethod systems. |
That would require some effort on @tarzzz 's behalf so that Scene can only optionally work with a System (an alternate constructor that just uses the System to initialize current constructor arguments). |
I'm fine merging this as is, but make an issue for the 0.3.0 milestone that requires us to support KanesMethod, LangrangesMethod, and some kind of generic method for when a user builds the model manually not using a method class. |
Should I merge? |
Sure |
@tarzzz feel free to start using this. |
@moorepants, can add your tests to this branch? From this point I'll write code in a test-driven way. It'd be fun to continue to work on this together, but I'm also happy to do the coding and you can review afterwards (if you're busy, etc.).
Edit Scene so that it takes in a System. @tarzzz ?Make KanesMethod and LagrangesMethod derive from a single abstract base class (ABC), perhaps MechanicsMethod. This ABC will be required to provide certain attributes. EDIT: Let's not do this now? [Jason: I'd wait on this too, it isn't necessary]specifieds
. @moorepants ?generate_ode_function
,integrate
. @moorepants?constants
,specifieds
andinitial-conditions
properties as dictsRewrite so that generate_ode_function takes a system. This may take a lot of work, since right now System is what calls generate_ode_function.Revertgenerate_ode_function
to original call signature and move the specified and constants dict parsing to the System class.