Spaces:
Sleeping
Sleeping
| from sympy import zeros, Matrix, diff, eye | |
| from sympy.core.sorting import default_sort_key | |
| from sympy.physics.vector import (ReferenceFrame, dynamicsymbols, | |
| partial_velocity) | |
| from sympy.physics.mechanics.method import _Methods | |
| from sympy.physics.mechanics.particle import Particle | |
| from sympy.physics.mechanics.rigidbody import RigidBody | |
| from sympy.physics.mechanics.functions import (msubs, find_dynamicsymbols, | |
| _f_list_parser, | |
| _validate_coordinates, | |
| _parse_linear_solver) | |
| from sympy.physics.mechanics.linearize import Linearizer | |
| from sympy.utilities.iterables import iterable | |
| __all__ = ['KanesMethod'] | |
| class KanesMethod(_Methods): | |
| r"""Kane's method object. | |
| Explanation | |
| =========== | |
| This object is used to do the "book-keeping" as you go through and form | |
| equations of motion in the way Kane presents in: | |
| Kane, T., Levinson, D. Dynamics Theory and Applications. 1985 McGraw-Hill | |
| The attributes are for equations in the form [M] udot = forcing. | |
| Attributes | |
| ========== | |
| q, u : Matrix | |
| Matrices of the generalized coordinates and speeds | |
| bodies : iterable | |
| Iterable of Particle and RigidBody objects in the system. | |
| loads : iterable | |
| Iterable of (Point, vector) or (ReferenceFrame, vector) tuples | |
| describing the forces on the system. | |
| auxiliary_eqs : Matrix | |
| If applicable, the set of auxiliary Kane's | |
| equations used to solve for non-contributing | |
| forces. | |
| mass_matrix : Matrix | |
| The system's dynamics mass matrix: [k_d; k_dnh] | |
| forcing : Matrix | |
| The system's dynamics forcing vector: -[f_d; f_dnh] | |
| mass_matrix_kin : Matrix | |
| The "mass matrix" for kinematic differential equations: k_kqdot | |
| forcing_kin : Matrix | |
| The forcing vector for kinematic differential equations: -(k_ku*u + f_k) | |
| mass_matrix_full : Matrix | |
| The "mass matrix" for the u's and q's with dynamics and kinematics | |
| forcing_full : Matrix | |
| The "forcing vector" for the u's and q's with dynamics and kinematics | |
| Parameters | |
| ========== | |
| frame : ReferenceFrame | |
| The inertial reference frame for the system. | |
| q_ind : iterable of dynamicsymbols | |
| Independent generalized coordinates. | |
| u_ind : iterable of dynamicsymbols | |
| Independent generalized speeds. | |
| kd_eqs : iterable of Expr, optional | |
| Kinematic differential equations, which linearly relate the generalized | |
| speeds to the time-derivatives of the generalized coordinates. | |
| q_dependent : iterable of dynamicsymbols, optional | |
| Dependent generalized coordinates. | |
| configuration_constraints : iterable of Expr, optional | |
| Constraints on the system's configuration, i.e. holonomic constraints. | |
| u_dependent : iterable of dynamicsymbols, optional | |
| Dependent generalized speeds. | |
| velocity_constraints : iterable of Expr, optional | |
| Constraints on the system's velocity, i.e. the combination of the | |
| nonholonomic constraints and the time-derivative of the holonomic | |
| constraints. | |
| acceleration_constraints : iterable of Expr, optional | |
| Constraints on the system's acceleration, by default these are the | |
| time-derivative of the velocity constraints. | |
| u_auxiliary : iterable of dynamicsymbols, optional | |
| Auxiliary generalized speeds. | |
| bodies : iterable of Particle and/or RigidBody, optional | |
| The particles and rigid bodies in the system. | |
| forcelist : iterable of tuple[Point | ReferenceFrame, Vector], optional | |
| Forces and torques applied on the system. | |
| explicit_kinematics : bool | |
| Boolean whether the mass matrices and forcing vectors should use the | |
| explicit form (default) or implicit form for kinematics. | |
| See the notes for more details. | |
| kd_eqs_solver : str, callable | |
| Method used to solve the kinematic differential equations. If a string | |
| is supplied, it should be a valid method that can be used with the | |
| :meth:`sympy.matrices.matrixbase.MatrixBase.solve`. If a callable is | |
| supplied, it should have the format ``f(A, rhs)``, where it solves the | |
| equations and returns the solution. The default utilizes LU solve. See | |
| the notes for more information. | |
| constraint_solver : str, callable | |
| Method used to solve the velocity constraints. If a string is | |
| supplied, it should be a valid method that can be used with the | |
| :meth:`sympy.matrices.matrixbase.MatrixBase.solve`. If a callable is | |
| supplied, it should have the format ``f(A, rhs)``, where it solves the | |
| equations and returns the solution. The default utilizes LU solve. See | |
| the notes for more information. | |
| Notes | |
| ===== | |
| The mass matrices and forcing vectors related to kinematic equations | |
| are given in the explicit form by default. In other words, the kinematic | |
| mass matrix is $\mathbf{k_{k\dot{q}}} = \mathbf{I}$. | |
| In order to get the implicit form of those matrices/vectors, you can set the | |
| ``explicit_kinematics`` attribute to ``False``. So $\mathbf{k_{k\dot{q}}}$ | |
| is not necessarily an identity matrix. This can provide more compact | |
| equations for non-simple kinematics. | |
| Two linear solvers can be supplied to ``KanesMethod``: one for solving the | |
| kinematic differential equations and one to solve the velocity constraints. | |
| Both of these sets of equations can be expressed as a linear system ``Ax = rhs``, | |
| which have to be solved in order to obtain the equations of motion. | |
| The default solver ``'LU'``, which stands for LU solve, results relatively low | |
| number of operations. The weakness of this method is that it can result in zero | |
| division errors. | |
| If zero divisions are encountered, a possible solver which may solve the problem | |
| is ``"CRAMER"``. This method uses Cramer's rule to solve the system. This method | |
| is slower and results in more operations than the default solver. However it only | |
| uses a single division by default per entry of the solution. | |
| While a valid list of solvers can be found at | |
| :meth:`sympy.matrices.matrixbase.MatrixBase.solve`, it is also possible to supply a | |
| `callable`. This way it is possible to use a different solver routine. If the | |
| kinematic differential equations are not too complex it can be worth it to simplify | |
| the solution by using ``lambda A, b: simplify(Matrix.LUsolve(A, b))``. Another | |
| option solver one may use is :func:`sympy.solvers.solveset.linsolve`. This can be | |
| done using `lambda A, b: tuple(linsolve((A, b)))[0]`, where we select the first | |
| solution as our system should have only one unique solution. | |
| Examples | |
| ======== | |
| This is a simple example for a one degree of freedom translational | |
| spring-mass-damper. | |
| In this example, we first need to do the kinematics. | |
| This involves creating generalized speeds and coordinates and their | |
| derivatives. | |
| Then we create a point and set its velocity in a frame. | |
| >>> from sympy import symbols | |
| >>> from sympy.physics.mechanics import dynamicsymbols, ReferenceFrame | |
| >>> from sympy.physics.mechanics import Point, Particle, KanesMethod | |
| >>> 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) | |
| Next we need to arrange/store information in the way that KanesMethod | |
| requires. The kinematic differential equations should be an iterable of | |
| expressions. A list of forces/torques must be constructed, where each entry | |
| in the list is a (Point, Vector) or (ReferenceFrame, Vector) tuple, where | |
| the Vectors represent the Force or Torque. | |
| Next a particle needs to be created, and it needs to have a point and mass | |
| assigned to it. | |
| Finally, a list of all bodies and particles needs to be created. | |
| >>> kd = [qd - u] | |
| >>> FL = [(P, (-k * q - c * u) * N.x)] | |
| >>> pa = Particle('pa', P, m) | |
| >>> BL = [pa] | |
| Finally we can generate the equations of motion. | |
| First we create the KanesMethod object and supply an inertial frame, | |
| coordinates, generalized speeds, and the kinematic differential equations. | |
| Additional quantities such as configuration and motion constraints, | |
| dependent coordinates and speeds, and auxiliary speeds are also supplied | |
| here (see the online documentation). | |
| Next we form FR* and FR to complete: Fr + Fr* = 0. | |
| We have the equations of motion at this point. | |
| It makes sense to rearrange them though, so we calculate the mass matrix and | |
| the forcing terms, for E.o.M. in the form: [MM] udot = forcing, where MM is | |
| the mass matrix, udot is a vector of the time derivatives of the | |
| generalized speeds, and forcing is a vector representing "forcing" terms. | |
| >>> KM = KanesMethod(N, q_ind=[q], u_ind=[u], kd_eqs=kd) | |
| >>> (fr, frstar) = KM.kanes_equations(BL, FL) | |
| >>> MM = KM.mass_matrix | |
| >>> forcing = KM.forcing | |
| >>> rhs = MM.inv() * forcing | |
| >>> rhs | |
| Matrix([[(-c*u(t) - k*q(t))/m]]) | |
| >>> KM.linearize(A_and_B=True)[0] | |
| Matrix([ | |
| [ 0, 1], | |
| [-k/m, -c/m]]) | |
| Please look at the documentation pages for more information on how to | |
| perform linearization and how to deal with dependent coordinates & speeds, | |
| and how do deal with bringing non-contributing forces into evidence. | |
| """ | |
| def __init__(self, frame, q_ind, u_ind, kd_eqs=None, q_dependent=None, | |
| configuration_constraints=None, u_dependent=None, | |
| velocity_constraints=None, acceleration_constraints=None, | |
| u_auxiliary=None, bodies=None, forcelist=None, | |
| explicit_kinematics=True, kd_eqs_solver='LU', | |
| constraint_solver='LU'): | |
| """Please read the online documentation. """ | |
| if not q_ind: | |
| q_ind = [dynamicsymbols('dummy_q')] | |
| kd_eqs = [dynamicsymbols('dummy_kd')] | |
| if not isinstance(frame, ReferenceFrame): | |
| raise TypeError('An inertial ReferenceFrame must be supplied') | |
| self._inertial = frame | |
| self._fr = None | |
| self._frstar = None | |
| self._forcelist = forcelist | |
| self._bodylist = bodies | |
| self.explicit_kinematics = explicit_kinematics | |
| self._constraint_solver = constraint_solver | |
| self._initialize_vectors(q_ind, q_dependent, u_ind, u_dependent, | |
| u_auxiliary) | |
| _validate_coordinates(self.q, self.u) | |
| self._initialize_kindiffeq_matrices(kd_eqs, kd_eqs_solver) | |
| self._initialize_constraint_matrices( | |
| configuration_constraints, velocity_constraints, | |
| acceleration_constraints, constraint_solver) | |
| def _initialize_vectors(self, q_ind, q_dep, u_ind, u_dep, u_aux): | |
| """Initialize the coordinate and speed vectors.""" | |
| none_handler = lambda x: Matrix(x) if x else Matrix() | |
| # Initialize generalized coordinates | |
| q_dep = none_handler(q_dep) | |
| if not iterable(q_ind): | |
| raise TypeError('Generalized coordinates must be an iterable.') | |
| if not iterable(q_dep): | |
| raise TypeError('Dependent coordinates must be an iterable.') | |
| q_ind = Matrix(q_ind) | |
| self._qdep = q_dep | |
| self._q = Matrix([q_ind, q_dep]) | |
| self._qdot = self.q.diff(dynamicsymbols._t) | |
| # Initialize generalized speeds | |
| u_dep = none_handler(u_dep) | |
| if not iterable(u_ind): | |
| raise TypeError('Generalized speeds must be an iterable.') | |
| if not iterable(u_dep): | |
| raise TypeError('Dependent speeds must be an iterable.') | |
| u_ind = Matrix(u_ind) | |
| self._udep = u_dep | |
| self._u = Matrix([u_ind, u_dep]) | |
| self._udot = self.u.diff(dynamicsymbols._t) | |
| self._uaux = none_handler(u_aux) | |
| def _initialize_constraint_matrices(self, config, vel, acc, linear_solver='LU'): | |
| """Initializes constraint matrices.""" | |
| linear_solver = _parse_linear_solver(linear_solver) | |
| # Define vector dimensions | |
| o = len(self.u) | |
| m = len(self._udep) | |
| p = o - m | |
| none_handler = lambda x: Matrix(x) if x else Matrix() | |
| # Initialize configuration constraints | |
| config = none_handler(config) | |
| if len(self._qdep) != len(config): | |
| raise ValueError('There must be an equal number of dependent ' | |
| 'coordinates and configuration constraints.') | |
| self._f_h = none_handler(config) | |
| # Initialize velocity and acceleration constraints | |
| vel = none_handler(vel) | |
| acc = none_handler(acc) | |
| if len(vel) != m: | |
| raise ValueError('There must be an equal number of dependent ' | |
| 'speeds and velocity constraints.') | |
| if acc and (len(acc) != m): | |
| raise ValueError('There must be an equal number of dependent ' | |
| 'speeds and acceleration constraints.') | |
| if vel: | |
| u_zero = dict.fromkeys(self.u, 0) | |
| udot_zero = dict.fromkeys(self._udot, 0) | |
| # When calling kanes_equations, another class instance will be | |
| # created if auxiliary u's are present. In this case, the | |
| # computation of kinetic differential equation matrices will be | |
| # skipped as this was computed during the original KanesMethod | |
| # object, and the qd_u_map will not be available. | |
| if self._qdot_u_map is not None: | |
| vel = msubs(vel, self._qdot_u_map) | |
| self._f_nh = msubs(vel, u_zero) | |
| self._k_nh = (vel - self._f_nh).jacobian(self.u) | |
| # If no acceleration constraints given, calculate them. | |
| if not acc: | |
| _f_dnh = (self._k_nh.diff(dynamicsymbols._t) * self.u + | |
| self._f_nh.diff(dynamicsymbols._t)) | |
| if self._qdot_u_map is not None: | |
| _f_dnh = msubs(_f_dnh, self._qdot_u_map) | |
| self._f_dnh = _f_dnh | |
| self._k_dnh = self._k_nh | |
| else: | |
| if self._qdot_u_map is not None: | |
| acc = msubs(acc, self._qdot_u_map) | |
| self._f_dnh = msubs(acc, udot_zero) | |
| self._k_dnh = (acc - self._f_dnh).jacobian(self._udot) | |
| # Form of non-holonomic constraints is B*u + C = 0. | |
| # We partition B into independent and dependent columns: | |
| # Ars is then -B_dep.inv() * B_ind, and it relates dependent speeds | |
| # to independent speeds as: udep = Ars*uind, neglecting the C term. | |
| B_ind = self._k_nh[:, :p] | |
| B_dep = self._k_nh[:, p:o] | |
| self._Ars = -linear_solver(B_dep, B_ind) | |
| else: | |
| self._f_nh = Matrix() | |
| self._k_nh = Matrix() | |
| self._f_dnh = Matrix() | |
| self._k_dnh = Matrix() | |
| self._Ars = Matrix() | |
| def _initialize_kindiffeq_matrices(self, kdeqs, linear_solver='LU'): | |
| """Initialize the kinematic differential equation matrices. | |
| Parameters | |
| ========== | |
| kdeqs : sequence of sympy expressions | |
| Kinematic differential equations in the form of f(u,q',q,t) where | |
| f() = 0. The equations have to be linear in the generalized | |
| coordinates and generalized speeds. | |
| """ | |
| linear_solver = _parse_linear_solver(linear_solver) | |
| if kdeqs: | |
| if len(self.q) != len(kdeqs): | |
| raise ValueError('There must be an equal number of kinematic ' | |
| 'differential equations and coordinates.') | |
| u = self.u | |
| qdot = self._qdot | |
| kdeqs = Matrix(kdeqs) | |
| u_zero = dict.fromkeys(u, 0) | |
| uaux_zero = dict.fromkeys(self._uaux, 0) | |
| qdot_zero = dict.fromkeys(qdot, 0) | |
| # Extract the linear coefficient matrices as per the following | |
| # equation: | |
| # | |
| # k_ku(q,t)*u(t) + k_kqdot(q,t)*q'(t) + f_k(q,t) = 0 | |
| # | |
| k_ku = kdeqs.jacobian(u) | |
| k_kqdot = kdeqs.jacobian(qdot) | |
| f_k = kdeqs.xreplace(u_zero).xreplace(qdot_zero) | |
| # The kinematic differential equations should be linear in both q' | |
| # and u, so check for u and q' in the components. | |
| dy_syms = find_dynamicsymbols(k_ku.row_join(k_kqdot).row_join(f_k)) | |
| nonlin_vars = [vari for vari in u[:] + qdot[:] if vari in dy_syms] | |
| if nonlin_vars: | |
| msg = ('The provided kinematic differential equations are ' | |
| 'nonlinear in {}. They must be linear in the ' | |
| 'generalized speeds and derivatives of the generalized ' | |
| 'coordinates.') | |
| raise ValueError(msg.format(nonlin_vars)) | |
| self._f_k_implicit = f_k.xreplace(uaux_zero) | |
| self._k_ku_implicit = k_ku.xreplace(uaux_zero) | |
| self._k_kqdot_implicit = k_kqdot | |
| # Solve for q'(t) such that the coefficient matrices are now in | |
| # this form: | |
| # | |
| # k_kqdot^-1*k_ku*u(t) + I*q'(t) + k_kqdot^-1*f_k = 0 | |
| # | |
| # NOTE : Solving the kinematic differential equations here is not | |
| # necessary and prevents the equations from being provided in fully | |
| # implicit form. | |
| f_k_explicit = linear_solver(k_kqdot, f_k) | |
| k_ku_explicit = linear_solver(k_kqdot, k_ku) | |
| self._qdot_u_map = dict(zip(qdot, -(k_ku_explicit*u + f_k_explicit))) | |
| self._f_k = f_k_explicit.xreplace(uaux_zero) | |
| self._k_ku = k_ku_explicit.xreplace(uaux_zero) | |
| self._k_kqdot = eye(len(qdot)) | |
| else: | |
| self._qdot_u_map = None | |
| self._f_k_implicit = self._f_k = Matrix() | |
| self._k_ku_implicit = self._k_ku = Matrix() | |
| self._k_kqdot_implicit = self._k_kqdot = Matrix() | |
| def _form_fr(self, fl): | |
| """Form the generalized active force.""" | |
| if fl is not None and (len(fl) == 0 or not iterable(fl)): | |
| raise ValueError('Force pairs must be supplied in an ' | |
| 'non-empty iterable or None.') | |
| N = self._inertial | |
| # pull out relevant velocities for constructing partial velocities | |
| vel_list, f_list = _f_list_parser(fl, N) | |
| vel_list = [msubs(i, self._qdot_u_map) for i in vel_list] | |
| f_list = [msubs(i, self._qdot_u_map) for i in f_list] | |
| # Fill Fr with dot product of partial velocities and forces | |
| o = len(self.u) | |
| b = len(f_list) | |
| FR = zeros(o, 1) | |
| partials = partial_velocity(vel_list, self.u, N) | |
| for i in range(o): | |
| FR[i] = sum(partials[j][i].dot(f_list[j]) for j in range(b)) | |
| # In case there are dependent speeds | |
| if self._udep: | |
| p = o - len(self._udep) | |
| FRtilde = FR[:p, 0] | |
| FRold = FR[p:o, 0] | |
| FRtilde += self._Ars.T * FRold | |
| FR = FRtilde | |
| self._forcelist = fl | |
| self._fr = FR | |
| return FR | |
| def _form_frstar(self, bl): | |
| """Form the generalized inertia force.""" | |
| if not iterable(bl): | |
| raise TypeError('Bodies must be supplied in an iterable.') | |
| t = dynamicsymbols._t | |
| N = self._inertial | |
| # Dicts setting things to zero | |
| udot_zero = dict.fromkeys(self._udot, 0) | |
| uaux_zero = dict.fromkeys(self._uaux, 0) | |
| uauxdot = [diff(i, t) for i in self._uaux] | |
| uauxdot_zero = dict.fromkeys(uauxdot, 0) | |
| # Dictionary of q' and q'' to u and u' | |
| q_ddot_u_map = {k.diff(t): v.diff(t).xreplace( | |
| self._qdot_u_map) for (k, v) in self._qdot_u_map.items()} | |
| q_ddot_u_map.update(self._qdot_u_map) | |
| # Fill up the list of partials: format is a list with num elements | |
| # equal to number of entries in body list. Each of these elements is a | |
| # list - either of length 1 for the translational components of | |
| # particles or of length 2 for the translational and rotational | |
| # components of rigid bodies. The inner most list is the list of | |
| # partial velocities. | |
| def get_partial_velocity(body): | |
| if isinstance(body, RigidBody): | |
| vlist = [body.masscenter.vel(N), body.frame.ang_vel_in(N)] | |
| elif isinstance(body, Particle): | |
| vlist = [body.point.vel(N),] | |
| else: | |
| raise TypeError('The body list may only contain either ' | |
| 'RigidBody or Particle as list elements.') | |
| v = [msubs(vel, self._qdot_u_map) for vel in vlist] | |
| return partial_velocity(v, self.u, N) | |
| partials = [get_partial_velocity(body) for body in bl] | |
| # Compute fr_star in two components: | |
| # fr_star = -(MM*u' + nonMM) | |
| o = len(self.u) | |
| MM = zeros(o, o) | |
| nonMM = zeros(o, 1) | |
| zero_uaux = lambda expr: msubs(expr, uaux_zero) | |
| zero_udot_uaux = lambda expr: msubs(msubs(expr, udot_zero), uaux_zero) | |
| for i, body in enumerate(bl): | |
| if isinstance(body, RigidBody): | |
| M = zero_uaux(body.mass) | |
| I = zero_uaux(body.central_inertia) | |
| vel = zero_uaux(body.masscenter.vel(N)) | |
| omega = zero_uaux(body.frame.ang_vel_in(N)) | |
| acc = zero_udot_uaux(body.masscenter.acc(N)) | |
| inertial_force = (M.diff(t) * vel + M * acc) | |
| inertial_torque = zero_uaux((I.dt(body.frame).dot(omega)) + | |
| msubs(I.dot(body.frame.ang_acc_in(N)), udot_zero) + | |
| (omega.cross(I.dot(omega)))) | |
| for j in range(o): | |
| tmp_vel = zero_uaux(partials[i][0][j]) | |
| tmp_ang = zero_uaux(I.dot(partials[i][1][j])) | |
| for k in range(o): | |
| # translational | |
| MM[j, k] += M*tmp_vel.dot(partials[i][0][k]) | |
| # rotational | |
| MM[j, k] += tmp_ang.dot(partials[i][1][k]) | |
| nonMM[j] += inertial_force.dot(partials[i][0][j]) | |
| nonMM[j] += inertial_torque.dot(partials[i][1][j]) | |
| else: | |
| M = zero_uaux(body.mass) | |
| vel = zero_uaux(body.point.vel(N)) | |
| acc = zero_udot_uaux(body.point.acc(N)) | |
| inertial_force = (M.diff(t) * vel + M * acc) | |
| for j in range(o): | |
| temp = zero_uaux(partials[i][0][j]) | |
| for k in range(o): | |
| MM[j, k] += M*temp.dot(partials[i][0][k]) | |
| nonMM[j] += inertial_force.dot(partials[i][0][j]) | |
| # Compose fr_star out of MM and nonMM | |
| MM = zero_uaux(msubs(MM, q_ddot_u_map)) | |
| nonMM = msubs(msubs(nonMM, q_ddot_u_map), | |
| udot_zero, uauxdot_zero, uaux_zero) | |
| fr_star = -(MM * msubs(Matrix(self._udot), uauxdot_zero) + nonMM) | |
| # If there are dependent speeds, we need to find fr_star_tilde | |
| if self._udep: | |
| p = o - len(self._udep) | |
| fr_star_ind = fr_star[:p, 0] | |
| fr_star_dep = fr_star[p:o, 0] | |
| fr_star = fr_star_ind + (self._Ars.T * fr_star_dep) | |
| # Apply the same to MM | |
| MMi = MM[:p, :] | |
| MMd = MM[p:o, :] | |
| MM = MMi + (self._Ars.T * MMd) | |
| # Apply the same to nonMM | |
| nonMM = nonMM[:p, :] + (self._Ars.T * nonMM[p:o, :]) | |
| self._bodylist = bl | |
| self._frstar = fr_star | |
| self._k_d = MM | |
| self._f_d = -(self._fr - nonMM) | |
| return fr_star | |
| def to_linearizer(self, linear_solver='LU'): | |
| """Returns an instance of the Linearizer class, initiated from the | |
| data in the KanesMethod class. This may be more desirable than using | |
| the linearize class method, as the Linearizer object will allow more | |
| efficient recalculation (i.e. about varying operating points). | |
| Parameters | |
| ========== | |
| linear_solver : str, callable | |
| Method used to solve the several symbolic linear systems of the | |
| form ``A*x=b`` in the linearization process. If a string is | |
| supplied, it should be a valid method that can be used with the | |
| :meth:`sympy.matrices.matrixbase.MatrixBase.solve`. If a callable is | |
| supplied, it should have the format ``x = f(A, b)``, where it | |
| solves the equations and returns the solution. The default is | |
| ``'LU'`` which corresponds to SymPy's ``A.LUsolve(b)``. | |
| ``LUsolve()`` is fast to compute but will often result in | |
| divide-by-zero and thus ``nan`` results. | |
| Returns | |
| ======= | |
| Linearizer | |
| An instantiated | |
| :class:`sympy.physics.mechanics.linearize.Linearizer`. | |
| """ | |
| if (self._fr is None) or (self._frstar is None): | |
| raise ValueError('Need to compute Fr, Fr* first.') | |
| # Get required equation components. The Kane's method class breaks | |
| # these into pieces. Need to reassemble | |
| f_c = self._f_h | |
| if self._f_nh and self._k_nh: | |
| f_v = self._f_nh + self._k_nh*Matrix(self.u) | |
| else: | |
| f_v = Matrix() | |
| if self._f_dnh and self._k_dnh: | |
| f_a = self._f_dnh + self._k_dnh*Matrix(self._udot) | |
| else: | |
| f_a = Matrix() | |
| # Dicts to sub to zero, for splitting up expressions | |
| u_zero = dict.fromkeys(self.u, 0) | |
| ud_zero = dict.fromkeys(self._udot, 0) | |
| qd_zero = dict.fromkeys(self._qdot, 0) | |
| qd_u_zero = dict.fromkeys(Matrix([self._qdot, self.u]), 0) | |
| # Break the kinematic differential eqs apart into f_0 and f_1 | |
| f_0 = msubs(self._f_k, u_zero) + self._k_kqdot*Matrix(self._qdot) | |
| f_1 = msubs(self._f_k, qd_zero) + self._k_ku*Matrix(self.u) | |
| # Break the dynamic differential eqs into f_2 and f_3 | |
| f_2 = msubs(self._frstar, qd_u_zero) | |
| f_3 = msubs(self._frstar, ud_zero) + self._fr | |
| f_4 = zeros(len(f_2), 1) | |
| # Get the required vector components | |
| q = self.q | |
| u = self.u | |
| if self._qdep: | |
| q_i = q[:-len(self._qdep)] | |
| else: | |
| q_i = q | |
| q_d = self._qdep | |
| if self._udep: | |
| u_i = u[:-len(self._udep)] | |
| else: | |
| u_i = u | |
| u_d = self._udep | |
| # Form dictionary to set auxiliary speeds & their derivatives to 0. | |
| uaux = self._uaux | |
| uauxdot = uaux.diff(dynamicsymbols._t) | |
| uaux_zero = dict.fromkeys(Matrix([uaux, uauxdot]), 0) | |
| # Checking for dynamic symbols outside the dynamic differential | |
| # equations; throws error if there is. | |
| sym_list = set(Matrix([q, self._qdot, u, self._udot, uaux, uauxdot])) | |
| if any(find_dynamicsymbols(i, sym_list) for i in [self._k_kqdot, | |
| self._k_ku, self._f_k, self._k_dnh, self._f_dnh, self._k_d]): | |
| raise ValueError('Cannot have dynamicsymbols outside dynamic \ | |
| forcing vector.') | |
| # Find all other dynamic symbols, forming the forcing vector r. | |
| # Sort r to make it canonical. | |
| r = list(find_dynamicsymbols(msubs(self._f_d, uaux_zero), sym_list)) | |
| r.sort(key=default_sort_key) | |
| # Check for any derivatives of variables in r that are also found in r. | |
| for i in r: | |
| if diff(i, dynamicsymbols._t) in r: | |
| raise ValueError('Cannot have derivatives of specified \ | |
| quantities when linearizing forcing terms.') | |
| return Linearizer(f_0, f_1, f_2, f_3, f_4, f_c, f_v, f_a, q, u, q_i, | |
| q_d, u_i, u_d, r, linear_solver=linear_solver) | |
| # TODO : Remove `new_method` after 1.1 has been released. | |
| def linearize(self, *, new_method=None, linear_solver='LU', **kwargs): | |
| """ Linearize the equations of motion about a symbolic operating point. | |
| Parameters | |
| ========== | |
| new_method | |
| Deprecated, does nothing and will be removed. | |
| linear_solver : str, callable | |
| Method used to solve the several symbolic linear systems of the | |
| form ``A*x=b`` in the linearization process. If a string is | |
| supplied, it should be a valid method that can be used with the | |
| :meth:`sympy.matrices.matrixbase.MatrixBase.solve`. If a callable is | |
| supplied, it should have the format ``x = f(A, b)``, where it | |
| solves the equations and returns the solution. The default is | |
| ``'LU'`` which corresponds to SymPy's ``A.LUsolve(b)``. | |
| ``LUsolve()`` is fast to compute but will often result in | |
| divide-by-zero and thus ``nan`` results. | |
| **kwargs | |
| Extra keyword arguments are passed to | |
| :meth:`sympy.physics.mechanics.linearize.Linearizer.linearize`. | |
| Explanation | |
| =========== | |
| If kwarg A_and_B is False (default), returns M, A, B, r for the | |
| linearized form, M*[q', u']^T = A*[q_ind, u_ind]^T + B*r. | |
| If kwarg A_and_B is True, returns A, B, r for the linearized form | |
| dx = A*x + B*r, where x = [q_ind, u_ind]^T. Note that this is | |
| computationally intensive if there are many symbolic parameters. For | |
| this reason, it may be more desirable to use the default A_and_B=False, | |
| returning M, A, and B. Values may then be substituted in to these | |
| matrices, and the state space form found as | |
| A = P.T*M.inv()*A, B = P.T*M.inv()*B, where P = Linearizer.perm_mat. | |
| In both cases, r is found as all dynamicsymbols in the equations of | |
| motion that are not part of q, u, q', or u'. They are sorted in | |
| canonical form. | |
| The operating points may be also entered using the ``op_point`` kwarg. | |
| This takes a dictionary of {symbol: value}, or a an iterable of such | |
| dictionaries. The values may be numeric or symbolic. The more values | |
| you can specify beforehand, the faster this computation will run. | |
| For more documentation, please see the ``Linearizer`` class. | |
| """ | |
| linearizer = self.to_linearizer(linear_solver=linear_solver) | |
| result = linearizer.linearize(**kwargs) | |
| return result + (linearizer.r,) | |
| def kanes_equations(self, bodies=None, loads=None): | |
| """ Method to form Kane's equations, Fr + Fr* = 0. | |
| Explanation | |
| =========== | |
| Returns (Fr, Fr*). In the case where auxiliary generalized speeds are | |
| present (say, s auxiliary speeds, o generalized speeds, and m motion | |
| constraints) the length of the returned vectors will be o - m + s in | |
| length. The first o - m equations will be the constrained Kane's | |
| equations, then the s auxiliary Kane's equations. These auxiliary | |
| equations can be accessed with the auxiliary_eqs property. | |
| Parameters | |
| ========== | |
| bodies : iterable | |
| An iterable of all RigidBody's and Particle's in the system. | |
| A system must have at least one body. | |
| loads : iterable | |
| Takes in an iterable of (Particle, Vector) or (ReferenceFrame, Vector) | |
| tuples which represent the force at a point or torque on a frame. | |
| Must be either a non-empty iterable of tuples or None which corresponds | |
| to a system with no constraints. | |
| """ | |
| if bodies is None: | |
| bodies = self.bodies | |
| if loads is None and self._forcelist is not None: | |
| loads = self._forcelist | |
| if loads == []: | |
| loads = None | |
| if not self._k_kqdot: | |
| raise AttributeError('Create an instance of KanesMethod with ' | |
| 'kinematic differential equations to use this method.') | |
| fr = self._form_fr(loads) | |
| frstar = self._form_frstar(bodies) | |
| if self._uaux: | |
| if not self._udep: | |
| km = KanesMethod(self._inertial, self.q, self._uaux, | |
| u_auxiliary=self._uaux, constraint_solver=self._constraint_solver) | |
| else: | |
| km = KanesMethod(self._inertial, self.q, self._uaux, | |
| u_auxiliary=self._uaux, u_dependent=self._udep, | |
| velocity_constraints=(self._k_nh * self.u + | |
| self._f_nh), | |
| acceleration_constraints=(self._k_dnh * self._udot + | |
| self._f_dnh), | |
| constraint_solver=self._constraint_solver | |
| ) | |
| km._qdot_u_map = self._qdot_u_map | |
| self._km = km | |
| fraux = km._form_fr(loads) | |
| frstaraux = km._form_frstar(bodies) | |
| self._aux_eq = fraux + frstaraux | |
| self._fr = fr.col_join(fraux) | |
| self._frstar = frstar.col_join(frstaraux) | |
| return (self._fr, self._frstar) | |
| def _form_eoms(self): | |
| fr, frstar = self.kanes_equations(self.bodylist, self.forcelist) | |
| return fr + frstar | |
| def rhs(self, inv_method=None): | |
| """Returns the system's equations of motion in first order form. The | |
| output is the right hand side of:: | |
| x' = |q'| =: f(q, u, r, p, t) | |
| |u'| | |
| The right hand side is what is needed by most numerical ODE | |
| integrators. | |
| Parameters | |
| ========== | |
| inv_method : str | |
| The specific sympy inverse matrix calculation method to use. For a | |
| list of valid methods, see | |
| :meth:`~sympy.matrices.matrixbase.MatrixBase.inv` | |
| """ | |
| rhs = zeros(len(self.q) + len(self.u), 1) | |
| kdes = self.kindiffdict() | |
| for i, q_i in enumerate(self.q): | |
| rhs[i] = kdes[q_i.diff()] | |
| if inv_method is None: | |
| rhs[len(self.q):, 0] = self.mass_matrix.LUsolve(self.forcing) | |
| else: | |
| rhs[len(self.q):, 0] = (self.mass_matrix.inv(inv_method, | |
| try_block_diag=True) * | |
| self.forcing) | |
| return rhs | |
| def kindiffdict(self): | |
| """Returns a dictionary mapping q' to u.""" | |
| if not self._qdot_u_map: | |
| raise AttributeError('Create an instance of KanesMethod with ' | |
| 'kinematic differential equations to use this method.') | |
| return self._qdot_u_map | |
| def auxiliary_eqs(self): | |
| """A matrix containing the auxiliary equations.""" | |
| if not self._fr or not self._frstar: | |
| raise ValueError('Need to compute Fr, Fr* first.') | |
| if not self._uaux: | |
| raise ValueError('No auxiliary speeds have been declared.') | |
| return self._aux_eq | |
| def mass_matrix_kin(self): | |
| r"""The kinematic "mass matrix" $\mathbf{k_{k\dot{q}}}$ of the system.""" | |
| return self._k_kqdot if self.explicit_kinematics else self._k_kqdot_implicit | |
| def forcing_kin(self): | |
| """The kinematic "forcing vector" of the system.""" | |
| if self.explicit_kinematics: | |
| return -(self._k_ku * Matrix(self.u) + self._f_k) | |
| else: | |
| return -(self._k_ku_implicit * Matrix(self.u) + self._f_k_implicit) | |
| def mass_matrix(self): | |
| """The mass matrix of the system.""" | |
| if not self._fr or not self._frstar: | |
| raise ValueError('Need to compute Fr, Fr* first.') | |
| return Matrix([self._k_d, self._k_dnh]) | |
| def forcing(self): | |
| """The forcing vector of the system.""" | |
| if not self._fr or not self._frstar: | |
| raise ValueError('Need to compute Fr, Fr* first.') | |
| return -Matrix([self._f_d, self._f_dnh]) | |
| def mass_matrix_full(self): | |
| """The mass matrix of the system, augmented by the kinematic | |
| differential equations in explicit or implicit form.""" | |
| if not self._fr or not self._frstar: | |
| raise ValueError('Need to compute Fr, Fr* first.') | |
| o, n = len(self.u), len(self.q) | |
| return (self.mass_matrix_kin.row_join(zeros(n, o))).col_join( | |
| zeros(o, n).row_join(self.mass_matrix)) | |
| def forcing_full(self): | |
| """The forcing vector of the system, augmented by the kinematic | |
| differential equations in explicit or implicit form.""" | |
| return Matrix([self.forcing_kin, self.forcing]) | |
| def q(self): | |
| return self._q | |
| def u(self): | |
| return self._u | |
| def bodylist(self): | |
| return self._bodylist | |
| def forcelist(self): | |
| return self._forcelist | |
| def bodies(self): | |
| return self._bodylist | |
| def loads(self): | |
| return self._forcelist | |