|
from sympy.utilities import dict_merge |
|
from sympy.utilities.iterables import iterable |
|
from sympy.physics.vector import (Dyadic, Vector, ReferenceFrame, |
|
Point, dynamicsymbols) |
|
from sympy.physics.vector.printing import (vprint, vsprint, vpprint, vlatex, |
|
init_vprinting) |
|
from sympy.physics.mechanics.particle import Particle |
|
from sympy.physics.mechanics.rigidbody import RigidBody |
|
from sympy.simplify.simplify import simplify |
|
from sympy import Matrix, Mul, Derivative, sin, cos, tan, S |
|
from sympy.core.function import AppliedUndef |
|
from sympy.physics.mechanics.inertia import (inertia as _inertia, |
|
inertia_of_point_mass as _inertia_of_point_mass) |
|
from sympy.utilities.exceptions import sympy_deprecation_warning |
|
|
|
__all__ = ['linear_momentum', |
|
'angular_momentum', |
|
'kinetic_energy', |
|
'potential_energy', |
|
'Lagrangian', |
|
'mechanics_printing', |
|
'mprint', |
|
'msprint', |
|
'mpprint', |
|
'mlatex', |
|
'msubs', |
|
'find_dynamicsymbols'] |
|
|
|
|
|
|
|
|
|
mprint = vprint |
|
msprint = vsprint |
|
mpprint = vpprint |
|
mlatex = vlatex |
|
|
|
|
|
def mechanics_printing(**kwargs): |
|
""" |
|
Initializes time derivative printing for all SymPy objects in |
|
mechanics module. |
|
""" |
|
|
|
init_vprinting(**kwargs) |
|
|
|
mechanics_printing.__doc__ = init_vprinting.__doc__ |
|
|
|
|
|
def inertia(frame, ixx, iyy, izz, ixy=0, iyz=0, izx=0): |
|
sympy_deprecation_warning( |
|
""" |
|
The inertia function has been moved. |
|
Import it from "sympy.physics.mechanics". |
|
""", |
|
deprecated_since_version="1.13", |
|
active_deprecations_target="moved-mechanics-functions" |
|
) |
|
return _inertia(frame, ixx, iyy, izz, ixy, iyz, izx) |
|
|
|
|
|
def inertia_of_point_mass(mass, pos_vec, frame): |
|
sympy_deprecation_warning( |
|
""" |
|
The inertia_of_point_mass function has been moved. |
|
Import it from "sympy.physics.mechanics". |
|
""", |
|
deprecated_since_version="1.13", |
|
active_deprecations_target="moved-mechanics-functions" |
|
) |
|
return _inertia_of_point_mass(mass, pos_vec, frame) |
|
|
|
|
|
def linear_momentum(frame, *body): |
|
"""Linear momentum of the system. |
|
|
|
Explanation |
|
=========== |
|
|
|
This function returns the linear momentum of a system of Particle's and/or |
|
RigidBody's. The linear momentum of a system is equal to the vector sum of |
|
the linear momentum of its constituents. Consider a system, S, comprised of |
|
a rigid body, A, and a particle, P. The linear momentum of the system, L, |
|
is equal to the vector sum of the linear momentum of the particle, L1, and |
|
the linear momentum of the rigid body, L2, i.e. |
|
|
|
L = L1 + L2 |
|
|
|
Parameters |
|
========== |
|
|
|
frame : ReferenceFrame |
|
The frame in which linear momentum is desired. |
|
body1, body2, body3... : Particle and/or RigidBody |
|
The body (or bodies) whose linear momentum is required. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.mechanics import Point, Particle, ReferenceFrame |
|
>>> from sympy.physics.mechanics import RigidBody, outer, linear_momentum |
|
>>> N = ReferenceFrame('N') |
|
>>> P = Point('P') |
|
>>> P.set_vel(N, 10 * N.x) |
|
>>> Pa = Particle('Pa', P, 1) |
|
>>> Ac = Point('Ac') |
|
>>> Ac.set_vel(N, 25 * N.y) |
|
>>> I = outer(N.x, N.x) |
|
>>> A = RigidBody('A', Ac, N, 20, (I, Ac)) |
|
>>> linear_momentum(N, A, Pa) |
|
10*N.x + 500*N.y |
|
|
|
""" |
|
|
|
if not isinstance(frame, ReferenceFrame): |
|
raise TypeError('Please specify a valid ReferenceFrame') |
|
else: |
|
linear_momentum_sys = Vector(0) |
|
for e in body: |
|
if isinstance(e, (RigidBody, Particle)): |
|
linear_momentum_sys += e.linear_momentum(frame) |
|
else: |
|
raise TypeError('*body must have only Particle or RigidBody') |
|
return linear_momentum_sys |
|
|
|
|
|
def angular_momentum(point, frame, *body): |
|
"""Angular momentum of a system. |
|
|
|
Explanation |
|
=========== |
|
|
|
This function returns the angular momentum of a system of Particle's and/or |
|
RigidBody's. The angular momentum of such a system is equal to the vector |
|
sum of the angular momentum of its constituents. Consider a system, S, |
|
comprised of a rigid body, A, and a particle, P. The angular momentum of |
|
the system, H, is equal to the vector sum of the angular momentum of the |
|
particle, H1, and the angular momentum of the rigid body, H2, i.e. |
|
|
|
H = H1 + H2 |
|
|
|
Parameters |
|
========== |
|
|
|
point : Point |
|
The point about which angular momentum of the system is desired. |
|
frame : ReferenceFrame |
|
The frame in which angular momentum is desired. |
|
body1, body2, body3... : Particle and/or RigidBody |
|
The body (or bodies) whose angular momentum is required. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.mechanics import Point, Particle, ReferenceFrame |
|
>>> from sympy.physics.mechanics import RigidBody, outer, angular_momentum |
|
>>> 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)) |
|
>>> angular_momentum(O, N, Pa, A) |
|
10*N.z |
|
|
|
""" |
|
|
|
if not isinstance(frame, ReferenceFrame): |
|
raise TypeError('Please enter a valid ReferenceFrame') |
|
if not isinstance(point, Point): |
|
raise TypeError('Please specify a valid Point') |
|
else: |
|
angular_momentum_sys = Vector(0) |
|
for e in body: |
|
if isinstance(e, (RigidBody, Particle)): |
|
angular_momentum_sys += e.angular_momentum(point, frame) |
|
else: |
|
raise TypeError('*body must have only Particle or RigidBody') |
|
return angular_momentum_sys |
|
|
|
|
|
def kinetic_energy(frame, *body): |
|
"""Kinetic energy of a multibody system. |
|
|
|
Explanation |
|
=========== |
|
|
|
This function returns the kinetic energy of a system of Particle's and/or |
|
RigidBody's. The kinetic energy of such a system is equal to the sum of |
|
the kinetic energies of its constituents. Consider a system, S, comprising |
|
a rigid body, A, and a particle, P. The kinetic energy of the system, T, |
|
is equal to the vector sum of the kinetic energy of the particle, T1, and |
|
the kinetic energy of the rigid body, T2, i.e. |
|
|
|
T = T1 + T2 |
|
|
|
Kinetic energy is a scalar. |
|
|
|
Parameters |
|
========== |
|
|
|
frame : ReferenceFrame |
|
The frame in which the velocity or angular velocity of the body is |
|
defined. |
|
body1, body2, body3... : Particle and/or RigidBody |
|
The body (or bodies) whose kinetic energy is required. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.mechanics import Point, Particle, ReferenceFrame |
|
>>> from sympy.physics.mechanics import RigidBody, outer, kinetic_energy |
|
>>> 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)) |
|
>>> kinetic_energy(N, Pa, A) |
|
350 |
|
|
|
""" |
|
|
|
if not isinstance(frame, ReferenceFrame): |
|
raise TypeError('Please enter a valid ReferenceFrame') |
|
ke_sys = S.Zero |
|
for e in body: |
|
if isinstance(e, (RigidBody, Particle)): |
|
ke_sys += e.kinetic_energy(frame) |
|
else: |
|
raise TypeError('*body must have only Particle or RigidBody') |
|
return ke_sys |
|
|
|
|
|
def potential_energy(*body): |
|
"""Potential energy of a multibody system. |
|
|
|
Explanation |
|
=========== |
|
|
|
This function returns the potential energy of a system of Particle's and/or |
|
RigidBody's. The potential energy of such a system is equal to the sum of |
|
the potential energy of its constituents. Consider a system, S, comprising |
|
a rigid body, A, and a particle, P. The potential energy of the system, V, |
|
is equal to the vector sum of the potential energy of the particle, V1, and |
|
the potential energy of the rigid body, V2, i.e. |
|
|
|
V = V1 + V2 |
|
|
|
Potential energy is a scalar. |
|
|
|
Parameters |
|
========== |
|
|
|
body1, body2, body3... : Particle and/or RigidBody |
|
The body (or bodies) whose potential energy is required. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.mechanics import Point, Particle, ReferenceFrame |
|
>>> from sympy.physics.mechanics import RigidBody, outer, potential_energy |
|
>>> from sympy import symbols |
|
>>> 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) |
|
>>> Pa = Particle('Pa', P, m) |
|
>>> Ac = O.locatenew('Ac', 2 * N.y) |
|
>>> a = ReferenceFrame('a') |
|
>>> 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 |
|
>>> potential_energy(Pa, A) |
|
M*g*h + g*h*m |
|
|
|
""" |
|
|
|
pe_sys = S.Zero |
|
for e in body: |
|
if isinstance(e, (RigidBody, Particle)): |
|
pe_sys += e.potential_energy |
|
else: |
|
raise TypeError('*body must have only Particle or RigidBody') |
|
return pe_sys |
|
|
|
|
|
def gravity(acceleration, *bodies): |
|
from sympy.physics.mechanics.loads import gravity as _gravity |
|
sympy_deprecation_warning( |
|
""" |
|
The gravity function has been moved. |
|
Import it from "sympy.physics.mechanics.loads". |
|
""", |
|
deprecated_since_version="1.13", |
|
active_deprecations_target="moved-mechanics-functions" |
|
) |
|
return _gravity(acceleration, *bodies) |
|
|
|
|
|
def center_of_mass(point, *bodies): |
|
""" |
|
Returns the position vector from the given point to the center of mass |
|
of the given bodies(particles or rigidbodies). |
|
|
|
Example |
|
======= |
|
|
|
>>> from sympy import symbols, S |
|
>>> from sympy.physics.vector import Point |
|
>>> from sympy.physics.mechanics import Particle, ReferenceFrame, RigidBody, outer |
|
>>> from sympy.physics.mechanics.functions import center_of_mass |
|
>>> a = ReferenceFrame('a') |
|
>>> m = symbols('m', real=True) |
|
>>> p1 = Particle('p1', Point('p1_pt'), S(1)) |
|
>>> 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 |
|
>>> point_o.pos_from(p1.point) |
|
5/(m + mb + 6)*a.x + (m + mb + 3)/(m + mb + 6)*a.y + mb/(m + mb + 6)*a.z |
|
|
|
""" |
|
if not bodies: |
|
raise TypeError("No bodies(instances of Particle or Rigidbody) were passed.") |
|
|
|
total_mass = 0 |
|
vec = Vector(0) |
|
for i in bodies: |
|
total_mass += i.mass |
|
|
|
masscenter = getattr(i, 'masscenter', None) |
|
if masscenter is None: |
|
masscenter = i.point |
|
vec += i.mass*masscenter.pos_from(point) |
|
|
|
return vec/total_mass |
|
|
|
|
|
def Lagrangian(frame, *body): |
|
"""Lagrangian of a multibody system. |
|
|
|
Explanation |
|
=========== |
|
|
|
This function returns the Lagrangian of a system of Particle's and/or |
|
RigidBody's. The Lagrangian of such a system is equal to the difference |
|
between the kinetic energies and potential energies of its constituents. If |
|
T and V are the kinetic and potential energies of a system then it's |
|
Lagrangian, L, is defined as |
|
|
|
L = T - V |
|
|
|
The Lagrangian is a scalar. |
|
|
|
Parameters |
|
========== |
|
|
|
frame : ReferenceFrame |
|
The frame in which the velocity or angular velocity of the body is |
|
defined to determine the kinetic energy. |
|
|
|
body1, body2, body3... : Particle and/or RigidBody |
|
The body (or bodies) whose Lagrangian is required. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.mechanics import Point, Particle, ReferenceFrame |
|
>>> from sympy.physics.mechanics import RigidBody, outer, Lagrangian |
|
>>> from sympy import symbols |
|
>>> 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 |
|
>>> Lagrangian(N, Pa, A) |
|
-M*g*h - g*h*m + 350 |
|
|
|
""" |
|
|
|
if not isinstance(frame, ReferenceFrame): |
|
raise TypeError('Please supply a valid ReferenceFrame') |
|
for e in body: |
|
if not isinstance(e, (RigidBody, Particle)): |
|
raise TypeError('*body must have only Particle or RigidBody') |
|
return kinetic_energy(frame, *body) - potential_energy(*body) |
|
|
|
|
|
def find_dynamicsymbols(expression, exclude=None, reference_frame=None): |
|
"""Find all dynamicsymbols in expression. |
|
|
|
Explanation |
|
=========== |
|
|
|
If the optional ``exclude`` kwarg is used, only dynamicsymbols |
|
not in the iterable ``exclude`` are returned. |
|
If we intend to apply this function on a vector, the optional |
|
``reference_frame`` is also used to inform about the corresponding frame |
|
with respect to which the dynamic symbols of the given vector is to be |
|
determined. |
|
|
|
Parameters |
|
========== |
|
|
|
expression : SymPy expression |
|
|
|
exclude : iterable of dynamicsymbols, optional |
|
|
|
reference_frame : ReferenceFrame, optional |
|
The frame with respect to which the dynamic symbols of the |
|
given vector is to be determined. |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.mechanics import dynamicsymbols, find_dynamicsymbols |
|
>>> from sympy.physics.mechanics import ReferenceFrame |
|
>>> x, y = dynamicsymbols('x, y') |
|
>>> expr = x + x.diff()*y |
|
>>> find_dynamicsymbols(expr) |
|
{x(t), y(t), Derivative(x(t), t)} |
|
>>> find_dynamicsymbols(expr, exclude=[x, y]) |
|
{Derivative(x(t), t)} |
|
>>> a, b, c = dynamicsymbols('a, b, c') |
|
>>> A = ReferenceFrame('A') |
|
>>> v = a * A.x + b * A.y + c * A.z |
|
>>> find_dynamicsymbols(v, reference_frame=A) |
|
{a(t), b(t), c(t)} |
|
|
|
""" |
|
t_set = {dynamicsymbols._t} |
|
if exclude: |
|
if iterable(exclude): |
|
exclude_set = set(exclude) |
|
else: |
|
raise TypeError("exclude kwarg must be iterable") |
|
else: |
|
exclude_set = set() |
|
if isinstance(expression, Vector): |
|
if reference_frame is None: |
|
raise ValueError("You must provide reference_frame when passing a " |
|
"vector expression, got %s." % reference_frame) |
|
else: |
|
expression = expression.to_matrix(reference_frame) |
|
return {i for i in expression.atoms(AppliedUndef, Derivative) if |
|
i.free_symbols == t_set} - exclude_set |
|
|
|
|
|
def msubs(expr, *sub_dicts, smart=False, **kwargs): |
|
"""A custom subs for use on expressions derived in physics.mechanics. |
|
|
|
Traverses the expression tree once, performing the subs found in sub_dicts. |
|
Terms inside ``Derivative`` expressions are ignored: |
|
|
|
Examples |
|
======== |
|
|
|
>>> from sympy.physics.mechanics import dynamicsymbols, msubs |
|
>>> x = dynamicsymbols('x') |
|
>>> msubs(x.diff() + x, {x: 1}) |
|
Derivative(x(t), t) + 1 |
|
|
|
Note that sub_dicts can be a single dictionary, or several dictionaries: |
|
|
|
>>> x, y, z = dynamicsymbols('x, y, z') |
|
>>> sub1 = {x: 1, y: 2} |
|
>>> sub2 = {z: 3, x.diff(): 4} |
|
>>> msubs(x.diff() + x + y + z, sub1, sub2) |
|
10 |
|
|
|
If smart=True (default False), also checks for conditions that may result |
|
in ``nan``, but if simplified would yield a valid expression. For example: |
|
|
|
>>> from sympy import sin, tan |
|
>>> (sin(x)/tan(x)).subs(x, 0) |
|
nan |
|
>>> msubs(sin(x)/tan(x), {x: 0}, smart=True) |
|
1 |
|
|
|
It does this by first replacing all ``tan`` with ``sin/cos``. Then each |
|
node is traversed. If the node is a fraction, subs is first evaluated on |
|
the denominator. If this results in 0, simplification of the entire |
|
fraction is attempted. Using this selective simplification, only |
|
subexpressions that result in 1/0 are targeted, resulting in faster |
|
performance. |
|
|
|
""" |
|
|
|
sub_dict = dict_merge(*sub_dicts) |
|
if smart: |
|
func = _smart_subs |
|
elif hasattr(expr, 'msubs'): |
|
return expr.msubs(sub_dict) |
|
else: |
|
func = lambda expr, sub_dict: _crawl(expr, _sub_func, sub_dict) |
|
if isinstance(expr, (Matrix, Vector, Dyadic)): |
|
return expr.applyfunc(lambda x: func(x, sub_dict)) |
|
else: |
|
return func(expr, sub_dict) |
|
|
|
|
|
def _crawl(expr, func, *args, **kwargs): |
|
"""Crawl the expression tree, and apply func to every node.""" |
|
val = func(expr, *args, **kwargs) |
|
if val is not None: |
|
return val |
|
new_args = (_crawl(arg, func, *args, **kwargs) for arg in expr.args) |
|
return expr.func(*new_args) |
|
|
|
|
|
def _sub_func(expr, sub_dict): |
|
"""Perform direct matching substitution, ignoring derivatives.""" |
|
if expr in sub_dict: |
|
return sub_dict[expr] |
|
elif not expr.args or expr.is_Derivative: |
|
return expr |
|
|
|
|
|
def _tan_repl_func(expr): |
|
"""Replace tan with sin/cos.""" |
|
if isinstance(expr, tan): |
|
return sin(*expr.args) / cos(*expr.args) |
|
elif not expr.args or expr.is_Derivative: |
|
return expr |
|
|
|
|
|
def _smart_subs(expr, sub_dict): |
|
"""Performs subs, checking for conditions that may result in `nan` or |
|
`oo`, and attempts to simplify them out. |
|
|
|
The expression tree is traversed twice, and the following steps are |
|
performed on each expression node: |
|
- First traverse: |
|
Replace all `tan` with `sin/cos`. |
|
- Second traverse: |
|
If node is a fraction, check if the denominator evaluates to 0. |
|
If so, attempt to simplify it out. Then if node is in sub_dict, |
|
sub in the corresponding value. |
|
|
|
""" |
|
expr = _crawl(expr, _tan_repl_func) |
|
|
|
def _recurser(expr, sub_dict): |
|
|
|
num, den = _fraction_decomp(expr) |
|
if den != 1: |
|
|
|
denom_subbed = _recurser(den, sub_dict) |
|
if denom_subbed.evalf() == 0: |
|
|
|
expr = simplify(expr) |
|
else: |
|
|
|
num_subbed = _recurser(num, sub_dict) |
|
return num_subbed / denom_subbed |
|
|
|
|
|
val = _sub_func(expr, sub_dict) |
|
if val is not None: |
|
return val |
|
new_args = (_recurser(arg, sub_dict) for arg in expr.args) |
|
return expr.func(*new_args) |
|
return _recurser(expr, sub_dict) |
|
|
|
|
|
def _fraction_decomp(expr): |
|
"""Return num, den such that expr = num/den.""" |
|
if not isinstance(expr, Mul): |
|
return expr, 1 |
|
num = [] |
|
den = [] |
|
for a in expr.args: |
|
if a.is_Pow and a.args[1] < 0: |
|
den.append(1 / a) |
|
else: |
|
num.append(a) |
|
if not den: |
|
return expr, 1 |
|
num = Mul(*num) |
|
den = Mul(*den) |
|
return num, den |
|
|
|
|
|
def _f_list_parser(fl, ref_frame): |
|
"""Parses the provided forcelist composed of items |
|
of the form (obj, force). |
|
Returns a tuple containing: |
|
vel_list: The velocity (ang_vel for Frames, vel for Points) in |
|
the provided reference frame. |
|
f_list: The forces. |
|
|
|
Used internally in the KanesMethod and LagrangesMethod classes. |
|
|
|
""" |
|
def flist_iter(): |
|
for pair in fl: |
|
obj, force = pair |
|
if isinstance(obj, ReferenceFrame): |
|
yield obj.ang_vel_in(ref_frame), force |
|
elif isinstance(obj, Point): |
|
yield obj.vel(ref_frame), force |
|
else: |
|
raise TypeError('First entry in each forcelist pair must ' |
|
'be a point or frame.') |
|
|
|
if not fl: |
|
vel_list, f_list = (), () |
|
else: |
|
unzip = lambda l: list(zip(*l)) if l[0] else [(), ()] |
|
vel_list, f_list = unzip(list(flist_iter())) |
|
return vel_list, f_list |
|
|
|
|
|
def _validate_coordinates(coordinates=None, speeds=None, check_duplicates=True, |
|
is_dynamicsymbols=True, u_auxiliary=None): |
|
"""Validate the generalized coordinates and generalized speeds. |
|
|
|
Parameters |
|
========== |
|
coordinates : iterable, optional |
|
Generalized coordinates to be validated. |
|
speeds : iterable, optional |
|
Generalized speeds to be validated. |
|
check_duplicates : bool, optional |
|
Checks if there are duplicates in the generalized coordinates and |
|
generalized speeds. If so it will raise a ValueError. The default is |
|
True. |
|
is_dynamicsymbols : iterable, optional |
|
Checks if all the generalized coordinates and generalized speeds are |
|
dynamicsymbols. If any is not a dynamicsymbol, a ValueError will be |
|
raised. The default is True. |
|
u_auxiliary : iterable, optional |
|
Auxiliary generalized speeds to be validated. |
|
|
|
""" |
|
t_set = {dynamicsymbols._t} |
|
|
|
if coordinates is None: |
|
coordinates = [] |
|
elif not iterable(coordinates): |
|
coordinates = [coordinates] |
|
if speeds is None: |
|
speeds = [] |
|
elif not iterable(speeds): |
|
speeds = [speeds] |
|
if u_auxiliary is None: |
|
u_auxiliary = [] |
|
elif not iterable(u_auxiliary): |
|
u_auxiliary = [u_auxiliary] |
|
|
|
msgs = [] |
|
if check_duplicates: |
|
seen = set() |
|
coord_duplicates = {x for x in coordinates if x in seen or seen.add(x)} |
|
seen = set() |
|
speed_duplicates = {x for x in speeds if x in seen or seen.add(x)} |
|
seen = set() |
|
aux_duplicates = {x for x in u_auxiliary if x in seen or seen.add(x)} |
|
overlap_coords = set(coordinates).intersection(speeds) |
|
overlap_aux = set(coordinates).union(speeds).intersection(u_auxiliary) |
|
if coord_duplicates: |
|
msgs.append(f'The generalized coordinates {coord_duplicates} are ' |
|
f'duplicated, all generalized coordinates should be ' |
|
f'unique.') |
|
if speed_duplicates: |
|
msgs.append(f'The generalized speeds {speed_duplicates} are ' |
|
f'duplicated, all generalized speeds should be unique.') |
|
if aux_duplicates: |
|
msgs.append(f'The auxiliary speeds {aux_duplicates} are duplicated,' |
|
f' all auxiliary speeds should be unique.') |
|
if overlap_coords: |
|
msgs.append(f'{overlap_coords} are defined as both generalized ' |
|
f'coordinates and generalized speeds.') |
|
if overlap_aux: |
|
msgs.append(f'The auxiliary speeds {overlap_aux} are also defined ' |
|
f'as generalized coordinates or generalized speeds.') |
|
if is_dynamicsymbols: |
|
for coordinate in coordinates: |
|
if not (isinstance(coordinate, (AppliedUndef, Derivative)) and |
|
coordinate.free_symbols == t_set): |
|
msgs.append(f'Generalized coordinate "{coordinate}" is not a ' |
|
f'dynamicsymbol.') |
|
for speed in speeds: |
|
if not (isinstance(speed, (AppliedUndef, Derivative)) and |
|
speed.free_symbols == t_set): |
|
msgs.append( |
|
f'Generalized speed "{speed}" is not a dynamicsymbol.') |
|
for aux in u_auxiliary: |
|
if not (isinstance(aux, (AppliedUndef, Derivative)) and |
|
aux.free_symbols == t_set): |
|
msgs.append( |
|
f'Auxiliary speed "{aux}" is not a dynamicsymbol.') |
|
if msgs: |
|
raise ValueError('\n'.join(msgs)) |
|
|
|
|
|
def _parse_linear_solver(linear_solver): |
|
"""Helper function to retrieve a specified linear solver.""" |
|
if callable(linear_solver): |
|
return linear_solver |
|
return lambda A, b: Matrix.solve(A, b, method=linear_solver) |
|
|