-
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
Changes from 2 commits
77cd5d4
61cd1a7
29fc9cb
418c1fc
2a5b721
3c6344a
bfe2138
9aef7d8
8f30674
35cba4d
d0d945f
0ca1d2b
331aa33
826f4d5
964dbe7
1778fee
fd981e7
f6c03a3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,6 @@ | |
# Create the visualization | ||
# ======================== | ||
|
||
scene.generate_visualization_json(coordinates + speeds, constants.keys(), x, | ||
constants.values()) | ||
scene.generate_visualization_json(sys) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. better, if we initialize Scene with a System instance, rather than passing it to a method. Ideally it should be on the lines:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. You're right. Also, I think the |
||
|
||
scene.display() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 commentThe reason will be displayed to describe this comment to others. Learn more. We should just have these models output a System in the future. |
||
"""Returns the symbolic equations of motion and associated variables for a | ||
simple one degree of freedom mass, spring, damper system with gravity | ||
and an optional external specified force. | ||
|
@@ -90,8 +90,11 @@ def generate_mass_spring_damper_equations_of_motion(external_force=True): | |
else: | ||
specified = None | ||
|
||
return (mass_matrix, forcing_vector, constants, coordinates, speeds, | ||
specified) | ||
if kane: | ||
return kane | ||
else: | ||
return (mass_matrix, forcing_vector, constants, coordinates, speeds, | ||
specified) | ||
|
||
|
||
def generate_n_link_pendulum_on_cart_equations_of_motion(n, cart_force=True, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
|
||
from .codegen.code import generate_ode_function | ||
from scipy.integrate import odeint | ||
|
||
class System(object): | ||
"""Manages the simulation (integration) of a system whose equations are | ||
given by KanesMethod or LagrangesMethod. | ||
|
||
All attributes can be set directly. With the exception of `method`, the | ||
attributes can also be set via keyword arguments to the constructor. | ||
|
||
Attributes | ||
---------- | ||
method : sympy.physics.mechanics.KanesMethod or | ||
sympy.physics.mechanics.LagrangesMethod | ||
The method used to generate the equations of motion. | ||
constants : dict, optional (default: all 1.0) | ||
Numerical values for the constants in the problem. Keys are the symbols | ||
for the constants, and values are floats. Constants that are not | ||
specified in this dict are given a default value of 1.0. | ||
specified_symbols : iterable of symbols, optional | ||
The symbols for the quantities whose numerical values you want to | ||
specify. The order of these must match the order of the | ||
`specified_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. | ||
specified_values : iterable of floats or functions, optional | ||
The numerical values of the `specified_symbols`, or functions (which | ||
take the same arguments as f, above) that can generate the numerical | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more. What's the difference between the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh. Reading further on in the code I can see these are There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @moorepants I don't know enough about how There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe 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. |
||
code_gen_backend : str, optional (default: 'lambdify') | ||
The backend used to generate the ode function. | ||
See the documentation of pydy.codegen.code.generate_ode_function`. | ||
ode_solver : function, optional (default: scipy.integrate.odeint) | ||
A function that performs forward integration. It must have the same | ||
signature as odeint, which is:: | ||
|
||
x_history = ode_solver(f, x0, t, args=(args,)) | ||
|
||
where f is a function f(x, t), x0 are the initial conditions, x_history | ||
is the history, and args is a keyword argument takes arguments that are | ||
then passed to f. TODO | ||
initial_conditions : dict, optional (default: all zero) | ||
Initial conditions for all coordinates and speeds. Keys are the symbols | ||
for the coordinates and speeds, and values are floats. Coordinates or | ||
speeds that are not specified in this dict are given a default value of | ||
zero. | ||
|
||
""" | ||
def __init__(self, method, **kwargs): | ||
self.method = method | ||
for k, v in kwargs: | ||
setattr(self, k, v) | ||
if self.constants != None: | ||
self.constants = self._find_constants() | ||
if self.specified_symbols != None and self.specified_values != None: | ||
self._check_specified() | ||
# TODO | ||
pass | ||
if self.code_gen_backend != None: | ||
self.code_gen_backend = 'lambdify' | ||
if ode_solver != None: | ||
self.ode_solver = odeint | ||
|
||
def _find_constants(self): | ||
othersymbols = method._find_othersymbols() | ||
return zip(othersymbols, len(othersymbols) * [1.0]) | ||
|
||
def _find_specified_symbols(self): | ||
specified_symbols = method._find_dynamicsymbols() | ||
return zip | ||
|
||
def generate_ode_function(self): | ||
"""Calls `pydy. TODO | ||
|
||
""" | ||
self.rhs = generate_ode_function( | ||
self.method.mass_matrix_full, self.method.forcing_full, | ||
self.constants.keys(), | ||
self.method._q, self.method._u) | ||
|
||
|
||
|
||
#for k, v in kwargs: | ||
# setattr(self, k, v) | ||
#sys.generate_ode_function(backend=...) | ||
#sys.code_gen_backend = | ||
#sys.ode_solver = odeint | ||
#sys.specified_symbols = | ||
#sys.specified_values = | ||
#sys.initial_conditions = | ||
#sys.constants = | ||
# | ||
#class Sys: | ||
# self.initial_conditions = zero( | ||
# def integrate(self, times): | ||
# # make rhs | ||
# sys.ode_solver(self.rhs, self.initial_conditions, times, | ||
# args=(self.con) | ||
# | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
|
||
import numpy as np | ||
from numpy import testing | ||
from sympy import symbols | ||
from sympy.physics.mechanics import dynamicsymbols | ||
from scipy.integrate import odeint | ||
|
||
from ..system import System | ||
from ..codegen.tests.models import \ | ||
generate_mass_spring_damper_equations_of_motion as mass_spring_damper | ||
|
||
|
||
class TestSystem(): | ||
|
||
def setup(self): | ||
|
||
self.kane = mass_spring_damper(kane=True) | ||
self.specified_symbol = dynamicsymbols('F') | ||
self.constant_map = dict(zip(symbols('m, k, c, g'), | ||
[2.0, 1.5, 0.5, 9.8])) | ||
self.sys = System(self.kane, | ||
code_gen_backend='lambdify', | ||
ode_solver=odeint, | ||
specified_symbols=(self.specified_symbol,), | ||
specified_values=np.ones(1), | ||
initial_conditions=np.zeros(2), | ||
constants=self.constant_map) | ||
|
||
def test_init(self): | ||
|
||
sys = System(self.kane) | ||
|
||
assert sys.method is self.kane | ||
assert sys.code_gen_backend == 'lambdify' | ||
assert sys.specified_symbols is dynamicsymbols('F') | ||
testing.assert_allclose(sys.specified_values, np.zeros(1)) | ||
testing.assert_allclose(sys.initial_conditions, np.zeros(2)) | ||
assert sys.constants == dict(zip(symbols('m, k, c, g'), | ||
[1.0, 1.0, 1.0, 1.0])) | ||
|
||
sys = System(self.kane, | ||
code_gen_backend='lambdify', | ||
ode_solver=odeint, | ||
specified_symbols=(self.specified_symbol,), | ||
specified_values=np.ones(1), | ||
initial_conditions=np.zeros(2), | ||
constants=self.constant_map) | ||
|
||
assert sys.method is self.kane | ||
assert sys.code_gen_backend == 'lambdify' | ||
assert sys.specified_symbols is dynamicsymbols('F') | ||
testing.assert_allclose(sys.specified_values, np.ones(1)) | ||
testing.assert_allclose(sys.initial_conditions, np.zeros(2)) | ||
assert sys.constants == self.constant_map | ||
|
||
sys.backend == 'cython' | ||
|
||
# You must set both specified symbols and values. | ||
assert_raises(ValueError, | ||
System(self.kane, | ||
specified_symbols=(self.specified_symbol))) | ||
|
||
assert_raises(ValueError, | ||
System(self.kane, | ||
specified_values=(self.specified_symbol))) | ||
|
||
# Check to make sure the specified function returns the correct | ||
# number of values. | ||
assert_raises(ValueError, | ||
System(self.kane, | ||
specified_symbols=(self.specified_symbol), | ||
specified_values=lambda x, t: np.ones(10)) | ||
|
||
|
||
# The specified symbol must exist in the equations of motion and not | ||
# be a state. | ||
assert_raises(ValueError, | ||
System(self.kane, | ||
specified_symbols=(dynamicsymbols('G')), | ||
specified_values=np.ones(1))) | ||
|
||
# The same number of specified symbols and values are needed. | ||
assert_raises(ValueError, | ||
System(self.kane, | ||
specified_symbols=(self.specified_symbol), | ||
specified_values=np.ones(2)) | ||
|
||
|
||
def test_generate_ode_function(self): | ||
|
||
rhs = self.sys.generate_ode_function() | ||
|
||
assert rhs is self.rhs | ||
|
||
args = {'constants': self.constant_map.values, | ||
'specified': np.zeros(1)} | ||
|
||
actual = rhs(np.ones(2), 0.0, args=(args,)) | ||
|
||
testing.assert_allclose(actual, | ||
np.array([ , ])) | ||
|
||
def find_constants(self): | ||
|
||
constant_dict = self.sys._find_constants() | ||
|
||
assert constant_dict = self.constant_map | ||
|
||
def find_specified_symbols(self): | ||
|
||
specified_dict = self.sys._find_specified_symbols() | ||
|
||
assert specified_dict = {self.dynamic_symbol: 0.0} | ||
|
||
def test_integrate(self): | ||
|
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
andsys
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.