switching to high quality piper tts and added label translations
This commit is contained in:
+1084
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,340 @@
|
||||
from sympy import (Symbol, symbols, sin, cos, Matrix, zeros,
|
||||
simplify)
|
||||
from sympy.physics.vector import Point, ReferenceFrame, dynamicsymbols, Dyadic
|
||||
from sympy.physics.mechanics import inertia, Body
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
|
||||
def test_default():
|
||||
with warns_deprecated_sympy():
|
||||
body = Body('body')
|
||||
assert body.name == 'body'
|
||||
assert body.loads == []
|
||||
point = Point('body_masscenter')
|
||||
point.set_vel(body.frame, 0)
|
||||
com = body.masscenter
|
||||
frame = body.frame
|
||||
assert com.vel(frame) == point.vel(frame)
|
||||
assert body.mass == Symbol('body_mass')
|
||||
ixx, iyy, izz = symbols('body_ixx body_iyy body_izz')
|
||||
ixy, iyz, izx = symbols('body_ixy body_iyz body_izx')
|
||||
assert body.inertia == (inertia(body.frame, ixx, iyy, izz, ixy, iyz, izx),
|
||||
body.masscenter)
|
||||
|
||||
|
||||
def test_custom_rigid_body():
|
||||
# Body with RigidBody.
|
||||
rigidbody_masscenter = Point('rigidbody_masscenter')
|
||||
rigidbody_mass = Symbol('rigidbody_mass')
|
||||
rigidbody_frame = ReferenceFrame('rigidbody_frame')
|
||||
body_inertia = inertia(rigidbody_frame, 1, 0, 0)
|
||||
with warns_deprecated_sympy():
|
||||
rigid_body = Body('rigidbody_body', rigidbody_masscenter,
|
||||
rigidbody_mass, rigidbody_frame, body_inertia)
|
||||
com = rigid_body.masscenter
|
||||
frame = rigid_body.frame
|
||||
rigidbody_masscenter.set_vel(rigidbody_frame, 0)
|
||||
assert com.vel(frame) == rigidbody_masscenter.vel(frame)
|
||||
assert com.pos_from(com) == rigidbody_masscenter.pos_from(com)
|
||||
|
||||
assert rigid_body.mass == rigidbody_mass
|
||||
assert rigid_body.inertia == (body_inertia, rigidbody_masscenter)
|
||||
|
||||
assert rigid_body.is_rigidbody
|
||||
|
||||
assert hasattr(rigid_body, 'masscenter')
|
||||
assert hasattr(rigid_body, 'mass')
|
||||
assert hasattr(rigid_body, 'frame')
|
||||
assert hasattr(rigid_body, 'inertia')
|
||||
|
||||
|
||||
def test_particle_body():
|
||||
# Body with Particle
|
||||
particle_masscenter = Point('particle_masscenter')
|
||||
particle_mass = Symbol('particle_mass')
|
||||
particle_frame = ReferenceFrame('particle_frame')
|
||||
with warns_deprecated_sympy():
|
||||
particle_body = Body('particle_body', particle_masscenter,
|
||||
particle_mass, particle_frame)
|
||||
com = particle_body.masscenter
|
||||
frame = particle_body.frame
|
||||
particle_masscenter.set_vel(particle_frame, 0)
|
||||
assert com.vel(frame) == particle_masscenter.vel(frame)
|
||||
assert com.pos_from(com) == particle_masscenter.pos_from(com)
|
||||
|
||||
assert particle_body.mass == particle_mass
|
||||
assert not hasattr(particle_body, "_inertia")
|
||||
assert hasattr(particle_body, 'frame')
|
||||
assert hasattr(particle_body, 'masscenter')
|
||||
assert hasattr(particle_body, 'mass')
|
||||
assert particle_body.inertia == (Dyadic(0), particle_body.masscenter)
|
||||
assert particle_body.central_inertia == Dyadic(0)
|
||||
assert not particle_body.is_rigidbody
|
||||
|
||||
particle_body.central_inertia = inertia(particle_frame, 1, 1, 1)
|
||||
assert particle_body.central_inertia == inertia(particle_frame, 1, 1, 1)
|
||||
assert particle_body.is_rigidbody
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
particle_body = Body('particle_body', mass=particle_mass)
|
||||
assert not particle_body.is_rigidbody
|
||||
point = particle_body.masscenter.locatenew('point', particle_body.x)
|
||||
point_inertia = particle_mass * inertia(particle_body.frame, 0, 1, 1)
|
||||
particle_body.inertia = (point_inertia, point)
|
||||
assert particle_body.inertia == (point_inertia, point)
|
||||
assert particle_body.central_inertia == Dyadic(0)
|
||||
assert particle_body.is_rigidbody
|
||||
|
||||
|
||||
def test_particle_body_add_force():
|
||||
# Body with Particle
|
||||
particle_masscenter = Point('particle_masscenter')
|
||||
particle_mass = Symbol('particle_mass')
|
||||
particle_frame = ReferenceFrame('particle_frame')
|
||||
with warns_deprecated_sympy():
|
||||
particle_body = Body('particle_body', particle_masscenter,
|
||||
particle_mass, particle_frame)
|
||||
|
||||
a = Symbol('a')
|
||||
force_vector = a * particle_body.frame.x
|
||||
particle_body.apply_force(force_vector, particle_body.masscenter)
|
||||
assert len(particle_body.loads) == 1
|
||||
point = particle_body.masscenter.locatenew(
|
||||
particle_body._name + '_point0', 0)
|
||||
point.set_vel(particle_body.frame, 0)
|
||||
force_point = particle_body.loads[0][0]
|
||||
|
||||
frame = particle_body.frame
|
||||
assert force_point.vel(frame) == point.vel(frame)
|
||||
assert force_point.pos_from(force_point) == point.pos_from(force_point)
|
||||
|
||||
assert particle_body.loads[0][1] == force_vector
|
||||
|
||||
|
||||
def test_body_add_force():
|
||||
# Body with RigidBody.
|
||||
rigidbody_masscenter = Point('rigidbody_masscenter')
|
||||
rigidbody_mass = Symbol('rigidbody_mass')
|
||||
rigidbody_frame = ReferenceFrame('rigidbody_frame')
|
||||
body_inertia = inertia(rigidbody_frame, 1, 0, 0)
|
||||
with warns_deprecated_sympy():
|
||||
rigid_body = Body('rigidbody_body', rigidbody_masscenter,
|
||||
rigidbody_mass, rigidbody_frame, body_inertia)
|
||||
|
||||
l = Symbol('l')
|
||||
Fa = Symbol('Fa')
|
||||
point = rigid_body.masscenter.locatenew(
|
||||
'rigidbody_body_point0',
|
||||
l * rigid_body.frame.x)
|
||||
point.set_vel(rigid_body.frame, 0)
|
||||
force_vector = Fa * rigid_body.frame.z
|
||||
# apply_force with point
|
||||
rigid_body.apply_force(force_vector, point)
|
||||
assert len(rigid_body.loads) == 1
|
||||
force_point = rigid_body.loads[0][0]
|
||||
frame = rigid_body.frame
|
||||
assert force_point.vel(frame) == point.vel(frame)
|
||||
assert force_point.pos_from(force_point) == point.pos_from(force_point)
|
||||
assert rigid_body.loads[0][1] == force_vector
|
||||
# apply_force without point
|
||||
rigid_body.apply_force(force_vector)
|
||||
assert len(rigid_body.loads) == 2
|
||||
assert rigid_body.loads[1][1] == force_vector
|
||||
# passing something else than point
|
||||
raises(TypeError, lambda: rigid_body.apply_force(force_vector, 0))
|
||||
raises(TypeError, lambda: rigid_body.apply_force(0))
|
||||
|
||||
def test_body_add_torque():
|
||||
with warns_deprecated_sympy():
|
||||
body = Body('body')
|
||||
torque_vector = body.frame.x
|
||||
body.apply_torque(torque_vector)
|
||||
|
||||
assert len(body.loads) == 1
|
||||
assert body.loads[0] == (body.frame, torque_vector)
|
||||
raises(TypeError, lambda: body.apply_torque(0))
|
||||
|
||||
def test_body_masscenter_vel():
|
||||
with warns_deprecated_sympy():
|
||||
A = Body('A')
|
||||
N = ReferenceFrame('N')
|
||||
with warns_deprecated_sympy():
|
||||
B = Body('B', frame=N)
|
||||
A.masscenter.set_vel(N, N.z)
|
||||
assert A.masscenter_vel(B) == N.z
|
||||
assert A.masscenter_vel(N) == N.z
|
||||
|
||||
def test_body_ang_vel():
|
||||
with warns_deprecated_sympy():
|
||||
A = Body('A')
|
||||
N = ReferenceFrame('N')
|
||||
with warns_deprecated_sympy():
|
||||
B = Body('B', frame=N)
|
||||
A.frame.set_ang_vel(N, N.y)
|
||||
assert A.ang_vel_in(B) == N.y
|
||||
assert B.ang_vel_in(A) == -N.y
|
||||
assert A.ang_vel_in(N) == N.y
|
||||
|
||||
def test_body_dcm():
|
||||
with warns_deprecated_sympy():
|
||||
A = Body('A')
|
||||
B = Body('B')
|
||||
A.frame.orient_axis(B.frame, B.frame.z, 10)
|
||||
assert A.dcm(B) == Matrix([[cos(10), sin(10), 0], [-sin(10), cos(10), 0], [0, 0, 1]])
|
||||
assert A.dcm(B.frame) == Matrix([[cos(10), sin(10), 0], [-sin(10), cos(10), 0], [0, 0, 1]])
|
||||
|
||||
def test_body_axis():
|
||||
N = ReferenceFrame('N')
|
||||
with warns_deprecated_sympy():
|
||||
B = Body('B', frame=N)
|
||||
assert B.x == N.x
|
||||
assert B.y == N.y
|
||||
assert B.z == N.z
|
||||
|
||||
def test_apply_force_multiple_one_point():
|
||||
a, b = symbols('a b')
|
||||
P = Point('P')
|
||||
with warns_deprecated_sympy():
|
||||
B = Body('B')
|
||||
f1 = a*B.x
|
||||
f2 = b*B.y
|
||||
B.apply_force(f1, P)
|
||||
assert B.loads == [(P, f1)]
|
||||
B.apply_force(f2, P)
|
||||
assert B.loads == [(P, f1+f2)]
|
||||
|
||||
def test_apply_force():
|
||||
f, g = symbols('f g')
|
||||
q, x, v1, v2 = dynamicsymbols('q x v1 v2')
|
||||
P1 = Point('P1')
|
||||
P2 = Point('P2')
|
||||
with warns_deprecated_sympy():
|
||||
B1 = Body('B1')
|
||||
B2 = Body('B2')
|
||||
N = ReferenceFrame('N')
|
||||
|
||||
P1.set_vel(B1.frame, v1*B1.x)
|
||||
P2.set_vel(B2.frame, v2*B2.x)
|
||||
force = f*q*N.z # time varying force
|
||||
|
||||
B1.apply_force(force, P1, B2, P2) #applying equal and opposite force on moving points
|
||||
assert B1.loads == [(P1, force)]
|
||||
assert B2.loads == [(P2, -force)]
|
||||
|
||||
g1 = B1.mass*g*N.y
|
||||
g2 = B2.mass*g*N.y
|
||||
|
||||
B1.apply_force(g1) #applying gravity on B1 masscenter
|
||||
B2.apply_force(g2) #applying gravity on B2 masscenter
|
||||
|
||||
assert B1.loads == [(P1,force), (B1.masscenter, g1)]
|
||||
assert B2.loads == [(P2, -force), (B2.masscenter, g2)]
|
||||
|
||||
force2 = x*N.x
|
||||
|
||||
B1.apply_force(force2, reaction_body=B2) #Applying time varying force on masscenter
|
||||
|
||||
assert B1.loads == [(P1, force), (B1.masscenter, force2+g1)]
|
||||
assert B2.loads == [(P2, -force), (B2.masscenter, -force2+g2)]
|
||||
|
||||
def test_apply_torque():
|
||||
t = symbols('t')
|
||||
q = dynamicsymbols('q')
|
||||
with warns_deprecated_sympy():
|
||||
B1 = Body('B1')
|
||||
B2 = Body('B2')
|
||||
N = ReferenceFrame('N')
|
||||
torque = t*q*N.x
|
||||
|
||||
B1.apply_torque(torque, B2) #Applying equal and opposite torque
|
||||
assert B1.loads == [(B1.frame, torque)]
|
||||
assert B2.loads == [(B2.frame, -torque)]
|
||||
|
||||
torque2 = t*N.y
|
||||
B1.apply_torque(torque2)
|
||||
assert B1.loads == [(B1.frame, torque+torque2)]
|
||||
|
||||
def test_clear_load():
|
||||
a = symbols('a')
|
||||
P = Point('P')
|
||||
with warns_deprecated_sympy():
|
||||
B = Body('B')
|
||||
force = a*B.z
|
||||
B.apply_force(force, P)
|
||||
assert B.loads == [(P, force)]
|
||||
B.clear_loads()
|
||||
assert B.loads == []
|
||||
|
||||
def test_remove_load():
|
||||
P1 = Point('P1')
|
||||
P2 = Point('P2')
|
||||
with warns_deprecated_sympy():
|
||||
B = Body('B')
|
||||
f1 = B.x
|
||||
f2 = B.y
|
||||
B.apply_force(f1, P1)
|
||||
B.apply_force(f2, P2)
|
||||
assert B.loads == [(P1, f1), (P2, f2)]
|
||||
B.remove_load(P2)
|
||||
assert B.loads == [(P1, f1)]
|
||||
B.apply_torque(f1.cross(f2))
|
||||
assert B.loads == [(P1, f1), (B.frame, f1.cross(f2))]
|
||||
B.remove_load()
|
||||
assert B.loads == [(P1, f1)]
|
||||
|
||||
def test_apply_loads_on_multi_degree_freedom_holonomic_system():
|
||||
"""Example based on: https://pydy.readthedocs.io/en/latest/examples/multidof-holonomic.html"""
|
||||
with warns_deprecated_sympy():
|
||||
W = Body('W') #Wall
|
||||
B = Body('B') #Block
|
||||
P = Body('P') #Pendulum
|
||||
b = Body('b') #bob
|
||||
q1, q2 = dynamicsymbols('q1 q2') #generalized coordinates
|
||||
k, c, g, kT = symbols('k c g kT') #constants
|
||||
F, T = dynamicsymbols('F T') #Specified forces
|
||||
|
||||
#Applying forces
|
||||
B.apply_force(F*W.x)
|
||||
W.apply_force(k*q1*W.x, reaction_body=B) #Spring force
|
||||
W.apply_force(c*q1.diff()*W.x, reaction_body=B) #dampner
|
||||
P.apply_force(P.mass*g*W.y)
|
||||
b.apply_force(b.mass*g*W.y)
|
||||
|
||||
#Applying torques
|
||||
P.apply_torque(kT*q2*W.z, reaction_body=b)
|
||||
P.apply_torque(T*W.z)
|
||||
|
||||
assert B.loads == [(B.masscenter, (F - k*q1 - c*q1.diff())*W.x)]
|
||||
assert P.loads == [(P.masscenter, P.mass*g*W.y), (P.frame, (T + kT*q2)*W.z)]
|
||||
assert b.loads == [(b.masscenter, b.mass*g*W.y), (b.frame, -kT*q2*W.z)]
|
||||
assert W.loads == [(W.masscenter, (c*q1.diff() + k*q1)*W.x)]
|
||||
|
||||
|
||||
def test_parallel_axis():
|
||||
N = ReferenceFrame('N')
|
||||
m, Ix, Iy, Iz, a, b = symbols('m, I_x, I_y, I_z, a, b')
|
||||
Io = inertia(N, Ix, Iy, Iz)
|
||||
# Test RigidBody
|
||||
o = Point('o')
|
||||
p = o.locatenew('p', a * N.x + b * N.y)
|
||||
with warns_deprecated_sympy():
|
||||
R = Body('R', masscenter=o, frame=N, mass=m, central_inertia=Io)
|
||||
Ip = R.parallel_axis(p)
|
||||
Ip_expected = inertia(N, Ix + m * b**2, Iy + m * a**2,
|
||||
Iz + m * (a**2 + b**2), ixy=-m * a * b)
|
||||
assert Ip == Ip_expected
|
||||
# Reference frame from which the parallel axis is viewed should not matter
|
||||
A = ReferenceFrame('A')
|
||||
A.orient_axis(N, N.z, 1)
|
||||
assert simplify(
|
||||
(R.parallel_axis(p, A) - Ip_expected).to_matrix(A)) == zeros(3, 3)
|
||||
# Test Particle
|
||||
o = Point('o')
|
||||
p = o.locatenew('p', a * N.x + b * N.y)
|
||||
with warns_deprecated_sympy():
|
||||
P = Body('P', masscenter=o, mass=m, frame=N)
|
||||
Ip = P.parallel_axis(p, N)
|
||||
Ip_expected = inertia(N, m * b ** 2, m * a ** 2, m * (a ** 2 + b ** 2),
|
||||
ixy=-m * a * b)
|
||||
assert not P.is_rigidbody
|
||||
assert Ip == Ip_expected
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
from sympy import sin, cos, tan, pi, symbols, Matrix, S, Function
|
||||
from sympy.physics.mechanics import (Particle, Point, ReferenceFrame,
|
||||
RigidBody)
|
||||
from sympy.physics.mechanics import (angular_momentum, dynamicsymbols,
|
||||
kinetic_energy, linear_momentum,
|
||||
outer, potential_energy, msubs,
|
||||
find_dynamicsymbols, Lagrangian)
|
||||
|
||||
from sympy.physics.mechanics.functions import (
|
||||
center_of_mass, _validate_coordinates, _parse_linear_solver)
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
|
||||
q1, q2, q3, q4, q5 = symbols('q1 q2 q3 q4 q5')
|
||||
N = ReferenceFrame('N')
|
||||
A = N.orientnew('A', 'Axis', [q1, N.z])
|
||||
B = A.orientnew('B', 'Axis', [q2, A.x])
|
||||
C = B.orientnew('C', 'Axis', [q3, B.y])
|
||||
|
||||
|
||||
def test_linear_momentum():
|
||||
N = ReferenceFrame('N')
|
||||
Ac = Point('Ac')
|
||||
Ac.set_vel(N, 25 * N.y)
|
||||
I = outer(N.x, N.x)
|
||||
A = RigidBody('A', Ac, N, 20, (I, Ac))
|
||||
P = Point('P')
|
||||
Pa = Particle('Pa', P, 1)
|
||||
Pa.point.set_vel(N, 10 * N.x)
|
||||
raises(TypeError, lambda: linear_momentum(A, A, Pa))
|
||||
raises(TypeError, lambda: linear_momentum(N, N, Pa))
|
||||
assert linear_momentum(N, A, Pa) == 10 * N.x + 500 * N.y
|
||||
|
||||
|
||||
def test_angular_momentum_and_linear_momentum():
|
||||
"""A rod with length 2l, centroidal inertia I, and mass M along with a
|
||||
particle of mass m fixed to the end of the rod rotate with an angular rate
|
||||
of omega about point O which is fixed to the non-particle end of the rod.
|
||||
The rod's reference frame is A and the inertial frame is N."""
|
||||
m, M, l, I = symbols('m, M, l, I')
|
||||
omega = dynamicsymbols('omega')
|
||||
N = ReferenceFrame('N')
|
||||
a = ReferenceFrame('a')
|
||||
O = Point('O')
|
||||
Ac = O.locatenew('Ac', l * N.x)
|
||||
P = Ac.locatenew('P', l * N.x)
|
||||
O.set_vel(N, 0 * N.x)
|
||||
a.set_ang_vel(N, omega * N.z)
|
||||
Ac.v2pt_theory(O, N, a)
|
||||
P.v2pt_theory(O, N, a)
|
||||
Pa = Particle('Pa', P, m)
|
||||
A = RigidBody('A', Ac, a, M, (I * outer(N.z, N.z), Ac))
|
||||
expected = 2 * m * omega * l * N.y + M * l * omega * N.y
|
||||
assert linear_momentum(N, A, Pa) == expected
|
||||
raises(TypeError, lambda: angular_momentum(N, N, A, Pa))
|
||||
raises(TypeError, lambda: angular_momentum(O, O, A, Pa))
|
||||
raises(TypeError, lambda: angular_momentum(O, N, O, Pa))
|
||||
expected = (I + M * l**2 + 4 * m * l**2) * omega * N.z
|
||||
assert angular_momentum(O, N, A, Pa) == expected
|
||||
|
||||
|
||||
def test_kinetic_energy():
|
||||
m, M, l1 = symbols('m M l1')
|
||||
omega = dynamicsymbols('omega')
|
||||
N = ReferenceFrame('N')
|
||||
O = Point('O')
|
||||
O.set_vel(N, 0 * N.x)
|
||||
Ac = O.locatenew('Ac', l1 * N.x)
|
||||
P = Ac.locatenew('P', l1 * N.x)
|
||||
a = ReferenceFrame('a')
|
||||
a.set_ang_vel(N, omega * N.z)
|
||||
Ac.v2pt_theory(O, N, a)
|
||||
P.v2pt_theory(O, N, a)
|
||||
Pa = Particle('Pa', P, m)
|
||||
I = outer(N.z, N.z)
|
||||
A = RigidBody('A', Ac, a, M, (I, Ac))
|
||||
raises(TypeError, lambda: kinetic_energy(Pa, Pa, A))
|
||||
raises(TypeError, lambda: kinetic_energy(N, N, A))
|
||||
assert 0 == (kinetic_energy(N, Pa, A) - (M*l1**2*omega**2/2
|
||||
+ 2*l1**2*m*omega**2 + omega**2/2)).expand()
|
||||
|
||||
|
||||
def test_potential_energy():
|
||||
m, M, l1, g, h, H = symbols('m M l1 g h H')
|
||||
omega = dynamicsymbols('omega')
|
||||
N = ReferenceFrame('N')
|
||||
O = Point('O')
|
||||
O.set_vel(N, 0 * N.x)
|
||||
Ac = O.locatenew('Ac', l1 * N.x)
|
||||
P = Ac.locatenew('P', l1 * N.x)
|
||||
a = ReferenceFrame('a')
|
||||
a.set_ang_vel(N, omega * N.z)
|
||||
Ac.v2pt_theory(O, N, a)
|
||||
P.v2pt_theory(O, N, a)
|
||||
Pa = Particle('Pa', P, m)
|
||||
I = outer(N.z, N.z)
|
||||
A = RigidBody('A', Ac, a, M, (I, Ac))
|
||||
Pa.potential_energy = m * g * h
|
||||
A.potential_energy = M * g * H
|
||||
assert potential_energy(A, Pa) == m * g * h + M * g * H
|
||||
|
||||
|
||||
def test_Lagrangian():
|
||||
M, m, g, h = symbols('M m g h')
|
||||
N = ReferenceFrame('N')
|
||||
O = Point('O')
|
||||
O.set_vel(N, 0 * N.x)
|
||||
P = O.locatenew('P', 1 * N.x)
|
||||
P.set_vel(N, 10 * N.x)
|
||||
Pa = Particle('Pa', P, 1)
|
||||
Ac = O.locatenew('Ac', 2 * N.y)
|
||||
Ac.set_vel(N, 5 * N.y)
|
||||
a = ReferenceFrame('a')
|
||||
a.set_ang_vel(N, 10 * N.z)
|
||||
I = outer(N.z, N.z)
|
||||
A = RigidBody('A', Ac, a, 20, (I, Ac))
|
||||
Pa.potential_energy = m * g * h
|
||||
A.potential_energy = M * g * h
|
||||
raises(TypeError, lambda: Lagrangian(A, A, Pa))
|
||||
raises(TypeError, lambda: Lagrangian(N, N, Pa))
|
||||
|
||||
|
||||
def test_msubs():
|
||||
a, b = symbols('a, b')
|
||||
x, y, z = dynamicsymbols('x, y, z')
|
||||
# Test simple substitution
|
||||
expr = Matrix([[a*x + b, x*y.diff() + y],
|
||||
[x.diff().diff(), z + sin(z.diff())]])
|
||||
sol = Matrix([[a + b, y],
|
||||
[x.diff().diff(), 1]])
|
||||
sd = {x: 1, z: 1, z.diff(): 0, y.diff(): 0}
|
||||
assert msubs(expr, sd) == sol
|
||||
# Test smart substitution
|
||||
expr = cos(x + y)*tan(x + y) + b*x.diff()
|
||||
sd = {x: 0, y: pi/2, x.diff(): 1}
|
||||
assert msubs(expr, sd, smart=True) == b + 1
|
||||
N = ReferenceFrame('N')
|
||||
v = x*N.x + y*N.y
|
||||
d = x*(N.x|N.x) + y*(N.y|N.y)
|
||||
v_sol = 1*N.y
|
||||
d_sol = 1*(N.y|N.y)
|
||||
sd = {x: 0, y: 1}
|
||||
assert msubs(v, sd) == v_sol
|
||||
assert msubs(d, sd) == d_sol
|
||||
|
||||
|
||||
def test_find_dynamicsymbols():
|
||||
a, b = symbols('a, b')
|
||||
x, y, z = dynamicsymbols('x, y, z')
|
||||
expr = Matrix([[a*x + b, x*y.diff() + y],
|
||||
[x.diff().diff(), z + sin(z.diff())]])
|
||||
# Test finding all dynamicsymbols
|
||||
sol = {x, y.diff(), y, x.diff().diff(), z, z.diff()}
|
||||
assert find_dynamicsymbols(expr) == sol
|
||||
# Test finding all but those in sym_list
|
||||
exclude_list = [x, y, z]
|
||||
sol = {y.diff(), x.diff().diff(), z.diff()}
|
||||
assert find_dynamicsymbols(expr, exclude=exclude_list) == sol
|
||||
# Test finding all dynamicsymbols in a vector with a given reference frame
|
||||
d, e, f = dynamicsymbols('d, e, f')
|
||||
A = ReferenceFrame('A')
|
||||
v = d * A.x + e * A.y + f * A.z
|
||||
sol = {d, e, f}
|
||||
assert find_dynamicsymbols(v, reference_frame=A) == sol
|
||||
# Test if a ValueError is raised on supplying only a vector as input
|
||||
raises(ValueError, lambda: find_dynamicsymbols(v))
|
||||
|
||||
|
||||
# This function tests the center_of_mass() function
|
||||
# that was added in PR #14758 to compute the center of
|
||||
# mass of a system of bodies.
|
||||
def test_center_of_mass():
|
||||
a = ReferenceFrame('a')
|
||||
m = symbols('m', real=True)
|
||||
p1 = Particle('p1', Point('p1_pt'), S.One)
|
||||
p2 = Particle('p2', Point('p2_pt'), S(2))
|
||||
p3 = Particle('p3', Point('p3_pt'), S(3))
|
||||
p4 = Particle('p4', Point('p4_pt'), m)
|
||||
b_f = ReferenceFrame('b_f')
|
||||
b_cm = Point('b_cm')
|
||||
mb = symbols('mb')
|
||||
b = RigidBody('b', b_cm, b_f, mb, (outer(b_f.x, b_f.x), b_cm))
|
||||
p2.point.set_pos(p1.point, a.x)
|
||||
p3.point.set_pos(p1.point, a.x + a.y)
|
||||
p4.point.set_pos(p1.point, a.y)
|
||||
b.masscenter.set_pos(p1.point, a.y + a.z)
|
||||
point_o=Point('o')
|
||||
point_o.set_pos(p1.point, center_of_mass(p1.point, p1, p2, p3, p4, b))
|
||||
expr = 5/(m + mb + 6)*a.x + (m + mb + 3)/(m + mb + 6)*a.y + mb/(m + mb + 6)*a.z
|
||||
assert point_o.pos_from(p1.point)-expr == 0
|
||||
|
||||
|
||||
def test_validate_coordinates():
|
||||
q1, q2, q3, u1, u2, u3, ua1, ua2, ua3 = dynamicsymbols('q1:4 u1:4 ua1:4')
|
||||
s1, s2, s3 = symbols('s1:4')
|
||||
# Test normal
|
||||
_validate_coordinates([q1, q2, q3], [u1, u2, u3],
|
||||
u_auxiliary=[ua1, ua2, ua3])
|
||||
# Test not equal number of coordinates and speeds
|
||||
_validate_coordinates([q1, q2])
|
||||
_validate_coordinates([q1, q2], [u1])
|
||||
_validate_coordinates(speeds=[u1, u2])
|
||||
# Test duplicate
|
||||
_validate_coordinates([q1, q2, q2], [u1, u2, u3], check_duplicates=False)
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[q1, q2, q2], [u1, u2, u3]))
|
||||
_validate_coordinates([q1, q2, q3], [u1, u2, u2], check_duplicates=False)
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[q1, q2, q3], [u1, u2, u2], check_duplicates=True))
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[q1, q2, q3], [q1, u2, u3], check_duplicates=True))
|
||||
_validate_coordinates([q1, q2, q3], [u1, u2, u3], check_duplicates=False,
|
||||
u_auxiliary=[u1, ua2, ua2])
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[q1, q2, q3], [u1, u2, u3], u_auxiliary=[u1, ua2, ua3]))
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[q1, q2, q3], [u1, u2, u3], u_auxiliary=[q1, ua2, ua3]))
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[q1, q2, q3], [u1, u2, u3], u_auxiliary=[ua1, ua2, ua2]))
|
||||
# Test is_dynamicsymbols
|
||||
_validate_coordinates([q1 + q2, q3], is_dynamicsymbols=False)
|
||||
raises(ValueError, lambda: _validate_coordinates([q1 + q2, q3]))
|
||||
_validate_coordinates([s1, q1, q2], [0, u1, u2], is_dynamicsymbols=False)
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[s1, q1, q2], [0, u1, u2], is_dynamicsymbols=True))
|
||||
_validate_coordinates([s1 + s2 + s3, q1], [0, u1], is_dynamicsymbols=False)
|
||||
raises(ValueError, lambda: _validate_coordinates(
|
||||
[s1 + s2 + s3, q1], [0, u1], is_dynamicsymbols=True))
|
||||
_validate_coordinates(u_auxiliary=[s1, ua1], is_dynamicsymbols=False)
|
||||
raises(ValueError, lambda: _validate_coordinates(u_auxiliary=[s1, ua1]))
|
||||
# Test normal function
|
||||
t = dynamicsymbols._t
|
||||
a = symbols('a')
|
||||
f1, f2 = symbols('f1:3', cls=Function)
|
||||
_validate_coordinates([f1(a), f2(a)], is_dynamicsymbols=False)
|
||||
raises(ValueError, lambda: _validate_coordinates([f1(a), f2(a)]))
|
||||
raises(ValueError, lambda: _validate_coordinates(speeds=[f1(a), f2(a)]))
|
||||
dynamicsymbols._t = a
|
||||
_validate_coordinates([f1(a), f2(a)])
|
||||
raises(ValueError, lambda: _validate_coordinates([f1(t), f2(t)]))
|
||||
dynamicsymbols._t = t
|
||||
|
||||
|
||||
def test_parse_linear_solver():
|
||||
A, b = Matrix(3, 3, symbols('a:9')), Matrix(3, 2, symbols('b:6'))
|
||||
assert _parse_linear_solver(Matrix.LUsolve) == Matrix.LUsolve # Test callable
|
||||
assert _parse_linear_solver('LU')(A, b) == Matrix.LUsolve(A, b)
|
||||
|
||||
|
||||
def test_deprecated_moved_functions():
|
||||
from sympy.physics.mechanics.functions import (
|
||||
inertia, inertia_of_point_mass, gravity)
|
||||
N = ReferenceFrame('N')
|
||||
with warns_deprecated_sympy():
|
||||
assert inertia(N, 0, 1, 0, 1) == (N.x | N.y) + (N.y | N.x) + (N.y | N.y)
|
||||
with warns_deprecated_sympy():
|
||||
assert inertia_of_point_mass(1, N.x + N.y, N) == (
|
||||
(N.x | N.x) + (N.y | N.y) + 2 * (N.z | N.z) -
|
||||
(N.x | N.y) - (N.y | N.x))
|
||||
p = Particle('P')
|
||||
with warns_deprecated_sympy():
|
||||
assert gravity(-2 * N.z, p) == [(p.masscenter, -2 * p.mass * N.z)]
|
||||
@@ -0,0 +1,71 @@
|
||||
from sympy import symbols
|
||||
from sympy.testing.pytest import raises
|
||||
from sympy.physics.mechanics import (inertia, inertia_of_point_mass,
|
||||
Inertia, ReferenceFrame, Point)
|
||||
|
||||
|
||||
def test_inertia_dyadic():
|
||||
N = ReferenceFrame('N')
|
||||
ixx, iyy, izz = symbols('ixx iyy izz')
|
||||
ixy, iyz, izx = symbols('ixy iyz izx')
|
||||
assert inertia(N, ixx, iyy, izz) == (ixx * (N.x | N.x) + iyy *
|
||||
(N.y | N.y) + izz * (N.z | N.z))
|
||||
assert inertia(N, 0, 0, 0) == 0 * (N.x | N.x)
|
||||
raises(TypeError, lambda: inertia(0, 0, 0, 0))
|
||||
assert inertia(N, ixx, iyy, izz, ixy, iyz, izx) == (ixx * (N.x | N.x) +
|
||||
ixy * (N.x | N.y) + izx * (N.x | N.z) + ixy * (N.y | N.x) + iyy *
|
||||
(N.y | N.y) + iyz * (N.y | N.z) + izx * (N.z | N.x) + iyz * (N.z |
|
||||
N.y) + izz * (N.z | N.z))
|
||||
|
||||
|
||||
def test_inertia_of_point_mass():
|
||||
r, s, t, m = symbols('r s t m')
|
||||
N = ReferenceFrame('N')
|
||||
|
||||
px = r * N.x
|
||||
I = inertia_of_point_mass(m, px, N)
|
||||
assert I == m * r**2 * (N.y | N.y) + m * r**2 * (N.z | N.z)
|
||||
|
||||
py = s * N.y
|
||||
I = inertia_of_point_mass(m, py, N)
|
||||
assert I == m * s**2 * (N.x | N.x) + m * s**2 * (N.z | N.z)
|
||||
|
||||
pz = t * N.z
|
||||
I = inertia_of_point_mass(m, pz, N)
|
||||
assert I == m * t**2 * (N.x | N.x) + m * t**2 * (N.y | N.y)
|
||||
|
||||
p = px + py + pz
|
||||
I = inertia_of_point_mass(m, p, N)
|
||||
assert I == (m * (s**2 + t**2) * (N.x | N.x) -
|
||||
m * r * s * (N.x | N.y) -
|
||||
m * r * t * (N.x | N.z) -
|
||||
m * r * s * (N.y | N.x) +
|
||||
m * (r**2 + t**2) * (N.y | N.y) -
|
||||
m * s * t * (N.y | N.z) -
|
||||
m * r * t * (N.z | N.x) -
|
||||
m * s * t * (N.z | N.y) +
|
||||
m * (r**2 + s**2) * (N.z | N.z))
|
||||
|
||||
|
||||
def test_inertia_object():
|
||||
N = ReferenceFrame('N')
|
||||
O = Point('O')
|
||||
ixx, iyy, izz = symbols('ixx iyy izz')
|
||||
I_dyadic = ixx * (N.x | N.x) + iyy * (N.y | N.y) + izz * (N.z | N.z)
|
||||
I = Inertia(inertia(N, ixx, iyy, izz), O)
|
||||
assert isinstance(I, tuple)
|
||||
assert I.__repr__() == ('Inertia(dyadic=ixx*(N.x|N.x) + iyy*(N.y|N.y) + '
|
||||
'izz*(N.z|N.z), point=O)')
|
||||
assert I.dyadic == I_dyadic
|
||||
assert I.point == O
|
||||
assert I[0] == I_dyadic
|
||||
assert I[1] == O
|
||||
assert I == (I_dyadic, O) # Test tuple equal
|
||||
raises(TypeError, lambda: I != (O, I_dyadic)) # Incorrect tuple order
|
||||
assert I == Inertia(O, I_dyadic) # Parse changed argument order
|
||||
assert I == Inertia.from_inertia_scalars(O, N, ixx, iyy, izz)
|
||||
# Test invalid tuple operations
|
||||
raises(TypeError, lambda: I + (1, 2))
|
||||
raises(TypeError, lambda: (1, 2) + I)
|
||||
raises(TypeError, lambda: I * 2)
|
||||
raises(TypeError, lambda: 2 * I)
|
||||
File diff suppressed because it is too large
Load Diff
+249
@@ -0,0 +1,249 @@
|
||||
from sympy.core.function import expand
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin)
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.simplify.trigsimp import trigsimp
|
||||
from sympy.physics.mechanics import (
|
||||
PinJoint, JointsMethod, RigidBody, Particle, Body, KanesMethod,
|
||||
PrismaticJoint, LagrangesMethod, inertia)
|
||||
from sympy.physics.vector import dynamicsymbols, ReferenceFrame
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
from sympy import zeros
|
||||
from sympy.utilities.lambdify import lambdify
|
||||
from sympy.solvers.solvers import solve
|
||||
|
||||
|
||||
t = dynamicsymbols._t # type: ignore
|
||||
|
||||
|
||||
def test_jointsmethod():
|
||||
with warns_deprecated_sympy():
|
||||
P = Body('P')
|
||||
C = Body('C')
|
||||
Pin = PinJoint('P1', P, C)
|
||||
C_ixx, g = symbols('C_ixx g')
|
||||
q, u = dynamicsymbols('q_P1, u_P1')
|
||||
P.apply_force(g*P.y)
|
||||
with warns_deprecated_sympy():
|
||||
method = JointsMethod(P, Pin)
|
||||
assert method.frame == P.frame
|
||||
assert method.bodies == [C, P]
|
||||
assert method.loads == [(P.masscenter, g*P.frame.y)]
|
||||
assert method.q == Matrix([q])
|
||||
assert method.u == Matrix([u])
|
||||
assert method.kdes == Matrix([u - q.diff()])
|
||||
soln = method.form_eoms()
|
||||
assert soln == Matrix([[-C_ixx*u.diff()]])
|
||||
assert method.forcing_full == Matrix([[u], [0]])
|
||||
assert method.mass_matrix_full == Matrix([[1, 0], [0, C_ixx]])
|
||||
assert isinstance(method.method, KanesMethod)
|
||||
|
||||
|
||||
def test_rigid_body_particle_compatibility():
|
||||
l, m, g = symbols('l m g')
|
||||
C = RigidBody('C')
|
||||
b = Particle('b', mass=m)
|
||||
b_frame = ReferenceFrame('b_frame')
|
||||
q, u = dynamicsymbols('q u')
|
||||
P = PinJoint('P', C, b, coordinates=q, speeds=u, child_interframe=b_frame,
|
||||
child_point=-l * b_frame.x, joint_axis=C.z)
|
||||
with warns_deprecated_sympy():
|
||||
method = JointsMethod(C, P)
|
||||
method.loads.append((b.masscenter, m * g * C.x))
|
||||
method.form_eoms()
|
||||
rhs = method.rhs()
|
||||
assert rhs[1] == -g*sin(q)/l
|
||||
|
||||
|
||||
def test_jointmethod_duplicate_coordinates_speeds():
|
||||
with warns_deprecated_sympy():
|
||||
P = Body('P')
|
||||
C = Body('C')
|
||||
T = Body('T')
|
||||
q, u = dynamicsymbols('q u')
|
||||
P1 = PinJoint('P1', P, C, q)
|
||||
P2 = PrismaticJoint('P2', C, T, q)
|
||||
with warns_deprecated_sympy():
|
||||
raises(ValueError, lambda: JointsMethod(P, P1, P2))
|
||||
|
||||
P1 = PinJoint('P1', P, C, speeds=u)
|
||||
P2 = PrismaticJoint('P2', C, T, speeds=u)
|
||||
with warns_deprecated_sympy():
|
||||
raises(ValueError, lambda: JointsMethod(P, P1, P2))
|
||||
|
||||
P1 = PinJoint('P1', P, C, q, u)
|
||||
P2 = PrismaticJoint('P2', C, T, q, u)
|
||||
with warns_deprecated_sympy():
|
||||
raises(ValueError, lambda: JointsMethod(P, P1, P2))
|
||||
|
||||
def test_complete_simple_double_pendulum():
|
||||
q1, q2 = dynamicsymbols('q1 q2')
|
||||
u1, u2 = dynamicsymbols('u1 u2')
|
||||
m, l, g = symbols('m l g')
|
||||
with warns_deprecated_sympy():
|
||||
C = Body('C') # ceiling
|
||||
PartP = Body('P', mass=m)
|
||||
PartR = Body('R', mass=m)
|
||||
J1 = PinJoint('J1', C, PartP, speeds=u1, coordinates=q1,
|
||||
child_point=-l*PartP.x, joint_axis=C.z)
|
||||
J2 = PinJoint('J2', PartP, PartR, speeds=u2, coordinates=q2,
|
||||
child_point=-l*PartR.x, joint_axis=PartP.z)
|
||||
|
||||
PartP.apply_force(m*g*C.x)
|
||||
PartR.apply_force(m*g*C.x)
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
method = JointsMethod(C, J1, J2)
|
||||
method.form_eoms()
|
||||
|
||||
assert expand(method.mass_matrix_full) == Matrix([[1, 0, 0, 0],
|
||||
[0, 1, 0, 0],
|
||||
[0, 0, 2*l**2*m*cos(q2) + 3*l**2*m, l**2*m*cos(q2) + l**2*m],
|
||||
[0, 0, l**2*m*cos(q2) + l**2*m, l**2*m]])
|
||||
assert trigsimp(method.forcing_full) == trigsimp(Matrix([[u1], [u2], [-g*l*m*(sin(q1 + q2) + sin(q1)) -
|
||||
g*l*m*sin(q1) + l**2*m*(2*u1 + u2)*u2*sin(q2)],
|
||||
[-g*l*m*sin(q1 + q2) - l**2*m*u1**2*sin(q2)]]))
|
||||
|
||||
def test_two_dof_joints():
|
||||
q1, q2, u1, u2 = dynamicsymbols('q1 q2 u1 u2')
|
||||
m, c1, c2, k1, k2 = symbols('m c1 c2 k1 k2')
|
||||
with warns_deprecated_sympy():
|
||||
W = Body('W')
|
||||
B1 = Body('B1', mass=m)
|
||||
B2 = Body('B2', mass=m)
|
||||
J1 = PrismaticJoint('J1', W, B1, coordinates=q1, speeds=u1)
|
||||
J2 = PrismaticJoint('J2', B1, B2, coordinates=q2, speeds=u2)
|
||||
W.apply_force(k1*q1*W.x, reaction_body=B1)
|
||||
W.apply_force(c1*u1*W.x, reaction_body=B1)
|
||||
B1.apply_force(k2*q2*W.x, reaction_body=B2)
|
||||
B1.apply_force(c2*u2*W.x, reaction_body=B2)
|
||||
with warns_deprecated_sympy():
|
||||
method = JointsMethod(W, J1, J2)
|
||||
method.form_eoms()
|
||||
MM = method.mass_matrix
|
||||
forcing = method.forcing
|
||||
rhs = MM.LUsolve(forcing)
|
||||
assert expand(rhs[0]) == expand((-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2)/m)
|
||||
assert expand(rhs[1]) == expand((k1 * q1 + c1 * u1 - 2 * k2 * q2 - 2 *
|
||||
c2 * u2) / m)
|
||||
|
||||
def test_simple_pedulum():
|
||||
l, m, g = symbols('l m g')
|
||||
with warns_deprecated_sympy():
|
||||
C = Body('C')
|
||||
b = Body('b', mass=m)
|
||||
q = dynamicsymbols('q')
|
||||
P = PinJoint('P', C, b, speeds=q.diff(t), coordinates=q,
|
||||
child_point=-l * b.x, joint_axis=C.z)
|
||||
b.potential_energy = - m * g * l * cos(q)
|
||||
with warns_deprecated_sympy():
|
||||
method = JointsMethod(C, P)
|
||||
method.form_eoms(LagrangesMethod)
|
||||
rhs = method.rhs()
|
||||
assert rhs[1] == -g*sin(q)/l
|
||||
|
||||
def test_chaos_pendulum():
|
||||
#https://www.pydy.org/examples/chaos_pendulum.html
|
||||
mA, mB, lA, lB, IAxx, IBxx, IByy, IBzz, g = symbols('mA, mB, lA, lB, IAxx, IBxx, IByy, IBzz, g')
|
||||
theta, phi, omega, alpha = dynamicsymbols('theta phi omega alpha')
|
||||
|
||||
A = ReferenceFrame('A')
|
||||
B = ReferenceFrame('B')
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
rod = Body('rod', mass=mA, frame=A,
|
||||
central_inertia=inertia(A, IAxx, IAxx, 0))
|
||||
plate = Body('plate', mass=mB, frame=B,
|
||||
central_inertia=inertia(B, IBxx, IByy, IBzz))
|
||||
C = Body('C')
|
||||
J1 = PinJoint('J1', C, rod, coordinates=theta, speeds=omega,
|
||||
child_point=-lA * rod.z, joint_axis=C.y)
|
||||
J2 = PinJoint('J2', rod, plate, coordinates=phi, speeds=alpha,
|
||||
parent_point=(lB - lA) * rod.z, joint_axis=rod.z)
|
||||
|
||||
rod.apply_force(mA*g*C.z)
|
||||
plate.apply_force(mB*g*C.z)
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
method = JointsMethod(C, J1, J2)
|
||||
method.form_eoms()
|
||||
|
||||
MM = method.mass_matrix
|
||||
forcing = method.forcing
|
||||
rhs = MM.LUsolve(forcing)
|
||||
xd = (-2 * IBxx * alpha * omega * sin(phi) * cos(phi) + 2 * IByy * alpha * omega * sin(phi) *
|
||||
cos(phi) - g * lA * mA * sin(theta) - g * lB * mB * sin(theta)) / (IAxx + IBxx *
|
||||
sin(phi)**2 + IByy * cos(phi)**2 + lA**2 * mA + lB**2 * mB)
|
||||
assert (rhs[0] - xd).simplify() == 0
|
||||
xd = (IBxx - IByy) * omega**2 * sin(phi) * cos(phi) / IBzz
|
||||
assert (rhs[1] - xd).simplify() == 0
|
||||
|
||||
def test_four_bar_linkage_with_manual_constraints():
|
||||
q1, q2, q3, u1, u2, u3 = dynamicsymbols('q1:4, u1:4')
|
||||
l1, l2, l3, l4, rho = symbols('l1:5, rho')
|
||||
|
||||
N = ReferenceFrame('N')
|
||||
inertias = [inertia(N, 0, 0, rho * l ** 3 / 12) for l in (l1, l2, l3, l4)]
|
||||
with warns_deprecated_sympy():
|
||||
link1 = Body('Link1', frame=N, mass=rho * l1,
|
||||
central_inertia=inertias[0])
|
||||
link2 = Body('Link2', mass=rho * l2, central_inertia=inertias[1])
|
||||
link3 = Body('Link3', mass=rho * l3, central_inertia=inertias[2])
|
||||
link4 = Body('Link4', mass=rho * l4, central_inertia=inertias[3])
|
||||
|
||||
joint1 = PinJoint(
|
||||
'J1', link1, link2, coordinates=q1, speeds=u1, joint_axis=link1.z,
|
||||
parent_point=l1 / 2 * link1.x, child_point=-l2 / 2 * link2.x)
|
||||
joint2 = PinJoint(
|
||||
'J2', link2, link3, coordinates=q2, speeds=u2, joint_axis=link2.z,
|
||||
parent_point=l2 / 2 * link2.x, child_point=-l3 / 2 * link3.x)
|
||||
joint3 = PinJoint(
|
||||
'J3', link3, link4, coordinates=q3, speeds=u3, joint_axis=link3.z,
|
||||
parent_point=l3 / 2 * link3.x, child_point=-l4 / 2 * link4.x)
|
||||
|
||||
loop = link4.masscenter.pos_from(link1.masscenter) \
|
||||
+ l1 / 2 * link1.x + l4 / 2 * link4.x
|
||||
|
||||
fh = Matrix([loop.dot(link1.x), loop.dot(link1.y)])
|
||||
|
||||
with warns_deprecated_sympy():
|
||||
method = JointsMethod(link1, joint1, joint2, joint3)
|
||||
|
||||
t = dynamicsymbols._t
|
||||
qdots = solve(method.kdes, [q1.diff(t), q2.diff(t), q3.diff(t)])
|
||||
fhd = fh.diff(t).subs(qdots)
|
||||
|
||||
kane = KanesMethod(method.frame, q_ind=[q1], u_ind=[u1],
|
||||
q_dependent=[q2, q3], u_dependent=[u2, u3],
|
||||
kd_eqs=method.kdes, configuration_constraints=fh,
|
||||
velocity_constraints=fhd, forcelist=method.loads,
|
||||
bodies=method.bodies)
|
||||
fr, frs = kane.kanes_equations()
|
||||
assert fr == zeros(1)
|
||||
|
||||
# Numerically check the mass- and forcing-matrix
|
||||
p = Matrix([l1, l2, l3, l4, rho])
|
||||
q = Matrix([q1, q2, q3])
|
||||
u = Matrix([u1, u2, u3])
|
||||
eval_m = lambdify((q, p), kane.mass_matrix)
|
||||
eval_f = lambdify((q, u, p), kane.forcing)
|
||||
eval_fhd = lambdify((q, u, p), fhd)
|
||||
|
||||
p_vals = [0.13, 0.24, 0.21, 0.34, 997]
|
||||
q_vals = [2.1, 0.6655470375077588, 2.527408138024188] # Satisfies fh
|
||||
u_vals = [0.2, -0.17963733938852067, 0.1309060540601612] # Satisfies fhd
|
||||
mass_check = Matrix([[3.452709815256506e+01, 7.003948798374735e+00,
|
||||
-4.939690970641498e+00],
|
||||
[-2.203792703880936e-14, 2.071702479957077e-01,
|
||||
2.842917573033711e-01],
|
||||
[-1.300000000000123e-01, -8.836934896046506e-03,
|
||||
1.864891330060847e-01]])
|
||||
forcing_check = Matrix([[-0.031211821321648],
|
||||
[-0.00066022608181],
|
||||
[0.001813559741243]])
|
||||
eps = 1e-10
|
||||
assert all(abs(x) < eps for x in eval_fhd(q_vals, u_vals, p_vals))
|
||||
assert all(abs(x) < eps for x in
|
||||
(Matrix(eval_m(q_vals, p_vals)) - mass_check))
|
||||
assert all(abs(x) < eps for x in
|
||||
(Matrix(eval_f(q_vals, u_vals, p_vals)) - forcing_check))
|
||||
@@ -0,0 +1,553 @@
|
||||
from sympy import solve
|
||||
from sympy import (cos, expand, Matrix, sin, symbols, tan, sqrt, S,
|
||||
zeros, eye)
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point,
|
||||
RigidBody, KanesMethod, inertia, Particle,
|
||||
dot, find_dynamicsymbols)
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_invalid_coordinates():
|
||||
# Simple pendulum, but use symbols instead of dynamicsymbols
|
||||
l, m, g = symbols('l m g')
|
||||
q, u = symbols('q u') # Generalized coordinate
|
||||
kd = [q.diff(dynamicsymbols._t) - u]
|
||||
N, O = ReferenceFrame('N'), Point('O')
|
||||
O.set_vel(N, 0)
|
||||
P = Particle('P', Point('P'), m)
|
||||
P.point.set_pos(O, l * (sin(q) * N.x - cos(q) * N.y))
|
||||
F = (P.point, -m * g * N.y)
|
||||
raises(ValueError, lambda: KanesMethod(N, [q], [u], kd, bodies=[P],
|
||||
forcelist=[F]))
|
||||
|
||||
|
||||
def test_one_dof():
|
||||
# This is for a 1 dof spring-mass-damper case.
|
||||
# It is described in more detail in the KanesMethod docstring.
|
||||
q, u = dynamicsymbols('q u')
|
||||
qd, ud = dynamicsymbols('q u', 1)
|
||||
m, c, k = symbols('m c k')
|
||||
N = ReferenceFrame('N')
|
||||
P = Point('P')
|
||||
P.set_vel(N, u * N.x)
|
||||
|
||||
kd = [qd - u]
|
||||
FL = [(P, (-k * q - c * u) * N.x)]
|
||||
pa = Particle('pa', P, m)
|
||||
BL = [pa]
|
||||
|
||||
KM = KanesMethod(N, [q], [u], kd)
|
||||
KM.kanes_equations(BL, FL)
|
||||
|
||||
assert KM.bodies == BL
|
||||
assert KM.loads == FL
|
||||
|
||||
MM = KM.mass_matrix
|
||||
forcing = KM.forcing
|
||||
rhs = MM.inv() * forcing
|
||||
assert expand(rhs[0]) == expand(-(q * k + u * c) / m)
|
||||
|
||||
assert simplify(KM.rhs() -
|
||||
KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(2, 1)
|
||||
|
||||
assert (KM.linearize(A_and_B=True, )[0] == Matrix([[0, 1], [-k/m, -c/m]]))
|
||||
|
||||
|
||||
def test_two_dof():
|
||||
# This is for a 2 d.o.f., 2 particle spring-mass-damper.
|
||||
# The first coordinate is the displacement of the first particle, and the
|
||||
# second is the relative displacement between the first and second
|
||||
# particles. Speeds are defined as the time derivatives of the particles.
|
||||
q1, q2, u1, u2 = dynamicsymbols('q1 q2 u1 u2')
|
||||
q1d, q2d, u1d, u2d = dynamicsymbols('q1 q2 u1 u2', 1)
|
||||
m, c1, c2, k1, k2 = symbols('m c1 c2 k1 k2')
|
||||
N = ReferenceFrame('N')
|
||||
P1 = Point('P1')
|
||||
P2 = Point('P2')
|
||||
P1.set_vel(N, u1 * N.x)
|
||||
P2.set_vel(N, (u1 + u2) * N.x)
|
||||
# Note we multiply the kinematic equation by an arbitrary factor
|
||||
# to test the implicit vs explicit kinematics attribute
|
||||
kd = [q1d/2 - u1/2, 2*q2d - 2*u2]
|
||||
|
||||
# Now we create the list of forces, then assign properties to each
|
||||
# particle, then create a list of all particles.
|
||||
FL = [(P1, (-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2) * N.x), (P2, (-k2 *
|
||||
q2 - c2 * u2) * N.x)]
|
||||
pa1 = Particle('pa1', P1, m)
|
||||
pa2 = Particle('pa2', P2, m)
|
||||
BL = [pa1, pa2]
|
||||
|
||||
# Finally we create the KanesMethod object, specify the inertial frame,
|
||||
# pass relevant information, and form Fr & Fr*. Then we calculate the mass
|
||||
# matrix and forcing terms, and finally solve for the udots.
|
||||
KM = KanesMethod(N, q_ind=[q1, q2], u_ind=[u1, u2], kd_eqs=kd)
|
||||
KM.kanes_equations(BL, FL)
|
||||
MM = KM.mass_matrix
|
||||
forcing = KM.forcing
|
||||
rhs = MM.inv() * forcing
|
||||
assert expand(rhs[0]) == expand((-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2)/m)
|
||||
assert expand(rhs[1]) == expand((k1 * q1 + c1 * u1 - 2 * k2 * q2 - 2 *
|
||||
c2 * u2) / m)
|
||||
|
||||
# Check that the explicit form is the default and kinematic mass matrix is identity
|
||||
assert KM.explicit_kinematics
|
||||
assert KM.mass_matrix_kin == eye(2)
|
||||
|
||||
# Check that for the implicit form the mass matrix is not identity
|
||||
KM.explicit_kinematics = False
|
||||
assert KM.mass_matrix_kin == Matrix([[S(1)/2, 0], [0, 2]])
|
||||
|
||||
# Check that whether using implicit or explicit kinematics the RHS
|
||||
# equations are consistent with the matrix form
|
||||
for explicit_kinematics in [False, True]:
|
||||
KM.explicit_kinematics = explicit_kinematics
|
||||
assert simplify(KM.rhs() -
|
||||
KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(4, 1)
|
||||
|
||||
# Make sure an error is raised if nonlinear kinematic differential
|
||||
# equations are supplied.
|
||||
kd = [q1d - u1**2, sin(q2d) - cos(u2)]
|
||||
raises(ValueError, lambda: KanesMethod(N, q_ind=[q1, q2],
|
||||
u_ind=[u1, u2], kd_eqs=kd))
|
||||
|
||||
def test_pend():
|
||||
q, u = dynamicsymbols('q u')
|
||||
qd, ud = dynamicsymbols('q u', 1)
|
||||
m, l, g = symbols('m l g')
|
||||
N = ReferenceFrame('N')
|
||||
P = Point('P')
|
||||
P.set_vel(N, -l * u * sin(q) * N.x + l * u * cos(q) * N.y)
|
||||
kd = [qd - u]
|
||||
|
||||
FL = [(P, m * g * N.x)]
|
||||
pa = Particle('pa', P, m)
|
||||
BL = [pa]
|
||||
|
||||
KM = KanesMethod(N, [q], [u], kd)
|
||||
KM.kanes_equations(BL, FL)
|
||||
MM = KM.mass_matrix
|
||||
forcing = KM.forcing
|
||||
rhs = MM.inv() * forcing
|
||||
rhs.simplify()
|
||||
assert expand(rhs[0]) == expand(-g / l * sin(q))
|
||||
assert simplify(KM.rhs() -
|
||||
KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(2, 1)
|
||||
|
||||
|
||||
def test_rolling_disc():
|
||||
# Rolling Disc Example
|
||||
# Here the rolling disc is formed from the contact point up, removing the
|
||||
# need to introduce generalized speeds. Only 3 configuration and three
|
||||
# speed variables are need to describe this system, along with the disc's
|
||||
# mass and radius, and the local gravity (note that mass will drop out).
|
||||
q1, q2, q3, u1, u2, u3 = dynamicsymbols('q1 q2 q3 u1 u2 u3')
|
||||
q1d, q2d, q3d, u1d, u2d, u3d = dynamicsymbols('q1 q2 q3 u1 u2 u3', 1)
|
||||
r, m, g = symbols('r m g')
|
||||
|
||||
# The kinematics are formed by a series of simple rotations. Each simple
|
||||
# rotation creates a new frame, and the next rotation is defined by the new
|
||||
# frame's basis vectors. This example uses a 3-1-2 series of rotations, or
|
||||
# Z, X, Y series of rotations. Angular velocity for this is defined using
|
||||
# the second frame's basis (the lean frame).
|
||||
N = ReferenceFrame('N')
|
||||
Y = N.orientnew('Y', 'Axis', [q1, N.z])
|
||||
L = Y.orientnew('L', 'Axis', [q2, Y.x])
|
||||
R = L.orientnew('R', 'Axis', [q3, L.y])
|
||||
w_R_N_qd = R.ang_vel_in(N)
|
||||
R.set_ang_vel(N, u1 * L.x + u2 * L.y + u3 * L.z)
|
||||
|
||||
# This is the translational kinematics. We create a point with no velocity
|
||||
# in N; this is the contact point between the disc and ground. Next we form
|
||||
# the position vector from the contact point to the disc's center of mass.
|
||||
# Finally we form the velocity and acceleration of the disc.
|
||||
C = Point('C')
|
||||
C.set_vel(N, 0)
|
||||
Dmc = C.locatenew('Dmc', r * L.z)
|
||||
Dmc.v2pt_theory(C, N, R)
|
||||
|
||||
# This is a simple way to form the inertia dyadic.
|
||||
I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
|
||||
|
||||
# Kinematic differential equations; how the generalized coordinate time
|
||||
# derivatives relate to generalized speeds.
|
||||
kd = [dot(R.ang_vel_in(N) - w_R_N_qd, uv) for uv in L]
|
||||
|
||||
# Creation of the force list; it is the gravitational force at the mass
|
||||
# center of the disc. Then we create the disc by assigning a Point to the
|
||||
# center of mass attribute, a ReferenceFrame to the frame attribute, and mass
|
||||
# and inertia. Then we form the body list.
|
||||
ForceList = [(Dmc, - m * g * Y.z)]
|
||||
BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
|
||||
BodyList = [BodyD]
|
||||
|
||||
# Finally we form the equations of motion, using the same steps we did
|
||||
# before. Specify inertial frame, supply generalized speeds, supply
|
||||
# kinematic differential equation dictionary, compute Fr from the force
|
||||
# list and Fr* from the body list, compute the mass matrix and forcing
|
||||
# terms, then solve for the u dots (time derivatives of the generalized
|
||||
# speeds).
|
||||
KM = KanesMethod(N, q_ind=[q1, q2, q3], u_ind=[u1, u2, u3], kd_eqs=kd)
|
||||
KM.kanes_equations(BodyList, ForceList)
|
||||
MM = KM.mass_matrix
|
||||
forcing = KM.forcing
|
||||
rhs = MM.inv() * forcing
|
||||
kdd = KM.kindiffdict()
|
||||
rhs = rhs.subs(kdd)
|
||||
rhs.simplify()
|
||||
assert rhs.expand() == Matrix([(6*u2*u3*r - u3**2*r*tan(q2) +
|
||||
4*g*sin(q2))/(5*r), -2*u1*u3/3, u1*(-2*u2 + u3*tan(q2))]).expand()
|
||||
assert simplify(KM.rhs() -
|
||||
KM.mass_matrix_full.LUsolve(KM.forcing_full)) == zeros(6, 1)
|
||||
|
||||
# This code tests our output vs. benchmark values. When r=g=m=1, the
|
||||
# critical speed (where all eigenvalues of the linearized equations are 0)
|
||||
# is 1 / sqrt(3) for the upright case.
|
||||
A = KM.linearize(A_and_B=True)[0]
|
||||
A_upright = A.subs({r: 1, g: 1, m: 1}).subs({q1: 0, q2: 0, q3: 0, u1: 0, u3: 0})
|
||||
import sympy
|
||||
assert sympy.sympify(A_upright.subs({u2: 1 / sqrt(3)})).eigenvals() == {S.Zero: 6}
|
||||
|
||||
|
||||
def test_aux():
|
||||
# Same as above, except we have 2 auxiliary speeds for the ground contact
|
||||
# point, which is known to be zero. In one case, we go through then
|
||||
# substitute the aux. speeds in at the end (they are zero, as well as their
|
||||
# derivative), in the other case, we use the built-in auxiliary speed part
|
||||
# of KanesMethod. The equations from each should be the same.
|
||||
q1, q2, q3, u1, u2, u3 = dynamicsymbols('q1 q2 q3 u1 u2 u3')
|
||||
q1d, q2d, q3d, u1d, u2d, u3d = dynamicsymbols('q1 q2 q3 u1 u2 u3', 1)
|
||||
u4, u5, f1, f2 = dynamicsymbols('u4, u5, f1, f2')
|
||||
u4d, u5d = dynamicsymbols('u4, u5', 1)
|
||||
r, m, g = symbols('r m g')
|
||||
|
||||
N = ReferenceFrame('N')
|
||||
Y = N.orientnew('Y', 'Axis', [q1, N.z])
|
||||
L = Y.orientnew('L', 'Axis', [q2, Y.x])
|
||||
R = L.orientnew('R', 'Axis', [q3, L.y])
|
||||
w_R_N_qd = R.ang_vel_in(N)
|
||||
R.set_ang_vel(N, u1 * L.x + u2 * L.y + u3 * L.z)
|
||||
|
||||
C = Point('C')
|
||||
C.set_vel(N, u4 * L.x + u5 * (Y.z ^ L.x))
|
||||
Dmc = C.locatenew('Dmc', r * L.z)
|
||||
Dmc.v2pt_theory(C, N, R)
|
||||
Dmc.a2pt_theory(C, N, R)
|
||||
|
||||
I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
|
||||
|
||||
kd = [dot(R.ang_vel_in(N) - w_R_N_qd, uv) for uv in L]
|
||||
|
||||
ForceList = [(Dmc, - m * g * Y.z), (C, f1 * L.x + f2 * (Y.z ^ L.x))]
|
||||
BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
|
||||
BodyList = [BodyD]
|
||||
|
||||
KM = KanesMethod(N, q_ind=[q1, q2, q3], u_ind=[u1, u2, u3, u4, u5],
|
||||
kd_eqs=kd)
|
||||
(fr, frstar) = KM.kanes_equations(BodyList, ForceList)
|
||||
fr = fr.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
|
||||
frstar = frstar.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
|
||||
|
||||
KM2 = KanesMethod(N, q_ind=[q1, q2, q3], u_ind=[u1, u2, u3], kd_eqs=kd,
|
||||
u_auxiliary=[u4, u5])
|
||||
(fr2, frstar2) = KM2.kanes_equations(BodyList, ForceList)
|
||||
fr2 = fr2.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
|
||||
frstar2 = frstar2.subs({u4d: 0, u5d: 0}).subs({u4: 0, u5: 0})
|
||||
|
||||
frstar.simplify()
|
||||
frstar2.simplify()
|
||||
|
||||
assert (fr - fr2).expand() == Matrix([0, 0, 0, 0, 0])
|
||||
assert (frstar - frstar2).expand() == Matrix([0, 0, 0, 0, 0])
|
||||
|
||||
|
||||
def test_parallel_axis():
|
||||
# This is for a 2 dof inverted pendulum on a cart.
|
||||
# This tests the parallel axis code in KanesMethod. The inertia of the
|
||||
# pendulum is defined about the hinge, not about the center of mass.
|
||||
|
||||
# Defining the constants and knowns of the system
|
||||
gravity = symbols('g')
|
||||
k, ls = symbols('k ls')
|
||||
a, mA, mC = symbols('a mA mC')
|
||||
F = dynamicsymbols('F')
|
||||
Ix, Iy, Iz = symbols('Ix Iy Iz')
|
||||
|
||||
# Declaring the Generalized coordinates and speeds
|
||||
q1, q2 = dynamicsymbols('q1 q2')
|
||||
q1d, q2d = dynamicsymbols('q1 q2', 1)
|
||||
u1, u2 = dynamicsymbols('u1 u2')
|
||||
u1d, u2d = dynamicsymbols('u1 u2', 1)
|
||||
|
||||
# Creating reference frames
|
||||
N = ReferenceFrame('N')
|
||||
A = ReferenceFrame('A')
|
||||
|
||||
A.orient(N, 'Axis', [-q2, N.z])
|
||||
A.set_ang_vel(N, -u2 * N.z)
|
||||
|
||||
# Origin of Newtonian reference frame
|
||||
O = Point('O')
|
||||
|
||||
# Creating and Locating the positions of the cart, C, and the
|
||||
# center of mass of the pendulum, A
|
||||
C = O.locatenew('C', q1 * N.x)
|
||||
Ao = C.locatenew('Ao', a * A.y)
|
||||
|
||||
# Defining velocities of the points
|
||||
O.set_vel(N, 0)
|
||||
C.set_vel(N, u1 * N.x)
|
||||
Ao.v2pt_theory(C, N, A)
|
||||
Cart = Particle('Cart', C, mC)
|
||||
Pendulum = RigidBody('Pendulum', Ao, A, mA, (inertia(A, Ix, Iy, Iz), C))
|
||||
|
||||
# kinematical differential equations
|
||||
|
||||
kindiffs = [q1d - u1, q2d - u2]
|
||||
|
||||
bodyList = [Cart, Pendulum]
|
||||
|
||||
forceList = [(Ao, -N.y * gravity * mA),
|
||||
(C, -N.y * gravity * mC),
|
||||
(C, -N.x * k * (q1 - ls)),
|
||||
(C, N.x * F)]
|
||||
|
||||
km = KanesMethod(N, [q1, q2], [u1, u2], kindiffs)
|
||||
(fr, frstar) = km.kanes_equations(bodyList, forceList)
|
||||
mm = km.mass_matrix_full
|
||||
assert mm[3, 3] == Iz
|
||||
|
||||
def test_input_format():
|
||||
# 1 dof problem from test_one_dof
|
||||
q, u = dynamicsymbols('q u')
|
||||
qd, ud = dynamicsymbols('q u', 1)
|
||||
m, c, k = symbols('m c k')
|
||||
N = ReferenceFrame('N')
|
||||
P = Point('P')
|
||||
P.set_vel(N, u * N.x)
|
||||
|
||||
kd = [qd - u]
|
||||
FL = [(P, (-k * q - c * u) * N.x)]
|
||||
pa = Particle('pa', P, m)
|
||||
BL = [pa]
|
||||
|
||||
KM = KanesMethod(N, [q], [u], kd)
|
||||
# test for input format kane.kanes_equations((body1, body2, particle1))
|
||||
assert KM.kanes_equations(BL)[0] == Matrix([0])
|
||||
# test for input format kane.kanes_equations(bodies=(body1, body 2), loads=(load1,load2))
|
||||
assert KM.kanes_equations(bodies=BL, loads=None)[0] == Matrix([0])
|
||||
# test for input format kane.kanes_equations(bodies=(body1, body 2), loads=None)
|
||||
assert KM.kanes_equations(BL, loads=None)[0] == Matrix([0])
|
||||
# test for input format kane.kanes_equations(bodies=(body1, body 2))
|
||||
assert KM.kanes_equations(BL)[0] == Matrix([0])
|
||||
# test for input format kane.kanes_equations(bodies=(body1, body2), loads=[])
|
||||
assert KM.kanes_equations(BL, [])[0] == Matrix([0])
|
||||
# test for error raised when a wrong force list (in this case a string) is provided
|
||||
raises(ValueError, lambda: KM._form_fr('bad input'))
|
||||
|
||||
# 1 dof problem from test_one_dof with FL & BL in instance
|
||||
KM = KanesMethod(N, [q], [u], kd, bodies=BL, forcelist=FL)
|
||||
assert KM.kanes_equations()[0] == Matrix([-c*u - k*q])
|
||||
|
||||
# 2 dof problem from test_two_dof
|
||||
q1, q2, u1, u2 = dynamicsymbols('q1 q2 u1 u2')
|
||||
q1d, q2d, u1d, u2d = dynamicsymbols('q1 q2 u1 u2', 1)
|
||||
m, c1, c2, k1, k2 = symbols('m c1 c2 k1 k2')
|
||||
N = ReferenceFrame('N')
|
||||
P1 = Point('P1')
|
||||
P2 = Point('P2')
|
||||
P1.set_vel(N, u1 * N.x)
|
||||
P2.set_vel(N, (u1 + u2) * N.x)
|
||||
kd = [q1d - u1, q2d - u2]
|
||||
|
||||
FL = ((P1, (-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2) * N.x), (P2, (-k2 *
|
||||
q2 - c2 * u2) * N.x))
|
||||
pa1 = Particle('pa1', P1, m)
|
||||
pa2 = Particle('pa2', P2, m)
|
||||
BL = (pa1, pa2)
|
||||
|
||||
KM = KanesMethod(N, q_ind=[q1, q2], u_ind=[u1, u2], kd_eqs=kd)
|
||||
# test for input format
|
||||
# kane.kanes_equations((body1, body2), (load1, load2))
|
||||
KM.kanes_equations(BL, FL)
|
||||
MM = KM.mass_matrix
|
||||
forcing = KM.forcing
|
||||
rhs = MM.inv() * forcing
|
||||
assert expand(rhs[0]) == expand((-k1 * q1 - c1 * u1 + k2 * q2 + c2 * u2)/m)
|
||||
assert expand(rhs[1]) == expand((k1 * q1 + c1 * u1 - 2 * k2 * q2 - 2 *
|
||||
c2 * u2) / m)
|
||||
|
||||
|
||||
def test_implicit_kinematics():
|
||||
# Test that implicit kinematics can handle complicated
|
||||
# equations that explicit form struggles with
|
||||
# See https://github.com/sympy/sympy/issues/22626
|
||||
|
||||
# Inertial frame
|
||||
NED = ReferenceFrame('NED')
|
||||
NED_o = Point('NED_o')
|
||||
NED_o.set_vel(NED, 0)
|
||||
|
||||
# body frame
|
||||
q_att = dynamicsymbols('lambda_0:4', real=True)
|
||||
B = NED.orientnew('B', 'Quaternion', q_att)
|
||||
|
||||
# Generalized coordinates
|
||||
q_pos = dynamicsymbols('B_x:z')
|
||||
B_cm = NED_o.locatenew('B_cm', q_pos[0]*B.x + q_pos[1]*B.y + q_pos[2]*B.z)
|
||||
|
||||
q_ind = q_att[1:] + q_pos
|
||||
q_dep = [q_att[0]]
|
||||
|
||||
kinematic_eqs = []
|
||||
|
||||
# Generalized velocities
|
||||
B_ang_vel = B.ang_vel_in(NED)
|
||||
P, Q, R = dynamicsymbols('P Q R')
|
||||
B.set_ang_vel(NED, P*B.x + Q*B.y + R*B.z)
|
||||
|
||||
B_ang_vel_kd = (B.ang_vel_in(NED) - B_ang_vel).simplify()
|
||||
|
||||
# Equating the two gives us the kinematic equation
|
||||
kinematic_eqs += [
|
||||
B_ang_vel_kd & B.x,
|
||||
B_ang_vel_kd & B.y,
|
||||
B_ang_vel_kd & B.z
|
||||
]
|
||||
|
||||
B_cm_vel = B_cm.vel(NED)
|
||||
U, V, W = dynamicsymbols('U V W')
|
||||
B_cm.set_vel(NED, U*B.x + V*B.y + W*B.z)
|
||||
|
||||
# Compute the velocity of the point using the two methods
|
||||
B_ref_vel_kd = (B_cm.vel(NED) - B_cm_vel)
|
||||
|
||||
# taking dot product with unit vectors to get kinematic equations
|
||||
# relating body coordinates and velocities
|
||||
|
||||
# Note, there is a choice to dot with NED.xyz here. That makes
|
||||
# the implicit form have some bigger terms but is still fine, the
|
||||
# explicit form still struggles though
|
||||
kinematic_eqs += [
|
||||
B_ref_vel_kd & B.x,
|
||||
B_ref_vel_kd & B.y,
|
||||
B_ref_vel_kd & B.z,
|
||||
]
|
||||
|
||||
u_ind = [U, V, W, P, Q, R]
|
||||
|
||||
# constraints
|
||||
q_att_vec = Matrix(q_att)
|
||||
config_cons = [(q_att_vec.T*q_att_vec)[0] - 1] #unit norm
|
||||
kinematic_eqs = kinematic_eqs + [(q_att_vec.T * q_att_vec.diff())[0]]
|
||||
|
||||
try:
|
||||
KM = KanesMethod(NED, q_ind, u_ind,
|
||||
q_dependent= q_dep,
|
||||
kd_eqs = kinematic_eqs,
|
||||
configuration_constraints = config_cons,
|
||||
velocity_constraints= [],
|
||||
u_dependent= [], #no dependent speeds
|
||||
u_auxiliary = [], # No auxiliary speeds
|
||||
explicit_kinematics = False # implicit kinematics
|
||||
)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
# mass and inertia dyadic relative to CM
|
||||
M_B = symbols('M_B')
|
||||
J_B = inertia(B, *[S(f'J_B_{ax}')*(1 if ax[0] == ax[1] else -1)
|
||||
for ax in ['xx', 'yy', 'zz', 'xy', 'yz', 'xz']])
|
||||
J_B = J_B.subs({S('J_B_xy'): 0, S('J_B_yz'): 0})
|
||||
RB = RigidBody('RB', B_cm, B, M_B, (J_B, B_cm))
|
||||
|
||||
rigid_bodies = [RB]
|
||||
# Forces
|
||||
force_list = [
|
||||
#gravity pointing down
|
||||
(RB.masscenter, RB.mass*S('g')*NED.z),
|
||||
#generic forces and torques in body frame(inputs)
|
||||
(RB.frame, dynamicsymbols('T_z')*B.z),
|
||||
(RB.masscenter, dynamicsymbols('F_z')*B.z)
|
||||
]
|
||||
|
||||
KM.kanes_equations(rigid_bodies, force_list)
|
||||
|
||||
# Expecting implicit form to be less than 5% of the flops
|
||||
n_ops_implicit = sum(
|
||||
[x.count_ops() for x in KM.forcing_full] +
|
||||
[x.count_ops() for x in KM.mass_matrix_full]
|
||||
)
|
||||
# Save implicit kinematic matrices to use later
|
||||
mass_matrix_kin_implicit = KM.mass_matrix_kin
|
||||
forcing_kin_implicit = KM.forcing_kin
|
||||
|
||||
KM.explicit_kinematics = True
|
||||
n_ops_explicit = sum(
|
||||
[x.count_ops() for x in KM.forcing_full] +
|
||||
[x.count_ops() for x in KM.mass_matrix_full]
|
||||
)
|
||||
forcing_kin_explicit = KM.forcing_kin
|
||||
|
||||
assert n_ops_implicit / n_ops_explicit < .05
|
||||
|
||||
# Ideally we would check that implicit and explicit equations give the same result as done in test_one_dof
|
||||
# But the whole raison-d'etre of the implicit equations is to deal with problems such
|
||||
# as this one where the explicit form is too complicated to handle, especially the angular part
|
||||
# (i.e. tests would be too slow)
|
||||
# Instead, we check that the kinematic equations are correct using more fundamental tests:
|
||||
#
|
||||
# (1) that we recover the kinematic equations we have provided
|
||||
assert (mass_matrix_kin_implicit * KM.q.diff() - forcing_kin_implicit) == Matrix(kinematic_eqs)
|
||||
|
||||
# (2) that rate of quaternions matches what 'textbook' solutions give
|
||||
# Note that we just use the explicit kinematics for the linear velocities
|
||||
# as they are not as complicated as the angular ones
|
||||
qdot_candidate = forcing_kin_explicit
|
||||
|
||||
quat_dot_textbook = Matrix([
|
||||
[0, -P, -Q, -R],
|
||||
[P, 0, R, -Q],
|
||||
[Q, -R, 0, P],
|
||||
[R, Q, -P, 0],
|
||||
]) * q_att_vec / 2
|
||||
|
||||
# Again, if we don't use this "textbook" solution
|
||||
# sympy will struggle to deal with the terms related to quaternion rates
|
||||
# due to the number of operations involved
|
||||
qdot_candidate[-1] = quat_dot_textbook[0] # lambda_0, note the [-1] as sympy's Kane puts the dependent coordinate last
|
||||
qdot_candidate[0] = quat_dot_textbook[1] # lambda_1
|
||||
qdot_candidate[1] = quat_dot_textbook[2] # lambda_2
|
||||
qdot_candidate[2] = quat_dot_textbook[3] # lambda_3
|
||||
|
||||
# sub the config constraint in the candidate solution and compare to the implicit rhs
|
||||
lambda_0_sol = solve(config_cons[0], q_att_vec[0])[1]
|
||||
lhs_candidate = simplify(mass_matrix_kin_implicit * qdot_candidate).subs({q_att_vec[0]: lambda_0_sol})
|
||||
assert lhs_candidate == forcing_kin_implicit
|
||||
|
||||
def test_issue_24887():
|
||||
# Spherical pendulum
|
||||
g, l, m, c = symbols('g l m c')
|
||||
q1, q2, q3, u1, u2, u3 = dynamicsymbols('q1:4 u1:4')
|
||||
N = ReferenceFrame('N')
|
||||
A = ReferenceFrame('A')
|
||||
A.orient_body_fixed(N, (q1, q2, q3), 'zxy')
|
||||
N_w_A = A.ang_vel_in(N)
|
||||
# A.set_ang_vel(N, u1 * A.x + u2 * A.y + u3 * A.z)
|
||||
kdes = [N_w_A.dot(A.x) - u1, N_w_A.dot(A.y) - u2, N_w_A.dot(A.z) - u3]
|
||||
O = Point('O')
|
||||
O.set_vel(N, 0)
|
||||
Po = O.locatenew('Po', -l * A.y)
|
||||
Po.set_vel(A, 0)
|
||||
P = Particle('P', Po, m)
|
||||
kane = KanesMethod(N, [q1, q2, q3], [u1, u2, u3], kdes, bodies=[P],
|
||||
forcelist=[(Po, -m * g * N.y)])
|
||||
kane.kanes_equations()
|
||||
expected_md = m * l ** 2 * Matrix([[1, 0, 0], [0, 0, 0], [0, 0, 1]])
|
||||
expected_fd = Matrix([
|
||||
[l*m*(g*(sin(q1)*sin(q3) - sin(q2)*cos(q1)*cos(q3)) - l*u2*u3)],
|
||||
[0], [l*m*(-g*(sin(q1)*cos(q3) + sin(q2)*sin(q3)*cos(q1)) + l*u1*u2)]])
|
||||
assert find_dynamicsymbols(kane.forcing).issubset({q1, q2, q3, u1, u2, u3})
|
||||
assert simplify(kane.mass_matrix - expected_md) == zeros(3, 3)
|
||||
assert simplify(kane.forcing - expected_fd) == zeros(3, 1)
|
||||
@@ -0,0 +1,464 @@
|
||||
from sympy import cos, Matrix, sin, zeros, tan, pi, symbols
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.simplify.trigsimp import trigsimp
|
||||
from sympy.solvers.solvers import solve
|
||||
from sympy.physics.mechanics import (cross, dot, dynamicsymbols,
|
||||
find_dynamicsymbols, KanesMethod, inertia,
|
||||
inertia_of_point_mass, Point,
|
||||
ReferenceFrame, RigidBody)
|
||||
|
||||
|
||||
def test_aux_dep():
|
||||
# This test is about rolling disc dynamics, comparing the results found
|
||||
# with KanesMethod to those found when deriving the equations "manually"
|
||||
# with SymPy.
|
||||
# The terms Fr, Fr*, and Fr*_steady are all compared between the two
|
||||
# methods. Here, Fr*_steady refers to the generalized inertia forces for an
|
||||
# equilibrium configuration.
|
||||
# Note: comparing to the test of test_rolling_disc() in test_kane.py, this
|
||||
# test also tests auxiliary speeds and configuration and motion constraints
|
||||
#, seen in the generalized dependent coordinates q[3], and depend speeds
|
||||
# u[3], u[4] and u[5].
|
||||
|
||||
|
||||
# First, manual derivation of Fr, Fr_star, Fr_star_steady.
|
||||
|
||||
# Symbols for time and constant parameters.
|
||||
# Symbols for contact forces: Fx, Fy, Fz.
|
||||
t, r, m, g, I, J = symbols('t r m g I J')
|
||||
Fx, Fy, Fz = symbols('Fx Fy Fz')
|
||||
|
||||
# Configuration variables and their time derivatives:
|
||||
# q[0] -- yaw
|
||||
# q[1] -- lean
|
||||
# q[2] -- spin
|
||||
# q[3] -- dot(-r*B.z, A.z) -- distance from ground plane to disc center in
|
||||
# A.z direction
|
||||
# Generalized speeds and their time derivatives:
|
||||
# u[0] -- disc angular velocity component, disc fixed x direction
|
||||
# u[1] -- disc angular velocity component, disc fixed y direction
|
||||
# u[2] -- disc angular velocity component, disc fixed z direction
|
||||
# u[3] -- disc velocity component, A.x direction
|
||||
# u[4] -- disc velocity component, A.y direction
|
||||
# u[5] -- disc velocity component, A.z direction
|
||||
# Auxiliary generalized speeds:
|
||||
# ua[0] -- contact point auxiliary generalized speed, A.x direction
|
||||
# ua[1] -- contact point auxiliary generalized speed, A.y direction
|
||||
# ua[2] -- contact point auxiliary generalized speed, A.z direction
|
||||
q = dynamicsymbols('q:4')
|
||||
qd = [qi.diff(t) for qi in q]
|
||||
u = dynamicsymbols('u:6')
|
||||
ud = [ui.diff(t) for ui in u]
|
||||
ud_zero = dict(zip(ud, [0.]*len(ud)))
|
||||
ua = dynamicsymbols('ua:3')
|
||||
ua_zero = dict(zip(ua, [0.]*len(ua))) # noqa:F841
|
||||
|
||||
# Reference frames:
|
||||
# Yaw intermediate frame: A.
|
||||
# Lean intermediate frame: B.
|
||||
# Disc fixed frame: C.
|
||||
N = ReferenceFrame('N')
|
||||
A = N.orientnew('A', 'Axis', [q[0], N.z])
|
||||
B = A.orientnew('B', 'Axis', [q[1], A.x])
|
||||
C = B.orientnew('C', 'Axis', [q[2], B.y])
|
||||
|
||||
# Angular velocity and angular acceleration of disc fixed frame
|
||||
# u[0], u[1] and u[2] are generalized independent speeds.
|
||||
C.set_ang_vel(N, u[0]*B.x + u[1]*B.y + u[2]*B.z)
|
||||
C.set_ang_acc(N, C.ang_vel_in(N).diff(t, B)
|
||||
+ cross(B.ang_vel_in(N), C.ang_vel_in(N)))
|
||||
|
||||
# Velocity and acceleration of points:
|
||||
# Disc-ground contact point: P.
|
||||
# Center of disc: O, defined from point P with depend coordinate: q[3]
|
||||
# u[3], u[4] and u[5] are generalized dependent speeds.
|
||||
P = Point('P')
|
||||
P.set_vel(N, ua[0]*A.x + ua[1]*A.y + ua[2]*A.z)
|
||||
O = P.locatenew('O', q[3]*A.z + r*sin(q[1])*A.y)
|
||||
O.set_vel(N, u[3]*A.x + u[4]*A.y + u[5]*A.z)
|
||||
O.set_acc(N, O.vel(N).diff(t, A) + cross(A.ang_vel_in(N), O.vel(N)))
|
||||
|
||||
# Kinematic differential equations:
|
||||
# Two equalities: one is w_c_n_qd = C.ang_vel_in(N) in three coordinates
|
||||
# directions of B, for qd0, qd1 and qd2.
|
||||
# the other is v_o_n_qd = O.vel(N) in A.z direction for qd3.
|
||||
# Then, solve for dq/dt's in terms of u's: qd_kd.
|
||||
w_c_n_qd = qd[0]*A.z + qd[1]*B.x + qd[2]*B.y
|
||||
v_o_n_qd = O.pos_from(P).diff(t, A) + cross(A.ang_vel_in(N), O.pos_from(P))
|
||||
kindiffs = Matrix([dot(w_c_n_qd - C.ang_vel_in(N), uv) for uv in B] +
|
||||
[dot(v_o_n_qd - O.vel(N), A.z)])
|
||||
qd_kd = solve(kindiffs, qd) # noqa:F841
|
||||
|
||||
# Values of generalized speeds during a steady turn for later substitution
|
||||
# into the Fr_star_steady.
|
||||
steady_conditions = solve(kindiffs.subs({qd[1] : 0, qd[3] : 0}), u)
|
||||
steady_conditions.update({qd[1] : 0, qd[3] : 0})
|
||||
|
||||
# Partial angular velocities and velocities.
|
||||
partial_w_C = [C.ang_vel_in(N).diff(ui, N) for ui in u + ua]
|
||||
partial_v_O = [O.vel(N).diff(ui, N) for ui in u + ua]
|
||||
partial_v_P = [P.vel(N).diff(ui, N) for ui in u + ua]
|
||||
|
||||
# Configuration constraint: f_c, the projection of radius r in A.z direction
|
||||
# is q[3].
|
||||
# Velocity constraints: f_v, for u3, u4 and u5.
|
||||
# Acceleration constraints: f_a.
|
||||
f_c = Matrix([dot(-r*B.z, A.z) - q[3]])
|
||||
f_v = Matrix([dot(O.vel(N) - (P.vel(N) + cross(C.ang_vel_in(N),
|
||||
O.pos_from(P))), ai).expand() for ai in A])
|
||||
v_o_n = cross(C.ang_vel_in(N), O.pos_from(P))
|
||||
a_o_n = v_o_n.diff(t, A) + cross(A.ang_vel_in(N), v_o_n)
|
||||
f_a = Matrix([dot(O.acc(N) - a_o_n, ai) for ai in A]) # noqa:F841
|
||||
|
||||
# Solve for constraint equations in the form of
|
||||
# u_dependent = A_rs * [u_i; u_aux].
|
||||
# First, obtain constraint coefficient matrix: M_v * [u; ua] = 0;
|
||||
# Second, taking u[0], u[1], u[2] as independent,
|
||||
# taking u[3], u[4], u[5] as dependent,
|
||||
# rearranging the matrix of M_v to be A_rs for u_dependent.
|
||||
# Third, u_aux ==0 for u_dep, and resulting dictionary of u_dep_dict.
|
||||
M_v = zeros(3, 9)
|
||||
for i in range(3):
|
||||
for j, ui in enumerate(u + ua):
|
||||
M_v[i, j] = f_v[i].diff(ui)
|
||||
|
||||
M_v_i = M_v[:, :3]
|
||||
M_v_d = M_v[:, 3:6]
|
||||
M_v_aux = M_v[:, 6:]
|
||||
M_v_i_aux = M_v_i.row_join(M_v_aux)
|
||||
A_rs = - M_v_d.inv() * M_v_i_aux
|
||||
|
||||
u_dep = A_rs[:, :3] * Matrix(u[:3])
|
||||
u_dep_dict = dict(zip(u[3:], u_dep))
|
||||
|
||||
# Active forces: F_O acting on point O; F_P acting on point P.
|
||||
# Generalized active forces (unconstrained): Fr_u = F_point * pv_point.
|
||||
F_O = m*g*A.z
|
||||
F_P = Fx * A.x + Fy * A.y + Fz * A.z
|
||||
Fr_u = Matrix([dot(F_O, pv_o) + dot(F_P, pv_p) for pv_o, pv_p in
|
||||
zip(partial_v_O, partial_v_P)])
|
||||
|
||||
# Inertia force: R_star_O.
|
||||
# Inertia of disc: I_C_O, where J is a inertia component about principal axis.
|
||||
# Inertia torque: T_star_C.
|
||||
# Generalized inertia forces (unconstrained): Fr_star_u.
|
||||
R_star_O = -m*O.acc(N)
|
||||
I_C_O = inertia(B, I, J, I)
|
||||
T_star_C = -(dot(I_C_O, C.ang_acc_in(N)) \
|
||||
+ cross(C.ang_vel_in(N), dot(I_C_O, C.ang_vel_in(N))))
|
||||
Fr_star_u = Matrix([dot(R_star_O, pv) + dot(T_star_C, pav) for pv, pav in
|
||||
zip(partial_v_O, partial_w_C)])
|
||||
|
||||
# Form nonholonomic Fr: Fr_c, and nonholonomic Fr_star: Fr_star_c.
|
||||
# Also, nonholonomic Fr_star in steady turning condition: Fr_star_steady.
|
||||
Fr_c = Fr_u[:3, :].col_join(Fr_u[6:, :]) + A_rs.T * Fr_u[3:6, :]
|
||||
Fr_star_c = Fr_star_u[:3, :].col_join(Fr_star_u[6:, :])\
|
||||
+ A_rs.T * Fr_star_u[3:6, :]
|
||||
Fr_star_steady = Fr_star_c.subs(ud_zero).subs(u_dep_dict)\
|
||||
.subs(steady_conditions).subs({q[3]: -r*cos(q[1])}).expand()
|
||||
|
||||
|
||||
# Second, using KaneMethod in mechanics for fr, frstar and frstar_steady.
|
||||
|
||||
# Rigid Bodies: disc, with inertia I_C_O.
|
||||
iner_tuple = (I_C_O, O)
|
||||
disc = RigidBody('disc', O, C, m, iner_tuple)
|
||||
bodyList = [disc]
|
||||
|
||||
# Generalized forces: Gravity: F_o; Auxiliary forces: F_p.
|
||||
F_o = (O, F_O)
|
||||
F_p = (P, F_P)
|
||||
forceList = [F_o, F_p]
|
||||
|
||||
# KanesMethod.
|
||||
kane = KanesMethod(
|
||||
N, q_ind= q[:3], u_ind= u[:3], kd_eqs=kindiffs,
|
||||
q_dependent=q[3:], configuration_constraints = f_c,
|
||||
u_dependent=u[3:], velocity_constraints= f_v,
|
||||
u_auxiliary=ua
|
||||
)
|
||||
|
||||
# fr, frstar, frstar_steady and kdd(kinematic differential equations).
|
||||
(fr, frstar)= kane.kanes_equations(bodyList, forceList)
|
||||
frstar_steady = frstar.subs(ud_zero).subs(u_dep_dict).subs(steady_conditions)\
|
||||
.subs({q[3]: -r*cos(q[1])}).expand()
|
||||
kdd = kane.kindiffdict()
|
||||
|
||||
assert Matrix(Fr_c).expand() == fr.expand()
|
||||
assert Matrix(Fr_star_c.subs(kdd)).expand() == frstar.expand()
|
||||
# These Matrices have some Integer(0) and some Float(0). Running under
|
||||
# SymEngine gives different types of zero.
|
||||
assert (simplify(Matrix(Fr_star_steady).expand()).xreplace({0:0.0}) ==
|
||||
simplify(frstar_steady.expand()).xreplace({0:0.0}))
|
||||
|
||||
syms_in_forcing = find_dynamicsymbols(kane.forcing)
|
||||
for qdi in qd:
|
||||
assert qdi not in syms_in_forcing
|
||||
|
||||
|
||||
def test_non_central_inertia():
|
||||
# This tests that the calculation of Fr* does not depend the point
|
||||
# about which the inertia of a rigid body is defined. This test solves
|
||||
# exercises 8.12, 8.17 from Kane 1985.
|
||||
|
||||
# Declare symbols
|
||||
q1, q2, q3 = dynamicsymbols('q1:4')
|
||||
q1d, q2d, q3d = dynamicsymbols('q1:4', level=1)
|
||||
u1, u2, u3, u4, u5 = dynamicsymbols('u1:6')
|
||||
u_prime, R, M, g, e, f, theta = symbols('u\' R, M, g, e, f, theta')
|
||||
a, b, mA, mB, IA, J, K, t = symbols('a b mA mB IA J K t')
|
||||
Q1, Q2, Q3 = symbols('Q1, Q2 Q3')
|
||||
IA22, IA23, IA33 = symbols('IA22 IA23 IA33')
|
||||
|
||||
# Reference Frames
|
||||
F = ReferenceFrame('F')
|
||||
P = F.orientnew('P', 'axis', [-theta, F.y])
|
||||
A = P.orientnew('A', 'axis', [q1, P.x])
|
||||
A.set_ang_vel(F, u1*A.x + u3*A.z)
|
||||
# define frames for wheels
|
||||
B = A.orientnew('B', 'axis', [q2, A.z])
|
||||
C = A.orientnew('C', 'axis', [q3, A.z])
|
||||
B.set_ang_vel(A, u4 * A.z)
|
||||
C.set_ang_vel(A, u5 * A.z)
|
||||
|
||||
# define points D, S*, Q on frame A and their velocities
|
||||
pD = Point('D')
|
||||
pD.set_vel(A, 0)
|
||||
# u3 will not change v_D_F since wheels are still assumed to roll without slip.
|
||||
pD.set_vel(F, u2 * A.y)
|
||||
|
||||
pS_star = pD.locatenew('S*', e*A.y)
|
||||
pQ = pD.locatenew('Q', f*A.y - R*A.x)
|
||||
for p in [pS_star, pQ]:
|
||||
p.v2pt_theory(pD, F, A)
|
||||
|
||||
# masscenters of bodies A, B, C
|
||||
pA_star = pD.locatenew('A*', a*A.y)
|
||||
pB_star = pD.locatenew('B*', b*A.z)
|
||||
pC_star = pD.locatenew('C*', -b*A.z)
|
||||
for p in [pA_star, pB_star, pC_star]:
|
||||
p.v2pt_theory(pD, F, A)
|
||||
|
||||
# points of B, C touching the plane P
|
||||
pB_hat = pB_star.locatenew('B^', -R*A.x)
|
||||
pC_hat = pC_star.locatenew('C^', -R*A.x)
|
||||
pB_hat.v2pt_theory(pB_star, F, B)
|
||||
pC_hat.v2pt_theory(pC_star, F, C)
|
||||
|
||||
# the velocities of B^, C^ are zero since B, C are assumed to roll without slip
|
||||
kde = [q1d - u1, q2d - u4, q3d - u5]
|
||||
vc = [dot(p.vel(F), A.y) for p in [pB_hat, pC_hat]]
|
||||
|
||||
# inertias of bodies A, B, C
|
||||
# IA22, IA23, IA33 are not specified in the problem statement, but are
|
||||
# necessary to define an inertia object. Although the values of
|
||||
# IA22, IA23, IA33 are not known in terms of the variables given in the
|
||||
# problem statement, they do not appear in the general inertia terms.
|
||||
inertia_A = inertia(A, IA, IA22, IA33, 0, IA23, 0)
|
||||
inertia_B = inertia(B, K, K, J)
|
||||
inertia_C = inertia(C, K, K, J)
|
||||
|
||||
# define the rigid bodies A, B, C
|
||||
rbA = RigidBody('rbA', pA_star, A, mA, (inertia_A, pA_star))
|
||||
rbB = RigidBody('rbB', pB_star, B, mB, (inertia_B, pB_star))
|
||||
rbC = RigidBody('rbC', pC_star, C, mB, (inertia_C, pC_star))
|
||||
|
||||
km = KanesMethod(F, q_ind=[q1, q2, q3], u_ind=[u1, u2], kd_eqs=kde,
|
||||
u_dependent=[u4, u5], velocity_constraints=vc,
|
||||
u_auxiliary=[u3])
|
||||
|
||||
forces = [(pS_star, -M*g*F.x), (pQ, Q1*A.x + Q2*A.y + Q3*A.z)]
|
||||
bodies = [rbA, rbB, rbC]
|
||||
fr, fr_star = km.kanes_equations(bodies, forces)
|
||||
vc_map = solve(vc, [u4, u5])
|
||||
|
||||
# KanesMethod returns the negative of Fr, Fr* as defined in Kane1985.
|
||||
fr_star_expected = Matrix([
|
||||
-(IA + 2*J*b**2/R**2 + 2*K +
|
||||
mA*a**2 + 2*mB*b**2) * u1.diff(t) - mA*a*u1*u2,
|
||||
-(mA + 2*mB +2*J/R**2) * u2.diff(t) + mA*a*u1**2,
|
||||
0])
|
||||
t = trigsimp(fr_star.subs(vc_map).subs({u3: 0})).doit().expand()
|
||||
assert ((fr_star_expected - t).expand() == zeros(3, 1))
|
||||
|
||||
# define inertias of rigid bodies A, B, C about point D
|
||||
# I_S/O = I_S/S* + I_S*/O
|
||||
bodies2 = []
|
||||
for rb, I_star in zip([rbA, rbB, rbC], [inertia_A, inertia_B, inertia_C]):
|
||||
I = I_star + inertia_of_point_mass(rb.mass,
|
||||
rb.masscenter.pos_from(pD),
|
||||
rb.frame)
|
||||
bodies2.append(RigidBody('', rb.masscenter, rb.frame, rb.mass,
|
||||
(I, pD)))
|
||||
fr2, fr_star2 = km.kanes_equations(bodies2, forces)
|
||||
|
||||
t = trigsimp(fr_star2.subs(vc_map).subs({u3: 0})).doit()
|
||||
assert (fr_star_expected - t).expand() == zeros(3, 1)
|
||||
|
||||
def test_sub_qdot():
|
||||
# This test solves exercises 8.12, 8.17 from Kane 1985 and defines
|
||||
# some velocities in terms of q, qdot.
|
||||
|
||||
## --- Declare symbols ---
|
||||
q1, q2, q3 = dynamicsymbols('q1:4')
|
||||
q1d, q2d, q3d = dynamicsymbols('q1:4', level=1)
|
||||
u1, u2, u3 = dynamicsymbols('u1:4')
|
||||
u_prime, R, M, g, e, f, theta = symbols('u\' R, M, g, e, f, theta')
|
||||
a, b, mA, mB, IA, J, K, t = symbols('a b mA mB IA J K t')
|
||||
IA22, IA23, IA33 = symbols('IA22 IA23 IA33')
|
||||
Q1, Q2, Q3 = symbols('Q1 Q2 Q3')
|
||||
|
||||
# --- Reference Frames ---
|
||||
F = ReferenceFrame('F')
|
||||
P = F.orientnew('P', 'axis', [-theta, F.y])
|
||||
A = P.orientnew('A', 'axis', [q1, P.x])
|
||||
A.set_ang_vel(F, u1*A.x + u3*A.z)
|
||||
# define frames for wheels
|
||||
B = A.orientnew('B', 'axis', [q2, A.z])
|
||||
C = A.orientnew('C', 'axis', [q3, A.z])
|
||||
|
||||
## --- define points D, S*, Q on frame A and their velocities ---
|
||||
pD = Point('D')
|
||||
pD.set_vel(A, 0)
|
||||
# u3 will not change v_D_F since wheels are still assumed to roll w/o slip
|
||||
pD.set_vel(F, u2 * A.y)
|
||||
|
||||
pS_star = pD.locatenew('S*', e*A.y)
|
||||
pQ = pD.locatenew('Q', f*A.y - R*A.x)
|
||||
# masscenters of bodies A, B, C
|
||||
pA_star = pD.locatenew('A*', a*A.y)
|
||||
pB_star = pD.locatenew('B*', b*A.z)
|
||||
pC_star = pD.locatenew('C*', -b*A.z)
|
||||
for p in [pS_star, pQ, pA_star, pB_star, pC_star]:
|
||||
p.v2pt_theory(pD, F, A)
|
||||
|
||||
# points of B, C touching the plane P
|
||||
pB_hat = pB_star.locatenew('B^', -R*A.x)
|
||||
pC_hat = pC_star.locatenew('C^', -R*A.x)
|
||||
pB_hat.v2pt_theory(pB_star, F, B)
|
||||
pC_hat.v2pt_theory(pC_star, F, C)
|
||||
|
||||
# --- relate qdot, u ---
|
||||
# the velocities of B^, C^ are zero since B, C are assumed to roll w/o slip
|
||||
kde = [dot(p.vel(F), A.y) for p in [pB_hat, pC_hat]]
|
||||
kde += [u1 - q1d]
|
||||
kde_map = solve(kde, [q1d, q2d, q3d])
|
||||
for k, v in list(kde_map.items()):
|
||||
kde_map[k.diff(t)] = v.diff(t)
|
||||
|
||||
# inertias of bodies A, B, C
|
||||
# IA22, IA23, IA33 are not specified in the problem statement, but are
|
||||
# necessary to define an inertia object. Although the values of
|
||||
# IA22, IA23, IA33 are not known in terms of the variables given in the
|
||||
# problem statement, they do not appear in the general inertia terms.
|
||||
inertia_A = inertia(A, IA, IA22, IA33, 0, IA23, 0)
|
||||
inertia_B = inertia(B, K, K, J)
|
||||
inertia_C = inertia(C, K, K, J)
|
||||
|
||||
# define the rigid bodies A, B, C
|
||||
rbA = RigidBody('rbA', pA_star, A, mA, (inertia_A, pA_star))
|
||||
rbB = RigidBody('rbB', pB_star, B, mB, (inertia_B, pB_star))
|
||||
rbC = RigidBody('rbC', pC_star, C, mB, (inertia_C, pC_star))
|
||||
|
||||
## --- use kanes method ---
|
||||
km = KanesMethod(F, [q1, q2, q3], [u1, u2], kd_eqs=kde, u_auxiliary=[u3])
|
||||
|
||||
forces = [(pS_star, -M*g*F.x), (pQ, Q1*A.x + Q2*A.y + Q3*A.z)]
|
||||
bodies = [rbA, rbB, rbC]
|
||||
|
||||
# Q2 = -u_prime * u2 * Q1 / sqrt(u2**2 + f**2 * u1**2)
|
||||
# -u_prime * R * u2 / sqrt(u2**2 + f**2 * u1**2) = R / Q1 * Q2
|
||||
fr_expected = Matrix([
|
||||
f*Q3 + M*g*e*sin(theta)*cos(q1),
|
||||
Q2 + M*g*sin(theta)*sin(q1),
|
||||
e*M*g*cos(theta) - Q1*f - Q2*R])
|
||||
#Q1 * (f - u_prime * R * u2 / sqrt(u2**2 + f**2 * u1**2)))])
|
||||
fr_star_expected = Matrix([
|
||||
-(IA + 2*J*b**2/R**2 + 2*K +
|
||||
mA*a**2 + 2*mB*b**2) * u1.diff(t) - mA*a*u1*u2,
|
||||
-(mA + 2*mB +2*J/R**2) * u2.diff(t) + mA*a*u1**2,
|
||||
0])
|
||||
|
||||
fr, fr_star = km.kanes_equations(bodies, forces)
|
||||
assert (fr.expand() == fr_expected.expand())
|
||||
assert ((fr_star_expected - trigsimp(fr_star)).expand() == zeros(3, 1))
|
||||
|
||||
def test_sub_qdot2():
|
||||
# This test solves exercises 8.3 from Kane 1985 and defines
|
||||
# all velocities in terms of q, qdot. We check that the generalized active
|
||||
# forces are correctly computed if u terms are only defined in the
|
||||
# kinematic differential equations.
|
||||
#
|
||||
# This functionality was added in PR 8948. Without qdot/u substitution, the
|
||||
# KanesMethod constructor will fail during the constraint initialization as
|
||||
# the B matrix will be poorly formed and inversion of the dependent part
|
||||
# will fail.
|
||||
|
||||
g, m, Px, Py, Pz, R, t = symbols('g m Px Py Pz R t')
|
||||
q = dynamicsymbols('q:5')
|
||||
qd = dynamicsymbols('q:5', level=1)
|
||||
u = dynamicsymbols('u:5')
|
||||
|
||||
## Define inertial, intermediate, and rigid body reference frames
|
||||
A = ReferenceFrame('A')
|
||||
B_prime = A.orientnew('B_prime', 'Axis', [q[0], A.z])
|
||||
B = B_prime.orientnew('B', 'Axis', [pi/2 - q[1], B_prime.x])
|
||||
C = B.orientnew('C', 'Axis', [q[2], B.z])
|
||||
|
||||
## Define points of interest and their velocities
|
||||
pO = Point('O')
|
||||
pO.set_vel(A, 0)
|
||||
|
||||
# R is the point in plane H that comes into contact with disk C.
|
||||
pR = pO.locatenew('R', q[3]*A.x + q[4]*A.y)
|
||||
pR.set_vel(A, pR.pos_from(pO).diff(t, A))
|
||||
pR.set_vel(B, 0)
|
||||
|
||||
# C^ is the point in disk C that comes into contact with plane H.
|
||||
pC_hat = pR.locatenew('C^', 0)
|
||||
pC_hat.set_vel(C, 0)
|
||||
|
||||
# C* is the point at the center of disk C.
|
||||
pCs = pC_hat.locatenew('C*', R*B.y)
|
||||
pCs.set_vel(C, 0)
|
||||
pCs.set_vel(B, 0)
|
||||
|
||||
# calculate velocites of points C* and C^ in frame A
|
||||
pCs.v2pt_theory(pR, A, B) # points C* and R are fixed in frame B
|
||||
pC_hat.v2pt_theory(pCs, A, C) # points C* and C^ are fixed in frame C
|
||||
|
||||
## Define forces on each point of the system
|
||||
R_C_hat = Px*A.x + Py*A.y + Pz*A.z
|
||||
R_Cs = -m*g*A.z
|
||||
forces = [(pC_hat, R_C_hat), (pCs, R_Cs)]
|
||||
|
||||
## Define kinematic differential equations
|
||||
# let ui = omega_C_A & bi (i = 1, 2, 3)
|
||||
# u4 = qd4, u5 = qd5
|
||||
u_expr = [C.ang_vel_in(A) & uv for uv in B]
|
||||
u_expr += qd[3:]
|
||||
kde = [ui - e for ui, e in zip(u, u_expr)]
|
||||
km1 = KanesMethod(A, q, u, kde)
|
||||
fr1, _ = km1.kanes_equations([], forces)
|
||||
|
||||
## Calculate generalized active forces if we impose the condition that the
|
||||
# disk C is rolling without slipping
|
||||
u_indep = u[:3]
|
||||
u_dep = list(set(u) - set(u_indep))
|
||||
vc = [pC_hat.vel(A) & uv for uv in [A.x, A.y]]
|
||||
km2 = KanesMethod(A, q, u_indep, kde,
|
||||
u_dependent=u_dep, velocity_constraints=vc)
|
||||
fr2, _ = km2.kanes_equations([], forces)
|
||||
|
||||
fr1_expected = Matrix([
|
||||
-R*g*m*sin(q[1]),
|
||||
-R*(Px*cos(q[0]) + Py*sin(q[0]))*tan(q[1]),
|
||||
R*(Px*cos(q[0]) + Py*sin(q[0])),
|
||||
Px,
|
||||
Py])
|
||||
fr2_expected = Matrix([
|
||||
-R*g*m*sin(q[1]),
|
||||
0,
|
||||
0])
|
||||
assert (trigsimp(fr1.expand()) == trigsimp(fr1_expected.expand()))
|
||||
assert (trigsimp(fr2.expand()) == trigsimp(fr2_expected.expand()))
|
||||
@@ -0,0 +1,315 @@
|
||||
from sympy.core.numbers import pi
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.miscellaneous import sqrt
|
||||
from sympy.functions.elementary.trigonometric import acos, sin, cos
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.physics.mechanics import (ReferenceFrame, dynamicsymbols,
|
||||
KanesMethod, inertia, Point, RigidBody,
|
||||
dot)
|
||||
from sympy.testing.pytest import slow
|
||||
|
||||
|
||||
@slow
|
||||
def test_bicycle():
|
||||
# Code to get equations of motion for a bicycle modeled as in:
|
||||
# J.P Meijaard, Jim M Papadopoulos, Andy Ruina and A.L Schwab. Linearized
|
||||
# dynamics equations for the balance and steer of a bicycle: a benchmark
|
||||
# and review. Proceedings of The Royal Society (2007) 463, 1955-1982
|
||||
# doi: 10.1098/rspa.2007.1857
|
||||
|
||||
# Note that this code has been crudely ported from Autolev, which is the
|
||||
# reason for some of the unusual naming conventions. It was purposefully as
|
||||
# similar as possible in order to aide debugging.
|
||||
|
||||
# Declare Coordinates & Speeds
|
||||
# Simple definitions for qdots - qd = u
|
||||
# Speeds are:
|
||||
# - u1: yaw frame ang. rate
|
||||
# - u2: roll frame ang. rate
|
||||
# - u3: rear wheel frame ang. rate (spinning motion)
|
||||
# - u4: frame ang. rate (pitching motion)
|
||||
# - u5: steering frame ang. rate
|
||||
# - u6: front wheel ang. rate (spinning motion)
|
||||
# Wheel positions are ignorable coordinates, so they are not introduced.
|
||||
q1, q2, q4, q5 = dynamicsymbols('q1 q2 q4 q5')
|
||||
q1d, q2d, q4d, q5d = dynamicsymbols('q1 q2 q4 q5', 1)
|
||||
u1, u2, u3, u4, u5, u6 = dynamicsymbols('u1 u2 u3 u4 u5 u6')
|
||||
u1d, u2d, u3d, u4d, u5d, u6d = dynamicsymbols('u1 u2 u3 u4 u5 u6', 1)
|
||||
|
||||
# Declare System's Parameters
|
||||
WFrad, WRrad, htangle, forkoffset = symbols('WFrad WRrad htangle forkoffset')
|
||||
forklength, framelength, forkcg1 = symbols('forklength framelength forkcg1')
|
||||
forkcg3, framecg1, framecg3, Iwr11 = symbols('forkcg3 framecg1 framecg3 Iwr11')
|
||||
Iwr22, Iwf11, Iwf22, Iframe11 = symbols('Iwr22 Iwf11 Iwf22 Iframe11')
|
||||
Iframe22, Iframe33, Iframe31, Ifork11 = symbols('Iframe22 Iframe33 Iframe31 Ifork11')
|
||||
Ifork22, Ifork33, Ifork31, g = symbols('Ifork22 Ifork33 Ifork31 g')
|
||||
mframe, mfork, mwf, mwr = symbols('mframe mfork mwf mwr')
|
||||
|
||||
# Set up reference frames for the system
|
||||
# N - inertial
|
||||
# Y - yaw
|
||||
# R - roll
|
||||
# WR - rear wheel, rotation angle is ignorable coordinate so not oriented
|
||||
# Frame - bicycle frame
|
||||
# TempFrame - statically rotated frame for easier reference inertia definition
|
||||
# Fork - bicycle fork
|
||||
# TempFork - statically rotated frame for easier reference inertia definition
|
||||
# WF - front wheel, again posses a ignorable coordinate
|
||||
N = ReferenceFrame('N')
|
||||
Y = N.orientnew('Y', 'Axis', [q1, N.z])
|
||||
R = Y.orientnew('R', 'Axis', [q2, Y.x])
|
||||
Frame = R.orientnew('Frame', 'Axis', [q4 + htangle, R.y])
|
||||
WR = ReferenceFrame('WR')
|
||||
TempFrame = Frame.orientnew('TempFrame', 'Axis', [-htangle, Frame.y])
|
||||
Fork = Frame.orientnew('Fork', 'Axis', [q5, Frame.x])
|
||||
TempFork = Fork.orientnew('TempFork', 'Axis', [-htangle, Fork.y])
|
||||
WF = ReferenceFrame('WF')
|
||||
|
||||
# Kinematics of the Bicycle First block of code is forming the positions of
|
||||
# the relevant points
|
||||
# rear wheel contact -> rear wheel mass center -> frame mass center +
|
||||
# frame/fork connection -> fork mass center + front wheel mass center ->
|
||||
# front wheel contact point
|
||||
WR_cont = Point('WR_cont')
|
||||
WR_mc = WR_cont.locatenew('WR_mc', WRrad * R.z)
|
||||
Steer = WR_mc.locatenew('Steer', framelength * Frame.z)
|
||||
Frame_mc = WR_mc.locatenew('Frame_mc', - framecg1 * Frame.x
|
||||
+ framecg3 * Frame.z)
|
||||
Fork_mc = Steer.locatenew('Fork_mc', - forkcg1 * Fork.x
|
||||
+ forkcg3 * Fork.z)
|
||||
WF_mc = Steer.locatenew('WF_mc', forklength * Fork.x + forkoffset * Fork.z)
|
||||
WF_cont = WF_mc.locatenew('WF_cont', WFrad * (dot(Fork.y, Y.z) * Fork.y -
|
||||
Y.z).normalize())
|
||||
|
||||
# Set the angular velocity of each frame.
|
||||
# Angular accelerations end up being calculated automatically by
|
||||
# differentiating the angular velocities when first needed.
|
||||
# u1 is yaw rate
|
||||
# u2 is roll rate
|
||||
# u3 is rear wheel rate
|
||||
# u4 is frame pitch rate
|
||||
# u5 is fork steer rate
|
||||
# u6 is front wheel rate
|
||||
Y.set_ang_vel(N, u1 * Y.z)
|
||||
R.set_ang_vel(Y, u2 * R.x)
|
||||
WR.set_ang_vel(Frame, u3 * Frame.y)
|
||||
Frame.set_ang_vel(R, u4 * Frame.y)
|
||||
Fork.set_ang_vel(Frame, u5 * Fork.x)
|
||||
WF.set_ang_vel(Fork, u6 * Fork.y)
|
||||
|
||||
# Form the velocities of the previously defined points, using the 2 - point
|
||||
# theorem (written out by hand here). Accelerations again are calculated
|
||||
# automatically when first needed.
|
||||
WR_cont.set_vel(N, 0)
|
||||
WR_mc.v2pt_theory(WR_cont, N, WR)
|
||||
Steer.v2pt_theory(WR_mc, N, Frame)
|
||||
Frame_mc.v2pt_theory(WR_mc, N, Frame)
|
||||
Fork_mc.v2pt_theory(Steer, N, Fork)
|
||||
WF_mc.v2pt_theory(Steer, N, Fork)
|
||||
WF_cont.v2pt_theory(WF_mc, N, WF)
|
||||
|
||||
# Sets the inertias of each body. Uses the inertia frame to construct the
|
||||
# inertia dyadics. Wheel inertias are only defined by principle moments of
|
||||
# inertia, and are in fact constant in the frame and fork reference frames;
|
||||
# it is for this reason that the orientations of the wheels does not need
|
||||
# to be defined. The frame and fork inertias are defined in the 'Temp'
|
||||
# frames which are fixed to the appropriate body frames; this is to allow
|
||||
# easier input of the reference values of the benchmark paper. Note that
|
||||
# due to slightly different orientations, the products of inertia need to
|
||||
# have their signs flipped; this is done later when entering the numerical
|
||||
# value.
|
||||
|
||||
Frame_I = (inertia(TempFrame, Iframe11, Iframe22, Iframe33, 0, 0, Iframe31), Frame_mc)
|
||||
Fork_I = (inertia(TempFork, Ifork11, Ifork22, Ifork33, 0, 0, Ifork31), Fork_mc)
|
||||
WR_I = (inertia(Frame, Iwr11, Iwr22, Iwr11), WR_mc)
|
||||
WF_I = (inertia(Fork, Iwf11, Iwf22, Iwf11), WF_mc)
|
||||
|
||||
# Declaration of the RigidBody containers. ::
|
||||
|
||||
BodyFrame = RigidBody('BodyFrame', Frame_mc, Frame, mframe, Frame_I)
|
||||
BodyFork = RigidBody('BodyFork', Fork_mc, Fork, mfork, Fork_I)
|
||||
BodyWR = RigidBody('BodyWR', WR_mc, WR, mwr, WR_I)
|
||||
BodyWF = RigidBody('BodyWF', WF_mc, WF, mwf, WF_I)
|
||||
|
||||
# The kinematic differential equations; they are defined quite simply. Each
|
||||
# entry in this list is equal to zero.
|
||||
kd = [q1d - u1, q2d - u2, q4d - u4, q5d - u5]
|
||||
|
||||
# The nonholonomic constraints are the velocity of the front wheel contact
|
||||
# point dotted into the X, Y, and Z directions; the yaw frame is used as it
|
||||
# is "closer" to the front wheel (1 less DCM connecting them). These
|
||||
# constraints force the velocity of the front wheel contact point to be 0
|
||||
# in the inertial frame; the X and Y direction constraints enforce a
|
||||
# "no-slip" condition, and the Z direction constraint forces the front
|
||||
# wheel contact point to not move away from the ground frame, essentially
|
||||
# replicating the holonomic constraint which does not allow the frame pitch
|
||||
# to change in an invalid fashion.
|
||||
|
||||
conlist_speed = [WF_cont.vel(N) & Y.x, WF_cont.vel(N) & Y.y, WF_cont.vel(N) & Y.z]
|
||||
|
||||
# The holonomic constraint is that the position from the rear wheel contact
|
||||
# point to the front wheel contact point when dotted into the
|
||||
# normal-to-ground plane direction must be zero; effectively that the front
|
||||
# and rear wheel contact points are always touching the ground plane. This
|
||||
# is actually not part of the dynamic equations, but instead is necessary
|
||||
# for the lineraization process.
|
||||
|
||||
conlist_coord = [WF_cont.pos_from(WR_cont) & Y.z]
|
||||
|
||||
# The force list; each body has the appropriate gravitational force applied
|
||||
# at its mass center.
|
||||
FL = [(Frame_mc, -mframe * g * Y.z),
|
||||
(Fork_mc, -mfork * g * Y.z),
|
||||
(WF_mc, -mwf * g * Y.z),
|
||||
(WR_mc, -mwr * g * Y.z)]
|
||||
BL = [BodyFrame, BodyFork, BodyWR, BodyWF]
|
||||
|
||||
|
||||
# The N frame is the inertial frame, coordinates are supplied in the order
|
||||
# of independent, dependent coordinates, as are the speeds. The kinematic
|
||||
# differential equation are also entered here. Here the dependent speeds
|
||||
# are specified, in the same order they were provided in earlier, along
|
||||
# with the non-holonomic constraints. The dependent coordinate is also
|
||||
# provided, with the holonomic constraint. Again, this is only provided
|
||||
# for the linearization process.
|
||||
|
||||
KM = KanesMethod(N, q_ind=[q1, q2, q5],
|
||||
q_dependent=[q4], configuration_constraints=conlist_coord,
|
||||
u_ind=[u2, u3, u5],
|
||||
u_dependent=[u1, u4, u6], velocity_constraints=conlist_speed,
|
||||
kd_eqs=kd,
|
||||
constraint_solver="CRAMER")
|
||||
(fr, frstar) = KM.kanes_equations(BL, FL)
|
||||
|
||||
# This is the start of entering in the numerical values from the benchmark
|
||||
# paper to validate the eigen values of the linearized equations from this
|
||||
# model to the reference eigen values. Look at the aforementioned paper for
|
||||
# more information. Some of these are intermediate values, used to
|
||||
# transform values from the paper into the coordinate systems used in this
|
||||
# model.
|
||||
PaperRadRear = 0.3
|
||||
PaperRadFront = 0.35
|
||||
HTA = (pi / 2 - pi / 10).evalf()
|
||||
TrailPaper = 0.08
|
||||
rake = (-(TrailPaper*sin(HTA)-(PaperRadFront*cos(HTA)))).evalf()
|
||||
PaperWb = 1.02
|
||||
PaperFrameCgX = 0.3
|
||||
PaperFrameCgZ = 0.9
|
||||
PaperForkCgX = 0.9
|
||||
PaperForkCgZ = 0.7
|
||||
FrameLength = (PaperWb*sin(HTA)-(rake-(PaperRadFront-PaperRadRear)*cos(HTA))).evalf()
|
||||
FrameCGNorm = ((PaperFrameCgZ - PaperRadRear-(PaperFrameCgX/sin(HTA))*cos(HTA))*sin(HTA)).evalf()
|
||||
FrameCGPar = (PaperFrameCgX / sin(HTA) + (PaperFrameCgZ - PaperRadRear - PaperFrameCgX / sin(HTA) * cos(HTA)) * cos(HTA)).evalf()
|
||||
tempa = (PaperForkCgZ - PaperRadFront)
|
||||
tempb = (PaperWb-PaperForkCgX)
|
||||
tempc = (sqrt(tempa**2+tempb**2)).evalf()
|
||||
PaperForkL = (PaperWb*cos(HTA)-(PaperRadFront-PaperRadRear)*sin(HTA)).evalf()
|
||||
ForkCGNorm = (rake+(tempc * sin(pi/2-HTA-acos(tempa/tempc)))).evalf()
|
||||
ForkCGPar = (tempc * cos((pi/2-HTA)-acos(tempa/tempc))-PaperForkL).evalf()
|
||||
|
||||
# Here is the final assembly of the numerical values. The symbol 'v' is the
|
||||
# forward speed of the bicycle (a concept which only makes sense in the
|
||||
# upright, static equilibrium case?). These are in a dictionary which will
|
||||
# later be substituted in. Again the sign on the *product* of inertia
|
||||
# values is flipped here, due to different orientations of coordinate
|
||||
# systems.
|
||||
v = symbols('v')
|
||||
val_dict = {WFrad: PaperRadFront,
|
||||
WRrad: PaperRadRear,
|
||||
htangle: HTA,
|
||||
forkoffset: rake,
|
||||
forklength: PaperForkL,
|
||||
framelength: FrameLength,
|
||||
forkcg1: ForkCGPar,
|
||||
forkcg3: ForkCGNorm,
|
||||
framecg1: FrameCGNorm,
|
||||
framecg3: FrameCGPar,
|
||||
Iwr11: 0.0603,
|
||||
Iwr22: 0.12,
|
||||
Iwf11: 0.1405,
|
||||
Iwf22: 0.28,
|
||||
Ifork11: 0.05892,
|
||||
Ifork22: 0.06,
|
||||
Ifork33: 0.00708,
|
||||
Ifork31: 0.00756,
|
||||
Iframe11: 9.2,
|
||||
Iframe22: 11,
|
||||
Iframe33: 2.8,
|
||||
Iframe31: -2.4,
|
||||
mfork: 4,
|
||||
mframe: 85,
|
||||
mwf: 3,
|
||||
mwr: 2,
|
||||
g: 9.81,
|
||||
q1: 0,
|
||||
q2: 0,
|
||||
q4: 0,
|
||||
q5: 0,
|
||||
u1: 0,
|
||||
u2: 0,
|
||||
u3: v / PaperRadRear,
|
||||
u4: 0,
|
||||
u5: 0,
|
||||
u6: v / PaperRadFront}
|
||||
|
||||
# Linearizes the forcing vector; the equations are set up as MM udot =
|
||||
# forcing, where MM is the mass matrix, udot is the vector representing the
|
||||
# time derivatives of the generalized speeds, and forcing is a vector which
|
||||
# contains both external forcing terms and internal forcing terms, such as
|
||||
# centripital or coriolis forces. This actually returns a matrix with as
|
||||
# many rows as *total* coordinates and speeds, but only as many columns as
|
||||
# independent coordinates and speeds.
|
||||
|
||||
A, B, _ = KM.linearize(
|
||||
A_and_B=True,
|
||||
op_point={
|
||||
# Operating points for the accelerations are required for the
|
||||
# linearizer to eliminate u' terms showing up in the coefficient
|
||||
# matrices.
|
||||
u1.diff(): 0,
|
||||
u2.diff(): 0,
|
||||
u3.diff(): 0,
|
||||
u4.diff(): 0,
|
||||
u5.diff(): 0,
|
||||
u6.diff(): 0,
|
||||
u1: 0,
|
||||
u2: 0,
|
||||
u3: v / PaperRadRear,
|
||||
u4: 0,
|
||||
u5: 0,
|
||||
u6: v / PaperRadFront,
|
||||
q1: 0,
|
||||
q2: 0,
|
||||
q4: 0,
|
||||
q5: 0,
|
||||
},
|
||||
linear_solver="CRAMER",
|
||||
)
|
||||
# As mentioned above, the size of the linearized forcing terms is expanded
|
||||
# to include both q's and u's, so the mass matrix must have this done as
|
||||
# well. This will likely be changed to be part of the linearized process,
|
||||
# for future reference.
|
||||
A_s = A.xreplace(val_dict)
|
||||
B_s = B.xreplace(val_dict)
|
||||
|
||||
A_s = A_s.evalf()
|
||||
B_s = B_s.evalf()
|
||||
|
||||
# Finally, we construct an "A" matrix for the form xdot = A x (x being the
|
||||
# state vector, although in this case, the sizes are a little off). The
|
||||
# following line extracts only the minimum entries required for eigenvalue
|
||||
# analysis, which correspond to rows and columns for lean, steer, lean
|
||||
# rate, and steer rate.
|
||||
A = A_s.extract([1, 2, 3, 5], [1, 2, 3, 5])
|
||||
|
||||
# Precomputed for comparison
|
||||
Res = Matrix([[ 0, 0, 1.0, 0],
|
||||
[ 0, 0, 0, 1.0],
|
||||
[9.48977444677355, -0.891197738059089*v**2 - 0.571523173729245, -0.105522449805691*v, -0.330515398992311*v],
|
||||
[11.7194768719633, -1.97171508499972*v**2 + 30.9087533932407, 3.67680523332152*v, -3.08486552743311*v]])
|
||||
|
||||
# Actual eigenvalue comparison
|
||||
eps = 1.e-12
|
||||
for i in range(6):
|
||||
error = Res.subs(v, i) - A.subs(v, i)
|
||||
assert all(abs(x) < eps for x in error)
|
||||
@@ -0,0 +1,115 @@
|
||||
from sympy import (cos, sin, Matrix, symbols)
|
||||
from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point,
|
||||
KanesMethod, Particle)
|
||||
|
||||
def test_replace_qdots_in_force():
|
||||
# Test PR 16700 "Replaces qdots with us in force-list in kanes.py"
|
||||
# The new functionality allows one to specify forces in qdots which will
|
||||
# automatically be replaced with u:s which are defined by the kde supplied
|
||||
# to KanesMethod. The test case is the double pendulum with interacting
|
||||
# forces in the example of chapter 4.7 "CONTRIBUTING INTERACTION FORCES"
|
||||
# in Ref. [1]. Reference list at end test function.
|
||||
|
||||
q1, q2 = dynamicsymbols('q1, q2')
|
||||
qd1, qd2 = dynamicsymbols('q1, q2', level=1)
|
||||
u1, u2 = dynamicsymbols('u1, u2')
|
||||
|
||||
l, m = symbols('l, m')
|
||||
|
||||
N = ReferenceFrame('N') # Inertial frame
|
||||
A = N.orientnew('A', 'Axis', (q1, N.z)) # Rod A frame
|
||||
B = A.orientnew('B', 'Axis', (q2, N.z)) # Rod B frame
|
||||
|
||||
O = Point('O') # Origo
|
||||
O.set_vel(N, 0)
|
||||
|
||||
P = O.locatenew('P', ( l * A.x )) # Point @ end of rod A
|
||||
P.v2pt_theory(O, N, A)
|
||||
|
||||
Q = P.locatenew('Q', ( l * B.x )) # Point @ end of rod B
|
||||
Q.v2pt_theory(P, N, B)
|
||||
|
||||
Ap = Particle('Ap', P, m)
|
||||
Bp = Particle('Bp', Q, m)
|
||||
|
||||
# The forces are specified below. sigma is the torsional spring stiffness
|
||||
# and delta is the viscous damping coefficient acting between the two
|
||||
# bodies. Here, we specify the viscous damper as function of qdots prior
|
||||
# forming the kde. In more complex systems it not might be obvious which
|
||||
# kde is most efficient, why it is convenient to specify viscous forces in
|
||||
# qdots independently of the kde.
|
||||
sig, delta = symbols('sigma, delta')
|
||||
Ta = (sig * q2 + delta * qd2) * N.z
|
||||
forces = [(A, Ta), (B, -Ta)]
|
||||
|
||||
# Try different kdes.
|
||||
kde1 = [u1 - qd1, u2 - qd2]
|
||||
kde2 = [u1 - qd1, u2 - (qd1 + qd2)]
|
||||
|
||||
KM1 = KanesMethod(N, [q1, q2], [u1, u2], kd_eqs=kde1)
|
||||
fr1, fstar1 = KM1.kanes_equations([Ap, Bp], forces)
|
||||
|
||||
KM2 = KanesMethod(N, [q1, q2], [u1, u2], kd_eqs=kde2)
|
||||
fr2, fstar2 = KM2.kanes_equations([Ap, Bp], forces)
|
||||
|
||||
# Check EOM for KM2:
|
||||
# Mass and force matrix from p.6 in Ref. [2] with added forces from
|
||||
# example of chapter 4.7 in [1] and without gravity.
|
||||
forcing_matrix_expected = Matrix( [ [ m * l**2 * sin(q2) * u2**2 + sig * q2
|
||||
+ delta * (u2 - u1)],
|
||||
[ m * l**2 * sin(q2) * -u1**2 - sig * q2
|
||||
- delta * (u2 - u1)] ] )
|
||||
mass_matrix_expected = Matrix( [ [ 2 * m * l**2, m * l**2 * cos(q2) ],
|
||||
[ m * l**2 * cos(q2), m * l**2 ] ] )
|
||||
|
||||
assert (KM2.mass_matrix.expand() == mass_matrix_expected.expand())
|
||||
assert (KM2.forcing.expand() == forcing_matrix_expected.expand())
|
||||
|
||||
# Check fr1 with reference fr_expected from [1] with u:s instead of qdots.
|
||||
fr1_expected = Matrix([ 0, -(sig*q2 + delta * u2) ])
|
||||
assert fr1.expand() == fr1_expected.expand()
|
||||
|
||||
# Check fr2
|
||||
fr2_expected = Matrix([sig * q2 + delta * (u2 - u1),
|
||||
- sig * q2 - delta * (u2 - u1)])
|
||||
assert fr2.expand() == fr2_expected.expand()
|
||||
|
||||
# Specifying forces in u:s should stay the same:
|
||||
Ta = (sig * q2 + delta * u2) * N.z
|
||||
forces = [(A, Ta), (B, -Ta)]
|
||||
KM1 = KanesMethod(N, [q1, q2], [u1, u2], kd_eqs=kde1)
|
||||
fr1, fstar1 = KM1.kanes_equations([Ap, Bp], forces)
|
||||
|
||||
assert fr1.expand() == fr1_expected.expand()
|
||||
|
||||
Ta = (sig * q2 + delta * (u2-u1)) * N.z
|
||||
forces = [(A, Ta), (B, -Ta)]
|
||||
KM2 = KanesMethod(N, [q1, q2], [u1, u2], kd_eqs=kde2)
|
||||
fr2, fstar2 = KM2.kanes_equations([Ap, Bp], forces)
|
||||
|
||||
assert fr2.expand() == fr2_expected.expand()
|
||||
|
||||
# Test if we have a qubic qdot force:
|
||||
Ta = (sig * q2 + delta * qd2**3) * N.z
|
||||
forces = [(A, Ta), (B, -Ta)]
|
||||
|
||||
KM1 = KanesMethod(N, [q1, q2], [u1, u2], kd_eqs=kde1)
|
||||
fr1, fstar1 = KM1.kanes_equations([Ap, Bp], forces)
|
||||
|
||||
fr1_cubic_expected = Matrix([ 0, -(sig*q2 + delta * u2**3) ])
|
||||
|
||||
assert fr1.expand() == fr1_cubic_expected.expand()
|
||||
|
||||
KM2 = KanesMethod(N, [q1, q2], [u1, u2], kd_eqs=kde2)
|
||||
fr2, fstar2 = KM2.kanes_equations([Ap, Bp], forces)
|
||||
|
||||
fr2_cubic_expected = Matrix([sig * q2 + delta * (u2 - u1)**3,
|
||||
- sig * q2 - delta * (u2 - u1)**3])
|
||||
|
||||
assert fr2.expand() == fr2_cubic_expected.expand()
|
||||
|
||||
# References:
|
||||
# [1] T.R. Kane, D. a Levinson, Dynamics Theory and Applications, 2005.
|
||||
# [2] Arun K Banerjee, Flexible Multibody Dynamics:Efficient Formulations
|
||||
# and Applications, John Wiley and Sons, Ltd, 2016.
|
||||
# doi:http://dx.doi.org/10.1002/9781119015635.
|
||||
@@ -0,0 +1,128 @@
|
||||
from sympy import (zeros, Matrix, symbols, lambdify, sqrt, pi,
|
||||
simplify)
|
||||
from sympy.physics.mechanics import (dynamicsymbols, cross, inertia, RigidBody,
|
||||
ReferenceFrame, KanesMethod)
|
||||
|
||||
|
||||
def _create_rolling_disc():
|
||||
# Define symbols and coordinates
|
||||
t = dynamicsymbols._t
|
||||
q1, q2, q3, q4, q5, u1, u2, u3, u4, u5 = dynamicsymbols('q1:6 u1:6')
|
||||
g, r, m = symbols('g r m')
|
||||
# Define bodies and frames
|
||||
ground = RigidBody('ground')
|
||||
disc = RigidBody('disk', mass=m)
|
||||
disc.inertia = (m * r ** 2 / 4 * inertia(disc.frame, 1, 2, 1),
|
||||
disc.masscenter)
|
||||
ground.masscenter.set_vel(ground.frame, 0)
|
||||
disc.masscenter.set_vel(disc.frame, 0)
|
||||
int_frame = ReferenceFrame('int_frame')
|
||||
# Orient frames
|
||||
int_frame.orient_body_fixed(ground.frame, (q1, q2, 0), 'zxy')
|
||||
disc.frame.orient_axis(int_frame, int_frame.y, q3)
|
||||
g_w_d = disc.frame.ang_vel_in(ground.frame)
|
||||
disc.frame.set_ang_vel(ground.frame,
|
||||
u1 * disc.x + u2 * disc.y + u3 * disc.z)
|
||||
# Define points
|
||||
cp = ground.masscenter.locatenew('contact_point',
|
||||
q4 * ground.x + q5 * ground.y)
|
||||
cp.set_vel(ground.frame, u4 * ground.x + u5 * ground.y)
|
||||
disc.masscenter.set_pos(cp, r * int_frame.z)
|
||||
disc.masscenter.set_vel(ground.frame, cross(
|
||||
disc.frame.ang_vel_in(ground.frame), disc.masscenter.pos_from(cp)))
|
||||
# Define kinematic differential equations
|
||||
kdes = [g_w_d.dot(disc.x) - u1, g_w_d.dot(disc.y) - u2,
|
||||
g_w_d.dot(disc.z) - u3, q4.diff(t) - u4, q5.diff(t) - u5]
|
||||
# Define nonholonomic constraints
|
||||
v0 = cp.vel(ground.frame) + cross(
|
||||
disc.frame.ang_vel_in(int_frame), cp.pos_from(disc.masscenter))
|
||||
fnh = [v0.dot(ground.x), v0.dot(ground.y)]
|
||||
# Define loads
|
||||
loads = [(disc.masscenter, -disc.mass * g * ground.z)]
|
||||
bodies = [disc]
|
||||
return {
|
||||
'frame': ground.frame,
|
||||
'q_ind': [q1, q2, q3, q4, q5],
|
||||
'u_ind': [u1, u2, u3],
|
||||
'u_dep': [u4, u5],
|
||||
'kdes': kdes,
|
||||
'fnh': fnh,
|
||||
'bodies': bodies,
|
||||
'loads': loads
|
||||
}
|
||||
|
||||
|
||||
def _verify_rolling_disc_numerically(kane, all_zero=False):
|
||||
q, u, p = dynamicsymbols('q1:6'), dynamicsymbols('u1:6'), symbols('g r m')
|
||||
eval_sys = lambdify((q, u, p), (kane.mass_matrix_full, kane.forcing_full),
|
||||
cse=True)
|
||||
solve_sys = lambda q, u, p: Matrix.LUsolve(
|
||||
*(Matrix(mat) for mat in eval_sys(q, u, p)))
|
||||
solve_u_dep = lambdify((q, u[:3], p), kane._Ars * Matrix(u[:3]), cse=True)
|
||||
eps = 1e-10
|
||||
p_vals = (9.81, 0.26, 3.43)
|
||||
# First numeric test
|
||||
q_vals = (0.3, 0.1, 1.97, -0.35, 2.27)
|
||||
u_vals = [-0.2, 1.3, 0.15]
|
||||
u_vals.extend(solve_u_dep(q_vals, u_vals, p_vals)[:2, 0])
|
||||
expected = Matrix([
|
||||
0.126603940595934, 0.215942571601660, 1.28736069604936,
|
||||
0.319764288376543, 0.0989146857254898, -0.925848952664489,
|
||||
-0.0181350656532944, 2.91695398184589, -0.00992793421754526,
|
||||
0.0412861634829171])
|
||||
assert all(abs(x) < eps for x in
|
||||
(solve_sys(q_vals, u_vals, p_vals) - expected))
|
||||
# Second numeric test
|
||||
q_vals = (3.97, -0.28, 8.2, -0.35, 2.27)
|
||||
u_vals = [-0.25, -2.2, 0.62]
|
||||
u_vals.extend(solve_u_dep(q_vals, u_vals, p_vals)[:2, 0])
|
||||
expected = Matrix([
|
||||
0.0259159090798597, 0.668041660387416, -2.19283799213811,
|
||||
0.385441810852219, 0.420109283790573, 1.45030568179066,
|
||||
-0.0110924422400793, -8.35617840186040, -0.154098542632173,
|
||||
-0.146102664410010])
|
||||
assert all(abs(x) < eps for x in
|
||||
(solve_sys(q_vals, u_vals, p_vals) - expected))
|
||||
if all_zero:
|
||||
q_vals = (0, 0, 0, 0, 0)
|
||||
u_vals = (0, 0, 0, 0, 0)
|
||||
assert solve_sys(q_vals, u_vals, p_vals) == zeros(10, 1)
|
||||
|
||||
|
||||
def test_kane_rolling_disc_lu():
|
||||
props = _create_rolling_disc()
|
||||
kane = KanesMethod(props['frame'], props['q_ind'], props['u_ind'],
|
||||
props['kdes'], u_dependent=props['u_dep'],
|
||||
velocity_constraints=props['fnh'],
|
||||
bodies=props['bodies'], forcelist=props['loads'],
|
||||
explicit_kinematics=False, constraint_solver='LU')
|
||||
kane.kanes_equations()
|
||||
_verify_rolling_disc_numerically(kane)
|
||||
|
||||
|
||||
def test_kane_rolling_disc_kdes_callable():
|
||||
props = _create_rolling_disc()
|
||||
kane = KanesMethod(
|
||||
props['frame'], props['q_ind'], props['u_ind'], props['kdes'],
|
||||
u_dependent=props['u_dep'], velocity_constraints=props['fnh'],
|
||||
bodies=props['bodies'], forcelist=props['loads'],
|
||||
explicit_kinematics=False,
|
||||
kd_eqs_solver=lambda A, b: simplify(A.LUsolve(b)))
|
||||
q, u, p = dynamicsymbols('q1:6'), dynamicsymbols('u1:6'), symbols('g r m')
|
||||
qd = dynamicsymbols('q1:6', 1)
|
||||
eval_kdes = lambdify((q, qd, u, p), tuple(kane.kindiffdict().items()))
|
||||
eps = 1e-10
|
||||
# Test with only zeros. If 'LU' would be used this would result in nan.
|
||||
p_vals = (9.81, 0.25, 3.5)
|
||||
zero_vals = (0, 0, 0, 0, 0)
|
||||
assert all(abs(qdi - fui) < eps for qdi, fui in
|
||||
eval_kdes(zero_vals, zero_vals, zero_vals, p_vals))
|
||||
# Test with some arbitrary values
|
||||
q_vals = tuple(map(float, (pi / 6, pi / 3, pi / 2, 0.42, 0.62)))
|
||||
qd_vals = tuple(map(float, (4, 1 / 3, 4 - 2 * sqrt(3),
|
||||
0.25 * (2 * sqrt(3) - 3),
|
||||
0.25 * (2 - sqrt(3)))))
|
||||
u_vals = tuple(map(float, (-2, 4, 1 / 3, 0.25 * (-3 + 2 * sqrt(3)),
|
||||
0.25 * (-sqrt(3) + 2))))
|
||||
assert all(abs(qdi - fui) < eps for qdi, fui in
|
||||
eval_kdes(q_vals, qd_vals, u_vals, p_vals))
|
||||
+247
@@ -0,0 +1,247 @@
|
||||
from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point,
|
||||
RigidBody, LagrangesMethod, Particle,
|
||||
inertia, Lagrangian)
|
||||
from sympy.core.function import (Derivative, Function)
|
||||
from sympy.core.numbers import pi
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.functions.elementary.trigonometric import (cos, sin, tan)
|
||||
from sympy.matrices.dense import Matrix
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
|
||||
def test_invalid_coordinates():
|
||||
# Simple pendulum, but use symbol instead of dynamicsymbol
|
||||
l, m, g = symbols('l m g')
|
||||
q = symbols('q') # Generalized coordinate
|
||||
N, O = ReferenceFrame('N'), Point('O')
|
||||
O.set_vel(N, 0)
|
||||
P = Particle('P', Point('P'), m)
|
||||
P.point.set_pos(O, l * (sin(q) * N.x - cos(q) * N.y))
|
||||
P.potential_energy = m * g * P.point.pos_from(O).dot(N.y)
|
||||
L = Lagrangian(N, P)
|
||||
raises(ValueError, lambda: LagrangesMethod(L, [q], bodies=P))
|
||||
|
||||
|
||||
def test_disc_on_an_incline_plane():
|
||||
# Disc rolling on an inclined plane
|
||||
# First the generalized coordinates are created. The mass center of the
|
||||
# disc is located from top vertex of the inclined plane by the generalized
|
||||
# coordinate 'y'. The orientation of the disc is defined by the angle
|
||||
# 'theta'. The mass of the disc is 'm' and its radius is 'R'. The length of
|
||||
# the inclined path is 'l', the angle of inclination is 'alpha'. 'g' is the
|
||||
# gravitational constant.
|
||||
y, theta = dynamicsymbols('y theta')
|
||||
yd, thetad = dynamicsymbols('y theta', 1)
|
||||
m, g, R, l, alpha = symbols('m g R l alpha')
|
||||
|
||||
# Next, we create the inertial reference frame 'N'. A reference frame 'A'
|
||||
# is attached to the inclined plane. Finally a frame is created which is attached to the disk.
|
||||
N = ReferenceFrame('N')
|
||||
A = N.orientnew('A', 'Axis', [pi/2 - alpha, N.z])
|
||||
B = A.orientnew('B', 'Axis', [-theta, A.z])
|
||||
|
||||
# Creating the disc 'D'; we create the point that represents the mass
|
||||
# center of the disc and set its velocity. The inertia dyadic of the disc
|
||||
# is created. Finally, we create the disc.
|
||||
Do = Point('Do')
|
||||
Do.set_vel(N, yd * A.x)
|
||||
I = m * R**2/2 * B.z | B.z
|
||||
D = RigidBody('D', Do, B, m, (I, Do))
|
||||
|
||||
# To construct the Lagrangian, 'L', of the disc, we determine its kinetic
|
||||
# and potential energies, T and U, respectively. L is defined as the
|
||||
# difference between T and U.
|
||||
D.potential_energy = m * g * (l - y) * sin(alpha)
|
||||
L = Lagrangian(N, D)
|
||||
|
||||
# We then create the list of generalized coordinates and constraint
|
||||
# equations. The constraint arises due to the disc rolling without slip on
|
||||
# on the inclined path. We then invoke the 'LagrangesMethod' class and
|
||||
# supply it the necessary arguments and generate the equations of motion.
|
||||
# The'rhs' method solves for the q_double_dots (i.e. the second derivative
|
||||
# with respect to time of the generalized coordinates and the lagrange
|
||||
# multipliers.
|
||||
q = [y, theta]
|
||||
hol_coneqs = [y - R * theta]
|
||||
m = LagrangesMethod(L, q, hol_coneqs=hol_coneqs)
|
||||
m.form_lagranges_equations()
|
||||
rhs = m.rhs()
|
||||
rhs.simplify()
|
||||
assert rhs[2] == 2*g*sin(alpha)/3
|
||||
|
||||
|
||||
def test_simp_pen():
|
||||
# This tests that the equations generated by LagrangesMethod are identical
|
||||
# to those obtained by hand calculations. The system under consideration is
|
||||
# the simple pendulum.
|
||||
# We begin by creating the generalized coordinates as per the requirements
|
||||
# of LagrangesMethod. Also we created the associate symbols
|
||||
# that characterize the system: 'm' is the mass of the bob, l is the length
|
||||
# of the massless rigid rod connecting the bob to a point O fixed in the
|
||||
# inertial frame.
|
||||
q, u = dynamicsymbols('q u')
|
||||
qd, ud = dynamicsymbols('q u ', 1)
|
||||
l, m, g = symbols('l m g')
|
||||
|
||||
# We then create the inertial frame and a frame attached to the massless
|
||||
# string following which we define the inertial angular velocity of the
|
||||
# string.
|
||||
N = ReferenceFrame('N')
|
||||
A = N.orientnew('A', 'Axis', [q, N.z])
|
||||
A.set_ang_vel(N, qd * N.z)
|
||||
|
||||
# Next, we create the point O and fix it in the inertial frame. We then
|
||||
# locate the point P to which the bob is attached. Its corresponding
|
||||
# velocity is then determined by the 'two point formula'.
|
||||
O = Point('O')
|
||||
O.set_vel(N, 0)
|
||||
P = O.locatenew('P', l * A.x)
|
||||
P.v2pt_theory(O, N, A)
|
||||
|
||||
# The 'Particle' which represents the bob is then created and its
|
||||
# Lagrangian generated.
|
||||
Pa = Particle('Pa', P, m)
|
||||
Pa.potential_energy = - m * g * l * cos(q)
|
||||
L = Lagrangian(N, Pa)
|
||||
|
||||
# The 'LagrangesMethod' class is invoked to obtain equations of motion.
|
||||
lm = LagrangesMethod(L, [q])
|
||||
lm.form_lagranges_equations()
|
||||
RHS = lm.rhs()
|
||||
assert RHS[1] == -g*sin(q)/l
|
||||
|
||||
|
||||
def test_nonminimal_pendulum():
|
||||
q1, q2 = dynamicsymbols('q1:3')
|
||||
q1d, q2d = dynamicsymbols('q1:3', level=1)
|
||||
L, m, t = symbols('L, m, t')
|
||||
g = 9.8
|
||||
# Compose World Frame
|
||||
N = ReferenceFrame('N')
|
||||
pN = Point('N*')
|
||||
pN.set_vel(N, 0)
|
||||
# Create point P, the pendulum mass
|
||||
P = pN.locatenew('P1', q1*N.x + q2*N.y)
|
||||
P.set_vel(N, P.pos_from(pN).dt(N))
|
||||
pP = Particle('pP', P, m)
|
||||
# Constraint Equations
|
||||
f_c = Matrix([q1**2 + q2**2 - L**2])
|
||||
# Calculate the lagrangian, and form the equations of motion
|
||||
Lag = Lagrangian(N, pP)
|
||||
LM = LagrangesMethod(Lag, [q1, q2], hol_coneqs=f_c,
|
||||
forcelist=[(P, m*g*N.x)], frame=N)
|
||||
LM.form_lagranges_equations()
|
||||
# Check solution
|
||||
lam1 = LM.lam_vec[0, 0]
|
||||
eom_sol = Matrix([[m*Derivative(q1, t, t) - 9.8*m + 2*lam1*q1],
|
||||
[m*Derivative(q2, t, t) + 2*lam1*q2]])
|
||||
assert LM.eom == eom_sol
|
||||
# Check multiplier solution
|
||||
lam_sol = Matrix([(19.6*q1 + 2*q1d**2 + 2*q2d**2)/(4*q1**2/m + 4*q2**2/m)])
|
||||
assert simplify(LM.solve_multipliers(sol_type='Matrix')) == simplify(lam_sol)
|
||||
|
||||
|
||||
def test_dub_pen():
|
||||
|
||||
# The system considered is the double pendulum. Like in the
|
||||
# test of the simple pendulum above, we begin by creating the generalized
|
||||
# coordinates and the simple generalized speeds and accelerations which
|
||||
# will be used later. Following this we create frames and points necessary
|
||||
# for the kinematics. The procedure isn't explicitly explained as this is
|
||||
# similar to the simple pendulum. Also this is documented on the pydy.org
|
||||
# website.
|
||||
q1, q2 = dynamicsymbols('q1 q2')
|
||||
q1d, q2d = dynamicsymbols('q1 q2', 1)
|
||||
q1dd, q2dd = dynamicsymbols('q1 q2', 2)
|
||||
u1, u2 = dynamicsymbols('u1 u2')
|
||||
u1d, u2d = dynamicsymbols('u1 u2', 1)
|
||||
l, m, g = symbols('l m g')
|
||||
|
||||
N = ReferenceFrame('N')
|
||||
A = N.orientnew('A', 'Axis', [q1, N.z])
|
||||
B = N.orientnew('B', 'Axis', [q2, N.z])
|
||||
|
||||
A.set_ang_vel(N, q1d * A.z)
|
||||
B.set_ang_vel(N, q2d * A.z)
|
||||
|
||||
O = Point('O')
|
||||
P = O.locatenew('P', l * A.x)
|
||||
R = P.locatenew('R', l * B.x)
|
||||
|
||||
O.set_vel(N, 0)
|
||||
P.v2pt_theory(O, N, A)
|
||||
R.v2pt_theory(P, N, B)
|
||||
|
||||
ParP = Particle('ParP', P, m)
|
||||
ParR = Particle('ParR', R, m)
|
||||
|
||||
ParP.potential_energy = - m * g * l * cos(q1)
|
||||
ParR.potential_energy = - m * g * l * cos(q1) - m * g * l * cos(q2)
|
||||
L = Lagrangian(N, ParP, ParR)
|
||||
lm = LagrangesMethod(L, [q1, q2], bodies=[ParP, ParR])
|
||||
lm.form_lagranges_equations()
|
||||
|
||||
assert simplify(l*m*(2*g*sin(q1) + l*sin(q1)*sin(q2)*q2dd
|
||||
+ l*sin(q1)*cos(q2)*q2d**2 - l*sin(q2)*cos(q1)*q2d**2
|
||||
+ l*cos(q1)*cos(q2)*q2dd + 2*l*q1dd) - lm.eom[0]) == 0
|
||||
assert simplify(l*m*(g*sin(q2) + l*sin(q1)*sin(q2)*q1dd
|
||||
- l*sin(q1)*cos(q2)*q1d**2 + l*sin(q2)*cos(q1)*q1d**2
|
||||
+ l*cos(q1)*cos(q2)*q1dd + l*q2dd) - lm.eom[1]) == 0
|
||||
assert lm.bodies == [ParP, ParR]
|
||||
|
||||
|
||||
def test_rolling_disc():
|
||||
# Rolling Disc Example
|
||||
# Here the rolling disc is formed from the contact point up, removing the
|
||||
# need to introduce generalized speeds. Only 3 configuration and 3
|
||||
# speed variables are need to describe this system, along with the
|
||||
# disc's mass and radius, and the local gravity.
|
||||
q1, q2, q3 = dynamicsymbols('q1 q2 q3')
|
||||
q1d, q2d, q3d = dynamicsymbols('q1 q2 q3', 1)
|
||||
r, m, g = symbols('r m g')
|
||||
|
||||
# The kinematics are formed by a series of simple rotations. Each simple
|
||||
# rotation creates a new frame, and the next rotation is defined by the new
|
||||
# frame's basis vectors. This example uses a 3-1-2 series of rotations, or
|
||||
# Z, X, Y series of rotations. Angular velocity for this is defined using
|
||||
# the second frame's basis (the lean frame).
|
||||
N = ReferenceFrame('N')
|
||||
Y = N.orientnew('Y', 'Axis', [q1, N.z])
|
||||
L = Y.orientnew('L', 'Axis', [q2, Y.x])
|
||||
R = L.orientnew('R', 'Axis', [q3, L.y])
|
||||
|
||||
# This is the translational kinematics. We create a point with no velocity
|
||||
# in N; this is the contact point between the disc and ground. Next we form
|
||||
# the position vector from the contact point to the disc's center of mass.
|
||||
# Finally we form the velocity and acceleration of the disc.
|
||||
C = Point('C')
|
||||
C.set_vel(N, 0)
|
||||
Dmc = C.locatenew('Dmc', r * L.z)
|
||||
Dmc.v2pt_theory(C, N, R)
|
||||
|
||||
# Forming the inertia dyadic.
|
||||
I = inertia(L, m/4 * r**2, m/2 * r**2, m/4 * r**2)
|
||||
BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
|
||||
|
||||
# Finally we form the equations of motion, using the same steps we did
|
||||
# before. Supply the Lagrangian, the generalized speeds.
|
||||
BodyD.potential_energy = - m * g * r * cos(q2)
|
||||
Lag = Lagrangian(N, BodyD)
|
||||
q = [q1, q2, q3]
|
||||
q1 = Function('q1')
|
||||
q2 = Function('q2')
|
||||
q3 = Function('q3')
|
||||
l = LagrangesMethod(Lag, q)
|
||||
l.form_lagranges_equations()
|
||||
RHS = l.rhs()
|
||||
RHS.simplify()
|
||||
t = symbols('t')
|
||||
|
||||
assert (l.mass_matrix[3:6] == [0, 5*m*r**2/4, 0])
|
||||
assert RHS[4].simplify() == (
|
||||
(-8*g*sin(q2(t)) + r*(5*sin(2*q2(t))*Derivative(q1(t), t) +
|
||||
12*cos(q2(t))*Derivative(q3(t), t))*Derivative(q1(t), t))/(10*r))
|
||||
assert RHS[5] == (-5*cos(q2(t))*Derivative(q1(t), t) + 6*tan(q2(t)
|
||||
)*Derivative(q3(t), t) + 4*Derivative(q1(t), t)/cos(q2(t))
|
||||
)*Derivative(q2(t), t)
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
from sympy import symbols
|
||||
from sympy.physics.mechanics import dynamicsymbols
|
||||
from sympy.physics.mechanics import ReferenceFrame, Point, Particle
|
||||
from sympy.physics.mechanics import LagrangesMethod, Lagrangian
|
||||
|
||||
### This test asserts that a system with more than one external forces
|
||||
### is accurately formed with Lagrange method (see issue #8626)
|
||||
|
||||
def test_lagrange_2forces():
|
||||
### Equations for two damped springs in series with two forces
|
||||
|
||||
### generalized coordinates
|
||||
q1, q2 = dynamicsymbols('q1, q2')
|
||||
### generalized speeds
|
||||
q1d, q2d = dynamicsymbols('q1, q2', 1)
|
||||
|
||||
### Mass, spring strength, friction coefficient
|
||||
m, k, nu = symbols('m, k, nu')
|
||||
|
||||
N = ReferenceFrame('N')
|
||||
O = Point('O')
|
||||
|
||||
### Two points
|
||||
P1 = O.locatenew('P1', q1 * N.x)
|
||||
P1.set_vel(N, q1d * N.x)
|
||||
P2 = O.locatenew('P1', q2 * N.x)
|
||||
P2.set_vel(N, q2d * N.x)
|
||||
|
||||
pP1 = Particle('pP1', P1, m)
|
||||
pP1.potential_energy = k * q1**2 / 2
|
||||
|
||||
pP2 = Particle('pP2', P2, m)
|
||||
pP2.potential_energy = k * (q1 - q2)**2 / 2
|
||||
|
||||
#### Friction forces
|
||||
forcelist = [(P1, - nu * q1d * N.x),
|
||||
(P2, - nu * q2d * N.x)]
|
||||
lag = Lagrangian(N, pP1, pP2)
|
||||
|
||||
l_method = LagrangesMethod(lag, (q1, q2), forcelist=forcelist, frame=N)
|
||||
l_method.form_lagranges_equations()
|
||||
|
||||
eq1 = l_method.eom[0]
|
||||
assert eq1.diff(q1d) == nu
|
||||
eq2 = l_method.eom[1]
|
||||
assert eq2.diff(q2d) == nu
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
from sympy import symbols, sin, cos
|
||||
from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point,
|
||||
KanesMethod)
|
||||
from sympy.testing import pytest
|
||||
from sympy.solvers.solveset import NonlinearError
|
||||
|
||||
def test_linearity_of_motion_constraints():
|
||||
# Test that an error is raised by KanesMethod if nonlinear velocity
|
||||
# constraints are supplied.
|
||||
# It is a simple pendulum.
|
||||
t = dynamicsymbols._t
|
||||
N, A = ReferenceFrame('N'), ReferenceFrame('A')
|
||||
O, P = Point('O'), Point('P')
|
||||
O.set_vel(N, 0)
|
||||
|
||||
l = symbols('l')
|
||||
q, x, y, u, ux, uy = dynamicsymbols('q x y u ux uy')
|
||||
|
||||
A.orient_axis(N, q, N.z)
|
||||
A.set_ang_vel(N, u * N.z)
|
||||
P.set_pos(O, -l * A.y)
|
||||
P.v2pt_theory(O, N, A)
|
||||
|
||||
kd = [u - q.diff(t), ux - x.diff(t), uy - y.diff(t)]
|
||||
config_constr = [x - l * sin(q), y - l * cos(q)]
|
||||
|
||||
q_ind = [q]
|
||||
q_dep = [x, y]
|
||||
u_ind = [u]
|
||||
u_dep = [ux, uy]
|
||||
|
||||
# Make sure an error is raised if nonlinear velocity constraints are
|
||||
# supplied.
|
||||
speed_constr = [ux - l * q.diff(t) * cos(q), sin(uy) +
|
||||
l * q.diff(t) * sin(q)]
|
||||
|
||||
with pytest.raises(NonlinearError):
|
||||
KanesMethod(N, q_ind=q_ind, q_dependent=q_dep, u_ind=u_ind,
|
||||
u_dependent=u_dep, kd_eqs=kd,
|
||||
configuration_constraints=config_constr,
|
||||
velocity_constraints=speed_constr)
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
from sympy import symbols, Matrix, cos, sin, atan, sqrt, Rational
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.solvers.solvers import solve
|
||||
from sympy.physics.mechanics import dynamicsymbols, ReferenceFrame, Point,\
|
||||
dot, cross, inertia, KanesMethod, Particle, RigidBody, Lagrangian,\
|
||||
LagrangesMethod
|
||||
from sympy.testing.pytest import slow
|
||||
|
||||
|
||||
@slow
|
||||
def test_linearize_rolling_disc_kane():
|
||||
# Symbols for time and constant parameters
|
||||
t, r, m, g, v = symbols('t r m g v')
|
||||
|
||||
# Configuration variables and their time derivatives
|
||||
q1, q2, q3, q4, q5, q6 = q = dynamicsymbols('q1:7')
|
||||
q1d, q2d, q3d, q4d, q5d, q6d = qd = [qi.diff(t) for qi in q]
|
||||
|
||||
# Generalized speeds and their time derivatives
|
||||
u = dynamicsymbols('u:6')
|
||||
u1, u2, u3, u4, u5, u6 = u = dynamicsymbols('u1:7')
|
||||
u1d, u2d, u3d, u4d, u5d, u6d = [ui.diff(t) for ui in u]
|
||||
|
||||
# Reference frames
|
||||
N = ReferenceFrame('N') # Inertial frame
|
||||
NO = Point('NO') # Inertial origin
|
||||
A = N.orientnew('A', 'Axis', [q1, N.z]) # Yaw intermediate frame
|
||||
B = A.orientnew('B', 'Axis', [q2, A.x]) # Lean intermediate frame
|
||||
C = B.orientnew('C', 'Axis', [q3, B.y]) # Disc fixed frame
|
||||
CO = NO.locatenew('CO', q4*N.x + q5*N.y + q6*N.z) # Disc center
|
||||
|
||||
# Disc angular velocity in N expressed using time derivatives of coordinates
|
||||
w_c_n_qd = C.ang_vel_in(N)
|
||||
w_b_n_qd = B.ang_vel_in(N)
|
||||
|
||||
# Inertial angular velocity and angular acceleration of disc fixed frame
|
||||
C.set_ang_vel(N, u1*B.x + u2*B.y + u3*B.z)
|
||||
|
||||
# Disc center velocity in N expressed using time derivatives of coordinates
|
||||
v_co_n_qd = CO.pos_from(NO).dt(N)
|
||||
|
||||
# Disc center velocity in N expressed using generalized speeds
|
||||
CO.set_vel(N, u4*C.x + u5*C.y + u6*C.z)
|
||||
|
||||
# Disc Ground Contact Point
|
||||
P = CO.locatenew('P', r*B.z)
|
||||
P.v2pt_theory(CO, N, C)
|
||||
|
||||
# Configuration constraint
|
||||
f_c = Matrix([q6 - dot(CO.pos_from(P), N.z)])
|
||||
|
||||
# Velocity level constraints
|
||||
f_v = Matrix([dot(P.vel(N), uv) for uv in C])
|
||||
|
||||
# Kinematic differential equations
|
||||
kindiffs = Matrix([dot(w_c_n_qd - C.ang_vel_in(N), uv) for uv in B] +
|
||||
[dot(v_co_n_qd - CO.vel(N), uv) for uv in N])
|
||||
qdots = solve(kindiffs, qd)
|
||||
|
||||
# Set angular velocity of remaining frames
|
||||
B.set_ang_vel(N, w_b_n_qd.subs(qdots))
|
||||
C.set_ang_acc(N, C.ang_vel_in(N).dt(B) + cross(B.ang_vel_in(N), C.ang_vel_in(N)))
|
||||
|
||||
# Active forces
|
||||
F_CO = m*g*A.z
|
||||
|
||||
# Create inertia dyadic of disc C about point CO
|
||||
I = (m * r**2) / 4
|
||||
J = (m * r**2) / 2
|
||||
I_C_CO = inertia(C, I, J, I)
|
||||
|
||||
Disc = RigidBody('Disc', CO, C, m, (I_C_CO, CO))
|
||||
BL = [Disc]
|
||||
FL = [(CO, F_CO)]
|
||||
KM = KanesMethod(N, [q1, q2, q3, q4, q5], [u1, u2, u3], kd_eqs=kindiffs,
|
||||
q_dependent=[q6], configuration_constraints=f_c,
|
||||
u_dependent=[u4, u5, u6], velocity_constraints=f_v)
|
||||
(fr, fr_star) = KM.kanes_equations(BL, FL)
|
||||
|
||||
# Test generalized form equations
|
||||
linearizer = KM.to_linearizer()
|
||||
assert linearizer.f_c == f_c
|
||||
assert linearizer.f_v == f_v
|
||||
assert linearizer.f_a == f_v.diff(t).subs(KM.kindiffdict())
|
||||
sol = solve(linearizer.f_0 + linearizer.f_1, qd)
|
||||
for qi in qdots.keys():
|
||||
assert sol[qi] == qdots[qi]
|
||||
assert simplify(linearizer.f_2 + linearizer.f_3 - fr - fr_star) == Matrix([0, 0, 0])
|
||||
|
||||
# Perform the linearization
|
||||
# Precomputed operating point
|
||||
q_op = {q6: -r*cos(q2)}
|
||||
u_op = {u1: 0,
|
||||
u2: sin(q2)*q1d + q3d,
|
||||
u3: cos(q2)*q1d,
|
||||
u4: -r*(sin(q2)*q1d + q3d)*cos(q3),
|
||||
u5: 0,
|
||||
u6: -r*(sin(q2)*q1d + q3d)*sin(q3)}
|
||||
qd_op = {q2d: 0,
|
||||
q4d: -r*(sin(q2)*q1d + q3d)*cos(q1),
|
||||
q5d: -r*(sin(q2)*q1d + q3d)*sin(q1),
|
||||
q6d: 0}
|
||||
ud_op = {u1d: 4*g*sin(q2)/(5*r) + sin(2*q2)*q1d**2/2 + 6*cos(q2)*q1d*q3d/5,
|
||||
u2d: 0,
|
||||
u3d: 0,
|
||||
u4d: r*(sin(q2)*sin(q3)*q1d*q3d + sin(q3)*q3d**2),
|
||||
u5d: r*(4*g*sin(q2)/(5*r) + sin(2*q2)*q1d**2/2 + 6*cos(q2)*q1d*q3d/5),
|
||||
u6d: -r*(sin(q2)*cos(q3)*q1d*q3d + cos(q3)*q3d**2)}
|
||||
|
||||
A, B = linearizer.linearize(op_point=[q_op, u_op, qd_op, ud_op], A_and_B=True, simplify=True)
|
||||
|
||||
upright_nominal = {q1d: 0, q2: 0, m: 1, r: 1, g: 1}
|
||||
|
||||
# Precomputed solution
|
||||
A_sol = Matrix([[0, 0, 0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0, 1, 0],
|
||||
[sin(q1)*q3d, 0, 0, 0, 0, -sin(q1), -cos(q1), 0],
|
||||
[-cos(q1)*q3d, 0, 0, 0, 0, cos(q1), -sin(q1), 0],
|
||||
[0, Rational(4, 5), 0, 0, 0, 0, 0, 6*q3d/5],
|
||||
[0, 0, 0, 0, 0, 0, 0, 0],
|
||||
[0, 0, 0, 0, 0, -2*q3d, 0, 0]])
|
||||
B_sol = Matrix([])
|
||||
|
||||
# Check that linearization is correct
|
||||
assert A.subs(upright_nominal) == A_sol
|
||||
assert B.subs(upright_nominal) == B_sol
|
||||
|
||||
# Check eigenvalues at critical speed are all zero:
|
||||
assert sympify(A.subs(upright_nominal).subs(q3d, 1/sqrt(3))).eigenvals() == {0: 8}
|
||||
|
||||
# Check whether alternative solvers work
|
||||
# symengine doesn't support method='GJ'
|
||||
linearizer = KM.to_linearizer(linear_solver='GJ')
|
||||
A, B = linearizer.linearize(op_point=[q_op, u_op, qd_op, ud_op],
|
||||
A_and_B=True, simplify=True)
|
||||
assert A.subs(upright_nominal) == A_sol
|
||||
assert B.subs(upright_nominal) == B_sol
|
||||
|
||||
def test_linearize_pendulum_kane_minimal():
|
||||
q1 = dynamicsymbols('q1') # angle of pendulum
|
||||
u1 = dynamicsymbols('u1') # Angular velocity
|
||||
q1d = dynamicsymbols('q1', 1) # Angular velocity
|
||||
L, m, t = symbols('L, m, t')
|
||||
g = 9.8
|
||||
|
||||
# Compose world frame
|
||||
N = ReferenceFrame('N')
|
||||
pN = Point('N*')
|
||||
pN.set_vel(N, 0)
|
||||
|
||||
# A.x is along the pendulum
|
||||
A = N.orientnew('A', 'axis', [q1, N.z])
|
||||
A.set_ang_vel(N, u1*N.z)
|
||||
|
||||
# Locate point P relative to the origin N*
|
||||
P = pN.locatenew('P', L*A.x)
|
||||
P.v2pt_theory(pN, N, A)
|
||||
pP = Particle('pP', P, m)
|
||||
|
||||
# Create Kinematic Differential Equations
|
||||
kde = Matrix([q1d - u1])
|
||||
|
||||
# Input the force resultant at P
|
||||
R = m*g*N.x
|
||||
|
||||
# Solve for eom with kanes method
|
||||
KM = KanesMethod(N, q_ind=[q1], u_ind=[u1], kd_eqs=kde)
|
||||
(fr, frstar) = KM.kanes_equations([pP], [(P, R)])
|
||||
|
||||
# Linearize
|
||||
A, B, inp_vec = KM.linearize(A_and_B=True, simplify=True)
|
||||
|
||||
assert A == Matrix([[0, 1], [-9.8*cos(q1)/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
def test_linearize_pendulum_kane_nonminimal():
|
||||
# Create generalized coordinates and speeds for this non-minimal realization
|
||||
# q1, q2 = N.x and N.y coordinates of pendulum
|
||||
# u1, u2 = N.x and N.y velocities of pendulum
|
||||
q1, q2 = dynamicsymbols('q1:3')
|
||||
q1d, q2d = dynamicsymbols('q1:3', level=1)
|
||||
u1, u2 = dynamicsymbols('u1:3')
|
||||
u1d, u2d = dynamicsymbols('u1:3', level=1)
|
||||
L, m, t = symbols('L, m, t')
|
||||
g = 9.8
|
||||
|
||||
# Compose world frame
|
||||
N = ReferenceFrame('N')
|
||||
pN = Point('N*')
|
||||
pN.set_vel(N, 0)
|
||||
|
||||
# A.x is along the pendulum
|
||||
theta1 = atan(q2/q1)
|
||||
A = N.orientnew('A', 'axis', [theta1, N.z])
|
||||
|
||||
# Locate the pendulum mass
|
||||
P = pN.locatenew('P1', q1*N.x + q2*N.y)
|
||||
pP = Particle('pP', P, m)
|
||||
|
||||
# Calculate the kinematic differential equations
|
||||
kde = Matrix([q1d - u1,
|
||||
q2d - u2])
|
||||
dq_dict = solve(kde, [q1d, q2d])
|
||||
|
||||
# Set velocity of point P
|
||||
P.set_vel(N, P.pos_from(pN).dt(N).subs(dq_dict))
|
||||
|
||||
# Configuration constraint is length of pendulum
|
||||
f_c = Matrix([P.pos_from(pN).magnitude() - L])
|
||||
|
||||
# Velocity constraint is that the velocity in the A.x direction is
|
||||
# always zero (the pendulum is never getting longer).
|
||||
f_v = Matrix([P.vel(N).express(A).dot(A.x)])
|
||||
f_v.simplify()
|
||||
|
||||
# Acceleration constraints is the time derivative of the velocity constraint
|
||||
f_a = f_v.diff(t)
|
||||
f_a.simplify()
|
||||
|
||||
# Input the force resultant at P
|
||||
R = m*g*N.x
|
||||
|
||||
# Derive the equations of motion using the KanesMethod class.
|
||||
KM = KanesMethod(N, q_ind=[q2], u_ind=[u2], q_dependent=[q1],
|
||||
u_dependent=[u1], configuration_constraints=f_c,
|
||||
velocity_constraints=f_v, acceleration_constraints=f_a, kd_eqs=kde)
|
||||
(fr, frstar) = KM.kanes_equations([pP], [(P, R)])
|
||||
|
||||
# Set the operating point to be straight down, and non-moving
|
||||
q_op = {q1: L, q2: 0}
|
||||
u_op = {u1: 0, u2: 0}
|
||||
ud_op = {u1d: 0, u2d: 0}
|
||||
|
||||
A, B, inp_vec = KM.linearize(op_point=[q_op, u_op, ud_op], A_and_B=True,
|
||||
simplify=True)
|
||||
|
||||
assert A.expand() == Matrix([[0, 1], [-9.8/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
|
||||
# symengine doesn't support method='GJ'
|
||||
A, B, inp_vec = KM.linearize(op_point=[q_op, u_op, ud_op], A_and_B=True,
|
||||
simplify=True, linear_solver='GJ')
|
||||
|
||||
assert A.expand() == Matrix([[0, 1], [-9.8/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
A, B, inp_vec = KM.linearize(op_point=[q_op, u_op, ud_op],
|
||||
A_and_B=True,
|
||||
simplify=True,
|
||||
linear_solver=lambda A, b: A.LUsolve(b))
|
||||
|
||||
assert A.expand() == Matrix([[0, 1], [-9.8/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
|
||||
def test_linearize_pendulum_lagrange_minimal():
|
||||
q1 = dynamicsymbols('q1') # angle of pendulum
|
||||
q1d = dynamicsymbols('q1', 1) # Angular velocity
|
||||
L, m, t = symbols('L, m, t')
|
||||
g = 9.8
|
||||
|
||||
# Compose world frame
|
||||
N = ReferenceFrame('N')
|
||||
pN = Point('N*')
|
||||
pN.set_vel(N, 0)
|
||||
|
||||
# A.x is along the pendulum
|
||||
A = N.orientnew('A', 'axis', [q1, N.z])
|
||||
A.set_ang_vel(N, q1d*N.z)
|
||||
|
||||
# Locate point P relative to the origin N*
|
||||
P = pN.locatenew('P', L*A.x)
|
||||
P.v2pt_theory(pN, N, A)
|
||||
pP = Particle('pP', P, m)
|
||||
|
||||
# Solve for eom with Lagranges method
|
||||
Lag = Lagrangian(N, pP)
|
||||
LM = LagrangesMethod(Lag, [q1], forcelist=[(P, m*g*N.x)], frame=N)
|
||||
LM.form_lagranges_equations()
|
||||
|
||||
# Linearize
|
||||
A, B, inp_vec = LM.linearize([q1], [q1d], A_and_B=True)
|
||||
|
||||
assert simplify(A) == Matrix([[0, 1], [-9.8*cos(q1)/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
# Check an alternative solver
|
||||
A, B, inp_vec = LM.linearize([q1], [q1d], A_and_B=True, linear_solver='GJ')
|
||||
|
||||
assert simplify(A) == Matrix([[0, 1], [-9.8*cos(q1)/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
|
||||
def test_linearize_pendulum_lagrange_nonminimal():
|
||||
q1, q2 = dynamicsymbols('q1:3')
|
||||
q1d, q2d = dynamicsymbols('q1:3', level=1)
|
||||
L, m, t = symbols('L, m, t')
|
||||
g = 9.8
|
||||
# Compose World Frame
|
||||
N = ReferenceFrame('N')
|
||||
pN = Point('N*')
|
||||
pN.set_vel(N, 0)
|
||||
# A.x is along the pendulum
|
||||
theta1 = atan(q2/q1)
|
||||
A = N.orientnew('A', 'axis', [theta1, N.z])
|
||||
# Create point P, the pendulum mass
|
||||
P = pN.locatenew('P1', q1*N.x + q2*N.y)
|
||||
P.set_vel(N, P.pos_from(pN).dt(N))
|
||||
pP = Particle('pP', P, m)
|
||||
# Constraint Equations
|
||||
f_c = Matrix([q1**2 + q2**2 - L**2])
|
||||
# Calculate the lagrangian, and form the equations of motion
|
||||
Lag = Lagrangian(N, pP)
|
||||
LM = LagrangesMethod(Lag, [q1, q2], hol_coneqs=f_c, forcelist=[(P, m*g*N.x)], frame=N)
|
||||
LM.form_lagranges_equations()
|
||||
# Compose operating point
|
||||
op_point = {q1: L, q2: 0, q1d: 0, q2d: 0, q1d.diff(t): 0, q2d.diff(t): 0}
|
||||
# Solve for multiplier operating point
|
||||
lam_op = LM.solve_multipliers(op_point=op_point)
|
||||
op_point.update(lam_op)
|
||||
# Perform the Linearization
|
||||
A, B, inp_vec = LM.linearize([q2], [q2d], [q1], [q1d],
|
||||
op_point=op_point, A_and_B=True)
|
||||
assert simplify(A) == Matrix([[0, 1], [-9.8/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
# Check if passing a function to linear_solver works
|
||||
A, B, inp_vec = LM.linearize([q2], [q2d], [q1], [q1d], op_point=op_point,
|
||||
A_and_B=True, linear_solver=lambda A, b:
|
||||
A.LUsolve(b))
|
||||
assert simplify(A) == Matrix([[0, 1], [-9.8/L, 0]])
|
||||
assert B == Matrix([])
|
||||
|
||||
def test_linearize_rolling_disc_lagrange():
|
||||
q1, q2, q3 = q = dynamicsymbols('q1 q2 q3')
|
||||
q1d, q2d, q3d = qd = dynamicsymbols('q1 q2 q3', 1)
|
||||
r, m, g = symbols('r m g')
|
||||
|
||||
N = ReferenceFrame('N')
|
||||
Y = N.orientnew('Y', 'Axis', [q1, N.z])
|
||||
L = Y.orientnew('L', 'Axis', [q2, Y.x])
|
||||
R = L.orientnew('R', 'Axis', [q3, L.y])
|
||||
|
||||
C = Point('C')
|
||||
C.set_vel(N, 0)
|
||||
Dmc = C.locatenew('Dmc', r * L.z)
|
||||
Dmc.v2pt_theory(C, N, R)
|
||||
|
||||
I = inertia(L, m / 4 * r**2, m / 2 * r**2, m / 4 * r**2)
|
||||
BodyD = RigidBody('BodyD', Dmc, R, m, (I, Dmc))
|
||||
BodyD.potential_energy = - m * g * r * cos(q2)
|
||||
|
||||
Lag = Lagrangian(N, BodyD)
|
||||
l = LagrangesMethod(Lag, q)
|
||||
l.form_lagranges_equations()
|
||||
|
||||
# Linearize about steady-state upright rolling
|
||||
op_point = {q1: 0, q2: 0, q3: 0,
|
||||
q1d: 0, q2d: 0,
|
||||
q1d.diff(): 0, q2d.diff(): 0, q3d.diff(): 0}
|
||||
A = l.linearize(q_ind=q, qd_ind=qd, op_point=op_point, A_and_B=True)[0]
|
||||
sol = Matrix([[0, 0, 0, 1, 0, 0],
|
||||
[0, 0, 0, 0, 1, 0],
|
||||
[0, 0, 0, 0, 0, 1],
|
||||
[0, 0, 0, 0, -6*q3d, 0],
|
||||
[0, -4*g/(5*r), 0, 6*q3d/5, 0, 0],
|
||||
[0, 0, 0, 0, 0, 0]])
|
||||
|
||||
assert A == sol
|
||||
@@ -0,0 +1,86 @@
|
||||
from pytest import raises
|
||||
|
||||
from sympy import symbols
|
||||
from sympy.physics.mechanics import (RigidBody, Particle, ReferenceFrame, Point,
|
||||
outer, dynamicsymbols, Force, Torque)
|
||||
from sympy.physics.mechanics.loads import gravity, _parse_load
|
||||
|
||||
|
||||
def test_force_default():
|
||||
N = ReferenceFrame('N')
|
||||
Po = Point('Po')
|
||||
f1 = Force(Po, N.x)
|
||||
assert f1.point == Po
|
||||
assert f1.force == N.x
|
||||
assert f1.__repr__() == 'Force(point=Po, force=N.x)'
|
||||
# Test tuple behaviour
|
||||
assert isinstance(f1, tuple)
|
||||
assert f1[0] == Po
|
||||
assert f1[1] == N.x
|
||||
assert f1 == (Po, N.x)
|
||||
assert f1 != (N.x, Po)
|
||||
assert f1 != (Po, N.x + N.y)
|
||||
assert f1 != (Point('Co'), N.x)
|
||||
# Test body as input
|
||||
P = Particle('P', Po)
|
||||
f2 = Force(P, N.x)
|
||||
assert f1 == f2
|
||||
|
||||
|
||||
def test_torque_default():
|
||||
N = ReferenceFrame('N')
|
||||
f1 = Torque(N, N.x)
|
||||
assert f1.frame == N
|
||||
assert f1.torque == N.x
|
||||
assert f1.__repr__() == 'Torque(frame=N, torque=N.x)'
|
||||
# Test tuple behaviour
|
||||
assert isinstance(f1, tuple)
|
||||
assert f1[0] == N
|
||||
assert f1[1] == N.x
|
||||
assert f1 == (N, N.x)
|
||||
assert f1 != (N.x, N)
|
||||
assert f1 != (N, N.x + N.y)
|
||||
assert f1 != (ReferenceFrame('A'), N.x)
|
||||
# Test body as input
|
||||
rb = RigidBody('P', frame=N)
|
||||
f2 = Torque(rb, N.x)
|
||||
assert f1 == f2
|
||||
|
||||
|
||||
def test_gravity():
|
||||
N = ReferenceFrame('N')
|
||||
m, M, g = symbols('m M g')
|
||||
F1, F2 = dynamicsymbols('F1 F2')
|
||||
po = Point('po')
|
||||
pa = Particle('pa', po, m)
|
||||
A = ReferenceFrame('A')
|
||||
P = Point('P')
|
||||
I = outer(A.x, A.x)
|
||||
B = RigidBody('B', P, A, M, (I, P))
|
||||
forceList = [(po, F1), (P, F2)]
|
||||
forceList.extend(gravity(g * N.y, pa, B))
|
||||
l = [(po, F1), (P, F2), (po, g * m * N.y), (P, g * M * N.y)]
|
||||
|
||||
for i in range(len(l)):
|
||||
for j in range(len(l[i])):
|
||||
assert forceList[i][j] == l[i][j]
|
||||
|
||||
|
||||
def test_parse_loads():
|
||||
N = ReferenceFrame('N')
|
||||
po = Point('po')
|
||||
assert _parse_load(Force(po, N.z)) == (po, N.z)
|
||||
assert _parse_load(Torque(N, N.x)) == (N, N.x)
|
||||
f1 = _parse_load((po, N.x)) # Test whether a force is recognized
|
||||
assert isinstance(f1, Force)
|
||||
assert f1 == Force(po, N.x)
|
||||
t1 = _parse_load((N, N.y)) # Test whether a torque is recognized
|
||||
assert isinstance(t1, Torque)
|
||||
assert t1 == Torque(N, N.y)
|
||||
# Bodies should be undetermined (even in case of a Particle)
|
||||
raises(ValueError, lambda: _parse_load((Particle('pa', po), N.x)))
|
||||
raises(ValueError, lambda: _parse_load((RigidBody('pa', po, N), N.x)))
|
||||
# Invalid tuple length
|
||||
raises(ValueError, lambda: _parse_load((po, N.x, po, N.x)))
|
||||
# Invalid type
|
||||
raises(TypeError, lambda: _parse_load([po, N.x]))
|
||||
@@ -0,0 +1,5 @@
|
||||
from sympy.physics.mechanics.method import _Methods
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
def test_method():
|
||||
raises(TypeError, lambda: _Methods())
|
||||
@@ -0,0 +1,117 @@
|
||||
import sympy.physics.mechanics.models as models
|
||||
from sympy import (cos, sin, Matrix, symbols, zeros)
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.physics.mechanics import (dynamicsymbols)
|
||||
|
||||
|
||||
def test_multi_mass_spring_damper_inputs():
|
||||
|
||||
c0, k0, m0 = symbols("c0 k0 m0")
|
||||
g = symbols("g")
|
||||
v0, x0, f0 = dynamicsymbols("v0 x0 f0")
|
||||
|
||||
kane1 = models.multi_mass_spring_damper(1)
|
||||
massmatrix1 = Matrix([[m0]])
|
||||
forcing1 = Matrix([[-c0*v0 - k0*x0]])
|
||||
assert simplify(massmatrix1 - kane1.mass_matrix) == Matrix([0])
|
||||
assert simplify(forcing1 - kane1.forcing) == Matrix([0])
|
||||
|
||||
kane2 = models.multi_mass_spring_damper(1, True)
|
||||
massmatrix2 = Matrix([[m0]])
|
||||
forcing2 = Matrix([[-c0*v0 + g*m0 - k0*x0]])
|
||||
assert simplify(massmatrix2 - kane2.mass_matrix) == Matrix([0])
|
||||
assert simplify(forcing2 - kane2.forcing) == Matrix([0])
|
||||
|
||||
kane3 = models.multi_mass_spring_damper(1, True, True)
|
||||
massmatrix3 = Matrix([[m0]])
|
||||
forcing3 = Matrix([[-c0*v0 + g*m0 - k0*x0 + f0]])
|
||||
assert simplify(massmatrix3 - kane3.mass_matrix) == Matrix([0])
|
||||
assert simplify(forcing3 - kane3.forcing) == Matrix([0])
|
||||
|
||||
kane4 = models.multi_mass_spring_damper(1, False, True)
|
||||
massmatrix4 = Matrix([[m0]])
|
||||
forcing4 = Matrix([[-c0*v0 - k0*x0 + f0]])
|
||||
assert simplify(massmatrix4 - kane4.mass_matrix) == Matrix([0])
|
||||
assert simplify(forcing4 - kane4.forcing) == Matrix([0])
|
||||
|
||||
|
||||
def test_multi_mass_spring_damper_higher_order():
|
||||
c0, k0, m0 = symbols("c0 k0 m0")
|
||||
c1, k1, m1 = symbols("c1 k1 m1")
|
||||
c2, k2, m2 = symbols("c2 k2 m2")
|
||||
v0, x0 = dynamicsymbols("v0 x0")
|
||||
v1, x1 = dynamicsymbols("v1 x1")
|
||||
v2, x2 = dynamicsymbols("v2 x2")
|
||||
|
||||
kane1 = models.multi_mass_spring_damper(3)
|
||||
massmatrix1 = Matrix([[m0 + m1 + m2, m1 + m2, m2],
|
||||
[m1 + m2, m1 + m2, m2],
|
||||
[m2, m2, m2]])
|
||||
forcing1 = Matrix([[-c0*v0 - k0*x0],
|
||||
[-c1*v1 - k1*x1],
|
||||
[-c2*v2 - k2*x2]])
|
||||
assert simplify(massmatrix1 - kane1.mass_matrix) == zeros(3)
|
||||
assert simplify(forcing1 - kane1.forcing) == Matrix([0, 0, 0])
|
||||
|
||||
|
||||
def test_n_link_pendulum_on_cart_inputs():
|
||||
l0, m0 = symbols("l0 m0")
|
||||
m1 = symbols("m1")
|
||||
g = symbols("g")
|
||||
q0, q1, F, T1 = dynamicsymbols("q0 q1 F T1")
|
||||
u0, u1 = dynamicsymbols("u0 u1")
|
||||
|
||||
kane1 = models.n_link_pendulum_on_cart(1)
|
||||
massmatrix1 = Matrix([[m0 + m1, -l0*m1*cos(q1)],
|
||||
[-l0*m1*cos(q1), l0**2*m1]])
|
||||
forcing1 = Matrix([[-l0*m1*u1**2*sin(q1) + F], [g*l0*m1*sin(q1)]])
|
||||
assert simplify(massmatrix1 - kane1.mass_matrix) == zeros(2)
|
||||
assert simplify(forcing1 - kane1.forcing) == Matrix([0, 0])
|
||||
|
||||
kane2 = models.n_link_pendulum_on_cart(1, False)
|
||||
massmatrix2 = Matrix([[m0 + m1, -l0*m1*cos(q1)],
|
||||
[-l0*m1*cos(q1), l0**2*m1]])
|
||||
forcing2 = Matrix([[-l0*m1*u1**2*sin(q1)], [g*l0*m1*sin(q1)]])
|
||||
assert simplify(massmatrix2 - kane2.mass_matrix) == zeros(2)
|
||||
assert simplify(forcing2 - kane2.forcing) == Matrix([0, 0])
|
||||
|
||||
kane3 = models.n_link_pendulum_on_cart(1, False, True)
|
||||
massmatrix3 = Matrix([[m0 + m1, -l0*m1*cos(q1)],
|
||||
[-l0*m1*cos(q1), l0**2*m1]])
|
||||
forcing3 = Matrix([[-l0*m1*u1**2*sin(q1)], [g*l0*m1*sin(q1) + T1]])
|
||||
assert simplify(massmatrix3 - kane3.mass_matrix) == zeros(2)
|
||||
assert simplify(forcing3 - kane3.forcing) == Matrix([0, 0])
|
||||
|
||||
kane4 = models.n_link_pendulum_on_cart(1, True, False)
|
||||
massmatrix4 = Matrix([[m0 + m1, -l0*m1*cos(q1)],
|
||||
[-l0*m1*cos(q1), l0**2*m1]])
|
||||
forcing4 = Matrix([[-l0*m1*u1**2*sin(q1) + F], [g*l0*m1*sin(q1)]])
|
||||
assert simplify(massmatrix4 - kane4.mass_matrix) == zeros(2)
|
||||
assert simplify(forcing4 - kane4.forcing) == Matrix([0, 0])
|
||||
|
||||
|
||||
def test_n_link_pendulum_on_cart_higher_order():
|
||||
l0, m0 = symbols("l0 m0")
|
||||
l1, m1 = symbols("l1 m1")
|
||||
m2 = symbols("m2")
|
||||
g = symbols("g")
|
||||
q0, q1, q2 = dynamicsymbols("q0 q1 q2")
|
||||
u0, u1, u2 = dynamicsymbols("u0 u1 u2")
|
||||
F, T1 = dynamicsymbols("F T1")
|
||||
|
||||
kane1 = models.n_link_pendulum_on_cart(2)
|
||||
massmatrix1 = Matrix([[m0 + m1 + m2, -l0*m1*cos(q1) - l0*m2*cos(q1),
|
||||
-l1*m2*cos(q2)],
|
||||
[-l0*m1*cos(q1) - l0*m2*cos(q1), l0**2*m1 + l0**2*m2,
|
||||
l0*l1*m2*(sin(q1)*sin(q2) + cos(q1)*cos(q2))],
|
||||
[-l1*m2*cos(q2),
|
||||
l0*l1*m2*(sin(q1)*sin(q2) + cos(q1)*cos(q2)),
|
||||
l1**2*m2]])
|
||||
forcing1 = Matrix([[-l0*m1*u1**2*sin(q1) - l0*m2*u1**2*sin(q1) -
|
||||
l1*m2*u2**2*sin(q2) + F],
|
||||
[g*l0*m1*sin(q1) + g*l0*m2*sin(q1) -
|
||||
l0*l1*m2*(sin(q1)*cos(q2) - sin(q2)*cos(q1))*u2**2],
|
||||
[g*l1*m2*sin(q2) - l0*l1*m2*(-sin(q1)*cos(q2) +
|
||||
sin(q2)*cos(q1))*u1**2]])
|
||||
assert simplify(massmatrix1 - kane1.mass_matrix) == zeros(3)
|
||||
assert simplify(forcing1 - kane1.forcing) == Matrix([0, 0, 0])
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
from sympy import symbols
|
||||
from sympy.physics.mechanics import Point, Particle, ReferenceFrame, inertia
|
||||
from sympy.physics.mechanics.body_base import BodyBase
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
|
||||
def test_particle_default():
|
||||
# Test default
|
||||
p = Particle('P')
|
||||
assert p.name == 'P'
|
||||
assert p.mass == symbols('P_mass')
|
||||
assert p.masscenter.name == 'P_masscenter'
|
||||
assert p.potential_energy == 0
|
||||
assert p.__str__() == 'P'
|
||||
assert p.__repr__() == ("Particle('P', masscenter=P_masscenter, "
|
||||
"mass=P_mass)")
|
||||
raises(AttributeError, lambda: p.frame)
|
||||
|
||||
|
||||
def test_particle():
|
||||
# Test initializing with parameters
|
||||
m, m2, v1, v2, v3, r, g, h = symbols('m m2 v1 v2 v3 r g h')
|
||||
P = Point('P')
|
||||
P2 = Point('P2')
|
||||
p = Particle('pa', P, m)
|
||||
assert isinstance(p, BodyBase)
|
||||
assert p.mass == m
|
||||
assert p.point == P
|
||||
# Test the mass setter
|
||||
p.mass = m2
|
||||
assert p.mass == m2
|
||||
# Test the point setter
|
||||
p.point = P2
|
||||
assert p.point == P2
|
||||
# Test the linear momentum function
|
||||
N = ReferenceFrame('N')
|
||||
O = Point('O')
|
||||
P2.set_pos(O, r * N.y)
|
||||
P2.set_vel(N, v1 * N.x)
|
||||
raises(TypeError, lambda: Particle(P, P, m))
|
||||
raises(TypeError, lambda: Particle('pa', m, m))
|
||||
assert p.linear_momentum(N) == m2 * v1 * N.x
|
||||
assert p.angular_momentum(O, N) == -m2 * r * v1 * N.z
|
||||
P2.set_vel(N, v2 * N.y)
|
||||
assert p.linear_momentum(N) == m2 * v2 * N.y
|
||||
assert p.angular_momentum(O, N) == 0
|
||||
P2.set_vel(N, v3 * N.z)
|
||||
assert p.linear_momentum(N) == m2 * v3 * N.z
|
||||
assert p.angular_momentum(O, N) == m2 * r * v3 * N.x
|
||||
P2.set_vel(N, v1 * N.x + v2 * N.y + v3 * N.z)
|
||||
assert p.linear_momentum(N) == m2 * (v1 * N.x + v2 * N.y + v3 * N.z)
|
||||
assert p.angular_momentum(O, N) == m2 * r * (v3 * N.x - v1 * N.z)
|
||||
p.potential_energy = m * g * h
|
||||
assert p.potential_energy == m * g * h
|
||||
# TODO make the result not be system-dependent
|
||||
assert p.kinetic_energy(
|
||||
N) in [m2 * (v1 ** 2 + v2 ** 2 + v3 ** 2) / 2,
|
||||
m2 * v1 ** 2 / 2 + m2 * v2 ** 2 / 2 + m2 * v3 ** 2 / 2]
|
||||
|
||||
|
||||
def test_parallel_axis():
|
||||
N = ReferenceFrame('N')
|
||||
m, a, b = symbols('m, a, b')
|
||||
o = Point('o')
|
||||
p = o.locatenew('p', a * N.x + b * N.y)
|
||||
P = Particle('P', o, m)
|
||||
Ip = P.parallel_axis(p, N)
|
||||
Ip_expected = inertia(N, m * b ** 2, m * a ** 2, m * (a ** 2 + b ** 2),
|
||||
ixy=-m * a * b)
|
||||
assert Ip == Ip_expected
|
||||
|
||||
|
||||
def test_deprecated_set_potential_energy():
|
||||
m, g, h = symbols('m g h')
|
||||
P = Point('P')
|
||||
p = Particle('pa', P, m)
|
||||
with warns_deprecated_sympy():
|
||||
p.set_potential_energy(m * g * h)
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
"""Tests for the ``sympy.physics.mechanics.pathway.py`` module."""
|
||||
|
||||
import pytest
|
||||
|
||||
from sympy import (
|
||||
Rational,
|
||||
Symbol,
|
||||
cos,
|
||||
pi,
|
||||
sin,
|
||||
sqrt,
|
||||
)
|
||||
from sympy.physics.mechanics import (
|
||||
Force,
|
||||
LinearPathway,
|
||||
ObstacleSetPathway,
|
||||
PathwayBase,
|
||||
Point,
|
||||
ReferenceFrame,
|
||||
WrappingCylinder,
|
||||
WrappingGeometryBase,
|
||||
WrappingPathway,
|
||||
WrappingSphere,
|
||||
dynamicsymbols,
|
||||
)
|
||||
from sympy.simplify.simplify import simplify
|
||||
|
||||
|
||||
def _simplify_loads(loads):
|
||||
return [
|
||||
load.__class__(load.location, load.vector.simplify())
|
||||
for load in loads
|
||||
]
|
||||
|
||||
|
||||
class TestLinearPathway:
|
||||
|
||||
def test_is_pathway_base_subclass(self):
|
||||
assert issubclass(LinearPathway, PathwayBase)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'args, kwargs',
|
||||
[
|
||||
((Point('pA'), Point('pB')), {}),
|
||||
]
|
||||
)
|
||||
def test_valid_constructor(args, kwargs):
|
||||
pointA, pointB = args
|
||||
instance = LinearPathway(*args, **kwargs)
|
||||
assert isinstance(instance, LinearPathway)
|
||||
assert hasattr(instance, 'attachments')
|
||||
assert len(instance.attachments) == 2
|
||||
assert instance.attachments[0] is pointA
|
||||
assert instance.attachments[1] is pointB
|
||||
assert isinstance(instance.attachments[0], Point)
|
||||
assert instance.attachments[0].name == 'pA'
|
||||
assert isinstance(instance.attachments[1], Point)
|
||||
assert instance.attachments[1].name == 'pB'
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'attachments',
|
||||
[
|
||||
(Point('pA'), ),
|
||||
(Point('pA'), Point('pB'), Point('pZ')),
|
||||
]
|
||||
)
|
||||
def test_invalid_attachments_incorrect_number(attachments):
|
||||
with pytest.raises(ValueError):
|
||||
_ = LinearPathway(*attachments)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'attachments',
|
||||
[
|
||||
(None, Point('pB')),
|
||||
(Point('pA'), None),
|
||||
]
|
||||
)
|
||||
def test_invalid_attachments_not_point(attachments):
|
||||
with pytest.raises(TypeError):
|
||||
_ = LinearPathway(*attachments)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _linear_pathway_fixture(self):
|
||||
self.N = ReferenceFrame('N')
|
||||
self.pA = Point('pA')
|
||||
self.pB = Point('pB')
|
||||
self.pathway = LinearPathway(self.pA, self.pB)
|
||||
self.q1 = dynamicsymbols('q1')
|
||||
self.q2 = dynamicsymbols('q2')
|
||||
self.q3 = dynamicsymbols('q3')
|
||||
self.q1d = dynamicsymbols('q1', 1)
|
||||
self.q2d = dynamicsymbols('q2', 1)
|
||||
self.q3d = dynamicsymbols('q3', 1)
|
||||
self.F = Symbol('F')
|
||||
|
||||
def test_properties_are_immutable(self):
|
||||
instance = LinearPathway(self.pA, self.pB)
|
||||
with pytest.raises(AttributeError):
|
||||
instance.attachments = None
|
||||
with pytest.raises(TypeError):
|
||||
instance.attachments[0] = None
|
||||
with pytest.raises(TypeError):
|
||||
instance.attachments[1] = None
|
||||
|
||||
def test_repr(self):
|
||||
pathway = LinearPathway(self.pA, self.pB)
|
||||
expected = 'LinearPathway(pA, pB)'
|
||||
assert repr(pathway) == expected
|
||||
|
||||
def test_static_pathway_length(self):
|
||||
self.pB.set_pos(self.pA, 2*self.N.x)
|
||||
assert self.pathway.length == 2
|
||||
|
||||
def test_static_pathway_extension_velocity(self):
|
||||
self.pB.set_pos(self.pA, 2*self.N.x)
|
||||
assert self.pathway.extension_velocity == 0
|
||||
|
||||
def test_static_pathway_to_loads(self):
|
||||
self.pB.set_pos(self.pA, 2*self.N.x)
|
||||
expected = [
|
||||
(self.pA, - self.F*self.N.x),
|
||||
(self.pB, self.F*self.N.x),
|
||||
]
|
||||
assert self.pathway.to_loads(self.F) == expected
|
||||
|
||||
def test_2D_pathway_length(self):
|
||||
self.pB.set_pos(self.pA, 2*self.q1*self.N.x)
|
||||
expected = 2*sqrt(self.q1**2)
|
||||
assert self.pathway.length == expected
|
||||
|
||||
def test_2D_pathway_extension_velocity(self):
|
||||
self.pB.set_pos(self.pA, 2*self.q1*self.N.x)
|
||||
expected = 2*sqrt(self.q1**2)*self.q1d/self.q1
|
||||
assert self.pathway.extension_velocity == expected
|
||||
|
||||
def test_2D_pathway_to_loads(self):
|
||||
self.pB.set_pos(self.pA, 2*self.q1*self.N.x)
|
||||
expected = [
|
||||
(self.pA, - self.F*(self.q1 / sqrt(self.q1**2))*self.N.x),
|
||||
(self.pB, self.F*(self.q1 / sqrt(self.q1**2))*self.N.x),
|
||||
]
|
||||
assert self.pathway.to_loads(self.F) == expected
|
||||
|
||||
def test_3D_pathway_length(self):
|
||||
self.pB.set_pos(
|
||||
self.pA,
|
||||
self.q1*self.N.x - self.q2*self.N.y + 2*self.q3*self.N.z,
|
||||
)
|
||||
expected = sqrt(self.q1**2 + self.q2**2 + 4*self.q3**2)
|
||||
assert simplify(self.pathway.length - expected) == 0
|
||||
|
||||
def test_3D_pathway_extension_velocity(self):
|
||||
self.pB.set_pos(
|
||||
self.pA,
|
||||
self.q1*self.N.x - self.q2*self.N.y + 2*self.q3*self.N.z,
|
||||
)
|
||||
length = sqrt(self.q1**2 + self.q2**2 + 4*self.q3**2)
|
||||
expected = (
|
||||
self.q1*self.q1d/length
|
||||
+ self.q2*self.q2d/length
|
||||
+ 4*self.q3*self.q3d/length
|
||||
)
|
||||
assert simplify(self.pathway.extension_velocity - expected) == 0
|
||||
|
||||
def test_3D_pathway_to_loads(self):
|
||||
self.pB.set_pos(
|
||||
self.pA,
|
||||
self.q1*self.N.x - self.q2*self.N.y + 2*self.q3*self.N.z,
|
||||
)
|
||||
length = sqrt(self.q1**2 + self.q2**2 + 4*self.q3**2)
|
||||
pO_force = (
|
||||
- self.F*self.q1*self.N.x/length
|
||||
+ self.F*self.q2*self.N.y/length
|
||||
- 2*self.F*self.q3*self.N.z/length
|
||||
)
|
||||
pI_force = (
|
||||
self.F*self.q1*self.N.x/length
|
||||
- self.F*self.q2*self.N.y/length
|
||||
+ 2*self.F*self.q3*self.N.z/length
|
||||
)
|
||||
expected = [
|
||||
(self.pA, pO_force),
|
||||
(self.pB, pI_force),
|
||||
]
|
||||
assert self.pathway.to_loads(self.F) == expected
|
||||
|
||||
|
||||
class TestObstacleSetPathway:
|
||||
|
||||
def test_is_pathway_base_subclass(self):
|
||||
assert issubclass(ObstacleSetPathway, PathwayBase)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'num_attachments, attachments',
|
||||
[
|
||||
(3, [Point(name) for name in ('pO', 'pA', 'pI')]),
|
||||
(4, [Point(name) for name in ('pO', 'pA', 'pB', 'pI')]),
|
||||
(5, [Point(name) for name in ('pO', 'pA', 'pB', 'pC', 'pI')]),
|
||||
(6, [Point(name) for name in ('pO', 'pA', 'pB', 'pC', 'pD', 'pI')]),
|
||||
]
|
||||
)
|
||||
def test_valid_constructor(num_attachments, attachments):
|
||||
instance = ObstacleSetPathway(*attachments)
|
||||
assert isinstance(instance, ObstacleSetPathway)
|
||||
assert hasattr(instance, 'attachments')
|
||||
assert len(instance.attachments) == num_attachments
|
||||
for attachment in instance.attachments:
|
||||
assert isinstance(attachment, Point)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'attachments',
|
||||
[[Point('pO')], [Point('pO'), Point('pI')]],
|
||||
)
|
||||
def test_invalid_constructor_attachments_incorrect_number(attachments):
|
||||
with pytest.raises(ValueError):
|
||||
_ = ObstacleSetPathway(*attachments)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'attachments',
|
||||
[
|
||||
(None, Point('pA'), Point('pI')),
|
||||
(Point('pO'), None, Point('pI')),
|
||||
(Point('pO'), Point('pA'), None),
|
||||
]
|
||||
)
|
||||
def test_invalid_constructor_attachments_not_point(attachments):
|
||||
with pytest.raises(TypeError):
|
||||
_ = WrappingPathway(*attachments) # type: ignore
|
||||
|
||||
def test_properties_are_immutable(self):
|
||||
pathway = ObstacleSetPathway(Point('pO'), Point('pA'), Point('pI'))
|
||||
with pytest.raises(AttributeError):
|
||||
pathway.attachments = None # type: ignore
|
||||
with pytest.raises(TypeError):
|
||||
pathway.attachments[0] = None # type: ignore
|
||||
with pytest.raises(TypeError):
|
||||
pathway.attachments[1] = None # type: ignore
|
||||
with pytest.raises(TypeError):
|
||||
pathway.attachments[-1] = None # type: ignore
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'attachments, expected',
|
||||
[
|
||||
(
|
||||
[Point(name) for name in ('pO', 'pA', 'pI')],
|
||||
'ObstacleSetPathway(pO, pA, pI)'
|
||||
),
|
||||
(
|
||||
[Point(name) for name in ('pO', 'pA', 'pB', 'pI')],
|
||||
'ObstacleSetPathway(pO, pA, pB, pI)'
|
||||
),
|
||||
(
|
||||
[Point(name) for name in ('pO', 'pA', 'pB', 'pC', 'pI')],
|
||||
'ObstacleSetPathway(pO, pA, pB, pC, pI)'
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_repr(attachments, expected):
|
||||
pathway = ObstacleSetPathway(*attachments)
|
||||
assert repr(pathway) == expected
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _obstacle_set_pathway_fixture(self):
|
||||
self.N = ReferenceFrame('N')
|
||||
self.pO = Point('pO')
|
||||
self.pI = Point('pI')
|
||||
self.pA = Point('pA')
|
||||
self.pB = Point('pB')
|
||||
self.q = dynamicsymbols('q')
|
||||
self.qd = dynamicsymbols('q', 1)
|
||||
self.F = Symbol('F')
|
||||
|
||||
def test_static_pathway_length(self):
|
||||
self.pA.set_pos(self.pO, self.N.x)
|
||||
self.pB.set_pos(self.pO, self.N.y)
|
||||
self.pI.set_pos(self.pO, self.N.z)
|
||||
pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
|
||||
assert pathway.length == 1 + 2 * sqrt(2)
|
||||
|
||||
def test_static_pathway_extension_velocity(self):
|
||||
self.pA.set_pos(self.pO, self.N.x)
|
||||
self.pB.set_pos(self.pO, self.N.y)
|
||||
self.pI.set_pos(self.pO, self.N.z)
|
||||
pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
|
||||
assert pathway.extension_velocity == 0
|
||||
|
||||
def test_static_pathway_to_loads(self):
|
||||
self.pA.set_pos(self.pO, self.N.x)
|
||||
self.pB.set_pos(self.pO, self.N.y)
|
||||
self.pI.set_pos(self.pO, self.N.z)
|
||||
pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
|
||||
expected = [
|
||||
Force(self.pO, -self.F * self.N.x),
|
||||
Force(self.pA, self.F * self.N.x),
|
||||
Force(self.pA, self.F * sqrt(2) / 2 * (self.N.x - self.N.y)),
|
||||
Force(self.pB, self.F * sqrt(2) / 2 * (self.N.y - self.N.x)),
|
||||
Force(self.pB, self.F * sqrt(2) / 2 * (self.N.y - self.N.z)),
|
||||
Force(self.pI, self.F * sqrt(2) / 2 * (self.N.z - self.N.y)),
|
||||
]
|
||||
assert pathway.to_loads(self.F) == expected
|
||||
|
||||
def test_2D_pathway_length(self):
|
||||
self.pA.set_pos(self.pO, -(self.N.x + self.N.y))
|
||||
self.pB.set_pos(
|
||||
self.pO, cos(self.q) * self.N.x - (sin(self.q) + 1) * self.N.y
|
||||
)
|
||||
self.pI.set_pos(
|
||||
self.pO, sin(self.q) * self.N.x + (cos(self.q) - 1) * self.N.y
|
||||
)
|
||||
pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
|
||||
expected = 2 * sqrt(2) + sqrt(2 + 2*cos(self.q))
|
||||
assert (pathway.length - expected).simplify() == 0
|
||||
|
||||
def test_2D_pathway_extension_velocity(self):
|
||||
self.pA.set_pos(self.pO, -(self.N.x + self.N.y))
|
||||
self.pB.set_pos(
|
||||
self.pO, cos(self.q) * self.N.x - (sin(self.q) + 1) * self.N.y
|
||||
)
|
||||
self.pI.set_pos(
|
||||
self.pO, sin(self.q) * self.N.x + (cos(self.q) - 1) * self.N.y
|
||||
)
|
||||
pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
|
||||
expected = - (sqrt(2) * sin(self.q) * self.qd) / (2 * sqrt(cos(self.q) + 1))
|
||||
assert (pathway.extension_velocity - expected).simplify() == 0
|
||||
|
||||
def test_2D_pathway_to_loads(self):
|
||||
self.pA.set_pos(self.pO, -(self.N.x + self.N.y))
|
||||
self.pB.set_pos(
|
||||
self.pO, cos(self.q) * self.N.x - (sin(self.q) + 1) * self.N.y
|
||||
)
|
||||
self.pI.set_pos(
|
||||
self.pO, sin(self.q) * self.N.x + (cos(self.q) - 1) * self.N.y
|
||||
)
|
||||
pathway = ObstacleSetPathway(self.pO, self.pA, self.pB, self.pI)
|
||||
pO_pA_force_vec = sqrt(2) / 2 * (self.N.x + self.N.y)
|
||||
pA_pB_force_vec = (
|
||||
- sqrt(2 * cos(self.q) + 2) / 2 * self.N.x
|
||||
+ sqrt(2) * sin(self.q) / (2 * sqrt(cos(self.q) + 1)) * self.N.y
|
||||
)
|
||||
pB_pI_force_vec = cos(self.q + pi/4) * self.N.x - sin(self.q + pi/4) * self.N.y
|
||||
expected = [
|
||||
Force(self.pO, self.F * pO_pA_force_vec),
|
||||
Force(self.pA, -self.F * pO_pA_force_vec),
|
||||
Force(self.pA, self.F * pA_pB_force_vec),
|
||||
Force(self.pB, -self.F * pA_pB_force_vec),
|
||||
Force(self.pB, self.F * pB_pI_force_vec),
|
||||
Force(self.pI, -self.F * pB_pI_force_vec),
|
||||
]
|
||||
assert _simplify_loads(pathway.to_loads(self.F)) == expected
|
||||
|
||||
|
||||
class TestWrappingPathway:
|
||||
|
||||
def test_is_pathway_base_subclass(self):
|
||||
assert issubclass(WrappingPathway, PathwayBase)
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _wrapping_pathway_fixture(self):
|
||||
self.pA = Point('pA')
|
||||
self.pB = Point('pB')
|
||||
self.r = Symbol('r', positive=True)
|
||||
self.pO = Point('pO')
|
||||
self.N = ReferenceFrame('N')
|
||||
self.ax = self.N.z
|
||||
self.sphere = WrappingSphere(self.r, self.pO)
|
||||
self.cylinder = WrappingCylinder(self.r, self.pO, self.ax)
|
||||
self.pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
|
||||
self.F = Symbol('F')
|
||||
|
||||
def test_valid_constructor(self):
|
||||
instance = WrappingPathway(self.pA, self.pB, self.cylinder)
|
||||
assert isinstance(instance, WrappingPathway)
|
||||
assert hasattr(instance, 'attachments')
|
||||
assert len(instance.attachments) == 2
|
||||
assert isinstance(instance.attachments[0], Point)
|
||||
assert instance.attachments[0] == self.pA
|
||||
assert isinstance(instance.attachments[1], Point)
|
||||
assert instance.attachments[1] == self.pB
|
||||
assert hasattr(instance, 'geometry')
|
||||
assert isinstance(instance.geometry, WrappingGeometryBase)
|
||||
assert instance.geometry == self.cylinder
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'attachments',
|
||||
[
|
||||
(Point('pA'), ),
|
||||
(Point('pA'), Point('pB'), Point('pZ')),
|
||||
]
|
||||
)
|
||||
def test_invalid_constructor_attachments_incorrect_number(self, attachments):
|
||||
with pytest.raises(TypeError):
|
||||
_ = WrappingPathway(*attachments, self.cylinder)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'attachments',
|
||||
[
|
||||
(None, Point('pB')),
|
||||
(Point('pA'), None),
|
||||
]
|
||||
)
|
||||
def test_invalid_constructor_attachments_not_point(attachments):
|
||||
with pytest.raises(TypeError):
|
||||
_ = WrappingPathway(*attachments)
|
||||
|
||||
def test_invalid_constructor_geometry_is_not_supplied(self):
|
||||
with pytest.raises(TypeError):
|
||||
_ = WrappingPathway(self.pA, self.pB)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'geometry',
|
||||
[
|
||||
Symbol('r'),
|
||||
dynamicsymbols('q'),
|
||||
ReferenceFrame('N'),
|
||||
ReferenceFrame('N').x,
|
||||
]
|
||||
)
|
||||
def test_invalid_geometry_not_geometry(self, geometry):
|
||||
with pytest.raises(TypeError):
|
||||
_ = WrappingPathway(self.pA, self.pB, geometry)
|
||||
|
||||
def test_attachments_property_is_immutable(self):
|
||||
with pytest.raises(TypeError):
|
||||
self.pathway.attachments[0] = self.pB
|
||||
with pytest.raises(TypeError):
|
||||
self.pathway.attachments[1] = self.pA
|
||||
|
||||
def test_geometry_property_is_immutable(self):
|
||||
with pytest.raises(AttributeError):
|
||||
self.pathway.geometry = None
|
||||
|
||||
def test_repr(self):
|
||||
expected = (
|
||||
f'WrappingPathway(pA, pB, '
|
||||
f'geometry={self.cylinder!r})'
|
||||
)
|
||||
assert repr(self.pathway) == expected
|
||||
|
||||
@staticmethod
|
||||
def _expand_pos_to_vec(pos, frame):
|
||||
return sum(mag*unit for (mag, unit) in zip(pos, frame))
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'pA_vec, pB_vec, factor',
|
||||
[
|
||||
((1, 0, 0), (0, 1, 0), pi/2),
|
||||
((0, 1, 0), (sqrt(2)/2, -sqrt(2)/2, 0), 3*pi/4),
|
||||
((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 0), pi/3),
|
||||
]
|
||||
)
|
||||
def test_static_pathway_on_sphere_length(self, pA_vec, pB_vec, factor):
|
||||
pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
|
||||
pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
|
||||
self.pA.set_pos(self.pO, self.r*pA_vec)
|
||||
self.pB.set_pos(self.pO, self.r*pB_vec)
|
||||
pathway = WrappingPathway(self.pA, self.pB, self.sphere)
|
||||
expected = factor*self.r
|
||||
assert simplify(pathway.length - expected) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'pA_vec, pB_vec, factor',
|
||||
[
|
||||
((1, 0, 0), (0, 1, 0), Rational(1, 2)*pi),
|
||||
((1, 0, 0), (-1, 0, 0), pi),
|
||||
((-1, 0, 0), (1, 0, 0), pi),
|
||||
((0, 1, 0), (sqrt(2)/2, -sqrt(2)/2, 0), 5*pi/4),
|
||||
((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 0), pi/3),
|
||||
(
|
||||
(0, 1, 0),
|
||||
(sqrt(2)*Rational(1, 2), -sqrt(2)*Rational(1, 2), 1),
|
||||
sqrt(1 + (Rational(5, 4)*pi)**2),
|
||||
),
|
||||
(
|
||||
(1, 0, 0),
|
||||
(Rational(1, 2), sqrt(3)*Rational(1, 2), 1),
|
||||
sqrt(1 + (Rational(1, 3)*pi)**2),
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_static_pathway_on_cylinder_length(self, pA_vec, pB_vec, factor):
|
||||
pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
|
||||
pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
|
||||
self.pA.set_pos(self.pO, self.r*pA_vec)
|
||||
self.pB.set_pos(self.pO, self.r*pB_vec)
|
||||
pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
|
||||
expected = factor*sqrt(self.r**2)
|
||||
assert simplify(pathway.length - expected) == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'pA_vec, pB_vec',
|
||||
[
|
||||
((1, 0, 0), (0, 1, 0)),
|
||||
((0, 1, 0), (sqrt(2)*Rational(1, 2), -sqrt(2)*Rational(1, 2), 0)),
|
||||
((1, 0, 0), (Rational(1, 2), sqrt(3)*Rational(1, 2), 0)),
|
||||
]
|
||||
)
|
||||
def test_static_pathway_on_sphere_extension_velocity(self, pA_vec, pB_vec):
|
||||
pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
|
||||
pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
|
||||
self.pA.set_pos(self.pO, self.r*pA_vec)
|
||||
self.pB.set_pos(self.pO, self.r*pB_vec)
|
||||
pathway = WrappingPathway(self.pA, self.pB, self.sphere)
|
||||
assert pathway.extension_velocity == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'pA_vec, pB_vec',
|
||||
[
|
||||
((1, 0, 0), (0, 1, 0)),
|
||||
((1, 0, 0), (-1, 0, 0)),
|
||||
((-1, 0, 0), (1, 0, 0)),
|
||||
((0, 1, 0), (sqrt(2)/2, -sqrt(2)/2, 0)),
|
||||
((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 0)),
|
||||
((0, 1, 0), (sqrt(2)*Rational(1, 2), -sqrt(2)/2, 1)),
|
||||
((1, 0, 0), (Rational(1, 2), sqrt(3)/2, 1)),
|
||||
]
|
||||
)
|
||||
def test_static_pathway_on_cylinder_extension_velocity(self, pA_vec, pB_vec):
|
||||
pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
|
||||
pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
|
||||
self.pA.set_pos(self.pO, self.r*pA_vec)
|
||||
self.pB.set_pos(self.pO, self.r*pB_vec)
|
||||
pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
|
||||
assert pathway.extension_velocity == 0
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'pA_vec, pB_vec, pA_vec_expected, pB_vec_expected, pO_vec_expected',
|
||||
(
|
||||
((1, 0, 0), (0, 1, 0), (0, 1, 0), (1, 0, 0), (-1, -1, 0)),
|
||||
(
|
||||
(0, 1, 0),
|
||||
(sqrt(2)/2, -sqrt(2)/2, 0),
|
||||
(1, 0, 0),
|
||||
(sqrt(2)/2, sqrt(2)/2, 0),
|
||||
(-1 - sqrt(2)/2, -sqrt(2)/2, 0)
|
||||
),
|
||||
(
|
||||
(1, 0, 0),
|
||||
(Rational(1, 2), sqrt(3)/2, 0),
|
||||
(0, 1, 0),
|
||||
(sqrt(3)/2, -Rational(1, 2), 0),
|
||||
(-sqrt(3)/2, Rational(1, 2) - 1, 0),
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_static_pathway_on_sphere_to_loads(
|
||||
self,
|
||||
pA_vec,
|
||||
pB_vec,
|
||||
pA_vec_expected,
|
||||
pB_vec_expected,
|
||||
pO_vec_expected,
|
||||
):
|
||||
pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
|
||||
pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
|
||||
self.pA.set_pos(self.pO, self.r*pA_vec)
|
||||
self.pB.set_pos(self.pO, self.r*pB_vec)
|
||||
pathway = WrappingPathway(self.pA, self.pB, self.sphere)
|
||||
|
||||
pA_vec_expected = sum(
|
||||
mag*unit for (mag, unit) in zip(pA_vec_expected, self.N)
|
||||
)
|
||||
pB_vec_expected = sum(
|
||||
mag*unit for (mag, unit) in zip(pB_vec_expected, self.N)
|
||||
)
|
||||
pO_vec_expected = sum(
|
||||
mag*unit for (mag, unit) in zip(pO_vec_expected, self.N)
|
||||
)
|
||||
expected = [
|
||||
Force(self.pA, self.F*(self.r**3/sqrt(self.r**6))*pA_vec_expected),
|
||||
Force(self.pB, self.F*(self.r**3/sqrt(self.r**6))*pB_vec_expected),
|
||||
Force(self.pO, self.F*(self.r**3/sqrt(self.r**6))*pO_vec_expected),
|
||||
]
|
||||
assert pathway.to_loads(self.F) == expected
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'pA_vec, pB_vec, pA_vec_expected, pB_vec_expected, pO_vec_expected',
|
||||
(
|
||||
((1, 0, 0), (0, 1, 0), (0, 1, 0), (1, 0, 0), (-1, -1, 0)),
|
||||
((1, 0, 0), (-1, 0, 0), (0, 1, 0), (0, 1, 0), (0, -2, 0)),
|
||||
((-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, -1, 0), (0, 2, 0)),
|
||||
(
|
||||
(0, 1, 0),
|
||||
(sqrt(2)/2, -sqrt(2)/2, 0),
|
||||
(-1, 0, 0),
|
||||
(-sqrt(2)/2, -sqrt(2)/2, 0),
|
||||
(1 + sqrt(2)/2, sqrt(2)/2, 0)
|
||||
),
|
||||
(
|
||||
(1, 0, 0),
|
||||
(Rational(1, 2), sqrt(3)/2, 0),
|
||||
(0, 1, 0),
|
||||
(sqrt(3)/2, -Rational(1, 2), 0),
|
||||
(-sqrt(3)/2, Rational(1, 2) - 1, 0),
|
||||
),
|
||||
(
|
||||
(1, 0, 0),
|
||||
(sqrt(2)/2, sqrt(2)/2, 0),
|
||||
(0, 1, 0),
|
||||
(sqrt(2)/2, -sqrt(2)/2, 0),
|
||||
(-sqrt(2)/2, sqrt(2)/2 - 1, 0),
|
||||
),
|
||||
((0, 1, 0), (0, 1, 1), (0, 0, 1), (0, 0, -1), (0, 0, 0)),
|
||||
(
|
||||
(0, 1, 0),
|
||||
(sqrt(2)/2, -sqrt(2)/2, 1),
|
||||
(-5*pi/sqrt(16 + 25*pi**2), 0, 4/sqrt(16 + 25*pi**2)),
|
||||
(
|
||||
-5*sqrt(2)*pi/(2*sqrt(16 + 25*pi**2)),
|
||||
-5*sqrt(2)*pi/(2*sqrt(16 + 25*pi**2)),
|
||||
-4/sqrt(16 + 25*pi**2),
|
||||
),
|
||||
(
|
||||
5*(sqrt(2) + 2)*pi/(2*sqrt(16 + 25*pi**2)),
|
||||
5*sqrt(2)*pi/(2*sqrt(16 + 25*pi**2)),
|
||||
0,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
def test_static_pathway_on_cylinder_to_loads(
|
||||
self,
|
||||
pA_vec,
|
||||
pB_vec,
|
||||
pA_vec_expected,
|
||||
pB_vec_expected,
|
||||
pO_vec_expected,
|
||||
):
|
||||
pA_vec = self._expand_pos_to_vec(pA_vec, self.N)
|
||||
pB_vec = self._expand_pos_to_vec(pB_vec, self.N)
|
||||
self.pA.set_pos(self.pO, self.r*pA_vec)
|
||||
self.pB.set_pos(self.pO, self.r*pB_vec)
|
||||
pathway = WrappingPathway(self.pA, self.pB, self.cylinder)
|
||||
|
||||
pA_force_expected = self.F*self._expand_pos_to_vec(pA_vec_expected,
|
||||
self.N)
|
||||
pB_force_expected = self.F*self._expand_pos_to_vec(pB_vec_expected,
|
||||
self.N)
|
||||
pO_force_expected = self.F*self._expand_pos_to_vec(pO_vec_expected,
|
||||
self.N)
|
||||
expected = [
|
||||
Force(self.pA, pA_force_expected),
|
||||
Force(self.pB, pB_force_expected),
|
||||
Force(self.pO, pO_force_expected),
|
||||
]
|
||||
assert _simplify_loads(pathway.to_loads(self.F)) == expected
|
||||
|
||||
def test_2D_pathway_on_cylinder_length(self):
|
||||
q = dynamicsymbols('q')
|
||||
pA_pos = self.r*self.N.x
|
||||
pB_pos = self.r*(cos(q)*self.N.x + sin(q)*self.N.y)
|
||||
self.pA.set_pos(self.pO, pA_pos)
|
||||
self.pB.set_pos(self.pO, pB_pos)
|
||||
expected = self.r*sqrt(q**2)
|
||||
assert simplify(self.pathway.length - expected) == 0
|
||||
|
||||
def test_2D_pathway_on_cylinder_extension_velocity(self):
|
||||
q = dynamicsymbols('q')
|
||||
qd = dynamicsymbols('q', 1)
|
||||
pA_pos = self.r*self.N.x
|
||||
pB_pos = self.r*(cos(q)*self.N.x + sin(q)*self.N.y)
|
||||
self.pA.set_pos(self.pO, pA_pos)
|
||||
self.pB.set_pos(self.pO, pB_pos)
|
||||
expected = self.r*(sqrt(q**2)/q)*qd
|
||||
assert simplify(self.pathway.extension_velocity - expected) == 0
|
||||
|
||||
def test_2D_pathway_on_cylinder_to_loads(self):
|
||||
q = dynamicsymbols('q')
|
||||
pA_pos = self.r*self.N.x
|
||||
pB_pos = self.r*(cos(q)*self.N.x + sin(q)*self.N.y)
|
||||
self.pA.set_pos(self.pO, pA_pos)
|
||||
self.pB.set_pos(self.pO, pB_pos)
|
||||
|
||||
pA_force = self.F*self.N.y
|
||||
pB_force = self.F*(sin(q)*self.N.x - cos(q)*self.N.y)
|
||||
pO_force = self.F*(-sin(q)*self.N.x + (cos(q) - 1)*self.N.y)
|
||||
expected = [
|
||||
Force(self.pA, pA_force),
|
||||
Force(self.pB, pB_force),
|
||||
Force(self.pO, pO_force),
|
||||
]
|
||||
|
||||
loads = _simplify_loads(self.pathway.to_loads(self.F))
|
||||
assert loads == expected
|
||||
+184
@@ -0,0 +1,184 @@
|
||||
from sympy.physics.mechanics import Point, ReferenceFrame, Dyadic, RigidBody
|
||||
from sympy.physics.mechanics import dynamicsymbols, outer, inertia, Inertia
|
||||
from sympy.physics.mechanics import inertia_of_point_mass
|
||||
from sympy import expand, zeros, simplify, symbols
|
||||
from sympy.testing.pytest import raises, warns_deprecated_sympy
|
||||
|
||||
|
||||
def test_rigidbody_default():
|
||||
# Test default
|
||||
b = RigidBody('B')
|
||||
I = inertia(b.frame, *symbols('B_ixx B_iyy B_izz B_ixy B_iyz B_izx'))
|
||||
assert b.name == 'B'
|
||||
assert b.mass == symbols('B_mass')
|
||||
assert b.masscenter.name == 'B_masscenter'
|
||||
assert b.inertia == (I, b.masscenter)
|
||||
assert b.central_inertia == I
|
||||
assert b.frame.name == 'B_frame'
|
||||
assert b.__str__() == 'B'
|
||||
assert b.__repr__() == (
|
||||
"RigidBody('B', masscenter=B_masscenter, frame=B_frame, mass=B_mass, "
|
||||
"inertia=Inertia(dyadic=B_ixx*(B_frame.x|B_frame.x) + "
|
||||
"B_ixy*(B_frame.x|B_frame.y) + B_izx*(B_frame.x|B_frame.z) + "
|
||||
"B_ixy*(B_frame.y|B_frame.x) + B_iyy*(B_frame.y|B_frame.y) + "
|
||||
"B_iyz*(B_frame.y|B_frame.z) + B_izx*(B_frame.z|B_frame.x) + "
|
||||
"B_iyz*(B_frame.z|B_frame.y) + B_izz*(B_frame.z|B_frame.z), "
|
||||
"point=B_masscenter))")
|
||||
|
||||
|
||||
def test_rigidbody():
|
||||
m, m2, v1, v2, v3, omega = symbols('m m2 v1 v2 v3 omega')
|
||||
A = ReferenceFrame('A')
|
||||
A2 = ReferenceFrame('A2')
|
||||
P = Point('P')
|
||||
P2 = Point('P2')
|
||||
I = Dyadic(0)
|
||||
I2 = Dyadic(0)
|
||||
B = RigidBody('B', P, A, m, (I, P))
|
||||
assert B.mass == m
|
||||
assert B.frame == A
|
||||
assert B.masscenter == P
|
||||
assert B.inertia == (I, B.masscenter)
|
||||
|
||||
B.mass = m2
|
||||
B.frame = A2
|
||||
B.masscenter = P2
|
||||
B.inertia = (I2, B.masscenter)
|
||||
raises(TypeError, lambda: RigidBody(P, P, A, m, (I, P)))
|
||||
raises(TypeError, lambda: RigidBody('B', P, P, m, (I, P)))
|
||||
raises(TypeError, lambda: RigidBody('B', P, A, m, (P, P)))
|
||||
raises(TypeError, lambda: RigidBody('B', P, A, m, (I, I)))
|
||||
assert B.__str__() == 'B'
|
||||
assert B.mass == m2
|
||||
assert B.frame == A2
|
||||
assert B.masscenter == P2
|
||||
assert B.inertia == (I2, B.masscenter)
|
||||
assert isinstance(B.inertia, Inertia)
|
||||
|
||||
# Testing linear momentum function assuming A2 is the inertial frame
|
||||
N = ReferenceFrame('N')
|
||||
P2.set_vel(N, v1 * N.x + v2 * N.y + v3 * N.z)
|
||||
assert B.linear_momentum(N) == m2 * (v1 * N.x + v2 * N.y + v3 * N.z)
|
||||
|
||||
|
||||
def test_rigidbody2():
|
||||
M, v, r, omega, g, h = dynamicsymbols('M v r omega g h')
|
||||
N = ReferenceFrame('N')
|
||||
b = ReferenceFrame('b')
|
||||
b.set_ang_vel(N, omega * b.x)
|
||||
P = Point('P')
|
||||
I = outer(b.x, b.x)
|
||||
Inertia_tuple = (I, P)
|
||||
B = RigidBody('B', P, b, M, Inertia_tuple)
|
||||
P.set_vel(N, v * b.x)
|
||||
assert B.angular_momentum(P, N) == omega * b.x
|
||||
O = Point('O')
|
||||
O.set_vel(N, v * b.x)
|
||||
P.set_pos(O, r * b.y)
|
||||
assert B.angular_momentum(O, N) == omega * b.x - M*v*r*b.z
|
||||
B.potential_energy = M * g * h
|
||||
assert B.potential_energy == M * g * h
|
||||
assert expand(2 * B.kinetic_energy(N)) == omega**2 + M * v**2
|
||||
|
||||
|
||||
def test_rigidbody3():
|
||||
q1, q2, q3, q4 = dynamicsymbols('q1:5')
|
||||
p1, p2, p3 = symbols('p1:4')
|
||||
m = symbols('m')
|
||||
|
||||
A = ReferenceFrame('A')
|
||||
B = A.orientnew('B', 'axis', [q1, A.x])
|
||||
O = Point('O')
|
||||
O.set_vel(A, q2*A.x + q3*A.y + q4*A.z)
|
||||
P = O.locatenew('P', p1*B.x + p2*B.y + p3*B.z)
|
||||
P.v2pt_theory(O, A, B)
|
||||
I = outer(B.x, B.x)
|
||||
|
||||
rb1 = RigidBody('rb1', P, B, m, (I, P))
|
||||
# I_S/O = I_S/S* + I_S*/O
|
||||
rb2 = RigidBody('rb2', P, B, m,
|
||||
(I + inertia_of_point_mass(m, P.pos_from(O), B), O))
|
||||
|
||||
assert rb1.central_inertia == rb2.central_inertia
|
||||
assert rb1.angular_momentum(O, A) == rb2.angular_momentum(O, A)
|
||||
|
||||
|
||||
def test_pendulum_angular_momentum():
|
||||
"""Consider a pendulum of length OA = 2a, of mass m as a rigid body of
|
||||
center of mass G (OG = a) which turn around (O,z). The angle between the
|
||||
reference frame R and the rod is q. The inertia of the body is I =
|
||||
(G,0,ma^2/3,ma^2/3). """
|
||||
|
||||
m, a = symbols('m, a')
|
||||
q = dynamicsymbols('q')
|
||||
|
||||
R = ReferenceFrame('R')
|
||||
R1 = R.orientnew('R1', 'Axis', [q, R.z])
|
||||
R1.set_ang_vel(R, q.diff() * R.z)
|
||||
|
||||
I = inertia(R1, 0, m * a**2 / 3, m * a**2 / 3)
|
||||
|
||||
O = Point('O')
|
||||
|
||||
A = O.locatenew('A', 2*a * R1.x)
|
||||
G = O.locatenew('G', a * R1.x)
|
||||
|
||||
S = RigidBody('S', G, R1, m, (I, G))
|
||||
|
||||
O.set_vel(R, 0)
|
||||
A.v2pt_theory(O, R, R1)
|
||||
G.v2pt_theory(O, R, R1)
|
||||
|
||||
assert (4 * m * a**2 / 3 * q.diff() * R.z -
|
||||
S.angular_momentum(O, R).express(R)) == 0
|
||||
|
||||
|
||||
def test_rigidbody_inertia():
|
||||
N = ReferenceFrame('N')
|
||||
m, Ix, Iy, Iz, a, b = symbols('m, I_x, I_y, I_z, a, b')
|
||||
Io = inertia(N, Ix, Iy, Iz)
|
||||
o = Point('o')
|
||||
p = o.locatenew('p', a * N.x + b * N.y)
|
||||
R = RigidBody('R', o, N, m, (Io, p))
|
||||
I_check = inertia(N, Ix - b ** 2 * m, Iy - a ** 2 * m,
|
||||
Iz - m * (a ** 2 + b ** 2), m * a * b)
|
||||
assert isinstance(R.inertia, Inertia)
|
||||
assert R.inertia == (Io, p)
|
||||
assert R.central_inertia == I_check
|
||||
R.central_inertia = Io
|
||||
assert R.inertia == (Io, o)
|
||||
assert R.central_inertia == Io
|
||||
R.inertia = (Io, p)
|
||||
assert R.inertia == (Io, p)
|
||||
assert R.central_inertia == I_check
|
||||
# parse Inertia object
|
||||
R.inertia = Inertia(Io, o)
|
||||
assert R.inertia == (Io, o)
|
||||
|
||||
|
||||
def test_parallel_axis():
|
||||
N = ReferenceFrame('N')
|
||||
m, Ix, Iy, Iz, a, b = symbols('m, I_x, I_y, I_z, a, b')
|
||||
Io = inertia(N, Ix, Iy, Iz)
|
||||
o = Point('o')
|
||||
p = o.locatenew('p', a * N.x + b * N.y)
|
||||
R = RigidBody('R', o, N, m, (Io, o))
|
||||
Ip = R.parallel_axis(p)
|
||||
Ip_expected = inertia(N, Ix + m * b**2, Iy + m * a**2,
|
||||
Iz + m * (a**2 + b**2), ixy=-m * a * b)
|
||||
assert Ip == Ip_expected
|
||||
# Reference frame from which the parallel axis is viewed should not matter
|
||||
A = ReferenceFrame('A')
|
||||
A.orient_axis(N, N.z, 1)
|
||||
assert simplify(
|
||||
(R.parallel_axis(p, A) - Ip_expected).to_matrix(A)) == zeros(3, 3)
|
||||
|
||||
|
||||
def test_deprecated_set_potential_energy():
|
||||
m, g, h = symbols('m g h')
|
||||
A = ReferenceFrame('A')
|
||||
P = Point('P')
|
||||
I = Dyadic(0)
|
||||
B = RigidBody('B', P, A, m, (I, P))
|
||||
with warns_deprecated_sympy():
|
||||
B.set_potential_energy(m*g*h)
|
||||
@@ -0,0 +1,245 @@
|
||||
from sympy import symbols, Matrix, atan, zeros
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.physics.mechanics import (dynamicsymbols, Particle, Point,
|
||||
ReferenceFrame, SymbolicSystem)
|
||||
from sympy.testing.pytest import raises
|
||||
|
||||
# This class is going to be tested using a simple pendulum set up in x and y
|
||||
# coordinates
|
||||
x, y, u, v, lam = dynamicsymbols('x y u v lambda')
|
||||
m, l, g = symbols('m l g')
|
||||
|
||||
# Set up the different forms the equations can take
|
||||
# [1] Explicit form where the kinematics and dynamics are combined
|
||||
# x' = F(x, t, r, p)
|
||||
#
|
||||
# [2] Implicit form where the kinematics and dynamics are combined
|
||||
# M(x, p) x' = F(x, t, r, p)
|
||||
#
|
||||
# [3] Implicit form where the kinematics and dynamics are separate
|
||||
# M(q, p) u' = F(q, u, t, r, p)
|
||||
# q' = G(q, u, t, r, p)
|
||||
dyn_implicit_mat = Matrix([[1, 0, -x/m],
|
||||
[0, 1, -y/m],
|
||||
[0, 0, l**2/m]])
|
||||
|
||||
dyn_implicit_rhs = Matrix([0, 0, u**2 + v**2 - g*y])
|
||||
|
||||
comb_implicit_mat = Matrix([[1, 0, 0, 0, 0],
|
||||
[0, 1, 0, 0, 0],
|
||||
[0, 0, 1, 0, -x/m],
|
||||
[0, 0, 0, 1, -y/m],
|
||||
[0, 0, 0, 0, l**2/m]])
|
||||
|
||||
comb_implicit_rhs = Matrix([u, v, 0, 0, u**2 + v**2 - g*y])
|
||||
|
||||
kin_explicit_rhs = Matrix([u, v])
|
||||
|
||||
comb_explicit_rhs = comb_implicit_mat.LUsolve(comb_implicit_rhs)
|
||||
|
||||
# Set up a body and load to pass into the system
|
||||
theta = atan(x/y)
|
||||
N = ReferenceFrame('N')
|
||||
A = N.orientnew('A', 'Axis', [theta, N.z])
|
||||
O = Point('O')
|
||||
P = O.locatenew('P', l * A.x)
|
||||
|
||||
Pa = Particle('Pa', P, m)
|
||||
|
||||
bodies = [Pa]
|
||||
loads = [(P, g * m * N.x)]
|
||||
|
||||
# Set up some output equations to be given to SymbolicSystem
|
||||
# Change to make these fit the pendulum
|
||||
PE = symbols("PE")
|
||||
out_eqns = {PE: m*g*(l+y)}
|
||||
|
||||
# Set up remaining arguments that can be passed to SymbolicSystem
|
||||
alg_con = [2]
|
||||
alg_con_full = [4]
|
||||
coordinates = (x, y, lam)
|
||||
speeds = (u, v)
|
||||
states = (x, y, u, v, lam)
|
||||
coord_idxs = (0, 1)
|
||||
speed_idxs = (2, 3)
|
||||
|
||||
|
||||
def test_form_1():
|
||||
symsystem1 = SymbolicSystem(states, comb_explicit_rhs,
|
||||
alg_con=alg_con_full, output_eqns=out_eqns,
|
||||
coord_idxs=coord_idxs, speed_idxs=speed_idxs,
|
||||
bodies=bodies, loads=loads)
|
||||
|
||||
assert symsystem1.coordinates == Matrix([x, y])
|
||||
assert symsystem1.speeds == Matrix([u, v])
|
||||
assert symsystem1.states == Matrix([x, y, u, v, lam])
|
||||
|
||||
assert symsystem1.alg_con == [4]
|
||||
|
||||
inter = comb_explicit_rhs
|
||||
assert simplify(symsystem1.comb_explicit_rhs - inter) == zeros(5, 1)
|
||||
|
||||
assert set(symsystem1.dynamic_symbols()) == {y, v, lam, u, x}
|
||||
assert type(symsystem1.dynamic_symbols()) == tuple
|
||||
assert set(symsystem1.constant_symbols()) == {l, g, m}
|
||||
assert type(symsystem1.constant_symbols()) == tuple
|
||||
|
||||
assert symsystem1.output_eqns == out_eqns
|
||||
|
||||
assert symsystem1.bodies == (Pa,)
|
||||
assert symsystem1.loads == ((P, g * m * N.x),)
|
||||
|
||||
|
||||
def test_form_2():
|
||||
symsystem2 = SymbolicSystem(coordinates, comb_implicit_rhs, speeds=speeds,
|
||||
mass_matrix=comb_implicit_mat,
|
||||
alg_con=alg_con_full, output_eqns=out_eqns,
|
||||
bodies=bodies, loads=loads)
|
||||
|
||||
assert symsystem2.coordinates == Matrix([x, y, lam])
|
||||
assert symsystem2.speeds == Matrix([u, v])
|
||||
assert symsystem2.states == Matrix([x, y, lam, u, v])
|
||||
|
||||
assert symsystem2.alg_con == [4]
|
||||
|
||||
inter = comb_implicit_rhs
|
||||
assert simplify(symsystem2.comb_implicit_rhs - inter) == zeros(5, 1)
|
||||
assert simplify(symsystem2.comb_implicit_mat-comb_implicit_mat) == zeros(5)
|
||||
|
||||
assert set(symsystem2.dynamic_symbols()) == {y, v, lam, u, x}
|
||||
assert type(symsystem2.dynamic_symbols()) == tuple
|
||||
assert set(symsystem2.constant_symbols()) == {l, g, m}
|
||||
assert type(symsystem2.constant_symbols()) == tuple
|
||||
|
||||
inter = comb_explicit_rhs
|
||||
symsystem2.compute_explicit_form()
|
||||
assert simplify(symsystem2.comb_explicit_rhs - inter) == zeros(5, 1)
|
||||
|
||||
|
||||
assert symsystem2.output_eqns == out_eqns
|
||||
|
||||
assert symsystem2.bodies == (Pa,)
|
||||
assert symsystem2.loads == ((P, g * m * N.x),)
|
||||
|
||||
|
||||
def test_form_3():
|
||||
symsystem3 = SymbolicSystem(states, dyn_implicit_rhs,
|
||||
mass_matrix=dyn_implicit_mat,
|
||||
coordinate_derivatives=kin_explicit_rhs,
|
||||
alg_con=alg_con, coord_idxs=coord_idxs,
|
||||
speed_idxs=speed_idxs, bodies=bodies,
|
||||
loads=loads)
|
||||
|
||||
assert symsystem3.coordinates == Matrix([x, y])
|
||||
assert symsystem3.speeds == Matrix([u, v])
|
||||
assert symsystem3.states == Matrix([x, y, u, v, lam])
|
||||
|
||||
assert symsystem3.alg_con == [4]
|
||||
|
||||
inter1 = kin_explicit_rhs
|
||||
inter2 = dyn_implicit_rhs
|
||||
assert simplify(symsystem3.kin_explicit_rhs - inter1) == zeros(2, 1)
|
||||
assert simplify(symsystem3.dyn_implicit_mat - dyn_implicit_mat) == zeros(3)
|
||||
assert simplify(symsystem3.dyn_implicit_rhs - inter2) == zeros(3, 1)
|
||||
|
||||
inter = comb_implicit_rhs
|
||||
assert simplify(symsystem3.comb_implicit_rhs - inter) == zeros(5, 1)
|
||||
assert simplify(symsystem3.comb_implicit_mat-comb_implicit_mat) == zeros(5)
|
||||
|
||||
inter = comb_explicit_rhs
|
||||
symsystem3.compute_explicit_form()
|
||||
assert simplify(symsystem3.comb_explicit_rhs - inter) == zeros(5, 1)
|
||||
|
||||
assert set(symsystem3.dynamic_symbols()) == {y, v, lam, u, x}
|
||||
assert type(symsystem3.dynamic_symbols()) == tuple
|
||||
assert set(symsystem3.constant_symbols()) == {l, g, m}
|
||||
assert type(symsystem3.constant_symbols()) == tuple
|
||||
|
||||
assert symsystem3.output_eqns == {}
|
||||
|
||||
assert symsystem3.bodies == (Pa,)
|
||||
assert symsystem3.loads == ((P, g * m * N.x),)
|
||||
|
||||
|
||||
def test_property_attributes():
|
||||
symsystem = SymbolicSystem(states, comb_explicit_rhs,
|
||||
alg_con=alg_con_full, output_eqns=out_eqns,
|
||||
coord_idxs=coord_idxs, speed_idxs=speed_idxs,
|
||||
bodies=bodies, loads=loads)
|
||||
|
||||
with raises(AttributeError):
|
||||
symsystem.bodies = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.coordinates = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.dyn_implicit_rhs = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.comb_implicit_rhs = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.loads = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.dyn_implicit_mat = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.comb_implicit_mat = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.kin_explicit_rhs = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.comb_explicit_rhs = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.speeds = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.states = 42
|
||||
with raises(AttributeError):
|
||||
symsystem.alg_con = 42
|
||||
|
||||
|
||||
def test_not_specified_errors():
|
||||
"""This test will cover errors that arise from trying to access attributes
|
||||
that were not specified upon object creation or were specified on creation
|
||||
and the user tries to recalculate them."""
|
||||
# Trying to access form 2 when form 1 given
|
||||
# Trying to access form 3 when form 2 given
|
||||
|
||||
symsystem1 = SymbolicSystem(states, comb_explicit_rhs)
|
||||
|
||||
with raises(AttributeError):
|
||||
symsystem1.comb_implicit_mat
|
||||
with raises(AttributeError):
|
||||
symsystem1.comb_implicit_rhs
|
||||
with raises(AttributeError):
|
||||
symsystem1.dyn_implicit_mat
|
||||
with raises(AttributeError):
|
||||
symsystem1.dyn_implicit_rhs
|
||||
with raises(AttributeError):
|
||||
symsystem1.kin_explicit_rhs
|
||||
with raises(AttributeError):
|
||||
symsystem1.compute_explicit_form()
|
||||
|
||||
symsystem2 = SymbolicSystem(coordinates, comb_implicit_rhs, speeds=speeds,
|
||||
mass_matrix=comb_implicit_mat)
|
||||
|
||||
with raises(AttributeError):
|
||||
symsystem2.dyn_implicit_mat
|
||||
with raises(AttributeError):
|
||||
symsystem2.dyn_implicit_rhs
|
||||
with raises(AttributeError):
|
||||
symsystem2.kin_explicit_rhs
|
||||
|
||||
# Attribute error when trying to access coordinates and speeds when only the
|
||||
# states were given.
|
||||
with raises(AttributeError):
|
||||
symsystem1.coordinates
|
||||
with raises(AttributeError):
|
||||
symsystem1.speeds
|
||||
|
||||
# Attribute error when trying to access bodies and loads when they are not
|
||||
# given
|
||||
with raises(AttributeError):
|
||||
symsystem1.bodies
|
||||
with raises(AttributeError):
|
||||
symsystem1.loads
|
||||
|
||||
# Attribute error when trying to access comb_explicit_rhs before it was
|
||||
# calculated
|
||||
with raises(AttributeError):
|
||||
symsystem2.comb_explicit_rhs
|
||||
+831
@@ -0,0 +1,831 @@
|
||||
import pytest
|
||||
|
||||
from sympy.core.symbol import symbols
|
||||
from sympy.core.sympify import sympify
|
||||
from sympy.functions.elementary.trigonometric import cos, sin
|
||||
from sympy.matrices.dense import eye, zeros
|
||||
from sympy.matrices.immutable import ImmutableMatrix
|
||||
from sympy.physics.mechanics import (
|
||||
Force, KanesMethod, LagrangesMethod, Particle, PinJoint, Point,
|
||||
PrismaticJoint, ReferenceFrame, RigidBody, Torque, TorqueActuator, System,
|
||||
dynamicsymbols)
|
||||
from sympy.simplify.simplify import simplify
|
||||
from sympy.solvers.solvers import solve
|
||||
|
||||
t = dynamicsymbols._t # type: ignore
|
||||
q = dynamicsymbols('q:6') # type: ignore
|
||||
qd = dynamicsymbols('q:6', 1) # type: ignore
|
||||
u = dynamicsymbols('u:6') # type: ignore
|
||||
ua = dynamicsymbols('ua:3') # type: ignore
|
||||
|
||||
|
||||
class TestSystemBase:
|
||||
@pytest.fixture()
|
||||
def _empty_system_setup(self):
|
||||
self.system = System(ReferenceFrame('frame'), Point('fixed_point'))
|
||||
|
||||
def _empty_system_check(self, exclude=()):
|
||||
matrices = ('q_ind', 'q_dep', 'q', 'u_ind', 'u_dep', 'u', 'u_aux',
|
||||
'kdes', 'holonomic_constraints', 'nonholonomic_constraints')
|
||||
tuples = ('loads', 'bodies', 'joints', 'actuators')
|
||||
for attr in matrices:
|
||||
if attr not in exclude:
|
||||
assert getattr(self.system, attr)[:] == []
|
||||
for attr in tuples:
|
||||
if attr not in exclude:
|
||||
assert getattr(self.system, attr) == ()
|
||||
if 'eom_method' not in exclude:
|
||||
assert self.system.eom_method is None
|
||||
|
||||
def _create_filled_system(self, with_speeds=True):
|
||||
self.system = System(ReferenceFrame('frame'), Point('fixed_point'))
|
||||
u = dynamicsymbols('u:6') if with_speeds else qd
|
||||
self.bodies = symbols('rb1:5', cls=RigidBody)
|
||||
self.joints = (
|
||||
PinJoint('J1', self.bodies[0], self.bodies[1], q[0], u[0]),
|
||||
PrismaticJoint('J2', self.bodies[1], self.bodies[2], q[1], u[1]),
|
||||
PinJoint('J3', self.bodies[2], self.bodies[3], q[2], u[2])
|
||||
)
|
||||
self.system.add_joints(*self.joints)
|
||||
self.system.add_coordinates(q[3], independent=[False])
|
||||
self.system.add_speeds(u[3], independent=False)
|
||||
if with_speeds:
|
||||
self.system.add_kdes(u[3] - qd[3])
|
||||
self.system.add_auxiliary_speeds(ua[0], ua[1])
|
||||
self.system.add_holonomic_constraints(q[2] - q[0] + q[1])
|
||||
self.system.add_nonholonomic_constraints(u[3] - qd[1] + u[2])
|
||||
self.system.u_ind = u[:2]
|
||||
self.system.u_dep = u[2:4]
|
||||
self.q_ind, self.q_dep = self.system.q_ind[:], self.system.q_dep[:]
|
||||
self.u_ind, self.u_dep = self.system.u_ind[:], self.system.u_dep[:]
|
||||
self.kdes = self.system.kdes[:]
|
||||
self.hc = self.system.holonomic_constraints[:]
|
||||
self.vc = self.system.velocity_constraints[:]
|
||||
self.nhc = self.system.nonholonomic_constraints[:]
|
||||
|
||||
@pytest.fixture()
|
||||
def _filled_system_setup(self):
|
||||
self._create_filled_system(with_speeds=True)
|
||||
|
||||
@pytest.fixture()
|
||||
def _filled_system_setup_no_speeds(self):
|
||||
self._create_filled_system(with_speeds=False)
|
||||
|
||||
def _filled_system_check(self, exclude=()):
|
||||
assert 'q_ind' in exclude or self.system.q_ind[:] == q[:3]
|
||||
assert 'q_dep' in exclude or self.system.q_dep[:] == [q[3]]
|
||||
assert 'q' in exclude or self.system.q[:] == q[:4]
|
||||
assert 'u_ind' in exclude or self.system.u_ind[:] == u[:2]
|
||||
assert 'u_dep' in exclude or self.system.u_dep[:] == u[2:4]
|
||||
assert 'u' in exclude or self.system.u[:] == u[:4]
|
||||
assert 'u_aux' in exclude or self.system.u_aux[:] == ua[:2]
|
||||
assert 'kdes' in exclude or self.system.kdes[:] == [
|
||||
ui - qdi for ui, qdi in zip(u[:4], qd[:4])]
|
||||
assert ('holonomic_constraints' in exclude or
|
||||
self.system.holonomic_constraints[:] == [q[2] - q[0] + q[1]])
|
||||
assert ('nonholonomic_constraints' in exclude or
|
||||
self.system.nonholonomic_constraints[:] == [u[3] - qd[1] + u[2]]
|
||||
)
|
||||
assert ('velocity_constraints' in exclude or
|
||||
self.system.velocity_constraints[:] == [
|
||||
qd[2] - qd[0] + qd[1], u[3] - qd[1] + u[2]])
|
||||
assert ('bodies' in exclude or
|
||||
self.system.bodies == tuple(self.bodies))
|
||||
assert ('joints' in exclude or
|
||||
self.system.joints == tuple(self.joints))
|
||||
|
||||
@pytest.fixture()
|
||||
def _moving_point_mass(self, _empty_system_setup):
|
||||
self.system.q_ind = q[0]
|
||||
self.system.u_ind = u[0]
|
||||
self.system.kdes = u[0] - q[0].diff(t)
|
||||
p = Particle('p', mass=symbols('m'))
|
||||
self.system.add_bodies(p)
|
||||
p.masscenter.set_pos(self.system.fixed_point, q[0] * self.system.x)
|
||||
|
||||
|
||||
class TestSystem(TestSystemBase):
|
||||
def test_empty_system(self, _empty_system_setup):
|
||||
self._empty_system_check()
|
||||
self.system.validate_system()
|
||||
|
||||
def test_filled_system(self, _filled_system_setup):
|
||||
self._filled_system_check()
|
||||
self.system.validate_system()
|
||||
|
||||
@pytest.mark.parametrize('frame', [None, ReferenceFrame('frame')])
|
||||
@pytest.mark.parametrize('fixed_point', [None, Point('fixed_point')])
|
||||
def test_init(self, frame, fixed_point):
|
||||
if fixed_point is None and frame is None:
|
||||
self.system = System()
|
||||
else:
|
||||
self.system = System(frame, fixed_point)
|
||||
if fixed_point is None:
|
||||
assert self.system.fixed_point.name == 'inertial_point'
|
||||
else:
|
||||
assert self.system.fixed_point == fixed_point
|
||||
if frame is None:
|
||||
assert self.system.frame.name == 'inertial_frame'
|
||||
else:
|
||||
assert self.system.frame == frame
|
||||
self._empty_system_check()
|
||||
assert isinstance(self.system.q_ind, ImmutableMatrix)
|
||||
assert isinstance(self.system.q_dep, ImmutableMatrix)
|
||||
assert isinstance(self.system.q, ImmutableMatrix)
|
||||
assert isinstance(self.system.u_ind, ImmutableMatrix)
|
||||
assert isinstance(self.system.u_dep, ImmutableMatrix)
|
||||
assert isinstance(self.system.u, ImmutableMatrix)
|
||||
assert isinstance(self.system.kdes, ImmutableMatrix)
|
||||
assert isinstance(self.system.holonomic_constraints, ImmutableMatrix)
|
||||
assert isinstance(self.system.nonholonomic_constraints, ImmutableMatrix)
|
||||
|
||||
def test_from_newtonian_rigid_body(self):
|
||||
rb = RigidBody('body')
|
||||
self.system = System.from_newtonian(rb)
|
||||
assert self.system.fixed_point == rb.masscenter
|
||||
assert self.system.frame == rb.frame
|
||||
self._empty_system_check(exclude=('bodies',))
|
||||
self.system.bodies = (rb,)
|
||||
|
||||
def test_from_newtonian_particle(self):
|
||||
pt = Particle('particle')
|
||||
with pytest.raises(TypeError):
|
||||
System.from_newtonian(pt)
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs, exp_q_ind, exp_q_dep, exp_q', [
|
||||
(q[:3], {}, q[:3], [], q[:3]),
|
||||
(q[:3], {'independent': True}, q[:3], [], q[:3]),
|
||||
(q[:3], {'independent': False}, [], q[:3], q[:3]),
|
||||
(q[:3], {'independent': [True, False, True]}, [q[0], q[2]], [q[1]],
|
||||
[q[0], q[2], q[1]]),
|
||||
])
|
||||
def test_coordinates(self, _empty_system_setup, args, kwargs,
|
||||
exp_q_ind, exp_q_dep, exp_q):
|
||||
# Test add_coordinates
|
||||
self.system.add_coordinates(*args, **kwargs)
|
||||
assert self.system.q_ind[:] == exp_q_ind
|
||||
assert self.system.q_dep[:] == exp_q_dep
|
||||
assert self.system.q[:] == exp_q
|
||||
self._empty_system_check(exclude=('q_ind', 'q_dep', 'q'))
|
||||
# Test setter for q_ind and q_dep
|
||||
self.system.q_ind = exp_q_ind
|
||||
self.system.q_dep = exp_q_dep
|
||||
assert self.system.q_ind[:] == exp_q_ind
|
||||
assert self.system.q_dep[:] == exp_q_dep
|
||||
assert self.system.q[:] == exp_q
|
||||
self._empty_system_check(exclude=('q_ind', 'q_dep', 'q'))
|
||||
|
||||
@pytest.mark.parametrize('func', ['add_coordinates', 'add_speeds'])
|
||||
@pytest.mark.parametrize('args, kwargs', [
|
||||
((q[0], q[5]), {}),
|
||||
((u[0], u[5]), {}),
|
||||
((q[0],), {'independent': False}),
|
||||
((u[0],), {'independent': False}),
|
||||
((u[0], q[5]), {}),
|
||||
((symbols('a'), q[5]), {}),
|
||||
])
|
||||
def test_coordinates_speeds_invalid(self, _filled_system_setup, func, args,
|
||||
kwargs):
|
||||
with pytest.raises(ValueError):
|
||||
getattr(self.system, func)(*args, **kwargs)
|
||||
self._filled_system_check()
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs, exp_u_ind, exp_u_dep, exp_u', [
|
||||
(u[:3], {}, u[:3], [], u[:3]),
|
||||
(u[:3], {'independent': True}, u[:3], [], u[:3]),
|
||||
(u[:3], {'independent': False}, [], u[:3], u[:3]),
|
||||
(u[:3], {'independent': [True, False, True]}, [u[0], u[2]], [u[1]],
|
||||
[u[0], u[2], u[1]]),
|
||||
])
|
||||
def test_speeds(self, _empty_system_setup, args, kwargs, exp_u_ind,
|
||||
exp_u_dep, exp_u):
|
||||
# Test add_speeds
|
||||
self.system.add_speeds(*args, **kwargs)
|
||||
assert self.system.u_ind[:] == exp_u_ind
|
||||
assert self.system.u_dep[:] == exp_u_dep
|
||||
assert self.system.u[:] == exp_u
|
||||
self._empty_system_check(exclude=('u_ind', 'u_dep', 'u'))
|
||||
# Test setter for u_ind and u_dep
|
||||
self.system.u_ind = exp_u_ind
|
||||
self.system.u_dep = exp_u_dep
|
||||
assert self.system.u_ind[:] == exp_u_ind
|
||||
assert self.system.u_dep[:] == exp_u_dep
|
||||
assert self.system.u[:] == exp_u
|
||||
self._empty_system_check(exclude=('u_ind', 'u_dep', 'u'))
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs, exp_u_aux', [
|
||||
(ua[:3], {}, ua[:3]),
|
||||
])
|
||||
def test_auxiliary_speeds(self, _empty_system_setup, args, kwargs,
|
||||
exp_u_aux):
|
||||
# Test add_speeds
|
||||
self.system.add_auxiliary_speeds(*args, **kwargs)
|
||||
assert self.system.u_aux[:] == exp_u_aux
|
||||
self._empty_system_check(exclude=('u_aux',))
|
||||
# Test setter for u_ind and u_dep
|
||||
self.system.u_aux = exp_u_aux
|
||||
assert self.system.u_aux[:] == exp_u_aux
|
||||
self._empty_system_check(exclude=('u_aux',))
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs', [
|
||||
((ua[2], q[0]), {}),
|
||||
((ua[2], u[1]), {}),
|
||||
((ua[0], ua[2]), {}),
|
||||
((symbols('a'), ua[2]), {}),
|
||||
])
|
||||
def test_auxiliary_invalid(self, _filled_system_setup, args, kwargs):
|
||||
with pytest.raises(ValueError):
|
||||
self.system.add_auxiliary_speeds(*args, **kwargs)
|
||||
self._filled_system_check()
|
||||
|
||||
@pytest.mark.parametrize('prop, add_func, args, kwargs', [
|
||||
('q_ind', 'add_coordinates', (q[0],), {}),
|
||||
('q_dep', 'add_coordinates', (q[3],), {'independent': False}),
|
||||
('u_ind', 'add_speeds', (u[0],), {}),
|
||||
('u_dep', 'add_speeds', (u[3],), {'independent': False}),
|
||||
('u_aux', 'add_auxiliary_speeds', (ua[2],), {}),
|
||||
('kdes', 'add_kdes', (qd[0] - u[0],), {}),
|
||||
('holonomic_constraints', 'add_holonomic_constraints',
|
||||
(q[0] - q[1],), {}),
|
||||
('nonholonomic_constraints', 'add_nonholonomic_constraints',
|
||||
(u[0] - u[1],), {}),
|
||||
('bodies', 'add_bodies', (RigidBody('body'),), {}),
|
||||
('loads', 'add_loads', (Force(Point('P'), ReferenceFrame('N').x),), {}),
|
||||
('actuators', 'add_actuators', (TorqueActuator(
|
||||
symbols('T'), ReferenceFrame('N').x, ReferenceFrame('A')),), {}),
|
||||
])
|
||||
def test_add_after_reset(self, _filled_system_setup, prop, add_func, args,
|
||||
kwargs):
|
||||
setattr(self.system, prop, ())
|
||||
exclude = (prop, 'q', 'u')
|
||||
if prop in ('holonomic_constraints', 'nonholonomic_constraints'):
|
||||
exclude += ('velocity_constraints',)
|
||||
self._filled_system_check(exclude=exclude)
|
||||
assert list(getattr(self.system, prop)[:]) == []
|
||||
getattr(self.system, add_func)(*args, **kwargs)
|
||||
assert list(getattr(self.system, prop)[:]) == list(args)
|
||||
|
||||
@pytest.mark.parametrize('prop, add_func, value, error', [
|
||||
('q_ind', 'add_coordinates', symbols('a'), ValueError),
|
||||
('q_dep', 'add_coordinates', symbols('a'), ValueError),
|
||||
('u_ind', 'add_speeds', symbols('a'), ValueError),
|
||||
('u_dep', 'add_speeds', symbols('a'), ValueError),
|
||||
('u_aux', 'add_auxiliary_speeds', symbols('a'), ValueError),
|
||||
('kdes', 'add_kdes', 7, TypeError),
|
||||
('holonomic_constraints', 'add_holonomic_constraints', 7, TypeError),
|
||||
('nonholonomic_constraints', 'add_nonholonomic_constraints', 7,
|
||||
TypeError),
|
||||
('bodies', 'add_bodies', symbols('a'), TypeError),
|
||||
('loads', 'add_loads', symbols('a'), TypeError),
|
||||
('actuators', 'add_actuators', symbols('a'), TypeError),
|
||||
])
|
||||
def test_type_error(self, _filled_system_setup, prop, add_func, value,
|
||||
error):
|
||||
with pytest.raises(error):
|
||||
getattr(self.system, add_func)(value)
|
||||
with pytest.raises(error):
|
||||
setattr(self.system, prop, value)
|
||||
self._filled_system_check()
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs, exp_kdes', [
|
||||
((), {}, [ui - qdi for ui, qdi in zip(u[:4], qd[:4])]),
|
||||
((u[4] - qd[4], u[5] - qd[5]), {},
|
||||
[ui - qdi for ui, qdi in zip(u[:6], qd[:6])]),
|
||||
])
|
||||
def test_kdes(self, _filled_system_setup, args, kwargs, exp_kdes):
|
||||
# Test add_speeds
|
||||
self.system.add_kdes(*args, **kwargs)
|
||||
self._filled_system_check(exclude=('kdes',))
|
||||
assert self.system.kdes[:] == exp_kdes
|
||||
# Test setter for kdes
|
||||
self.system.kdes = exp_kdes
|
||||
self._filled_system_check(exclude=('kdes',))
|
||||
assert self.system.kdes[:] == exp_kdes
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs', [
|
||||
((u[0] - qd[0], u[4] - qd[4]), {}),
|
||||
((-(u[0] - qd[0]), u[4] - qd[4]), {}),
|
||||
(([u[0] - u[0], u[4] - qd[4]]), {}),
|
||||
])
|
||||
def test_kdes_invalid(self, _filled_system_setup, args, kwargs):
|
||||
with pytest.raises(ValueError):
|
||||
self.system.add_kdes(*args, **kwargs)
|
||||
self._filled_system_check()
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs, exp_con', [
|
||||
((), {}, [q[2] - q[0] + q[1]]),
|
||||
((q[4] - q[5], q[5] + q[3]), {},
|
||||
[q[2] - q[0] + q[1], q[4] - q[5], q[5] + q[3]]),
|
||||
])
|
||||
def test_holonomic_constraints(self, _filled_system_setup, args, kwargs,
|
||||
exp_con):
|
||||
exclude = ('holonomic_constraints', 'velocity_constraints')
|
||||
exp_vel_con = [c.diff(t) for c in exp_con] + self.nhc
|
||||
# Test add_holonomic_constraints
|
||||
self.system.add_holonomic_constraints(*args, **kwargs)
|
||||
self._filled_system_check(exclude=exclude)
|
||||
assert self.system.holonomic_constraints[:] == exp_con
|
||||
assert self.system.velocity_constraints[:] == exp_vel_con
|
||||
# Test setter for holonomic_constraints
|
||||
self.system.holonomic_constraints = exp_con
|
||||
self._filled_system_check(exclude=exclude)
|
||||
assert self.system.holonomic_constraints[:] == exp_con
|
||||
assert self.system.velocity_constraints[:] == exp_vel_con
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs', [
|
||||
((q[2] - q[0] + q[1], q[4] - q[3]), {}),
|
||||
((-(q[2] - q[0] + q[1]), q[4] - q[3]), {}),
|
||||
((q[0] - q[0], q[4] - q[3]), {}),
|
||||
])
|
||||
def test_holonomic_constraints_invalid(self, _filled_system_setup, args,
|
||||
kwargs):
|
||||
with pytest.raises(ValueError):
|
||||
self.system.add_holonomic_constraints(*args, **kwargs)
|
||||
self._filled_system_check()
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs, exp_con', [
|
||||
((), {}, [u[3] - qd[1] + u[2]]),
|
||||
((u[4] - u[5], u[5] + u[3]), {},
|
||||
[u[3] - qd[1] + u[2], u[4] - u[5], u[5] + u[3]]),
|
||||
])
|
||||
def test_nonholonomic_constraints(self, _filled_system_setup, args, kwargs,
|
||||
exp_con):
|
||||
exclude = ('nonholonomic_constraints', 'velocity_constraints')
|
||||
exp_vel_con = self.vc[:len(self.hc)] + exp_con
|
||||
# Test add_nonholonomic_constraints
|
||||
self.system.add_nonholonomic_constraints(*args, **kwargs)
|
||||
self._filled_system_check(exclude=exclude)
|
||||
assert self.system.nonholonomic_constraints[:] == exp_con
|
||||
assert self.system.velocity_constraints[:] == exp_vel_con
|
||||
# Test setter for nonholonomic_constraints
|
||||
self.system.nonholonomic_constraints = exp_con
|
||||
self._filled_system_check(exclude=exclude)
|
||||
assert self.system.nonholonomic_constraints[:] == exp_con
|
||||
assert self.system.velocity_constraints[:] == exp_vel_con
|
||||
|
||||
@pytest.mark.parametrize('args, kwargs', [
|
||||
((u[3] - qd[1] + u[2], u[4] - u[3]), {}),
|
||||
((-(u[3] - qd[1] + u[2]), u[4] - u[3]), {}),
|
||||
((u[0] - u[0], u[4] - u[3]), {}),
|
||||
(([u[0] - u[0], u[4] - u[3]]), {}),
|
||||
])
|
||||
def test_nonholonomic_constraints_invalid(self, _filled_system_setup, args,
|
||||
kwargs):
|
||||
with pytest.raises(ValueError):
|
||||
self.system.add_nonholonomic_constraints(*args, **kwargs)
|
||||
self._filled_system_check()
|
||||
|
||||
@pytest.mark.parametrize('constraints, expected', [
|
||||
([], []),
|
||||
(qd[2] - qd[0] + qd[1], [qd[2] - qd[0] + qd[1]]),
|
||||
([qd[2] + qd[1], u[2] - u[1]], [qd[2] + qd[1], u[2] - u[1]]),
|
||||
])
|
||||
def test_velocity_constraints_overwrite(self, _filled_system_setup,
|
||||
constraints, expected):
|
||||
self.system.velocity_constraints = constraints
|
||||
self._filled_system_check(exclude=('velocity_constraints',))
|
||||
assert self.system.velocity_constraints[:] == expected
|
||||
|
||||
def test_velocity_constraints_back_to_auto(self, _filled_system_setup):
|
||||
self.system.velocity_constraints = qd[3] - qd[2]
|
||||
self._filled_system_check(exclude=('velocity_constraints',))
|
||||
assert self.system.velocity_constraints[:] == [qd[3] - qd[2]]
|
||||
self.system.velocity_constraints = None
|
||||
self._filled_system_check()
|
||||
|
||||
def test_bodies(self, _filled_system_setup):
|
||||
rb1, rb2 = RigidBody('rb1'), RigidBody('rb2')
|
||||
p1, p2 = Particle('p1'), Particle('p2')
|
||||
self.system.add_bodies(rb1, p1)
|
||||
assert self.system.bodies == (*self.bodies, rb1, p1)
|
||||
self.system.add_bodies(p2)
|
||||
assert self.system.bodies == (*self.bodies, rb1, p1, p2)
|
||||
self.system.bodies = []
|
||||
assert self.system.bodies == ()
|
||||
self.system.bodies = p2
|
||||
assert self.system.bodies == (p2,)
|
||||
symb = symbols('symb')
|
||||
pytest.raises(TypeError, lambda: self.system.add_bodies(symb))
|
||||
pytest.raises(ValueError, lambda: self.system.add_bodies(p2))
|
||||
with pytest.raises(TypeError):
|
||||
self.system.bodies = (rb1, rb2, p1, p2, symb)
|
||||
assert self.system.bodies == (p2,)
|
||||
|
||||
def test_add_loads(self):
|
||||
system = System()
|
||||
N, A = ReferenceFrame('N'), ReferenceFrame('A')
|
||||
rb1 = RigidBody('rb1', frame=N)
|
||||
mc1 = Point('mc1')
|
||||
p1 = Particle('p1', mc1)
|
||||
system.add_loads(Torque(rb1, N.x), (mc1, A.x), Force(p1, A.x))
|
||||
assert system.loads == ((N, N.x), (mc1, A.x), (mc1, A.x))
|
||||
system.loads = [(A, A.x)]
|
||||
assert system.loads == ((A, A.x),)
|
||||
pytest.raises(ValueError, lambda: system.add_loads((N, N.x, N.y)))
|
||||
with pytest.raises(TypeError):
|
||||
system.loads = (N, N.x)
|
||||
assert system.loads == ((A, A.x),)
|
||||
|
||||
def test_add_actuators(self):
|
||||
system = System()
|
||||
N, A = ReferenceFrame('N'), ReferenceFrame('A')
|
||||
act1 = TorqueActuator(symbols('T1'), N.x, N)
|
||||
act2 = TorqueActuator(symbols('T2'), N.y, N, A)
|
||||
system.add_actuators(act1)
|
||||
assert system.actuators == (act1,)
|
||||
assert system.loads == ()
|
||||
system.actuators = (act2,)
|
||||
assert system.actuators == (act2,)
|
||||
|
||||
def test_add_joints(self):
|
||||
q1, q2, q3, q4, u1, u2, u3 = dynamicsymbols('q1:5 u1:4')
|
||||
rb1, rb2, rb3, rb4, rb5 = symbols('rb1:6', cls=RigidBody)
|
||||
J1 = PinJoint('J1', rb1, rb2, q1, u1)
|
||||
J2 = PrismaticJoint('J2', rb2, rb3, q2, u2)
|
||||
J3 = PinJoint('J3', rb3, rb4, q3, u3)
|
||||
J_lag = PinJoint('J_lag', rb4, rb5, q4, q4.diff(t))
|
||||
system = System()
|
||||
system.add_joints(J1)
|
||||
assert system.joints == (J1,)
|
||||
assert system.bodies == (rb1, rb2)
|
||||
assert system.q_ind == ImmutableMatrix([q1])
|
||||
assert system.u_ind == ImmutableMatrix([u1])
|
||||
assert system.kdes == ImmutableMatrix([u1 - q1.diff(t)])
|
||||
system.add_bodies(rb4)
|
||||
system.add_coordinates(q3)
|
||||
system.add_kdes(u3 - q3.diff(t))
|
||||
system.add_joints(J3)
|
||||
assert system.joints == (J1, J3)
|
||||
assert system.bodies == (rb1, rb2, rb4, rb3)
|
||||
assert system.q_ind == ImmutableMatrix([q1, q3])
|
||||
assert system.u_ind == ImmutableMatrix([u1, u3])
|
||||
assert system.kdes == ImmutableMatrix(
|
||||
[u1 - q1.diff(t), u3 - q3.diff(t)])
|
||||
system.add_kdes(-(u2 - q2.diff(t)))
|
||||
system.add_joints(J2)
|
||||
assert system.joints == (J1, J3, J2)
|
||||
assert system.bodies == (rb1, rb2, rb4, rb3)
|
||||
assert system.q_ind == ImmutableMatrix([q1, q3, q2])
|
||||
assert system.u_ind == ImmutableMatrix([u1, u3, u2])
|
||||
assert system.kdes == ImmutableMatrix([u1 - q1.diff(t), u3 - q3.diff(t),
|
||||
-(u2 - q2.diff(t))])
|
||||
system.add_joints(J_lag)
|
||||
assert system.joints == (J1, J3, J2, J_lag)
|
||||
assert system.bodies == (rb1, rb2, rb4, rb3, rb5)
|
||||
assert system.q_ind == ImmutableMatrix([q1, q3, q2, q4])
|
||||
assert system.u_ind == ImmutableMatrix([u1, u3, u2, q4.diff(t)])
|
||||
assert system.kdes == ImmutableMatrix([u1 - q1.diff(t), u3 - q3.diff(t),
|
||||
-(u2 - q2.diff(t))])
|
||||
assert system.q_dep[:] == []
|
||||
assert system.u_dep[:] == []
|
||||
pytest.raises(ValueError, lambda: system.add_joints(J2))
|
||||
pytest.raises(TypeError, lambda: system.add_joints(rb1))
|
||||
|
||||
def test_joints_setter(self, _filled_system_setup):
|
||||
self.system.joints = self.joints[1:]
|
||||
assert self.system.joints == self.joints[1:]
|
||||
self._filled_system_check(exclude=('joints',))
|
||||
self.system.q_ind = ()
|
||||
self.system.u_ind = ()
|
||||
self.system.joints = self.joints
|
||||
self._filled_system_check()
|
||||
|
||||
@pytest.mark.parametrize('name, joint_index', [
|
||||
('J1', 0),
|
||||
('J2', 1),
|
||||
('not_existing', None),
|
||||
])
|
||||
def test_get_joint(self, _filled_system_setup, name, joint_index):
|
||||
joint = self.system.get_joint(name)
|
||||
if joint_index is None:
|
||||
assert joint is None
|
||||
else:
|
||||
assert joint == self.joints[joint_index]
|
||||
|
||||
@pytest.mark.parametrize('name, body_index', [
|
||||
('rb1', 0),
|
||||
('rb3', 2),
|
||||
('not_existing', None),
|
||||
])
|
||||
def test_get_body(self, _filled_system_setup, name, body_index):
|
||||
body = self.system.get_body(name)
|
||||
if body_index is None:
|
||||
assert body is None
|
||||
else:
|
||||
assert body == self.bodies[body_index]
|
||||
|
||||
@pytest.mark.parametrize('eom_method', [KanesMethod, LagrangesMethod])
|
||||
def test_form_eoms_calls_subclass(self, _moving_point_mass, eom_method):
|
||||
class MyMethod(eom_method):
|
||||
pass
|
||||
|
||||
self.system.form_eoms(eom_method=MyMethod)
|
||||
assert isinstance(self.system.eom_method, MyMethod)
|
||||
|
||||
@pytest.mark.parametrize('kwargs, expected', [
|
||||
({}, ImmutableMatrix([[-1, 0], [0, symbols('m')]])),
|
||||
({'explicit_kinematics': True}, ImmutableMatrix([[1, 0],
|
||||
[0, symbols('m')]])),
|
||||
])
|
||||
def test_system_kane_form_eoms_kwargs(self, _moving_point_mass, kwargs,
|
||||
expected):
|
||||
self.system.form_eoms(**kwargs)
|
||||
assert self.system.mass_matrix_full == expected
|
||||
|
||||
@pytest.mark.parametrize('kwargs, mm, gm', [
|
||||
({}, ImmutableMatrix([[1, 0], [0, symbols('m')]]),
|
||||
ImmutableMatrix([q[0].diff(t), 0])),
|
||||
])
|
||||
def test_system_lagrange_form_eoms_kwargs(self, _moving_point_mass, kwargs,
|
||||
mm, gm):
|
||||
self.system.form_eoms(eom_method=LagrangesMethod, **kwargs)
|
||||
assert self.system.mass_matrix_full == mm
|
||||
assert self.system.forcing_full == gm
|
||||
|
||||
@pytest.mark.parametrize('eom_method, kwargs, error', [
|
||||
(KanesMethod, {'non_existing_kwarg': 1}, TypeError),
|
||||
(LagrangesMethod, {'non_existing_kwarg': 1}, TypeError),
|
||||
(KanesMethod, {'bodies': []}, ValueError),
|
||||
(KanesMethod, {'kd_eqs': []}, ValueError),
|
||||
(LagrangesMethod, {'bodies': []}, ValueError),
|
||||
(LagrangesMethod, {'Lagrangian': 1}, ValueError),
|
||||
])
|
||||
def test_form_eoms_kwargs_errors(self, _empty_system_setup, eom_method,
|
||||
kwargs, error):
|
||||
self.system.q_ind = q[0]
|
||||
p = Particle('p', mass=symbols('m'))
|
||||
self.system.add_bodies(p)
|
||||
p.masscenter.set_pos(self.system.fixed_point, q[0] * self.system.x)
|
||||
with pytest.raises(error):
|
||||
self.system.form_eoms(eom_method=eom_method, **kwargs)
|
||||
|
||||
|
||||
class TestValidateSystem(TestSystemBase):
|
||||
@pytest.mark.parametrize('valid_method, invalid_method, with_speeds', [
|
||||
(KanesMethod, LagrangesMethod, True),
|
||||
(LagrangesMethod, KanesMethod, False)
|
||||
])
|
||||
def test_only_valid(self, valid_method, invalid_method, with_speeds):
|
||||
self._create_filled_system(with_speeds=with_speeds)
|
||||
self.system.validate_system(valid_method)
|
||||
# Test Lagrange should fail due to the usage of generalized speeds
|
||||
with pytest.raises(ValueError):
|
||||
self.system.validate_system(invalid_method)
|
||||
|
||||
@pytest.mark.parametrize('method, with_speeds', [
|
||||
(KanesMethod, True), (LagrangesMethod, False)])
|
||||
def test_missing_joint_coordinate(self, method, with_speeds):
|
||||
self._create_filled_system(with_speeds=with_speeds)
|
||||
self.system.q_ind = self.q_ind[1:]
|
||||
self.system.u_ind = self.u_ind[:-1]
|
||||
self.system.kdes = self.kdes[:-1]
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system(method))
|
||||
|
||||
def test_missing_joint_speed(self, _filled_system_setup):
|
||||
self.system.q_ind = self.q_ind[:-1]
|
||||
self.system.u_ind = self.u_ind[1:]
|
||||
self.system.kdes = self.kdes[:-1]
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system())
|
||||
|
||||
def test_missing_joint_kdes(self, _filled_system_setup):
|
||||
self.system.kdes = self.kdes[1:]
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system())
|
||||
|
||||
def test_negative_joint_kdes(self, _filled_system_setup):
|
||||
self.system.kdes = [-self.kdes[0]] + self.kdes[1:]
|
||||
self.system.validate_system()
|
||||
|
||||
@pytest.mark.parametrize('method, with_speeds', [
|
||||
(KanesMethod, True), (LagrangesMethod, False)])
|
||||
def test_missing_holonomic_constraint(self, method, with_speeds):
|
||||
self._create_filled_system(with_speeds=with_speeds)
|
||||
self.system.holonomic_constraints = []
|
||||
self.system.nonholonomic_constraints = self.nhc + [
|
||||
self.u_ind[1] - self.u_dep[0] + self.u_ind[0]]
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system(method))
|
||||
self.system.q_dep = []
|
||||
self.system.q_ind = self.q_ind + self.q_dep
|
||||
self.system.validate_system(method)
|
||||
|
||||
def test_missing_nonholonomic_constraint(self, _filled_system_setup):
|
||||
self.system.nonholonomic_constraints = []
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system())
|
||||
self.system.u_dep = self.u_dep[1]
|
||||
self.system.u_ind = self.u_ind + [self.u_dep[0]]
|
||||
self.system.validate_system()
|
||||
|
||||
def test_number_of_coordinates_speeds(self, _filled_system_setup):
|
||||
# Test more speeds than coordinates
|
||||
self.system.u_ind = self.u_ind + [u[5]]
|
||||
self.system.kdes = self.kdes + [u[5] - qd[5]]
|
||||
self.system.validate_system()
|
||||
# Test more coordinates than speeds
|
||||
self.system.q_ind = self.q_ind
|
||||
self.system.u_ind = self.u_ind[:-1]
|
||||
self.system.kdes = self.kdes[:-1]
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system())
|
||||
|
||||
def test_number_of_kdes(self, _filled_system_setup):
|
||||
# Test wrong number of kdes
|
||||
self.system.kdes = self.kdes[:-1]
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system())
|
||||
self.system.kdes = self.kdes + [u[2] + u[1] - qd[2]]
|
||||
pytest.raises(ValueError, lambda: self.system.validate_system())
|
||||
|
||||
def test_duplicates(self, _filled_system_setup):
|
||||
# This is basically a redundant feature, which should never fail
|
||||
self.system.validate_system(check_duplicates=True)
|
||||
|
||||
def test_speeds_in_lagrange(self, _filled_system_setup_no_speeds):
|
||||
self.system.u_ind = u[:len(self.u_ind)]
|
||||
with pytest.raises(ValueError):
|
||||
self.system.validate_system(LagrangesMethod)
|
||||
self.system.u_ind = []
|
||||
self.system.validate_system(LagrangesMethod)
|
||||
self.system.u_aux = ua
|
||||
with pytest.raises(ValueError):
|
||||
self.system.validate_system(LagrangesMethod)
|
||||
self.system.u_aux = []
|
||||
self.system.validate_system(LagrangesMethod)
|
||||
self.system.add_joints(
|
||||
PinJoint('Ju', RigidBody('rbu1'), RigidBody('rbu2')))
|
||||
self.system.u_ind = []
|
||||
with pytest.raises(ValueError):
|
||||
self.system.validate_system(LagrangesMethod)
|
||||
|
||||
|
||||
class TestSystemExamples:
|
||||
def test_cart_pendulum_kanes(self):
|
||||
# This example is the same as in the top documentation of System
|
||||
# Added a spring to the cart
|
||||
g, l, mc, mp, k = symbols('g l mc mp k')
|
||||
F, qp, qc, up, uc = dynamicsymbols('F qp qc up uc')
|
||||
rail = RigidBody('rail')
|
||||
cart = RigidBody('cart', mass=mc)
|
||||
bob = Particle('bob', mass=mp)
|
||||
bob_frame = ReferenceFrame('bob_frame')
|
||||
system = System.from_newtonian(rail)
|
||||
assert system.bodies == (rail,)
|
||||
assert system.frame == rail.frame
|
||||
assert system.fixed_point == rail.masscenter
|
||||
slider = PrismaticJoint('slider', rail, cart, qc, uc, joint_axis=rail.x)
|
||||
pin = PinJoint('pin', cart, bob, qp, up, joint_axis=cart.z,
|
||||
child_interframe=bob_frame, child_point=l * bob_frame.y)
|
||||
system.add_joints(slider, pin)
|
||||
assert system.joints == (slider, pin)
|
||||
assert system.get_joint('slider') == slider
|
||||
assert system.get_body('bob') == bob
|
||||
system.apply_uniform_gravity(-g * system.y)
|
||||
system.add_loads((cart.masscenter, F * rail.x))
|
||||
system.add_actuators(TorqueActuator(k * qp, cart.z, bob_frame, cart))
|
||||
system.validate_system()
|
||||
system.form_eoms()
|
||||
assert isinstance(system.eom_method, KanesMethod)
|
||||
assert (simplify(system.mass_matrix - ImmutableMatrix(
|
||||
[[mp + mc, mp * l * cos(qp)], [mp * l * cos(qp), mp * l ** 2]]))
|
||||
== zeros(2, 2))
|
||||
assert (simplify(system.forcing - ImmutableMatrix([
|
||||
[mp * l * up ** 2 * sin(qp) + F],
|
||||
[-mp * g * l * sin(qp) + k * qp]])) == zeros(2, 1))
|
||||
|
||||
system.add_holonomic_constraints(
|
||||
sympify(bob.masscenter.pos_from(rail.masscenter).dot(system.x)))
|
||||
assert system.eom_method is None
|
||||
system.q_ind, system.q_dep = qp, qc
|
||||
system.u_ind, system.u_dep = up, uc
|
||||
system.validate_system()
|
||||
|
||||
# Computed solution based on manually solving the constraints
|
||||
subs = {qc: -l * sin(qp),
|
||||
uc: -l * cos(qp) * up,
|
||||
uc.diff(t): l * (up ** 2 * sin(qp) - up.diff(t) * cos(qp))}
|
||||
upd_expected = (
|
||||
(-g * mp * sin(qp) + k * qp / l + l * mc * sin(2 * qp) * up ** 2 / 2
|
||||
- l * mp * sin(2 * qp) * up ** 2 / 2 - F * cos(qp)) /
|
||||
(l * (mc * cos(qp) ** 2 + mp * sin(qp) ** 2)))
|
||||
upd_sol = tuple(solve(system.form_eoms().xreplace(subs),
|
||||
up.diff(t)).values())[0]
|
||||
assert simplify(upd_sol - upd_expected) == 0
|
||||
assert isinstance(system.eom_method, KanesMethod)
|
||||
|
||||
# Test other output
|
||||
Mk = -ImmutableMatrix([[0, 1], [1, 0]])
|
||||
gk = -ImmutableMatrix([uc, up])
|
||||
Md = ImmutableMatrix([[-l ** 2 * mp * cos(qp) ** 2 + l ** 2 * mp,
|
||||
l * mp * cos(qp) - l * (mc + mp) * cos(qp)],
|
||||
[l * cos(qp), 1]])
|
||||
gd = ImmutableMatrix(
|
||||
[[-g * l * mp * sin(qp) + k * qp - l ** 2 * mp * up ** 2 * sin(qp) *
|
||||
cos(qp) - l * F * cos(qp)], [l * up ** 2 * sin(qp)]])
|
||||
Mm = (Mk.row_join(zeros(2, 2))).col_join(zeros(2, 2).row_join(Md))
|
||||
gm = gk.col_join(gd)
|
||||
assert simplify(system.mass_matrix - Md) == zeros(2, 2)
|
||||
assert simplify(system.forcing - gd) == zeros(2, 1)
|
||||
assert simplify(system.mass_matrix_full - Mm) == zeros(4, 4)
|
||||
assert simplify(system.forcing_full - gm) == zeros(4, 1)
|
||||
|
||||
def test_cart_pendulum_lagrange(self):
|
||||
# Lagrange version of test_cart_pendulus_kanes
|
||||
# Added a spring to the cart
|
||||
g, l, mc, mp, k = symbols('g l mc mp k')
|
||||
F, qp, qc = dynamicsymbols('F qp qc')
|
||||
qpd, qcd = dynamicsymbols('qp qc', 1)
|
||||
rail = RigidBody('rail')
|
||||
cart = RigidBody('cart', mass=mc)
|
||||
bob = Particle('bob', mass=mp)
|
||||
bob_frame = ReferenceFrame('bob_frame')
|
||||
system = System.from_newtonian(rail)
|
||||
assert system.bodies == (rail,)
|
||||
assert system.frame == rail.frame
|
||||
assert system.fixed_point == rail.masscenter
|
||||
slider = PrismaticJoint('slider', rail, cart, qc, qcd,
|
||||
joint_axis=rail.x)
|
||||
pin = PinJoint('pin', cart, bob, qp, qpd, joint_axis=cart.z,
|
||||
child_interframe=bob_frame, child_point=l * bob_frame.y)
|
||||
system.add_joints(slider, pin)
|
||||
assert system.joints == (slider, pin)
|
||||
assert system.get_joint('slider') == slider
|
||||
assert system.get_body('bob') == bob
|
||||
for body in system.bodies:
|
||||
body.potential_energy = body.mass * g * body.masscenter.pos_from(
|
||||
system.fixed_point).dot(system.y)
|
||||
system.add_loads((cart.masscenter, F * rail.x))
|
||||
system.add_actuators(TorqueActuator(k * qp, cart.z, bob_frame, cart))
|
||||
system.validate_system(LagrangesMethod)
|
||||
system.form_eoms(LagrangesMethod)
|
||||
assert (simplify(system.mass_matrix - ImmutableMatrix(
|
||||
[[mp + mc, mp * l * cos(qp)], [mp * l * cos(qp), mp * l ** 2]]))
|
||||
== zeros(2, 2))
|
||||
assert (simplify(system.forcing - ImmutableMatrix([
|
||||
[mp * l * qpd ** 2 * sin(qp) + F], [-mp * g * l * sin(qp) + k * qp]]
|
||||
)) == zeros(2, 1))
|
||||
|
||||
system.add_holonomic_constraints(
|
||||
sympify(bob.masscenter.pos_from(rail.masscenter).dot(system.x)))
|
||||
assert system.eom_method is None
|
||||
system.q_ind, system.q_dep = qp, qc
|
||||
|
||||
# Computed solution based on manually solving the constraints
|
||||
subs = {qc: -l * sin(qp),
|
||||
qcd: -l * cos(qp) * qpd,
|
||||
qcd.diff(t): l * (qpd ** 2 * sin(qp) - qpd.diff(t) * cos(qp))}
|
||||
qpdd_expected = (
|
||||
(-g * mp * sin(qp) + k * qp / l + l * mc * sin(2 * qp) * qpd ** 2 /
|
||||
2 - l * mp * sin(2 * qp) * qpd ** 2 / 2 - F * cos(qp)) /
|
||||
(l * (mc * cos(qp) ** 2 + mp * sin(qp) ** 2)))
|
||||
eoms = system.form_eoms(LagrangesMethod)
|
||||
lam1 = system.eom_method.lam_vec[0]
|
||||
lam1_sol = system.eom_method.solve_multipliers()[lam1]
|
||||
qpdd_sol = solve(eoms[0].xreplace({lam1: lam1_sol}).xreplace(subs),
|
||||
qpd.diff(t))[0]
|
||||
assert simplify(qpdd_sol - qpdd_expected) == 0
|
||||
assert isinstance(system.eom_method, LagrangesMethod)
|
||||
|
||||
# Test other output
|
||||
Md = ImmutableMatrix([[l ** 2 * mp, l * mp * cos(qp), -l * cos(qp)],
|
||||
[l * mp * cos(qp), mc + mp, -1]])
|
||||
gd = ImmutableMatrix(
|
||||
[[-g * l * mp * sin(qp) + k * qp],
|
||||
[l * mp * sin(qp) * qpd ** 2 + F]])
|
||||
Mm = (eye(2).row_join(zeros(2, 3))).col_join(zeros(3, 2).row_join(
|
||||
Md.col_join(ImmutableMatrix([l * cos(qp), 1, 0]).T)))
|
||||
gm = ImmutableMatrix([qpd, qcd] + gd[:] + [l * sin(qp) * qpd ** 2])
|
||||
assert simplify(system.mass_matrix - Md) == zeros(2, 3)
|
||||
assert simplify(system.forcing - gd) == zeros(2, 1)
|
||||
assert simplify(system.mass_matrix_full - Mm) == zeros(5, 5)
|
||||
assert simplify(system.forcing_full - gm) == zeros(5, 1)
|
||||
|
||||
def test_box_on_ground(self):
|
||||
# Particle sliding on ground with friction. The applied force is assumed
|
||||
# to be positive and to be higher than the friction force.
|
||||
g, m, mu = symbols('g m mu')
|
||||
q, u, ua = dynamicsymbols('q u ua')
|
||||
N, F = dynamicsymbols('N F', positive=True)
|
||||
P = Particle("P", mass=m)
|
||||
system = System()
|
||||
system.add_bodies(P)
|
||||
P.masscenter.set_pos(system.fixed_point, q * system.x)
|
||||
P.masscenter.set_vel(system.frame, u * system.x + ua * system.y)
|
||||
system.q_ind, system.u_ind, system.u_aux = [q], [u], [ua]
|
||||
system.kdes = [q.diff(t) - u]
|
||||
system.apply_uniform_gravity(-g * system.y)
|
||||
system.add_loads(
|
||||
Force(P, N * system.y),
|
||||
Force(P, F * system.x - mu * N * system.x))
|
||||
system.validate_system()
|
||||
system.form_eoms()
|
||||
|
||||
# Test other output
|
||||
Mk = ImmutableMatrix([1])
|
||||
gk = ImmutableMatrix([u])
|
||||
Md = ImmutableMatrix([m])
|
||||
gd = ImmutableMatrix([F - mu * N])
|
||||
Mm = (Mk.row_join(zeros(1, 1))).col_join(zeros(1, 1).row_join(Md))
|
||||
gm = gk.col_join(gd)
|
||||
aux_eqs = ImmutableMatrix([N - m * g])
|
||||
assert simplify(system.mass_matrix - Md) == zeros(1, 1)
|
||||
assert simplify(system.forcing - gd) == zeros(1, 1)
|
||||
assert simplify(system.mass_matrix_full - Mm) == zeros(2, 2)
|
||||
assert simplify(system.forcing_full - gm) == zeros(2, 1)
|
||||
assert simplify(system.eom_method.auxiliary_eqs - aux_eqs
|
||||
) == zeros(1, 1)
|
||||
+363
@@ -0,0 +1,363 @@
|
||||
"""Tests for the ``sympy.physics.mechanics.wrapping_geometry.py`` module."""
|
||||
|
||||
import pytest
|
||||
|
||||
from sympy import (
|
||||
Integer,
|
||||
Rational,
|
||||
S,
|
||||
Symbol,
|
||||
acos,
|
||||
cos,
|
||||
pi,
|
||||
sin,
|
||||
sqrt,
|
||||
)
|
||||
from sympy.core.relational import Eq
|
||||
from sympy.physics.mechanics import (
|
||||
Point,
|
||||
ReferenceFrame,
|
||||
WrappingCylinder,
|
||||
WrappingSphere,
|
||||
dynamicsymbols,
|
||||
)
|
||||
from sympy.simplify.simplify import simplify
|
||||
|
||||
|
||||
r = Symbol('r', positive=True)
|
||||
x = Symbol('x')
|
||||
q = dynamicsymbols('q')
|
||||
N = ReferenceFrame('N')
|
||||
|
||||
|
||||
class TestWrappingSphere:
|
||||
|
||||
@staticmethod
|
||||
def test_valid_constructor():
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
sphere = WrappingSphere(r, pO)
|
||||
assert isinstance(sphere, WrappingSphere)
|
||||
assert hasattr(sphere, 'radius')
|
||||
assert sphere.radius == r
|
||||
assert hasattr(sphere, 'point')
|
||||
assert sphere.point == pO
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize('position', [S.Zero, Integer(2)*r*N.x])
|
||||
def test_geodesic_length_point_not_on_surface_invalid(position):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
sphere = WrappingSphere(r, pO)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position)
|
||||
|
||||
error_msg = r'point .* does not lie on the surface of'
|
||||
with pytest.raises(ValueError, match=error_msg):
|
||||
sphere.geodesic_length(p1, p2)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'position_1, position_2, expected',
|
||||
[
|
||||
(r*N.x, r*N.x, S.Zero),
|
||||
(r*N.x, r*N.y, S.Half*pi*r),
|
||||
(r*N.x, r*-N.x, pi*r),
|
||||
(r*-N.x, r*N.x, pi*r),
|
||||
(r*N.x, r*sqrt(2)*S.Half*(N.x + N.y), Rational(1, 4)*pi*r),
|
||||
(
|
||||
r*sqrt(2)*S.Half*(N.x + N.y),
|
||||
r*sqrt(3)*Rational(1, 3)*(N.x + N.y + N.z),
|
||||
r*acos(sqrt(6)*Rational(1, 3)),
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_geodesic_length(position_1, position_2, expected):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
sphere = WrappingSphere(r, pO)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position_1)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position_2)
|
||||
|
||||
assert simplify(Eq(sphere.geodesic_length(p1, p2), expected))
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'position_1, position_2, vector_1, vector_2',
|
||||
[
|
||||
(r * N.x, r * N.y, N.y, N.x),
|
||||
(r * N.x, -r * N.y, -N.y, N.x),
|
||||
(
|
||||
r * N.y,
|
||||
sqrt(2)/2 * r * N.x - sqrt(2)/2 * r * N.y,
|
||||
N.x,
|
||||
sqrt(2)/2 * N.x + sqrt(2)/2 * N.y,
|
||||
),
|
||||
(
|
||||
r * N.x,
|
||||
r / 2 * N.x + sqrt(3)/2 * r * N.y,
|
||||
N.y,
|
||||
sqrt(3)/2 * N.x - 1/2 * N.y,
|
||||
),
|
||||
(
|
||||
r * N.x,
|
||||
sqrt(2)/2 * r * N.x + sqrt(2)/2 * r * N.y,
|
||||
N.y,
|
||||
sqrt(2)/2 * N.x - sqrt(2)/2 * N.y,
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_geodesic_end_vectors(position_1, position_2, vector_1, vector_2):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
sphere = WrappingSphere(r, pO)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position_1)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position_2)
|
||||
|
||||
expected = (vector_1, vector_2)
|
||||
|
||||
assert sphere.geodesic_end_vectors(p1, p2) == expected
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'position',
|
||||
[r * N.x, r * cos(q) * N.x + r * sin(q) * N.y]
|
||||
)
|
||||
def test_geodesic_end_vectors_invalid_coincident(position):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
sphere = WrappingSphere(r, pO)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_ = sphere.geodesic_end_vectors(p1, p2)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'position_1, position_2',
|
||||
[
|
||||
(r * N.x, -r * N.x),
|
||||
(-r * N.y, r * N.y),
|
||||
(
|
||||
r * cos(q) * N.x + r * sin(q) * N.y,
|
||||
-r * cos(q) * N.x - r * sin(q) * N.y,
|
||||
)
|
||||
]
|
||||
)
|
||||
def test_geodesic_end_vectors_invalid_diametrically_opposite(
|
||||
position_1,
|
||||
position_2,
|
||||
):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
sphere = WrappingSphere(r, pO)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position_1)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position_2)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_ = sphere.geodesic_end_vectors(p1, p2)
|
||||
|
||||
|
||||
class TestWrappingCylinder:
|
||||
|
||||
@staticmethod
|
||||
def test_valid_constructor():
|
||||
N = ReferenceFrame('N')
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
cylinder = WrappingCylinder(r, pO, N.x)
|
||||
assert isinstance(cylinder, WrappingCylinder)
|
||||
assert hasattr(cylinder, 'radius')
|
||||
assert cylinder.radius == r
|
||||
assert hasattr(cylinder, 'point')
|
||||
assert cylinder.point == pO
|
||||
assert hasattr(cylinder, 'axis')
|
||||
assert cylinder.axis == N.x
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'position, expected',
|
||||
[
|
||||
(S.Zero, False),
|
||||
(r*N.y, True),
|
||||
(r*N.z, True),
|
||||
(r*(N.y + N.z).normalize(), True),
|
||||
(Integer(2)*r*N.y, False),
|
||||
(r*(N.x + N.y), True),
|
||||
(r*(Integer(2)*N.x + N.y), True),
|
||||
(Integer(2)*N.x + r*(Integer(2)*N.y + N.z).normalize(), True),
|
||||
(r*(cos(q)*N.y + sin(q)*N.z), True)
|
||||
]
|
||||
)
|
||||
def test_point_is_on_surface(position, expected):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
cylinder = WrappingCylinder(r, pO, N.x)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position)
|
||||
|
||||
assert cylinder.point_on_surface(p1) is expected
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize('position', [S.Zero, Integer(2)*r*N.y])
|
||||
def test_geodesic_length_point_not_on_surface_invalid(position):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
cylinder = WrappingCylinder(r, pO, N.x)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position)
|
||||
|
||||
error_msg = r'point .* does not lie on the surface of'
|
||||
with pytest.raises(ValueError, match=error_msg):
|
||||
cylinder.geodesic_length(p1, p2)
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'axis, position_1, position_2, expected',
|
||||
[
|
||||
(N.x, r*N.y, r*N.y, S.Zero),
|
||||
(N.x, r*N.y, N.x + r*N.y, S.One),
|
||||
(N.x, r*N.y, -x*N.x + r*N.y, sqrt(x**2)),
|
||||
(-N.x, r*N.y, x*N.x + r*N.y, sqrt(x**2)),
|
||||
(N.x, r*N.y, r*N.z, S.Half*pi*sqrt(r**2)),
|
||||
(-N.x, r*N.y, r*N.z, Integer(3)*S.Half*pi*sqrt(r**2)),
|
||||
(N.x, r*N.z, r*N.y, Integer(3)*S.Half*pi*sqrt(r**2)),
|
||||
(-N.x, r*N.z, r*N.y, S.Half*pi*sqrt(r**2)),
|
||||
(N.x, r*N.y, r*(cos(q)*N.y + sin(q)*N.z), sqrt(r**2*q**2)),
|
||||
(
|
||||
-N.x, r*N.y,
|
||||
r*(cos(q)*N.y + sin(q)*N.z),
|
||||
sqrt(r**2*(Integer(2)*pi - q)**2),
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_geodesic_length(axis, position_1, position_2, expected):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
cylinder = WrappingCylinder(r, pO, axis)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position_1)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position_2)
|
||||
|
||||
assert simplify(Eq(cylinder.geodesic_length(p1, p2), expected))
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'axis, position_1, position_2, vector_1, vector_2',
|
||||
[
|
||||
(N.z, r * N.x, r * N.y, N.y, N.x),
|
||||
(N.z, r * N.x, -r * N.x, N.y, N.y),
|
||||
(N.z, -r * N.x, r * N.x, -N.y, -N.y),
|
||||
(-N.z, r * N.x, -r * N.x, -N.y, -N.y),
|
||||
(-N.z, -r * N.x, r * N.x, N.y, N.y),
|
||||
(N.z, r * N.x, -r * N.y, N.y, -N.x),
|
||||
(
|
||||
N.z,
|
||||
r * N.y,
|
||||
sqrt(2)/2 * r * N.x - sqrt(2)/2 * r * N.y,
|
||||
- N.x,
|
||||
- sqrt(2)/2 * N.x - sqrt(2)/2 * N.y,
|
||||
),
|
||||
(
|
||||
N.z,
|
||||
r * N.x,
|
||||
r / 2 * N.x + sqrt(3)/2 * r * N.y,
|
||||
N.y,
|
||||
sqrt(3)/2 * N.x - 1/2 * N.y,
|
||||
),
|
||||
(
|
||||
N.z,
|
||||
r * N.x,
|
||||
sqrt(2)/2 * r * N.x + sqrt(2)/2 * r * N.y,
|
||||
N.y,
|
||||
sqrt(2)/2 * N.x - sqrt(2)/2 * N.y,
|
||||
),
|
||||
(
|
||||
N.z,
|
||||
r * N.x,
|
||||
r * N.x + N.z,
|
||||
N.z,
|
||||
-N.z,
|
||||
),
|
||||
(
|
||||
N.z,
|
||||
r * N.x,
|
||||
r * N.y + pi/2 * r * N.z,
|
||||
sqrt(2)/2 * N.y + sqrt(2)/2 * N.z,
|
||||
sqrt(2)/2 * N.x - sqrt(2)/2 * N.z,
|
||||
),
|
||||
(
|
||||
N.z,
|
||||
r * N.x,
|
||||
r * cos(q) * N.x + r * sin(q) * N.y,
|
||||
N.y,
|
||||
sin(q) * N.x - cos(q) * N.y,
|
||||
),
|
||||
]
|
||||
)
|
||||
def test_geodesic_end_vectors(
|
||||
axis,
|
||||
position_1,
|
||||
position_2,
|
||||
vector_1,
|
||||
vector_2,
|
||||
):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
cylinder = WrappingCylinder(r, pO, axis)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position_1)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position_2)
|
||||
|
||||
expected = (vector_1, vector_2)
|
||||
end_vectors = tuple(
|
||||
end_vector.simplify()
|
||||
for end_vector in cylinder.geodesic_end_vectors(p1, p2)
|
||||
)
|
||||
|
||||
assert end_vectors == expected
|
||||
|
||||
@staticmethod
|
||||
@pytest.mark.parametrize(
|
||||
'axis, position',
|
||||
[
|
||||
(N.z, r * N.x),
|
||||
(N.z, r * cos(q) * N.x + r * sin(q) * N.y + N.z),
|
||||
]
|
||||
)
|
||||
def test_geodesic_end_vectors_invalid_coincident(axis, position):
|
||||
r = Symbol('r', positive=True)
|
||||
pO = Point('pO')
|
||||
cylinder = WrappingCylinder(r, pO, axis)
|
||||
|
||||
p1 = Point('p1')
|
||||
p1.set_pos(pO, position)
|
||||
p2 = Point('p2')
|
||||
p2.set_pos(pO, position)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_ = cylinder.geodesic_end_vectors(p1, p2)
|
||||
Reference in New Issue
Block a user