Spaces:
Sleeping
Sleeping
"""Implementations of musculotendon models. | |
Musculotendon models are a critical component of biomechanical models, one that | |
differentiates them from pure multibody systems. Musculotendon models produce a | |
force dependent on their level of activation, their length, and their | |
extension velocity. Length- and extension velocity-dependent force production | |
are governed by force-length and force-velocity characteristics. | |
These are normalized functions that are dependent on the musculotendon's state | |
and are specific to a given musculotendon model. | |
""" | |
from abc import abstractmethod | |
from enum import IntEnum, unique | |
from sympy.core.numbers import Float, Integer | |
from sympy.core.symbol import Symbol, symbols | |
from sympy.functions.elementary.miscellaneous import sqrt | |
from sympy.functions.elementary.trigonometric import cos, sin | |
from sympy.matrices.dense import MutableDenseMatrix as Matrix, diag, eye, zeros | |
from sympy.physics.biomechanics.activation import ActivationBase | |
from sympy.physics.biomechanics.curve import ( | |
CharacteristicCurveCollection, | |
FiberForceLengthActiveDeGroote2016, | |
FiberForceLengthPassiveDeGroote2016, | |
FiberForceLengthPassiveInverseDeGroote2016, | |
FiberForceVelocityDeGroote2016, | |
FiberForceVelocityInverseDeGroote2016, | |
TendonForceLengthDeGroote2016, | |
TendonForceLengthInverseDeGroote2016, | |
) | |
from sympy.physics.biomechanics._mixin import _NamedMixin | |
from sympy.physics.mechanics.actuator import ForceActuator | |
from sympy.physics.vector.functions import dynamicsymbols | |
__all__ = [ | |
'MusculotendonBase', | |
'MusculotendonDeGroote2016', | |
'MusculotendonFormulation', | |
] | |
class MusculotendonFormulation(IntEnum): | |
"""Enumeration of types of musculotendon dynamics formulations. | |
Explanation | |
=========== | |
An (integer) enumeration is used as it allows for clearer selection of the | |
different formulations of musculotendon dynamics. | |
Members | |
======= | |
RIGID_TENDON : 0 | |
A rigid tendon model. | |
FIBER_LENGTH_EXPLICIT : 1 | |
An explicit elastic tendon model with the muscle fiber length (l_M) as | |
the state variable. | |
TENDON_FORCE_EXPLICIT : 2 | |
An explicit elastic tendon model with the tendon force (F_T) as the | |
state variable. | |
FIBER_LENGTH_IMPLICIT : 3 | |
An implicit elastic tendon model with the muscle fiber length (l_M) as | |
the state variable and the muscle fiber velocity as an additional input | |
variable. | |
TENDON_FORCE_IMPLICIT : 4 | |
An implicit elastic tendon model with the tendon force (F_T) as the | |
state variable as the muscle fiber velocity as an additional input | |
variable. | |
""" | |
RIGID_TENDON = 0 | |
FIBER_LENGTH_EXPLICIT = 1 | |
TENDON_FORCE_EXPLICIT = 2 | |
FIBER_LENGTH_IMPLICIT = 3 | |
TENDON_FORCE_IMPLICIT = 4 | |
def __str__(self): | |
"""Returns a string representation of the enumeration value. | |
Notes | |
===== | |
This hard coding is required due to an incompatibility between the | |
``IntEnum`` implementations in Python 3.10 and Python 3.11 | |
(https://github.com/python/cpython/issues/84247). From Python 3.11 | |
onwards, the ``__str__`` method uses ``int.__str__``, whereas prior it | |
used ``Enum.__str__``. Once Python 3.11 becomes the minimum version | |
supported by SymPy, this method override can be removed. | |
""" | |
return str(self.value) | |
_DEFAULT_MUSCULOTENDON_FORMULATION = MusculotendonFormulation.RIGID_TENDON | |
class MusculotendonBase(ForceActuator, _NamedMixin): | |
r"""Abstract base class for all musculotendon classes to inherit from. | |
Explanation | |
=========== | |
A musculotendon generates a contractile force based on its activation, | |
length, and shortening velocity. This abstract base class is to be inherited | |
by all musculotendon subclasses that implement different characteristic | |
musculotendon curves. Characteristic musculotendon curves are required for | |
the tendon force-length, passive fiber force-length, active fiber force- | |
length, and fiber force-velocity relationships. | |
Parameters | |
========== | |
name : str | |
The name identifier associated with the musculotendon. This name is used | |
as a suffix when automatically generated symbols are instantiated. It | |
must be a string of nonzero length. | |
pathway : PathwayBase | |
The pathway that the actuator follows. This must be an instance of a | |
concrete subclass of ``PathwayBase``, e.g. ``LinearPathway``. | |
activation_dynamics : ActivationBase | |
The activation dynamics that will be modeled within the musculotendon. | |
This must be an instance of a concrete subclass of ``ActivationBase``, | |
e.g. ``FirstOrderActivationDeGroote2016``. | |
musculotendon_dynamics : MusculotendonFormulation | int | |
The formulation of musculotendon dynamics that should be used | |
internally, i.e. rigid or elastic tendon model, the choice of | |
musculotendon state etc. This must be a member of the integer | |
enumeration ``MusculotendonFormulation`` or an integer that can be cast | |
to a member. To use a rigid tendon formulation, set this to | |
``MusculotendonFormulation.RIGID_TENDON`` (or the integer value ``0``, | |
which will be cast to the enumeration member). There are four possible | |
formulations for an elastic tendon model. To use an explicit formulation | |
with the fiber length as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_EXPLICIT`` (or the integer value | |
``1``). To use an explicit formulation with the tendon force as the | |
state, set this to ``MusculotendonFormulation.TENDON_FORCE_EXPLICIT`` | |
(or the integer value ``2``). To use an implicit formulation with the | |
fiber length as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_IMPLICIT`` (or the integer value | |
``3``). To use an implicit formulation with the tendon force as the | |
state, set this to ``MusculotendonFormulation.TENDON_FORCE_IMPLICIT`` | |
(or the integer value ``4``). The default is | |
``MusculotendonFormulation.RIGID_TENDON``, which corresponds to a rigid | |
tendon formulation. | |
tendon_slack_length : Expr | None | |
The length of the tendon when the musculotendon is in its unloaded | |
state. In a rigid tendon model the tendon length is the tendon slack | |
length. In all musculotendon models, tendon slack length is used to | |
normalize tendon length to give | |
:math:`\tilde{l}^T = \frac{l^T}{l^T_{slack}}`. | |
peak_isometric_force : Expr | None | |
The maximum force that the muscle fiber can produce when it is | |
undergoing an isometric contraction (no lengthening velocity). In all | |
musculotendon models, peak isometric force is used to normalized tendon | |
and muscle fiber force to give | |
:math:`\tilde{F}^T = \frac{F^T}{F^M_{max}}`. | |
optimal_fiber_length : Expr | None | |
The muscle fiber length at which the muscle fibers produce no passive | |
force and their maximum active force. In all musculotendon models, | |
optimal fiber length is used to normalize muscle fiber length to give | |
:math:`\tilde{l}^M = \frac{l^M}{l^M_{opt}}`. | |
maximal_fiber_velocity : Expr | None | |
The fiber velocity at which, during muscle fiber shortening, the muscle | |
fibers are unable to produce any active force. In all musculotendon | |
models, maximal fiber velocity is used to normalize muscle fiber | |
extension velocity to give :math:`\tilde{v}^M = \frac{v^M}{v^M_{max}}`. | |
optimal_pennation_angle : Expr | None | |
The pennation angle when muscle fiber length equals the optimal fiber | |
length. | |
fiber_damping_coefficient : Expr | None | |
The coefficient of damping to be used in the damping element in the | |
muscle fiber model. | |
with_defaults : bool | |
Whether ``with_defaults`` alternate constructors should be used when | |
automatically constructing child classes. Default is ``False``. | |
""" | |
def __init__( | |
self, | |
name, | |
pathway, | |
activation_dynamics, | |
*, | |
musculotendon_dynamics=_DEFAULT_MUSCULOTENDON_FORMULATION, | |
tendon_slack_length=None, | |
peak_isometric_force=None, | |
optimal_fiber_length=None, | |
maximal_fiber_velocity=None, | |
optimal_pennation_angle=None, | |
fiber_damping_coefficient=None, | |
with_defaults=False, | |
): | |
self.name = name | |
# Supply a placeholder force to the super initializer, this will be | |
# replaced later | |
super().__init__(Symbol('F'), pathway) | |
# Activation dynamics | |
if not isinstance(activation_dynamics, ActivationBase): | |
msg = ( | |
f'Can\'t set attribute `activation_dynamics` to ' | |
f'{activation_dynamics} as it must be of type ' | |
f'`ActivationBase`, not {type(activation_dynamics)}.' | |
) | |
raise TypeError(msg) | |
self._activation_dynamics = activation_dynamics | |
self._child_objects = (self._activation_dynamics, ) | |
# Constants | |
if tendon_slack_length is not None: | |
self._l_T_slack = tendon_slack_length | |
else: | |
self._l_T_slack = Symbol(f'l_T_slack_{self.name}') | |
if peak_isometric_force is not None: | |
self._F_M_max = peak_isometric_force | |
else: | |
self._F_M_max = Symbol(f'F_M_max_{self.name}') | |
if optimal_fiber_length is not None: | |
self._l_M_opt = optimal_fiber_length | |
else: | |
self._l_M_opt = Symbol(f'l_M_opt_{self.name}') | |
if maximal_fiber_velocity is not None: | |
self._v_M_max = maximal_fiber_velocity | |
else: | |
self._v_M_max = Symbol(f'v_M_max_{self.name}') | |
if optimal_pennation_angle is not None: | |
self._alpha_opt = optimal_pennation_angle | |
else: | |
self._alpha_opt = Symbol(f'alpha_opt_{self.name}') | |
if fiber_damping_coefficient is not None: | |
self._beta = fiber_damping_coefficient | |
else: | |
self._beta = Symbol(f'beta_{self.name}') | |
# Musculotendon dynamics | |
self._with_defaults = with_defaults | |
if musculotendon_dynamics == MusculotendonFormulation.RIGID_TENDON: | |
self._rigid_tendon_musculotendon_dynamics() | |
elif musculotendon_dynamics == MusculotendonFormulation.FIBER_LENGTH_EXPLICIT: | |
self._fiber_length_explicit_musculotendon_dynamics() | |
elif musculotendon_dynamics == MusculotendonFormulation.TENDON_FORCE_EXPLICIT: | |
self._tendon_force_explicit_musculotendon_dynamics() | |
elif musculotendon_dynamics == MusculotendonFormulation.FIBER_LENGTH_IMPLICIT: | |
self._fiber_length_implicit_musculotendon_dynamics() | |
elif musculotendon_dynamics == MusculotendonFormulation.TENDON_FORCE_IMPLICIT: | |
self._tendon_force_implicit_musculotendon_dynamics() | |
else: | |
msg = ( | |
f'Musculotendon dynamics {repr(musculotendon_dynamics)} ' | |
f'passed to `musculotendon_dynamics` was of type ' | |
f'{type(musculotendon_dynamics)}, must be ' | |
f'{MusculotendonFormulation}.' | |
) | |
raise TypeError(msg) | |
self._musculotendon_dynamics = musculotendon_dynamics | |
# Must override the placeholder value in `self._force` now that the | |
# actual force has been calculated by | |
# `self._<MUSCULOTENDON FORMULATION>_musculotendon_dynamics`. | |
# Note that `self._force` assumes forces are expansile, musculotendon | |
# forces are contractile hence the minus sign preceeding `self._F_T` | |
# (the tendon force). | |
self._force = -self._F_T | |
def with_defaults( | |
cls, | |
name, | |
pathway, | |
activation_dynamics, | |
*, | |
musculotendon_dynamics=_DEFAULT_MUSCULOTENDON_FORMULATION, | |
tendon_slack_length=None, | |
peak_isometric_force=None, | |
optimal_fiber_length=None, | |
maximal_fiber_velocity=Float('10.0'), | |
optimal_pennation_angle=Float('0.0'), | |
fiber_damping_coefficient=Float('0.1'), | |
): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the musculotendon class using recommended | |
values for ``v_M_max``, ``alpha_opt``, and ``beta``. The values are: | |
:math:`v^M_{max} = 10` | |
:math:`\alpha_{opt} = 0` | |
:math:`\beta = \frac{1}{10}` | |
The musculotendon curves are also instantiated using the constants from | |
the original publication. | |
Parameters | |
========== | |
name : str | |
The name identifier associated with the musculotendon. This name is | |
used as a suffix when automatically generated symbols are | |
instantiated. It must be a string of nonzero length. | |
pathway : PathwayBase | |
The pathway that the actuator follows. This must be an instance of a | |
concrete subclass of ``PathwayBase``, e.g. ``LinearPathway``. | |
activation_dynamics : ActivationBase | |
The activation dynamics that will be modeled within the | |
musculotendon. This must be an instance of a concrete subclass of | |
``ActivationBase``, e.g. ``FirstOrderActivationDeGroote2016``. | |
musculotendon_dynamics : MusculotendonFormulation | int | |
The formulation of musculotendon dynamics that should be used | |
internally, i.e. rigid or elastic tendon model, the choice of | |
musculotendon state etc. This must be a member of the integer | |
enumeration ``MusculotendonFormulation`` or an integer that can be | |
cast to a member. To use a rigid tendon formulation, set this to | |
``MusculotendonFormulation.RIGID_TENDON`` (or the integer value | |
``0``, which will be cast to the enumeration member). There are four | |
possible formulations for an elastic tendon model. To use an | |
explicit formulation with the fiber length as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_EXPLICIT`` (or the integer | |
value ``1``). To use an explicit formulation with the tendon force | |
as the state, set this to | |
``MusculotendonFormulation.TENDON_FORCE_EXPLICIT`` (or the integer | |
value ``2``). To use an implicit formulation with the fiber length | |
as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_IMPLICIT`` (or the integer | |
value ``3``). To use an implicit formulation with the tendon force | |
as the state, set this to | |
``MusculotendonFormulation.TENDON_FORCE_IMPLICIT`` (or the integer | |
value ``4``). The default is | |
``MusculotendonFormulation.RIGID_TENDON``, which corresponds to a | |
rigid tendon formulation. | |
tendon_slack_length : Expr | None | |
The length of the tendon when the musculotendon is in its unloaded | |
state. In a rigid tendon model the tendon length is the tendon slack | |
length. In all musculotendon models, tendon slack length is used to | |
normalize tendon length to give | |
:math:`\tilde{l}^T = \frac{l^T}{l^T_{slack}}`. | |
peak_isometric_force : Expr | None | |
The maximum force that the muscle fiber can produce when it is | |
undergoing an isometric contraction (no lengthening velocity). In | |
all musculotendon models, peak isometric force is used to normalized | |
tendon and muscle fiber force to give | |
:math:`\tilde{F}^T = \frac{F^T}{F^M_{max}}`. | |
optimal_fiber_length : Expr | None | |
The muscle fiber length at which the muscle fibers produce no | |
passive force and their maximum active force. In all musculotendon | |
models, optimal fiber length is used to normalize muscle fiber | |
length to give :math:`\tilde{l}^M = \frac{l^M}{l^M_{opt}}`. | |
maximal_fiber_velocity : Expr | None | |
The fiber velocity at which, during muscle fiber shortening, the | |
muscle fibers are unable to produce any active force. In all | |
musculotendon models, maximal fiber velocity is used to normalize | |
muscle fiber extension velocity to give | |
:math:`\tilde{v}^M = \frac{v^M}{v^M_{max}}`. | |
optimal_pennation_angle : Expr | None | |
The pennation angle when muscle fiber length equals the optimal | |
fiber length. | |
fiber_damping_coefficient : Expr | None | |
The coefficient of damping to be used in the damping element in the | |
muscle fiber model. | |
""" | |
return cls( | |
name, | |
pathway, | |
activation_dynamics=activation_dynamics, | |
musculotendon_dynamics=musculotendon_dynamics, | |
tendon_slack_length=tendon_slack_length, | |
peak_isometric_force=peak_isometric_force, | |
optimal_fiber_length=optimal_fiber_length, | |
maximal_fiber_velocity=maximal_fiber_velocity, | |
optimal_pennation_angle=optimal_pennation_angle, | |
fiber_damping_coefficient=fiber_damping_coefficient, | |
with_defaults=True, | |
) | |
def curves(cls): | |
"""Return a ``CharacteristicCurveCollection`` of the curves related to | |
the specific model.""" | |
pass | |
def tendon_slack_length(self): | |
r"""Symbol or value corresponding to the tendon slack length constant. | |
Explanation | |
=========== | |
The length of the tendon when the musculotendon is in its unloaded | |
state. In a rigid tendon model the tendon length is the tendon slack | |
length. In all musculotendon models, tendon slack length is used to | |
normalize tendon length to give | |
:math:`\tilde{l}^T = \frac{l^T}{l^T_{slack}}`. | |
The alias ``l_T_slack`` can also be used to access the same attribute. | |
""" | |
return self._l_T_slack | |
def l_T_slack(self): | |
r"""Symbol or value corresponding to the tendon slack length constant. | |
Explanation | |
=========== | |
The length of the tendon when the musculotendon is in its unloaded | |
state. In a rigid tendon model the tendon length is the tendon slack | |
length. In all musculotendon models, tendon slack length is used to | |
normalize tendon length to give | |
:math:`\tilde{l}^T = \frac{l^T}{l^T_{slack}}`. | |
The alias ``tendon_slack_length`` can also be used to access the same | |
attribute. | |
""" | |
return self._l_T_slack | |
def peak_isometric_force(self): | |
r"""Symbol or value corresponding to the peak isometric force constant. | |
Explanation | |
=========== | |
The maximum force that the muscle fiber can produce when it is | |
undergoing an isometric contraction (no lengthening velocity). In all | |
musculotendon models, peak isometric force is used to normalized tendon | |
and muscle fiber force to give | |
:math:`\tilde{F}^T = \frac{F^T}{F^M_{max}}`. | |
The alias ``F_M_max`` can also be used to access the same attribute. | |
""" | |
return self._F_M_max | |
def F_M_max(self): | |
r"""Symbol or value corresponding to the peak isometric force constant. | |
Explanation | |
=========== | |
The maximum force that the muscle fiber can produce when it is | |
undergoing an isometric contraction (no lengthening velocity). In all | |
musculotendon models, peak isometric force is used to normalized tendon | |
and muscle fiber force to give | |
:math:`\tilde{F}^T = \frac{F^T}{F^M_{max}}`. | |
The alias ``peak_isometric_force`` can also be used to access the same | |
attribute. | |
""" | |
return self._F_M_max | |
def optimal_fiber_length(self): | |
r"""Symbol or value corresponding to the optimal fiber length constant. | |
Explanation | |
=========== | |
The muscle fiber length at which the muscle fibers produce no passive | |
force and their maximum active force. In all musculotendon models, | |
optimal fiber length is used to normalize muscle fiber length to give | |
:math:`\tilde{l}^M = \frac{l^M}{l^M_{opt}}`. | |
The alias ``l_M_opt`` can also be used to access the same attribute. | |
""" | |
return self._l_M_opt | |
def l_M_opt(self): | |
r"""Symbol or value corresponding to the optimal fiber length constant. | |
Explanation | |
=========== | |
The muscle fiber length at which the muscle fibers produce no passive | |
force and their maximum active force. In all musculotendon models, | |
optimal fiber length is used to normalize muscle fiber length to give | |
:math:`\tilde{l}^M = \frac{l^M}{l^M_{opt}}`. | |
The alias ``optimal_fiber_length`` can also be used to access the same | |
attribute. | |
""" | |
return self._l_M_opt | |
def maximal_fiber_velocity(self): | |
r"""Symbol or value corresponding to the maximal fiber velocity constant. | |
Explanation | |
=========== | |
The fiber velocity at which, during muscle fiber shortening, the muscle | |
fibers are unable to produce any active force. In all musculotendon | |
models, maximal fiber velocity is used to normalize muscle fiber | |
extension velocity to give :math:`\tilde{v}^M = \frac{v^M}{v^M_{max}}`. | |
The alias ``v_M_max`` can also be used to access the same attribute. | |
""" | |
return self._v_M_max | |
def v_M_max(self): | |
r"""Symbol or value corresponding to the maximal fiber velocity constant. | |
Explanation | |
=========== | |
The fiber velocity at which, during muscle fiber shortening, the muscle | |
fibers are unable to produce any active force. In all musculotendon | |
models, maximal fiber velocity is used to normalize muscle fiber | |
extension velocity to give :math:`\tilde{v}^M = \frac{v^M}{v^M_{max}}`. | |
The alias ``maximal_fiber_velocity`` can also be used to access the same | |
attribute. | |
""" | |
return self._v_M_max | |
def optimal_pennation_angle(self): | |
"""Symbol or value corresponding to the optimal pennation angle | |
constant. | |
Explanation | |
=========== | |
The pennation angle when muscle fiber length equals the optimal fiber | |
length. | |
The alias ``alpha_opt`` can also be used to access the same attribute. | |
""" | |
return self._alpha_opt | |
def alpha_opt(self): | |
"""Symbol or value corresponding to the optimal pennation angle | |
constant. | |
Explanation | |
=========== | |
The pennation angle when muscle fiber length equals the optimal fiber | |
length. | |
The alias ``optimal_pennation_angle`` can also be used to access the | |
same attribute. | |
""" | |
return self._alpha_opt | |
def fiber_damping_coefficient(self): | |
"""Symbol or value corresponding to the fiber damping coefficient | |
constant. | |
Explanation | |
=========== | |
The coefficient of damping to be used in the damping element in the | |
muscle fiber model. | |
The alias ``beta`` can also be used to access the same attribute. | |
""" | |
return self._beta | |
def beta(self): | |
"""Symbol or value corresponding to the fiber damping coefficient | |
constant. | |
Explanation | |
=========== | |
The coefficient of damping to be used in the damping element in the | |
muscle fiber model. | |
The alias ``fiber_damping_coefficient`` can also be used to access the | |
same attribute. | |
""" | |
return self._beta | |
def activation_dynamics(self): | |
"""Activation dynamics model governing this musculotendon's activation. | |
Explanation | |
=========== | |
Returns the instance of a subclass of ``ActivationBase`` that governs | |
the relationship between excitation and activation that is used to | |
represent the activation dynamics of this musculotendon. | |
""" | |
return self._activation_dynamics | |
def excitation(self): | |
"""Dynamic symbol representing excitation. | |
Explanation | |
=========== | |
The alias ``e`` can also be used to access the same attribute. | |
""" | |
return self._activation_dynamics._e | |
def e(self): | |
"""Dynamic symbol representing excitation. | |
Explanation | |
=========== | |
The alias ``excitation`` can also be used to access the same attribute. | |
""" | |
return self._activation_dynamics._e | |
def activation(self): | |
"""Dynamic symbol representing activation. | |
Explanation | |
=========== | |
The alias ``a`` can also be used to access the same attribute. | |
""" | |
return self._activation_dynamics._a | |
def a(self): | |
"""Dynamic symbol representing activation. | |
Explanation | |
=========== | |
The alias ``activation`` can also be used to access the same attribute. | |
""" | |
return self._activation_dynamics._a | |
def musculotendon_dynamics(self): | |
"""The choice of rigid or type of elastic tendon musculotendon dynamics. | |
Explanation | |
=========== | |
The formulation of musculotendon dynamics that should be used | |
internally, i.e. rigid or elastic tendon model, the choice of | |
musculotendon state etc. This must be a member of the integer | |
enumeration ``MusculotendonFormulation`` or an integer that can be cast | |
to a member. To use a rigid tendon formulation, set this to | |
``MusculotendonFormulation.RIGID_TENDON`` (or the integer value ``0``, | |
which will be cast to the enumeration member). There are four possible | |
formulations for an elastic tendon model. To use an explicit formulation | |
with the fiber length as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_EXPLICIT`` (or the integer value | |
``1``). To use an explicit formulation with the tendon force as the | |
state, set this to ``MusculotendonFormulation.TENDON_FORCE_EXPLICIT`` | |
(or the integer value ``2``). To use an implicit formulation with the | |
fiber length as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_IMPLICIT`` (or the integer value | |
``3``). To use an implicit formulation with the tendon force as the | |
state, set this to ``MusculotendonFormulation.TENDON_FORCE_IMPLICIT`` | |
(or the integer value ``4``). The default is | |
``MusculotendonFormulation.RIGID_TENDON``, which corresponds to a rigid | |
tendon formulation. | |
""" | |
return self._musculotendon_dynamics | |
def _rigid_tendon_musculotendon_dynamics(self): | |
"""Rigid tendon musculotendon.""" | |
self._l_MT = self.pathway.length | |
self._v_MT = self.pathway.extension_velocity | |
self._l_T = self._l_T_slack | |
self._l_T_tilde = Integer(1) | |
self._l_M = sqrt((self._l_MT - self._l_T)**2 + (self._l_M_opt*sin(self._alpha_opt))**2) | |
self._l_M_tilde = self._l_M/self._l_M_opt | |
self._v_M = self._v_MT*(self._l_MT - self._l_T_slack)/self._l_M | |
self._v_M_tilde = self._v_M/self._v_M_max | |
if self._with_defaults: | |
self._fl_T = self.curves.tendon_force_length.with_defaults(self._l_T_tilde) | |
self._fl_M_pas = self.curves.fiber_force_length_passive.with_defaults(self._l_M_tilde) | |
self._fl_M_act = self.curves.fiber_force_length_active.with_defaults(self._l_M_tilde) | |
self._fv_M = self.curves.fiber_force_velocity.with_defaults(self._v_M_tilde) | |
else: | |
fl_T_constants = symbols(f'c_0:4_fl_T_{self.name}') | |
self._fl_T = self.curves.tendon_force_length(self._l_T_tilde, *fl_T_constants) | |
fl_M_pas_constants = symbols(f'c_0:2_fl_M_pas_{self.name}') | |
self._fl_M_pas = self.curves.fiber_force_length_passive(self._l_M_tilde, *fl_M_pas_constants) | |
fl_M_act_constants = symbols(f'c_0:12_fl_M_act_{self.name}') | |
self._fl_M_act = self.curves.fiber_force_length_active(self._l_M_tilde, *fl_M_act_constants) | |
fv_M_constants = symbols(f'c_0:4_fv_M_{self.name}') | |
self._fv_M = self.curves.fiber_force_velocity(self._v_M_tilde, *fv_M_constants) | |
self._F_M_tilde = self.a*self._fl_M_act*self._fv_M + self._fl_M_pas + self._beta*self._v_M_tilde | |
self._F_T_tilde = self._F_M_tilde | |
self._F_M = self._F_M_tilde*self._F_M_max | |
self._cos_alpha = cos(self._alpha_opt) | |
self._F_T = self._F_M*self._cos_alpha | |
# Containers | |
self._state_vars = zeros(0, 1) | |
self._input_vars = zeros(0, 1) | |
self._state_eqns = zeros(0, 1) | |
self._curve_constants = Matrix( | |
fl_T_constants | |
+ fl_M_pas_constants | |
+ fl_M_act_constants | |
+ fv_M_constants | |
) if not self._with_defaults else zeros(0, 1) | |
def _fiber_length_explicit_musculotendon_dynamics(self): | |
"""Elastic tendon musculotendon using `l_M_tilde` as a state.""" | |
self._l_M_tilde = dynamicsymbols(f'l_M_tilde_{self.name}') | |
self._l_MT = self.pathway.length | |
self._v_MT = self.pathway.extension_velocity | |
self._l_M = self._l_M_tilde*self._l_M_opt | |
self._l_T = self._l_MT - sqrt(self._l_M**2 - (self._l_M_opt*sin(self._alpha_opt))**2) | |
self._l_T_tilde = self._l_T/self._l_T_slack | |
self._cos_alpha = (self._l_MT - self._l_T)/self._l_M | |
if self._with_defaults: | |
self._fl_T = self.curves.tendon_force_length.with_defaults(self._l_T_tilde) | |
self._fl_M_pas = self.curves.fiber_force_length_passive.with_defaults(self._l_M_tilde) | |
self._fl_M_act = self.curves.fiber_force_length_active.with_defaults(self._l_M_tilde) | |
else: | |
fl_T_constants = symbols(f'c_0:4_fl_T_{self.name}') | |
self._fl_T = self.curves.tendon_force_length(self._l_T_tilde, *fl_T_constants) | |
fl_M_pas_constants = symbols(f'c_0:2_fl_M_pas_{self.name}') | |
self._fl_M_pas = self.curves.fiber_force_length_passive(self._l_M_tilde, *fl_M_pas_constants) | |
fl_M_act_constants = symbols(f'c_0:12_fl_M_act_{self.name}') | |
self._fl_M_act = self.curves.fiber_force_length_active(self._l_M_tilde, *fl_M_act_constants) | |
self._F_T_tilde = self._fl_T | |
self._F_T = self._F_T_tilde*self._F_M_max | |
self._F_M = self._F_T/self._cos_alpha | |
self._F_M_tilde = self._F_M/self._F_M_max | |
self._fv_M = (self._F_M_tilde - self._fl_M_pas)/(self.a*self._fl_M_act) | |
if self._with_defaults: | |
self._v_M_tilde = self.curves.fiber_force_velocity_inverse.with_defaults(self._fv_M) | |
else: | |
fv_M_constants = symbols(f'c_0:4_fv_M_{self.name}') | |
self._v_M_tilde = self.curves.fiber_force_velocity_inverse(self._fv_M, *fv_M_constants) | |
self._dl_M_tilde_dt = (self._v_M_max/self._l_M_opt)*self._v_M_tilde | |
self._state_vars = Matrix([self._l_M_tilde]) | |
self._input_vars = zeros(0, 1) | |
self._state_eqns = Matrix([self._dl_M_tilde_dt]) | |
self._curve_constants = Matrix( | |
fl_T_constants | |
+ fl_M_pas_constants | |
+ fl_M_act_constants | |
+ fv_M_constants | |
) if not self._with_defaults else zeros(0, 1) | |
def _tendon_force_explicit_musculotendon_dynamics(self): | |
"""Elastic tendon musculotendon using `F_T_tilde` as a state.""" | |
self._F_T_tilde = dynamicsymbols(f'F_T_tilde_{self.name}') | |
self._l_MT = self.pathway.length | |
self._v_MT = self.pathway.extension_velocity | |
self._fl_T = self._F_T_tilde | |
if self._with_defaults: | |
self._fl_T_inv = self.curves.tendon_force_length_inverse.with_defaults(self._fl_T) | |
else: | |
fl_T_constants = symbols(f'c_0:4_fl_T_{self.name}') | |
self._fl_T_inv = self.curves.tendon_force_length_inverse(self._fl_T, *fl_T_constants) | |
self._l_T_tilde = self._fl_T_inv | |
self._l_T = self._l_T_tilde*self._l_T_slack | |
self._l_M = sqrt((self._l_MT - self._l_T)**2 + (self._l_M_opt*sin(self._alpha_opt))**2) | |
self._l_M_tilde = self._l_M/self._l_M_opt | |
if self._with_defaults: | |
self._fl_M_pas = self.curves.fiber_force_length_passive.with_defaults(self._l_M_tilde) | |
self._fl_M_act = self.curves.fiber_force_length_active.with_defaults(self._l_M_tilde) | |
else: | |
fl_M_pas_constants = symbols(f'c_0:2_fl_M_pas_{self.name}') | |
self._fl_M_pas = self.curves.fiber_force_length_passive(self._l_M_tilde, *fl_M_pas_constants) | |
fl_M_act_constants = symbols(f'c_0:12_fl_M_act_{self.name}') | |
self._fl_M_act = self.curves.fiber_force_length_active(self._l_M_tilde, *fl_M_act_constants) | |
self._cos_alpha = (self._l_MT - self._l_T)/self._l_M | |
self._F_T = self._F_T_tilde*self._F_M_max | |
self._F_M = self._F_T/self._cos_alpha | |
self._F_M_tilde = self._F_M/self._F_M_max | |
self._fv_M = (self._F_M_tilde - self._fl_M_pas)/(self.a*self._fl_M_act) | |
if self._with_defaults: | |
self._fv_M_inv = self.curves.fiber_force_velocity_inverse.with_defaults(self._fv_M) | |
else: | |
fv_M_constants = symbols(f'c_0:4_fv_M_{self.name}') | |
self._fv_M_inv = self.curves.fiber_force_velocity_inverse(self._fv_M, *fv_M_constants) | |
self._v_M_tilde = self._fv_M_inv | |
self._v_M = self._v_M_tilde*self._v_M_max | |
self._v_T = self._v_MT - (self._v_M/self._cos_alpha) | |
self._v_T_tilde = self._v_T/self._l_T_slack | |
if self._with_defaults: | |
self._fl_T = self.curves.tendon_force_length.with_defaults(self._l_T_tilde) | |
else: | |
self._fl_T = self.curves.tendon_force_length(self._l_T_tilde, *fl_T_constants) | |
self._dF_T_tilde_dt = self._fl_T.diff(dynamicsymbols._t).subs({self._l_T_tilde.diff(dynamicsymbols._t): self._v_T_tilde}) | |
self._state_vars = Matrix([self._F_T_tilde]) | |
self._input_vars = zeros(0, 1) | |
self._state_eqns = Matrix([self._dF_T_tilde_dt]) | |
self._curve_constants = Matrix( | |
fl_T_constants | |
+ fl_M_pas_constants | |
+ fl_M_act_constants | |
+ fv_M_constants | |
) if not self._with_defaults else zeros(0, 1) | |
def _fiber_length_implicit_musculotendon_dynamics(self): | |
raise NotImplementedError | |
def _tendon_force_implicit_musculotendon_dynamics(self): | |
raise NotImplementedError | |
def state_vars(self): | |
"""Ordered column matrix of functions of time that represent the state | |
variables. | |
Explanation | |
=========== | |
The alias ``x`` can also be used to access the same attribute. | |
""" | |
state_vars = [self._state_vars] | |
for child in self._child_objects: | |
state_vars.append(child.state_vars) | |
return Matrix.vstack(*state_vars) | |
def x(self): | |
"""Ordered column matrix of functions of time that represent the state | |
variables. | |
Explanation | |
=========== | |
The alias ``state_vars`` can also be used to access the same attribute. | |
""" | |
state_vars = [self._state_vars] | |
for child in self._child_objects: | |
state_vars.append(child.state_vars) | |
return Matrix.vstack(*state_vars) | |
def input_vars(self): | |
"""Ordered column matrix of functions of time that represent the input | |
variables. | |
Explanation | |
=========== | |
The alias ``r`` can also be used to access the same attribute. | |
""" | |
input_vars = [self._input_vars] | |
for child in self._child_objects: | |
input_vars.append(child.input_vars) | |
return Matrix.vstack(*input_vars) | |
def r(self): | |
"""Ordered column matrix of functions of time that represent the input | |
variables. | |
Explanation | |
=========== | |
The alias ``input_vars`` can also be used to access the same attribute. | |
""" | |
input_vars = [self._input_vars] | |
for child in self._child_objects: | |
input_vars.append(child.input_vars) | |
return Matrix.vstack(*input_vars) | |
def constants(self): | |
"""Ordered column matrix of non-time varying symbols present in ``M`` | |
and ``F``. | |
Explanation | |
=========== | |
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) | |
has been used instead of ``Symbol`` for a constant then that attribute | |
will not be included in the matrix returned by this property. This is | |
because the primary use of this property attribute is to provide an | |
ordered sequence of the still-free symbols that require numeric values | |
during code generation. | |
The alias ``p`` can also be used to access the same attribute. | |
""" | |
musculotendon_constants = [ | |
self._l_T_slack, | |
self._F_M_max, | |
self._l_M_opt, | |
self._v_M_max, | |
self._alpha_opt, | |
self._beta, | |
] | |
musculotendon_constants = [ | |
c for c in musculotendon_constants if not c.is_number | |
] | |
constants = [ | |
Matrix(musculotendon_constants) | |
if musculotendon_constants | |
else zeros(0, 1) | |
] | |
for child in self._child_objects: | |
constants.append(child.constants) | |
constants.append(self._curve_constants) | |
return Matrix.vstack(*constants) | |
def p(self): | |
"""Ordered column matrix of non-time varying symbols present in ``M`` | |
and ``F``. | |
Explanation | |
=========== | |
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) | |
has been used instead of ``Symbol`` for a constant then that attribute | |
will not be included in the matrix returned by this property. This is | |
because the primary use of this property attribute is to provide an | |
ordered sequence of the still-free symbols that require numeric values | |
during code generation. | |
The alias ``constants`` can also be used to access the same attribute. | |
""" | |
musculotendon_constants = [ | |
self._l_T_slack, | |
self._F_M_max, | |
self._l_M_opt, | |
self._v_M_max, | |
self._alpha_opt, | |
self._beta, | |
] | |
musculotendon_constants = [ | |
c for c in musculotendon_constants if not c.is_number | |
] | |
constants = [ | |
Matrix(musculotendon_constants) | |
if musculotendon_constants | |
else zeros(0, 1) | |
] | |
for child in self._child_objects: | |
constants.append(child.constants) | |
constants.append(self._curve_constants) | |
return Matrix.vstack(*constants) | |
def M(self): | |
"""Ordered square matrix of coefficients on the LHS of ``M x' = F``. | |
Explanation | |
=========== | |
The square matrix that forms part of the LHS of the linear system of | |
ordinary differential equations governing the activation dynamics: | |
``M(x, r, t, p) x' = F(x, r, t, p)``. | |
As zeroth-order activation dynamics have no state variables, this | |
linear system has dimension 0 and therefore ``M`` is an empty square | |
``Matrix`` with shape (0, 0). | |
""" | |
M = [eye(len(self._state_vars))] | |
for child in self._child_objects: | |
M.append(child.M) | |
return diag(*M) | |
def F(self): | |
"""Ordered column matrix of equations on the RHS of ``M x' = F``. | |
Explanation | |
=========== | |
The column matrix that forms the RHS of the linear system of ordinary | |
differential equations governing the activation dynamics: | |
``M(x, r, t, p) x' = F(x, r, t, p)``. | |
As zeroth-order activation dynamics have no state variables, this | |
linear system has dimension 0 and therefore ``F`` is an empty column | |
``Matrix`` with shape (0, 1). | |
""" | |
F = [self._state_eqns] | |
for child in self._child_objects: | |
F.append(child.F) | |
return Matrix.vstack(*F) | |
def rhs(self): | |
"""Ordered column matrix of equations for the solution of ``M x' = F``. | |
Explanation | |
=========== | |
The solution to the linear system of ordinary differential equations | |
governing the activation dynamics: | |
``M(x, r, t, p) x' = F(x, r, t, p)``. | |
As zeroth-order activation dynamics have no state variables, this | |
linear has dimension 0 and therefore this method returns an empty | |
column ``Matrix`` with shape (0, 1). | |
""" | |
is_explicit = ( | |
MusculotendonFormulation.FIBER_LENGTH_EXPLICIT, | |
MusculotendonFormulation.TENDON_FORCE_EXPLICIT, | |
) | |
if self.musculotendon_dynamics is MusculotendonFormulation.RIGID_TENDON: | |
child_rhs = [child.rhs() for child in self._child_objects] | |
return Matrix.vstack(*child_rhs) | |
elif self.musculotendon_dynamics in is_explicit: | |
rhs = self._state_eqns | |
child_rhs = [child.rhs() for child in self._child_objects] | |
return Matrix.vstack(rhs, *child_rhs) | |
return self.M.solve(self.F) | |
def __repr__(self): | |
"""Returns a string representation to reinstantiate the model.""" | |
return ( | |
f'{self.__class__.__name__}({self.name!r}, ' | |
f'pathway={self.pathway!r}, ' | |
f'activation_dynamics={self.activation_dynamics!r}, ' | |
f'musculotendon_dynamics={self.musculotendon_dynamics}, ' | |
f'tendon_slack_length={self._l_T_slack!r}, ' | |
f'peak_isometric_force={self._F_M_max!r}, ' | |
f'optimal_fiber_length={self._l_M_opt!r}, ' | |
f'maximal_fiber_velocity={self._v_M_max!r}, ' | |
f'optimal_pennation_angle={self._alpha_opt!r}, ' | |
f'fiber_damping_coefficient={self._beta!r})' | |
) | |
def __str__(self): | |
"""Returns a string representation of the expression for musculotendon | |
force.""" | |
return str(self.force) | |
class MusculotendonDeGroote2016(MusculotendonBase): | |
r"""Musculotendon model using the curves of De Groote et al., 2016 [1]_. | |
Examples | |
======== | |
This class models the musculotendon actuator parametrized by the | |
characteristic curves described in De Groote et al., 2016 [1]_. Like all | |
musculotendon models in SymPy's biomechanics module, it requires a pathway | |
to define its line of action. We'll begin by creating a simple | |
``LinearPathway`` between two points that our musculotendon will follow. | |
We'll create a point ``O`` to represent the musculotendon's origin and | |
another ``I`` to represent its insertion. | |
>>> from sympy import symbols | |
>>> from sympy.physics.mechanics import (LinearPathway, Point, | |
... ReferenceFrame, dynamicsymbols) | |
>>> N = ReferenceFrame('N') | |
>>> O, I = O, P = symbols('O, I', cls=Point) | |
>>> q, u = dynamicsymbols('q, u', real=True) | |
>>> I.set_pos(O, q*N.x) | |
>>> O.set_vel(N, 0) | |
>>> I.set_vel(N, u*N.x) | |
>>> pathway = LinearPathway(O, I) | |
>>> pathway.attachments | |
(O, I) | |
>>> pathway.length | |
Abs(q(t)) | |
>>> pathway.extension_velocity | |
sign(q(t))*Derivative(q(t), t) | |
A musculotendon also takes an instance of an activation dynamics model as | |
this will be used to provide symbols for the activation in the formulation | |
of the musculotendon dynamics. We'll use an instance of | |
``FirstOrderActivationDeGroote2016`` to represent first-order activation | |
dynamics. Note that a single name argument needs to be provided as SymPy | |
will use this as a suffix. | |
>>> from sympy.physics.biomechanics import FirstOrderActivationDeGroote2016 | |
>>> activation = FirstOrderActivationDeGroote2016('muscle') | |
>>> activation.x | |
Matrix([[a_muscle(t)]]) | |
>>> activation.r | |
Matrix([[e_muscle(t)]]) | |
>>> activation.p | |
Matrix([ | |
[tau_a_muscle], | |
[tau_d_muscle], | |
[ b_muscle]]) | |
>>> activation.rhs() | |
Matrix([[((1/2 - tanh(b_muscle*(-a_muscle(t) + e_muscle(t)))/2)*(3*...]]) | |
The musculotendon class requires symbols or values to be passed to represent | |
the constants in the musculotendon dynamics. We'll use SymPy's ``symbols`` | |
function to create symbols for the maximum isometric force ``F_M_max``, | |
optimal fiber length ``l_M_opt``, tendon slack length ``l_T_slack``, maximum | |
fiber velocity ``v_M_max``, optimal pennation angle ``alpha_opt, and fiber | |
damping coefficient ``beta``. | |
>>> F_M_max = symbols('F_M_max', real=True) | |
>>> l_M_opt = symbols('l_M_opt', real=True) | |
>>> l_T_slack = symbols('l_T_slack', real=True) | |
>>> v_M_max = symbols('v_M_max', real=True) | |
>>> alpha_opt = symbols('alpha_opt', real=True) | |
>>> beta = symbols('beta', real=True) | |
We can then import the class ``MusculotendonDeGroote2016`` from the | |
biomechanics module and create an instance by passing in the various objects | |
we have previously instantiated. By default, a musculotendon model with | |
rigid tendon musculotendon dynamics will be created. | |
>>> from sympy.physics.biomechanics import MusculotendonDeGroote2016 | |
>>> rigid_tendon_muscle = MusculotendonDeGroote2016( | |
... 'muscle', | |
... pathway, | |
... activation, | |
... tendon_slack_length=l_T_slack, | |
... peak_isometric_force=F_M_max, | |
... optimal_fiber_length=l_M_opt, | |
... maximal_fiber_velocity=v_M_max, | |
... optimal_pennation_angle=alpha_opt, | |
... fiber_damping_coefficient=beta, | |
... ) | |
We can inspect the various properties of the musculotendon, including | |
getting the symbolic expression describing the force it produces using its | |
``force`` attribute. | |
>>> rigid_tendon_muscle.force | |
-F_M_max*(beta*(-l_T_slack + Abs(q(t)))*sign(q(t))*Derivative(q(t), t)... | |
When we created the musculotendon object, we passed in an instance of an | |
activation dynamics object that governs the activation within the | |
musculotendon. SymPy makes a design choice here that the activation dynamics | |
instance will be treated as a child object of the musculotendon dynamics. | |
Therefore, if we want to inspect the state and input variables associated | |
with the musculotendon model, we will also be returned the state and input | |
variables associated with the child object, or the activation dynamics in | |
this case. As the musculotendon model that we created here uses rigid tendon | |
dynamics, no additional states or inputs relating to the musculotendon are | |
introduces. Consequently, the model has a single state associated with it, | |
the activation, and a single input associated with it, the excitation. The | |
states and inputs can be inspected using the ``x`` and ``r`` attributes | |
respectively. Note that both ``x`` and ``r`` have the alias attributes of | |
``state_vars`` and ``input_vars``. | |
>>> rigid_tendon_muscle.x | |
Matrix([[a_muscle(t)]]) | |
>>> rigid_tendon_muscle.r | |
Matrix([[e_muscle(t)]]) | |
To see which constants are symbolic in the musculotendon model, we can use | |
the ``p`` or ``constants`` attribute. This returns a ``Matrix`` populated | |
by the constants that are represented by a ``Symbol`` rather than a numeric | |
value. | |
>>> rigid_tendon_muscle.p | |
Matrix([ | |
[ l_T_slack], | |
[ F_M_max], | |
[ l_M_opt], | |
[ v_M_max], | |
[ alpha_opt], | |
[ beta], | |
[ tau_a_muscle], | |
[ tau_d_muscle], | |
[ b_muscle], | |
[ c_0_fl_T_muscle], | |
[ c_1_fl_T_muscle], | |
[ c_2_fl_T_muscle], | |
[ c_3_fl_T_muscle], | |
[ c_0_fl_M_pas_muscle], | |
[ c_1_fl_M_pas_muscle], | |
[ c_0_fl_M_act_muscle], | |
[ c_1_fl_M_act_muscle], | |
[ c_2_fl_M_act_muscle], | |
[ c_3_fl_M_act_muscle], | |
[ c_4_fl_M_act_muscle], | |
[ c_5_fl_M_act_muscle], | |
[ c_6_fl_M_act_muscle], | |
[ c_7_fl_M_act_muscle], | |
[ c_8_fl_M_act_muscle], | |
[ c_9_fl_M_act_muscle], | |
[c_10_fl_M_act_muscle], | |
[c_11_fl_M_act_muscle], | |
[ c_0_fv_M_muscle], | |
[ c_1_fv_M_muscle], | |
[ c_2_fv_M_muscle], | |
[ c_3_fv_M_muscle]]) | |
Finally, we can call the ``rhs`` method to return a ``Matrix`` that | |
contains as its elements the righthand side of the ordinary differential | |
equations corresponding to each of the musculotendon's states. Like the | |
method with the same name on the ``Method`` classes in SymPy's mechanics | |
module, this returns a column vector where the number of rows corresponds to | |
the number of states. For our example here, we have a single state, the | |
dynamic symbol ``a_muscle(t)``, so the returned value is a 1-by-1 | |
``Matrix``. | |
>>> rigid_tendon_muscle.rhs() | |
Matrix([[((1/2 - tanh(b_muscle*(-a_muscle(t) + e_muscle(t)))/2)*(3*...]]) | |
The musculotendon class supports elastic tendon musculotendon models in | |
addition to rigid tendon ones. You can choose to either use the fiber length | |
or tendon force as an additional state. You can also specify whether an | |
explicit or implicit formulation should be used. To select a formulation, | |
pass a member of the ``MusculotendonFormulation`` enumeration to the | |
``musculotendon_dynamics`` parameter when calling the constructor. This | |
enumeration is an ``IntEnum``, so you can also pass an integer, however it | |
is recommended to use the enumeration as it is clearer which formulation you | |
are actually selecting. Below, we'll use the ``FIBER_LENGTH_EXPLICIT`` | |
member to create a musculotendon with an elastic tendon that will use the | |
(normalized) muscle fiber length as an additional state and will produce | |
the governing ordinary differential equation in explicit form. | |
>>> from sympy.physics.biomechanics import MusculotendonFormulation | |
>>> elastic_tendon_muscle = MusculotendonDeGroote2016( | |
... 'muscle', | |
... pathway, | |
... activation, | |
... musculotendon_dynamics=MusculotendonFormulation.FIBER_LENGTH_EXPLICIT, | |
... tendon_slack_length=l_T_slack, | |
... peak_isometric_force=F_M_max, | |
... optimal_fiber_length=l_M_opt, | |
... maximal_fiber_velocity=v_M_max, | |
... optimal_pennation_angle=alpha_opt, | |
... fiber_damping_coefficient=beta, | |
... ) | |
>>> elastic_tendon_muscle.force | |
-F_M_max*TendonForceLengthDeGroote2016((-sqrt(l_M_opt**2*... | |
>>> elastic_tendon_muscle.x | |
Matrix([ | |
[l_M_tilde_muscle(t)], | |
[ a_muscle(t)]]) | |
>>> elastic_tendon_muscle.r | |
Matrix([[e_muscle(t)]]) | |
>>> elastic_tendon_muscle.p | |
Matrix([ | |
[ l_T_slack], | |
[ F_M_max], | |
[ l_M_opt], | |
[ v_M_max], | |
[ alpha_opt], | |
[ beta], | |
[ tau_a_muscle], | |
[ tau_d_muscle], | |
[ b_muscle], | |
[ c_0_fl_T_muscle], | |
[ c_1_fl_T_muscle], | |
[ c_2_fl_T_muscle], | |
[ c_3_fl_T_muscle], | |
[ c_0_fl_M_pas_muscle], | |
[ c_1_fl_M_pas_muscle], | |
[ c_0_fl_M_act_muscle], | |
[ c_1_fl_M_act_muscle], | |
[ c_2_fl_M_act_muscle], | |
[ c_3_fl_M_act_muscle], | |
[ c_4_fl_M_act_muscle], | |
[ c_5_fl_M_act_muscle], | |
[ c_6_fl_M_act_muscle], | |
[ c_7_fl_M_act_muscle], | |
[ c_8_fl_M_act_muscle], | |
[ c_9_fl_M_act_muscle], | |
[c_10_fl_M_act_muscle], | |
[c_11_fl_M_act_muscle], | |
[ c_0_fv_M_muscle], | |
[ c_1_fv_M_muscle], | |
[ c_2_fv_M_muscle], | |
[ c_3_fv_M_muscle]]) | |
>>> elastic_tendon_muscle.rhs() | |
Matrix([ | |
[v_M_max*FiberForceVelocityInverseDeGroote2016((l_M_opt*...], | |
[ ((1/2 - tanh(b_muscle*(-a_muscle(t) + e_muscle(t)))/2)*(3*...]]) | |
It is strongly recommended to use the alternate ``with_defaults`` | |
constructor when creating an instance because this will ensure that the | |
published constants are used in the musculotendon characteristic curves. | |
>>> elastic_tendon_muscle = MusculotendonDeGroote2016.with_defaults( | |
... 'muscle', | |
... pathway, | |
... activation, | |
... musculotendon_dynamics=MusculotendonFormulation.FIBER_LENGTH_EXPLICIT, | |
... tendon_slack_length=l_T_slack, | |
... peak_isometric_force=F_M_max, | |
... optimal_fiber_length=l_M_opt, | |
... ) | |
>>> elastic_tendon_muscle.x | |
Matrix([ | |
[l_M_tilde_muscle(t)], | |
[ a_muscle(t)]]) | |
>>> elastic_tendon_muscle.r | |
Matrix([[e_muscle(t)]]) | |
>>> elastic_tendon_muscle.p | |
Matrix([ | |
[ l_T_slack], | |
[ F_M_max], | |
[ l_M_opt], | |
[tau_a_muscle], | |
[tau_d_muscle], | |
[ b_muscle]]) | |
Parameters | |
========== | |
name : str | |
The name identifier associated with the musculotendon. This name is used | |
as a suffix when automatically generated symbols are instantiated. It | |
must be a string of nonzero length. | |
pathway : PathwayBase | |
The pathway that the actuator follows. This must be an instance of a | |
concrete subclass of ``PathwayBase``, e.g. ``LinearPathway``. | |
activation_dynamics : ActivationBase | |
The activation dynamics that will be modeled within the musculotendon. | |
This must be an instance of a concrete subclass of ``ActivationBase``, | |
e.g. ``FirstOrderActivationDeGroote2016``. | |
musculotendon_dynamics : MusculotendonFormulation | int | |
The formulation of musculotendon dynamics that should be used | |
internally, i.e. rigid or elastic tendon model, the choice of | |
musculotendon state etc. This must be a member of the integer | |
enumeration ``MusculotendonFormulation`` or an integer that can be cast | |
to a member. To use a rigid tendon formulation, set this to | |
``MusculotendonFormulation.RIGID_TENDON`` (or the integer value ``0``, | |
which will be cast to the enumeration member). There are four possible | |
formulations for an elastic tendon model. To use an explicit formulation | |
with the fiber length as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_EXPLICIT`` (or the integer value | |
``1``). To use an explicit formulation with the tendon force as the | |
state, set this to ``MusculotendonFormulation.TENDON_FORCE_EXPLICIT`` | |
(or the integer value ``2``). To use an implicit formulation with the | |
fiber length as the state, set this to | |
``MusculotendonFormulation.FIBER_LENGTH_IMPLICIT`` (or the integer value | |
``3``). To use an implicit formulation with the tendon force as the | |
state, set this to ``MusculotendonFormulation.TENDON_FORCE_IMPLICIT`` | |
(or the integer value ``4``). The default is | |
``MusculotendonFormulation.RIGID_TENDON``, which corresponds to a rigid | |
tendon formulation. | |
tendon_slack_length : Expr | None | |
The length of the tendon when the musculotendon is in its unloaded | |
state. In a rigid tendon model the tendon length is the tendon slack | |
length. In all musculotendon models, tendon slack length is used to | |
normalize tendon length to give | |
:math:`\tilde{l}^T = \frac{l^T}{l^T_{slack}}`. | |
peak_isometric_force : Expr | None | |
The maximum force that the muscle fiber can produce when it is | |
undergoing an isometric contraction (no lengthening velocity). In all | |
musculotendon models, peak isometric force is used to normalized tendon | |
and muscle fiber force to give | |
:math:`\tilde{F}^T = \frac{F^T}{F^M_{max}}`. | |
optimal_fiber_length : Expr | None | |
The muscle fiber length at which the muscle fibers produce no passive | |
force and their maximum active force. In all musculotendon models, | |
optimal fiber length is used to normalize muscle fiber length to give | |
:math:`\tilde{l}^M = \frac{l^M}{l^M_{opt}}`. | |
maximal_fiber_velocity : Expr | None | |
The fiber velocity at which, during muscle fiber shortening, the muscle | |
fibers are unable to produce any active force. In all musculotendon | |
models, maximal fiber velocity is used to normalize muscle fiber | |
extension velocity to give :math:`\tilde{v}^M = \frac{v^M}{v^M_{max}}`. | |
optimal_pennation_angle : Expr | None | |
The pennation angle when muscle fiber length equals the optimal fiber | |
length. | |
fiber_damping_coefficient : Expr | None | |
The coefficient of damping to be used in the damping element in the | |
muscle fiber model. | |
with_defaults : bool | |
Whether ``with_defaults`` alternate constructors should be used when | |
automatically constructing child classes. Default is ``False``. | |
References | |
========== | |
.. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation | |
of direct collocation optimal control problem formulations for | |
solving the muscle redundancy problem, Annals of biomedical | |
engineering, 44(10), (2016) pp. 2922-2936 | |
""" | |
curves = CharacteristicCurveCollection( | |
tendon_force_length=TendonForceLengthDeGroote2016, | |
tendon_force_length_inverse=TendonForceLengthInverseDeGroote2016, | |
fiber_force_length_passive=FiberForceLengthPassiveDeGroote2016, | |
fiber_force_length_passive_inverse=FiberForceLengthPassiveInverseDeGroote2016, | |
fiber_force_length_active=FiberForceLengthActiveDeGroote2016, | |
fiber_force_velocity=FiberForceVelocityDeGroote2016, | |
fiber_force_velocity_inverse=FiberForceVelocityInverseDeGroote2016, | |
) | |