Spaces:
Sleeping
Sleeping
"""Implementations of characteristic curves for musculotendon models.""" | |
from dataclasses import dataclass | |
from sympy.core.expr import UnevaluatedExpr | |
from sympy.core.function import ArgumentIndexError, Function | |
from sympy.core.numbers import Float, Integer | |
from sympy.functions.elementary.exponential import exp, log | |
from sympy.functions.elementary.hyperbolic import cosh, sinh | |
from sympy.functions.elementary.miscellaneous import sqrt | |
from sympy.printing.precedence import PRECEDENCE | |
__all__ = [ | |
'CharacteristicCurveCollection', | |
'CharacteristicCurveFunction', | |
'FiberForceLengthActiveDeGroote2016', | |
'FiberForceLengthPassiveDeGroote2016', | |
'FiberForceLengthPassiveInverseDeGroote2016', | |
'FiberForceVelocityDeGroote2016', | |
'FiberForceVelocityInverseDeGroote2016', | |
'TendonForceLengthDeGroote2016', | |
'TendonForceLengthInverseDeGroote2016', | |
] | |
class CharacteristicCurveFunction(Function): | |
"""Base class for all musculotendon characteristic curve functions.""" | |
def eval(cls): | |
msg = ( | |
f'Cannot directly instantiate {cls.__name__!r}, instances of ' | |
f'characteristic curves must be of a concrete subclass.' | |
) | |
raise TypeError(msg) | |
def _print_code(self, printer): | |
"""Print code for the function defining the curve using a printer. | |
Explanation | |
=========== | |
The order of operations may need to be controlled as constant folding | |
the numeric terms within the equations of a musculotendon | |
characteristic curve can sometimes results in a numerically-unstable | |
expression. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print a string representation of the | |
characteristic curve as valid code in the target language. | |
""" | |
return printer._print(printer.parenthesize( | |
self.doit(deep=False, evaluate=False), PRECEDENCE['Atom'], | |
)) | |
_ccode = _print_code | |
_cupycode = _print_code | |
_cxxcode = _print_code | |
_fcode = _print_code | |
_jaxcode = _print_code | |
_lambdacode = _print_code | |
_mpmathcode = _print_code | |
_octave = _print_code | |
_pythoncode = _print_code | |
_numpycode = _print_code | |
_scipycode = _print_code | |
class TendonForceLengthDeGroote2016(CharacteristicCurveFunction): | |
r"""Tendon force-length curve based on De Groote et al., 2016 [1]_. | |
Explanation | |
=========== | |
Gives the normalized tendon force produced as a function of normalized | |
tendon length. | |
The function is defined by the equation: | |
$fl^T = c_0 \exp{c_3 \left( \tilde{l}^T - c_1 \right)} - c_2$ | |
with constant values of $c_0 = 0.2$, $c_1 = 0.995$, $c_2 = 0.25$, and | |
$c_3 = 33.93669377311689$. | |
While it is possible to change the constant values, these were carefully | |
selected in the original publication to give the characteristic curve | |
specific and required properties. For example, the function produces no | |
force when the tendon is in an unstrained state. It also produces a force | |
of 1 normalized unit when the tendon is under a 5% strain. | |
Examples | |
======== | |
The preferred way to instantiate :class:`TendonForceLengthDeGroote2016` is using | |
the :meth:`~.with_defaults` constructor because this will automatically | |
populate the constants within the characteristic curve equation with the | |
floating point values from the original publication. This constructor takes | |
a single argument corresponding to normalized tendon length. We'll create a | |
:class:`~.Symbol` called ``l_T_tilde`` to represent this. | |
>>> from sympy import Symbol | |
>>> from sympy.physics.biomechanics import TendonForceLengthDeGroote2016 | |
>>> l_T_tilde = Symbol('l_T_tilde') | |
>>> fl_T = TendonForceLengthDeGroote2016.with_defaults(l_T_tilde) | |
>>> fl_T | |
TendonForceLengthDeGroote2016(l_T_tilde, 0.2, 0.995, 0.25, | |
33.93669377311689) | |
It's also possible to populate the four constants with your own values too. | |
>>> from sympy import symbols | |
>>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') | |
>>> fl_T = TendonForceLengthDeGroote2016(l_T_tilde, c0, c1, c2, c3) | |
>>> fl_T | |
TendonForceLengthDeGroote2016(l_T_tilde, c0, c1, c2, c3) | |
You don't just have to use symbols as the arguments, it's also possible to | |
use expressions. Let's create a new pair of symbols, ``l_T`` and | |
``l_T_slack``, representing tendon length and tendon slack length | |
respectively. We can then represent ``l_T_tilde`` as an expression, the | |
ratio of these. | |
>>> l_T, l_T_slack = symbols('l_T l_T_slack') | |
>>> l_T_tilde = l_T/l_T_slack | |
>>> fl_T = TendonForceLengthDeGroote2016.with_defaults(l_T_tilde) | |
>>> fl_T | |
TendonForceLengthDeGroote2016(l_T/l_T_slack, 0.2, 0.995, 0.25, | |
33.93669377311689) | |
To inspect the actual symbolic expression that this function represents, | |
we can call the :meth:`~.doit` method on an instance. We'll use the keyword | |
argument ``evaluate=False`` as this will keep the expression in its | |
canonical form and won't simplify any constants. | |
>>> fl_T.doit(evaluate=False) | |
-0.25 + 0.2*exp(33.93669377311689*(l_T/l_T_slack - 0.995)) | |
The function can also be differentiated. We'll differentiate with respect | |
to l_T using the ``diff`` method on an instance with the single positional | |
argument ``l_T``. | |
>>> fl_T.diff(l_T) | |
6.787338754623378*exp(33.93669377311689*(l_T/l_T_slack - 0.995))/l_T_slack | |
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 | |
""" | |
def with_defaults(cls, l_T_tilde): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the tendon force-length function using the | |
four constant values specified in the original publication. | |
These have the values: | |
$c_0 = 0.2$ | |
$c_1 = 0.995$ | |
$c_2 = 0.25$ | |
$c_3 = 33.93669377311689$ | |
Parameters | |
========== | |
l_T_tilde : Any (sympifiable) | |
Normalized tendon length. | |
""" | |
c0 = Float('0.2') | |
c1 = Float('0.995') | |
c2 = Float('0.25') | |
c3 = Float('33.93669377311689') | |
return cls(l_T_tilde, c0, c1, c2, c3) | |
def eval(cls, l_T_tilde, c0, c1, c2, c3): | |
"""Evaluation of basic inputs. | |
Parameters | |
========== | |
l_T_tilde : Any (sympifiable) | |
Normalized tendon length. | |
c0 : Any (sympifiable) | |
The first constant in the characteristic equation. The published | |
value is ``0.2``. | |
c1 : Any (sympifiable) | |
The second constant in the characteristic equation. The published | |
value is ``0.995``. | |
c2 : Any (sympifiable) | |
The third constant in the characteristic equation. The published | |
value is ``0.25``. | |
c3 : Any (sympifiable) | |
The fourth constant in the characteristic equation. The published | |
value is ``33.93669377311689``. | |
""" | |
pass | |
def _eval_evalf(self, prec): | |
"""Evaluate the expression numerically using ``evalf``.""" | |
return self.doit(deep=False, evaluate=False)._eval_evalf(prec) | |
def doit(self, deep=True, evaluate=True, **hints): | |
"""Evaluate the expression defining the function. | |
Parameters | |
========== | |
deep : bool | |
Whether ``doit`` should be recursively called. Default is ``True``. | |
evaluate : bool. | |
Whether the SymPy expression should be evaluated as it is | |
constructed. If ``False``, then no constant folding will be | |
conducted which will leave the expression in a more numerically- | |
stable for values of ``l_T_tilde`` that correspond to a sensible | |
operating range for a musculotendon. Default is ``True``. | |
**kwargs : dict[str, Any] | |
Additional keyword argument pairs to be recursively passed to | |
``doit``. | |
""" | |
l_T_tilde, *constants = self.args | |
if deep: | |
hints['evaluate'] = evaluate | |
l_T_tilde = l_T_tilde.doit(deep=deep, **hints) | |
c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] | |
else: | |
c0, c1, c2, c3 = constants | |
if evaluate: | |
return c0*exp(c3*(l_T_tilde - c1)) - c2 | |
return c0*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) - c2 | |
def fdiff(self, argindex=1): | |
"""Derivative of the function with respect to a single argument. | |
Parameters | |
========== | |
argindex : int | |
The index of the function's arguments with respect to which the | |
derivative should be taken. Argument indexes start at ``1``. | |
Default is ``1``. | |
""" | |
l_T_tilde, c0, c1, c2, c3 = self.args | |
if argindex == 1: | |
return c0*c3*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) | |
elif argindex == 2: | |
return exp(c3*UnevaluatedExpr(l_T_tilde - c1)) | |
elif argindex == 3: | |
return -c0*c3*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) | |
elif argindex == 4: | |
return Integer(-1) | |
elif argindex == 5: | |
return c0*(l_T_tilde - c1)*exp(c3*UnevaluatedExpr(l_T_tilde - c1)) | |
raise ArgumentIndexError(self, argindex) | |
def inverse(self, argindex=1): | |
"""Inverse function. | |
Parameters | |
========== | |
argindex : int | |
Value to start indexing the arguments at. Default is ``1``. | |
""" | |
return TendonForceLengthInverseDeGroote2016 | |
def _latex(self, printer): | |
"""Print a LaTeX representation of the function defining the curve. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print the LaTeX string representation. | |
""" | |
l_T_tilde = self.args[0] | |
_l_T_tilde = printer._print(l_T_tilde) | |
return r'\operatorname{fl}^T \left( %s \right)' % _l_T_tilde | |
class TendonForceLengthInverseDeGroote2016(CharacteristicCurveFunction): | |
r"""Inverse tendon force-length curve based on De Groote et al., 2016 [1]_. | |
Explanation | |
=========== | |
Gives the normalized tendon length that produces a specific normalized | |
tendon force. | |
The function is defined by the equation: | |
${fl^T}^{-1} = frac{\log{\frac{fl^T + c_2}{c_0}}}{c_3} + c_1$ | |
with constant values of $c_0 = 0.2$, $c_1 = 0.995$, $c_2 = 0.25$, and | |
$c_3 = 33.93669377311689$. This function is the exact analytical inverse | |
of the related tendon force-length curve ``TendonForceLengthDeGroote2016``. | |
While it is possible to change the constant values, these were carefully | |
selected in the original publication to give the characteristic curve | |
specific and required properties. For example, the function produces no | |
force when the tendon is in an unstrained state. It also produces a force | |
of 1 normalized unit when the tendon is under a 5% strain. | |
Examples | |
======== | |
The preferred way to instantiate :class:`TendonForceLengthInverseDeGroote2016` is | |
using the :meth:`~.with_defaults` constructor because this will automatically | |
populate the constants within the characteristic curve equation with the | |
floating point values from the original publication. This constructor takes | |
a single argument corresponding to normalized tendon force-length, which is | |
equal to the tendon force. We'll create a :class:`~.Symbol` called ``fl_T`` to | |
represent this. | |
>>> from sympy import Symbol | |
>>> from sympy.physics.biomechanics import TendonForceLengthInverseDeGroote2016 | |
>>> fl_T = Symbol('fl_T') | |
>>> l_T_tilde = TendonForceLengthInverseDeGroote2016.with_defaults(fl_T) | |
>>> l_T_tilde | |
TendonForceLengthInverseDeGroote2016(fl_T, 0.2, 0.995, 0.25, | |
33.93669377311689) | |
It's also possible to populate the four constants with your own values too. | |
>>> from sympy import symbols | |
>>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') | |
>>> l_T_tilde = TendonForceLengthInverseDeGroote2016(fl_T, c0, c1, c2, c3) | |
>>> l_T_tilde | |
TendonForceLengthInverseDeGroote2016(fl_T, c0, c1, c2, c3) | |
To inspect the actual symbolic expression that this function represents, | |
we can call the :meth:`~.doit` method on an instance. We'll use the keyword | |
argument ``evaluate=False`` as this will keep the expression in its | |
canonical form and won't simplify any constants. | |
>>> l_T_tilde.doit(evaluate=False) | |
c1 + log((c2 + fl_T)/c0)/c3 | |
The function can also be differentiated. We'll differentiate with respect | |
to l_T using the ``diff`` method on an instance with the single positional | |
argument ``l_T``. | |
>>> l_T_tilde.diff(fl_T) | |
1/(c3*(c2 + fl_T)) | |
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 | |
""" | |
def with_defaults(cls, fl_T): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the inverse tendon force-length function | |
using the four constant values specified in the original publication. | |
These have the values: | |
$c_0 = 0.2$ | |
$c_1 = 0.995$ | |
$c_2 = 0.25$ | |
$c_3 = 33.93669377311689$ | |
Parameters | |
========== | |
fl_T : Any (sympifiable) | |
Normalized tendon force as a function of tendon length. | |
""" | |
c0 = Float('0.2') | |
c1 = Float('0.995') | |
c2 = Float('0.25') | |
c3 = Float('33.93669377311689') | |
return cls(fl_T, c0, c1, c2, c3) | |
def eval(cls, fl_T, c0, c1, c2, c3): | |
"""Evaluation of basic inputs. | |
Parameters | |
========== | |
fl_T : Any (sympifiable) | |
Normalized tendon force as a function of tendon length. | |
c0 : Any (sympifiable) | |
The first constant in the characteristic equation. The published | |
value is ``0.2``. | |
c1 : Any (sympifiable) | |
The second constant in the characteristic equation. The published | |
value is ``0.995``. | |
c2 : Any (sympifiable) | |
The third constant in the characteristic equation. The published | |
value is ``0.25``. | |
c3 : Any (sympifiable) | |
The fourth constant in the characteristic equation. The published | |
value is ``33.93669377311689``. | |
""" | |
pass | |
def _eval_evalf(self, prec): | |
"""Evaluate the expression numerically using ``evalf``.""" | |
return self.doit(deep=False, evaluate=False)._eval_evalf(prec) | |
def doit(self, deep=True, evaluate=True, **hints): | |
"""Evaluate the expression defining the function. | |
Parameters | |
========== | |
deep : bool | |
Whether ``doit`` should be recursively called. Default is ``True``. | |
evaluate : bool. | |
Whether the SymPy expression should be evaluated as it is | |
constructed. If ``False``, then no constant folding will be | |
conducted which will leave the expression in a more numerically- | |
stable for values of ``l_T_tilde`` that correspond to a sensible | |
operating range for a musculotendon. Default is ``True``. | |
**kwargs : dict[str, Any] | |
Additional keyword argument pairs to be recursively passed to | |
``doit``. | |
""" | |
fl_T, *constants = self.args | |
if deep: | |
hints['evaluate'] = evaluate | |
fl_T = fl_T.doit(deep=deep, **hints) | |
c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] | |
else: | |
c0, c1, c2, c3 = constants | |
if evaluate: | |
return log((fl_T + c2)/c0)/c3 + c1 | |
return log(UnevaluatedExpr((fl_T + c2)/c0))/c3 + c1 | |
def fdiff(self, argindex=1): | |
"""Derivative of the function with respect to a single argument. | |
Parameters | |
========== | |
argindex : int | |
The index of the function's arguments with respect to which the | |
derivative should be taken. Argument indexes start at ``1``. | |
Default is ``1``. | |
""" | |
fl_T, c0, c1, c2, c3 = self.args | |
if argindex == 1: | |
return 1/(c3*(fl_T + c2)) | |
elif argindex == 2: | |
return -1/(c0*c3) | |
elif argindex == 3: | |
return Integer(1) | |
elif argindex == 4: | |
return 1/(c3*(fl_T + c2)) | |
elif argindex == 5: | |
return -log(UnevaluatedExpr((fl_T + c2)/c0))/c3**2 | |
raise ArgumentIndexError(self, argindex) | |
def inverse(self, argindex=1): | |
"""Inverse function. | |
Parameters | |
========== | |
argindex : int | |
Value to start indexing the arguments at. Default is ``1``. | |
""" | |
return TendonForceLengthDeGroote2016 | |
def _latex(self, printer): | |
"""Print a LaTeX representation of the function defining the curve. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print the LaTeX string representation. | |
""" | |
fl_T = self.args[0] | |
_fl_T = printer._print(fl_T) | |
return r'\left( \operatorname{fl}^T \right)^{-1} \left( %s \right)' % _fl_T | |
class FiberForceLengthPassiveDeGroote2016(CharacteristicCurveFunction): | |
r"""Passive muscle fiber force-length curve based on De Groote et al., 2016 | |
[1]_. | |
Explanation | |
=========== | |
The function is defined by the equation: | |
$fl^M_{pas} = \frac{\frac{\exp{c_1 \left(\tilde{l^M} - 1\right)}}{c_0} - 1}{\exp{c_1} - 1}$ | |
with constant values of $c_0 = 0.6$ and $c_1 = 4.0$. | |
While it is possible to change the constant values, these were carefully | |
selected in the original publication to give the characteristic curve | |
specific and required properties. For example, the function produces a | |
passive fiber force very close to 0 for all normalized fiber lengths | |
between 0 and 1. | |
Examples | |
======== | |
The preferred way to instantiate :class:`FiberForceLengthPassiveDeGroote2016` is | |
using the :meth:`~.with_defaults` constructor because this will automatically | |
populate the constants within the characteristic curve equation with the | |
floating point values from the original publication. This constructor takes | |
a single argument corresponding to normalized muscle fiber length. We'll | |
create a :class:`~.Symbol` called ``l_M_tilde`` to represent this. | |
>>> from sympy import Symbol | |
>>> from sympy.physics.biomechanics import FiberForceLengthPassiveDeGroote2016 | |
>>> l_M_tilde = Symbol('l_M_tilde') | |
>>> fl_M = FiberForceLengthPassiveDeGroote2016.with_defaults(l_M_tilde) | |
>>> fl_M | |
FiberForceLengthPassiveDeGroote2016(l_M_tilde, 0.6, 4.0) | |
It's also possible to populate the two constants with your own values too. | |
>>> from sympy import symbols | |
>>> c0, c1 = symbols('c0 c1') | |
>>> fl_M = FiberForceLengthPassiveDeGroote2016(l_M_tilde, c0, c1) | |
>>> fl_M | |
FiberForceLengthPassiveDeGroote2016(l_M_tilde, c0, c1) | |
You don't just have to use symbols as the arguments, it's also possible to | |
use expressions. Let's create a new pair of symbols, ``l_M`` and | |
``l_M_opt``, representing muscle fiber length and optimal muscle fiber | |
length respectively. We can then represent ``l_M_tilde`` as an expression, | |
the ratio of these. | |
>>> l_M, l_M_opt = symbols('l_M l_M_opt') | |
>>> l_M_tilde = l_M/l_M_opt | |
>>> fl_M = FiberForceLengthPassiveDeGroote2016.with_defaults(l_M_tilde) | |
>>> fl_M | |
FiberForceLengthPassiveDeGroote2016(l_M/l_M_opt, 0.6, 4.0) | |
To inspect the actual symbolic expression that this function represents, | |
we can call the :meth:`~.doit` method on an instance. We'll use the keyword | |
argument ``evaluate=False`` as this will keep the expression in its | |
canonical form and won't simplify any constants. | |
>>> fl_M.doit(evaluate=False) | |
0.0186573603637741*(-1 + exp(6.66666666666667*(l_M/l_M_opt - 1))) | |
The function can also be differentiated. We'll differentiate with respect | |
to l_M using the ``diff`` method on an instance with the single positional | |
argument ``l_M``. | |
>>> fl_M.diff(l_M) | |
0.12438240242516*exp(6.66666666666667*(l_M/l_M_opt - 1))/l_M_opt | |
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 | |
""" | |
def with_defaults(cls, l_M_tilde): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the muscle fiber passive force-length | |
function using the four constant values specified in the original | |
publication. | |
These have the values: | |
$c_0 = 0.6$ | |
$c_1 = 4.0$ | |
Parameters | |
========== | |
l_M_tilde : Any (sympifiable) | |
Normalized muscle fiber length. | |
""" | |
c0 = Float('0.6') | |
c1 = Float('4.0') | |
return cls(l_M_tilde, c0, c1) | |
def eval(cls, l_M_tilde, c0, c1): | |
"""Evaluation of basic inputs. | |
Parameters | |
========== | |
l_M_tilde : Any (sympifiable) | |
Normalized muscle fiber length. | |
c0 : Any (sympifiable) | |
The first constant in the characteristic equation. The published | |
value is ``0.6``. | |
c1 : Any (sympifiable) | |
The second constant in the characteristic equation. The published | |
value is ``4.0``. | |
""" | |
pass | |
def _eval_evalf(self, prec): | |
"""Evaluate the expression numerically using ``evalf``.""" | |
return self.doit(deep=False, evaluate=False)._eval_evalf(prec) | |
def doit(self, deep=True, evaluate=True, **hints): | |
"""Evaluate the expression defining the function. | |
Parameters | |
========== | |
deep : bool | |
Whether ``doit`` should be recursively called. Default is ``True``. | |
evaluate : bool. | |
Whether the SymPy expression should be evaluated as it is | |
constructed. If ``False``, then no constant folding will be | |
conducted which will leave the expression in a more numerically- | |
stable for values of ``l_T_tilde`` that correspond to a sensible | |
operating range for a musculotendon. Default is ``True``. | |
**kwargs : dict[str, Any] | |
Additional keyword argument pairs to be recursively passed to | |
``doit``. | |
""" | |
l_M_tilde, *constants = self.args | |
if deep: | |
hints['evaluate'] = evaluate | |
l_M_tilde = l_M_tilde.doit(deep=deep, **hints) | |
c0, c1 = [c.doit(deep=deep, **hints) for c in constants] | |
else: | |
c0, c1 = constants | |
if evaluate: | |
return (exp((c1*(l_M_tilde - 1))/c0) - 1)/(exp(c1) - 1) | |
return (exp((c1*UnevaluatedExpr(l_M_tilde - 1))/c0) - 1)/(exp(c1) - 1) | |
def fdiff(self, argindex=1): | |
"""Derivative of the function with respect to a single argument. | |
Parameters | |
========== | |
argindex : int | |
The index of the function's arguments with respect to which the | |
derivative should be taken. Argument indexes start at ``1``. | |
Default is ``1``. | |
""" | |
l_M_tilde, c0, c1 = self.args | |
if argindex == 1: | |
return c1*exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0)/(c0*(exp(c1) - 1)) | |
elif argindex == 2: | |
return ( | |
-c1*exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0) | |
*UnevaluatedExpr(l_M_tilde - 1)/(c0**2*(exp(c1) - 1)) | |
) | |
elif argindex == 3: | |
return ( | |
-exp(c1)*(-1 + exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0))/(exp(c1) - 1)**2 | |
+ exp(c1*UnevaluatedExpr(l_M_tilde - 1)/c0)*(l_M_tilde - 1)/(c0*(exp(c1) - 1)) | |
) | |
raise ArgumentIndexError(self, argindex) | |
def inverse(self, argindex=1): | |
"""Inverse function. | |
Parameters | |
========== | |
argindex : int | |
Value to start indexing the arguments at. Default is ``1``. | |
""" | |
return FiberForceLengthPassiveInverseDeGroote2016 | |
def _latex(self, printer): | |
"""Print a LaTeX representation of the function defining the curve. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print the LaTeX string representation. | |
""" | |
l_M_tilde = self.args[0] | |
_l_M_tilde = printer._print(l_M_tilde) | |
return r'\operatorname{fl}^M_{pas} \left( %s \right)' % _l_M_tilde | |
class FiberForceLengthPassiveInverseDeGroote2016(CharacteristicCurveFunction): | |
r"""Inverse passive muscle fiber force-length curve based on De Groote et | |
al., 2016 [1]_. | |
Explanation | |
=========== | |
Gives the normalized muscle fiber length that produces a specific normalized | |
passive muscle fiber force. | |
The function is defined by the equation: | |
${fl^M_{pas}}^{-1} = \frac{c_0 \log{\left(\exp{c_1} - 1\right)fl^M_pas + 1}}{c_1} + 1$ | |
with constant values of $c_0 = 0.6$ and $c_1 = 4.0$. This function is the | |
exact analytical inverse of the related tendon force-length curve | |
``FiberForceLengthPassiveDeGroote2016``. | |
While it is possible to change the constant values, these were carefully | |
selected in the original publication to give the characteristic curve | |
specific and required properties. For example, the function produces a | |
passive fiber force very close to 0 for all normalized fiber lengths | |
between 0 and 1. | |
Examples | |
======== | |
The preferred way to instantiate | |
:class:`FiberForceLengthPassiveInverseDeGroote2016` is using the | |
:meth:`~.with_defaults` constructor because this will automatically populate the | |
constants within the characteristic curve equation with the floating point | |
values from the original publication. This constructor takes a single | |
argument corresponding to the normalized passive muscle fiber length-force | |
component of the muscle fiber force. We'll create a :class:`~.Symbol` called | |
``fl_M_pas`` to represent this. | |
>>> from sympy import Symbol | |
>>> from sympy.physics.biomechanics import FiberForceLengthPassiveInverseDeGroote2016 | |
>>> fl_M_pas = Symbol('fl_M_pas') | |
>>> l_M_tilde = FiberForceLengthPassiveInverseDeGroote2016.with_defaults(fl_M_pas) | |
>>> l_M_tilde | |
FiberForceLengthPassiveInverseDeGroote2016(fl_M_pas, 0.6, 4.0) | |
It's also possible to populate the two constants with your own values too. | |
>>> from sympy import symbols | |
>>> c0, c1 = symbols('c0 c1') | |
>>> l_M_tilde = FiberForceLengthPassiveInverseDeGroote2016(fl_M_pas, c0, c1) | |
>>> l_M_tilde | |
FiberForceLengthPassiveInverseDeGroote2016(fl_M_pas, c0, c1) | |
To inspect the actual symbolic expression that this function represents, | |
we can call the :meth:`~.doit` method on an instance. We'll use the keyword | |
argument ``evaluate=False`` as this will keep the expression in its | |
canonical form and won't simplify any constants. | |
>>> l_M_tilde.doit(evaluate=False) | |
c0*log(1 + fl_M_pas*(exp(c1) - 1))/c1 + 1 | |
The function can also be differentiated. We'll differentiate with respect | |
to fl_M_pas using the ``diff`` method on an instance with the single positional | |
argument ``fl_M_pas``. | |
>>> l_M_tilde.diff(fl_M_pas) | |
c0*(exp(c1) - 1)/(c1*(fl_M_pas*(exp(c1) - 1) + 1)) | |
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 | |
""" | |
def with_defaults(cls, fl_M_pas): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the inverse muscle fiber passive force-length | |
function using the four constant values specified in the original | |
publication. | |
These have the values: | |
$c_0 = 0.6$ | |
$c_1 = 4.0$ | |
Parameters | |
========== | |
fl_M_pas : Any (sympifiable) | |
Normalized passive muscle fiber force as a function of muscle fiber | |
length. | |
""" | |
c0 = Float('0.6') | |
c1 = Float('4.0') | |
return cls(fl_M_pas, c0, c1) | |
def eval(cls, fl_M_pas, c0, c1): | |
"""Evaluation of basic inputs. | |
Parameters | |
========== | |
fl_M_pas : Any (sympifiable) | |
Normalized passive muscle fiber force. | |
c0 : Any (sympifiable) | |
The first constant in the characteristic equation. The published | |
value is ``0.6``. | |
c1 : Any (sympifiable) | |
The second constant in the characteristic equation. The published | |
value is ``4.0``. | |
""" | |
pass | |
def _eval_evalf(self, prec): | |
"""Evaluate the expression numerically using ``evalf``.""" | |
return self.doit(deep=False, evaluate=False)._eval_evalf(prec) | |
def doit(self, deep=True, evaluate=True, **hints): | |
"""Evaluate the expression defining the function. | |
Parameters | |
========== | |
deep : bool | |
Whether ``doit`` should be recursively called. Default is ``True``. | |
evaluate : bool. | |
Whether the SymPy expression should be evaluated as it is | |
constructed. If ``False``, then no constant folding will be | |
conducted which will leave the expression in a more numerically- | |
stable for values of ``l_T_tilde`` that correspond to a sensible | |
operating range for a musculotendon. Default is ``True``. | |
**kwargs : dict[str, Any] | |
Additional keyword argument pairs to be recursively passed to | |
``doit``. | |
""" | |
fl_M_pas, *constants = self.args | |
if deep: | |
hints['evaluate'] = evaluate | |
fl_M_pas = fl_M_pas.doit(deep=deep, **hints) | |
c0, c1 = [c.doit(deep=deep, **hints) for c in constants] | |
else: | |
c0, c1 = constants | |
if evaluate: | |
return c0*log(fl_M_pas*(exp(c1) - 1) + 1)/c1 + 1 | |
return c0*log(UnevaluatedExpr(fl_M_pas*(exp(c1) - 1)) + 1)/c1 + 1 | |
def fdiff(self, argindex=1): | |
"""Derivative of the function with respect to a single argument. | |
Parameters | |
========== | |
argindex : int | |
The index of the function's arguments with respect to which the | |
derivative should be taken. Argument indexes start at ``1``. | |
Default is ``1``. | |
""" | |
fl_M_pas, c0, c1 = self.args | |
if argindex == 1: | |
return c0*(exp(c1) - 1)/(c1*(fl_M_pas*(exp(c1) - 1) + 1)) | |
elif argindex == 2: | |
return log(fl_M_pas*(exp(c1) - 1) + 1)/c1 | |
elif argindex == 3: | |
return ( | |
c0*fl_M_pas*exp(c1)/(c1*(fl_M_pas*(exp(c1) - 1) + 1)) | |
- c0*log(fl_M_pas*(exp(c1) - 1) + 1)/c1**2 | |
) | |
raise ArgumentIndexError(self, argindex) | |
def inverse(self, argindex=1): | |
"""Inverse function. | |
Parameters | |
========== | |
argindex : int | |
Value to start indexing the arguments at. Default is ``1``. | |
""" | |
return FiberForceLengthPassiveDeGroote2016 | |
def _latex(self, printer): | |
"""Print a LaTeX representation of the function defining the curve. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print the LaTeX string representation. | |
""" | |
fl_M_pas = self.args[0] | |
_fl_M_pas = printer._print(fl_M_pas) | |
return r'\left( \operatorname{fl}^M_{pas} \right)^{-1} \left( %s \right)' % _fl_M_pas | |
class FiberForceLengthActiveDeGroote2016(CharacteristicCurveFunction): | |
r"""Active muscle fiber force-length curve based on De Groote et al., 2016 | |
[1]_. | |
Explanation | |
=========== | |
The function is defined by the equation: | |
$fl_{\text{act}}^M = c_0 \exp\left(-\frac{1}{2}\left(\frac{\tilde{l}^M - c_1}{c_2 + c_3 \tilde{l}^M}\right)^2\right) | |
+ c_4 \exp\left(-\frac{1}{2}\left(\frac{\tilde{l}^M - c_5}{c_6 + c_7 \tilde{l}^M}\right)^2\right) | |
+ c_8 \exp\left(-\frac{1}{2}\left(\frac{\tilde{l}^M - c_9}{c_{10} + c_{11} \tilde{l}^M}\right)^2\right)$ | |
with constant values of $c0 = 0.814$, $c1 = 1.06$, $c2 = 0.162$, | |
$c3 = 0.0633$, $c4 = 0.433$, $c5 = 0.717$, $c6 = -0.0299$, $c7 = 0.2$, | |
$c8 = 0.1$, $c9 = 1.0$, $c10 = 0.354$, and $c11 = 0.0$. | |
While it is possible to change the constant values, these were carefully | |
selected in the original publication to give the characteristic curve | |
specific and required properties. For example, the function produces a | |
active fiber force of 1 at a normalized fiber length of 1, and an active | |
fiber force of 0 at normalized fiber lengths of 0 and 2. | |
Examples | |
======== | |
The preferred way to instantiate :class:`FiberForceLengthActiveDeGroote2016` is | |
using the :meth:`~.with_defaults` constructor because this will automatically | |
populate the constants within the characteristic curve equation with the | |
floating point values from the original publication. This constructor takes | |
a single argument corresponding to normalized muscle fiber length. We'll | |
create a :class:`~.Symbol` called ``l_M_tilde`` to represent this. | |
>>> from sympy import Symbol | |
>>> from sympy.physics.biomechanics import FiberForceLengthActiveDeGroote2016 | |
>>> l_M_tilde = Symbol('l_M_tilde') | |
>>> fl_M = FiberForceLengthActiveDeGroote2016.with_defaults(l_M_tilde) | |
>>> fl_M | |
FiberForceLengthActiveDeGroote2016(l_M_tilde, 0.814, 1.06, 0.162, 0.0633, | |
0.433, 0.717, -0.0299, 0.2, 0.1, 1.0, 0.354, 0.0) | |
It's also possible to populate the two constants with your own values too. | |
>>> from sympy import symbols | |
>>> c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = symbols('c0:12') | |
>>> fl_M = FiberForceLengthActiveDeGroote2016(l_M_tilde, c0, c1, c2, c3, | |
... c4, c5, c6, c7, c8, c9, c10, c11) | |
>>> fl_M | |
FiberForceLengthActiveDeGroote2016(l_M_tilde, c0, c1, c2, c3, c4, c5, c6, | |
c7, c8, c9, c10, c11) | |
You don't just have to use symbols as the arguments, it's also possible to | |
use expressions. Let's create a new pair of symbols, ``l_M`` and | |
``l_M_opt``, representing muscle fiber length and optimal muscle fiber | |
length respectively. We can then represent ``l_M_tilde`` as an expression, | |
the ratio of these. | |
>>> l_M, l_M_opt = symbols('l_M l_M_opt') | |
>>> l_M_tilde = l_M/l_M_opt | |
>>> fl_M = FiberForceLengthActiveDeGroote2016.with_defaults(l_M_tilde) | |
>>> fl_M | |
FiberForceLengthActiveDeGroote2016(l_M/l_M_opt, 0.814, 1.06, 0.162, 0.0633, | |
0.433, 0.717, -0.0299, 0.2, 0.1, 1.0, 0.354, 0.0) | |
To inspect the actual symbolic expression that this function represents, | |
we can call the :meth:`~.doit` method on an instance. We'll use the keyword | |
argument ``evaluate=False`` as this will keep the expression in its | |
canonical form and won't simplify any constants. | |
>>> fl_M.doit(evaluate=False) | |
0.814*exp(-19.0519737844841*(l_M/l_M_opt | |
- 1.06)**2/(0.390740740740741*l_M/l_M_opt + 1)**2) | |
+ 0.433*exp(-12.5*(l_M/l_M_opt - 0.717)**2/(l_M/l_M_opt - 0.1495)**2) | |
+ 0.1*exp(-3.98991349867535*(l_M/l_M_opt - 1.0)**2) | |
The function can also be differentiated. We'll differentiate with respect | |
to l_M using the ``diff`` method on an instance with the single positional | |
argument ``l_M``. | |
>>> fl_M.diff(l_M) | |
((-0.79798269973507*l_M/l_M_opt | |
+ 0.79798269973507)*exp(-3.98991349867535*(l_M/l_M_opt - 1.0)**2) | |
+ (10.825*(-l_M/l_M_opt + 0.717)/(l_M/l_M_opt - 0.1495)**2 | |
+ 10.825*(l_M/l_M_opt - 0.717)**2/(l_M/l_M_opt | |
- 0.1495)**3)*exp(-12.5*(l_M/l_M_opt - 0.717)**2/(l_M/l_M_opt - 0.1495)**2) | |
+ (31.0166133211401*(-l_M/l_M_opt + 1.06)/(0.390740740740741*l_M/l_M_opt | |
+ 1)**2 + 13.6174190361677*(0.943396226415094*l_M/l_M_opt | |
- 1)**2/(0.390740740740741*l_M/l_M_opt | |
+ 1)**3)*exp(-21.4067977442463*(0.943396226415094*l_M/l_M_opt | |
- 1)**2/(0.390740740740741*l_M/l_M_opt + 1)**2))/l_M_opt | |
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 | |
""" | |
def with_defaults(cls, l_M_tilde): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the inverse muscle fiber act force-length | |
function using the four constant values specified in the original | |
publication. | |
These have the values: | |
$c0 = 0.814$ | |
$c1 = 1.06$ | |
$c2 = 0.162$ | |
$c3 = 0.0633$ | |
$c4 = 0.433$ | |
$c5 = 0.717$ | |
$c6 = -0.0299$ | |
$c7 = 0.2$ | |
$c8 = 0.1$ | |
$c9 = 1.0$ | |
$c10 = 0.354$ | |
$c11 = 0.0$ | |
Parameters | |
========== | |
fl_M_act : Any (sympifiable) | |
Normalized passive muscle fiber force as a function of muscle fiber | |
length. | |
""" | |
c0 = Float('0.814') | |
c1 = Float('1.06') | |
c2 = Float('0.162') | |
c3 = Float('0.0633') | |
c4 = Float('0.433') | |
c5 = Float('0.717') | |
c6 = Float('-0.0299') | |
c7 = Float('0.2') | |
c8 = Float('0.1') | |
c9 = Float('1.0') | |
c10 = Float('0.354') | |
c11 = Float('0.0') | |
return cls(l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11) | |
def eval(cls, l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11): | |
"""Evaluation of basic inputs. | |
Parameters | |
========== | |
l_M_tilde : Any (sympifiable) | |
Normalized muscle fiber length. | |
c0 : Any (sympifiable) | |
The first constant in the characteristic equation. The published | |
value is ``0.814``. | |
c1 : Any (sympifiable) | |
The second constant in the characteristic equation. The published | |
value is ``1.06``. | |
c2 : Any (sympifiable) | |
The third constant in the characteristic equation. The published | |
value is ``0.162``. | |
c3 : Any (sympifiable) | |
The fourth constant in the characteristic equation. The published | |
value is ``0.0633``. | |
c4 : Any (sympifiable) | |
The fifth constant in the characteristic equation. The published | |
value is ``0.433``. | |
c5 : Any (sympifiable) | |
The sixth constant in the characteristic equation. The published | |
value is ``0.717``. | |
c6 : Any (sympifiable) | |
The seventh constant in the characteristic equation. The published | |
value is ``-0.0299``. | |
c7 : Any (sympifiable) | |
The eighth constant in the characteristic equation. The published | |
value is ``0.2``. | |
c8 : Any (sympifiable) | |
The ninth constant in the characteristic equation. The published | |
value is ``0.1``. | |
c9 : Any (sympifiable) | |
The tenth constant in the characteristic equation. The published | |
value is ``1.0``. | |
c10 : Any (sympifiable) | |
The eleventh constant in the characteristic equation. The published | |
value is ``0.354``. | |
c11 : Any (sympifiable) | |
The tweflth constant in the characteristic equation. The published | |
value is ``0.0``. | |
""" | |
pass | |
def _eval_evalf(self, prec): | |
"""Evaluate the expression numerically using ``evalf``.""" | |
return self.doit(deep=False, evaluate=False)._eval_evalf(prec) | |
def doit(self, deep=True, evaluate=True, **hints): | |
"""Evaluate the expression defining the function. | |
Parameters | |
========== | |
deep : bool | |
Whether ``doit`` should be recursively called. Default is ``True``. | |
evaluate : bool. | |
Whether the SymPy expression should be evaluated as it is | |
constructed. If ``False``, then no constant folding will be | |
conducted which will leave the expression in a more numerically- | |
stable for values of ``l_M_tilde`` that correspond to a sensible | |
operating range for a musculotendon. Default is ``True``. | |
**kwargs : dict[str, Any] | |
Additional keyword argument pairs to be recursively passed to | |
``doit``. | |
""" | |
l_M_tilde, *constants = self.args | |
if deep: | |
hints['evaluate'] = evaluate | |
l_M_tilde = l_M_tilde.doit(deep=deep, **hints) | |
constants = [c.doit(deep=deep, **hints) for c in constants] | |
c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = constants | |
if evaluate: | |
return ( | |
c0*exp(-(((l_M_tilde - c1)/(c2 + c3*l_M_tilde))**2)/2) | |
+ c4*exp(-(((l_M_tilde - c5)/(c6 + c7*l_M_tilde))**2)/2) | |
+ c8*exp(-(((l_M_tilde - c9)/(c10 + c11*l_M_tilde))**2)/2) | |
) | |
return ( | |
c0*exp(-((UnevaluatedExpr(l_M_tilde - c1)/(c2 + c3*l_M_tilde))**2)/2) | |
+ c4*exp(-((UnevaluatedExpr(l_M_tilde - c5)/(c6 + c7*l_M_tilde))**2)/2) | |
+ c8*exp(-((UnevaluatedExpr(l_M_tilde - c9)/(c10 + c11*l_M_tilde))**2)/2) | |
) | |
def fdiff(self, argindex=1): | |
"""Derivative of the function with respect to a single argument. | |
Parameters | |
========== | |
argindex : int | |
The index of the function's arguments with respect to which the | |
derivative should be taken. Argument indexes start at ``1``. | |
Default is ``1``. | |
""" | |
l_M_tilde, c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, c10, c11 = self.args | |
if argindex == 1: | |
return ( | |
c0*( | |
c3*(l_M_tilde - c1)**2/(c2 + c3*l_M_tilde)**3 | |
+ (c1 - l_M_tilde)/((c2 + c3*l_M_tilde)**2) | |
)*exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) | |
+ c4*( | |
c7*(l_M_tilde - c5)**2/(c6 + c7*l_M_tilde)**3 | |
+ (c5 - l_M_tilde)/((c6 + c7*l_M_tilde)**2) | |
)*exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) | |
+ c8*( | |
c11*(l_M_tilde - c9)**2/(c10 + c11*l_M_tilde)**3 | |
+ (c9 - l_M_tilde)/((c10 + c11*l_M_tilde)**2) | |
)*exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) | |
) | |
elif argindex == 2: | |
return exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) | |
elif argindex == 3: | |
return ( | |
c0*(l_M_tilde - c1)/(c2 + c3*l_M_tilde)**2 | |
*exp(-(l_M_tilde - c1)**2 /(2*(c2 + c3*l_M_tilde)**2)) | |
) | |
elif argindex == 4: | |
return ( | |
c0*(l_M_tilde - c1)**2/(c2 + c3*l_M_tilde)**3 | |
*exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) | |
) | |
elif argindex == 5: | |
return ( | |
c0*l_M_tilde*(l_M_tilde - c1)**2/(c2 + c3*l_M_tilde)**3 | |
*exp(-(l_M_tilde - c1)**2/(2*(c2 + c3*l_M_tilde)**2)) | |
) | |
elif argindex == 6: | |
return exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) | |
elif argindex == 7: | |
return ( | |
c4*(l_M_tilde - c5)/(c6 + c7*l_M_tilde)**2 | |
*exp(-(l_M_tilde - c5)**2 /(2*(c6 + c7*l_M_tilde)**2)) | |
) | |
elif argindex == 8: | |
return ( | |
c4*(l_M_tilde - c5)**2/(c6 + c7*l_M_tilde)**3 | |
*exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) | |
) | |
elif argindex == 9: | |
return ( | |
c4*l_M_tilde*(l_M_tilde - c5)**2/(c6 + c7*l_M_tilde)**3 | |
*exp(-(l_M_tilde - c5)**2/(2*(c6 + c7*l_M_tilde)**2)) | |
) | |
elif argindex == 10: | |
return exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) | |
elif argindex == 11: | |
return ( | |
c8*(l_M_tilde - c9)/(c10 + c11*l_M_tilde)**2 | |
*exp(-(l_M_tilde - c9)**2 /(2*(c10 + c11*l_M_tilde)**2)) | |
) | |
elif argindex == 12: | |
return ( | |
c8*(l_M_tilde - c9)**2/(c10 + c11*l_M_tilde)**3 | |
*exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) | |
) | |
elif argindex == 13: | |
return ( | |
c8*l_M_tilde*(l_M_tilde - c9)**2/(c10 + c11*l_M_tilde)**3 | |
*exp(-(l_M_tilde - c9)**2/(2*(c10 + c11*l_M_tilde)**2)) | |
) | |
raise ArgumentIndexError(self, argindex) | |
def _latex(self, printer): | |
"""Print a LaTeX representation of the function defining the curve. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print the LaTeX string representation. | |
""" | |
l_M_tilde = self.args[0] | |
_l_M_tilde = printer._print(l_M_tilde) | |
return r'\operatorname{fl}^M_{act} \left( %s \right)' % _l_M_tilde | |
class FiberForceVelocityDeGroote2016(CharacteristicCurveFunction): | |
r"""Muscle fiber force-velocity curve based on De Groote et al., 2016 [1]_. | |
Explanation | |
=========== | |
Gives the normalized muscle fiber force produced as a function of | |
normalized tendon velocity. | |
The function is defined by the equation: | |
$fv^M = c_0 \log{\left(c_1 \tilde{v}_m + c_2\right) + \sqrt{\left(c_1 \tilde{v}_m + c_2\right)^2 + 1}} + c_3$ | |
with constant values of $c_0 = -0.318$, $c_1 = -8.149$, $c_2 = -0.374$, and | |
$c_3 = 0.886$. | |
While it is possible to change the constant values, these were carefully | |
selected in the original publication to give the characteristic curve | |
specific and required properties. For example, the function produces a | |
normalized muscle fiber force of 1 when the muscle fibers are contracting | |
isometrically (they have an extension rate of 0). | |
Examples | |
======== | |
The preferred way to instantiate :class:`FiberForceVelocityDeGroote2016` is using | |
the :meth:`~.with_defaults` constructor because this will automatically populate | |
the constants within the characteristic curve equation with the floating | |
point values from the original publication. This constructor takes a single | |
argument corresponding to normalized muscle fiber extension velocity. We'll | |
create a :class:`~.Symbol` called ``v_M_tilde`` to represent this. | |
>>> from sympy import Symbol | |
>>> from sympy.physics.biomechanics import FiberForceVelocityDeGroote2016 | |
>>> v_M_tilde = Symbol('v_M_tilde') | |
>>> fv_M = FiberForceVelocityDeGroote2016.with_defaults(v_M_tilde) | |
>>> fv_M | |
FiberForceVelocityDeGroote2016(v_M_tilde, -0.318, -8.149, -0.374, 0.886) | |
It's also possible to populate the four constants with your own values too. | |
>>> from sympy import symbols | |
>>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') | |
>>> fv_M = FiberForceVelocityDeGroote2016(v_M_tilde, c0, c1, c2, c3) | |
>>> fv_M | |
FiberForceVelocityDeGroote2016(v_M_tilde, c0, c1, c2, c3) | |
You don't just have to use symbols as the arguments, it's also possible to | |
use expressions. Let's create a new pair of symbols, ``v_M`` and | |
``v_M_max``, representing muscle fiber extension velocity and maximum | |
muscle fiber extension velocity respectively. We can then represent | |
``v_M_tilde`` as an expression, the ratio of these. | |
>>> v_M, v_M_max = symbols('v_M v_M_max') | |
>>> v_M_tilde = v_M/v_M_max | |
>>> fv_M = FiberForceVelocityDeGroote2016.with_defaults(v_M_tilde) | |
>>> fv_M | |
FiberForceVelocityDeGroote2016(v_M/v_M_max, -0.318, -8.149, -0.374, 0.886) | |
To inspect the actual symbolic expression that this function represents, | |
we can call the :meth:`~.doit` method on an instance. We'll use the keyword | |
argument ``evaluate=False`` as this will keep the expression in its | |
canonical form and won't simplify any constants. | |
>>> fv_M.doit(evaluate=False) | |
0.886 - 0.318*log(-8.149*v_M/v_M_max - 0.374 + sqrt(1 + (-8.149*v_M/v_M_max | |
- 0.374)**2)) | |
The function can also be differentiated. We'll differentiate with respect | |
to v_M using the ``diff`` method on an instance with the single positional | |
argument ``v_M``. | |
>>> fv_M.diff(v_M) | |
2.591382*(1 + (-8.149*v_M/v_M_max - 0.374)**2)**(-1/2)/v_M_max | |
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 | |
""" | |
def with_defaults(cls, v_M_tilde): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the muscle fiber force-velocity function | |
using the four constant values specified in the original publication. | |
These have the values: | |
$c_0 = -0.318$ | |
$c_1 = -8.149$ | |
$c_2 = -0.374$ | |
$c_3 = 0.886$ | |
Parameters | |
========== | |
v_M_tilde : Any (sympifiable) | |
Normalized muscle fiber extension velocity. | |
""" | |
c0 = Float('-0.318') | |
c1 = Float('-8.149') | |
c2 = Float('-0.374') | |
c3 = Float('0.886') | |
return cls(v_M_tilde, c0, c1, c2, c3) | |
def eval(cls, v_M_tilde, c0, c1, c2, c3): | |
"""Evaluation of basic inputs. | |
Parameters | |
========== | |
v_M_tilde : Any (sympifiable) | |
Normalized muscle fiber extension velocity. | |
c0 : Any (sympifiable) | |
The first constant in the characteristic equation. The published | |
value is ``-0.318``. | |
c1 : Any (sympifiable) | |
The second constant in the characteristic equation. The published | |
value is ``-8.149``. | |
c2 : Any (sympifiable) | |
The third constant in the characteristic equation. The published | |
value is ``-0.374``. | |
c3 : Any (sympifiable) | |
The fourth constant in the characteristic equation. The published | |
value is ``0.886``. | |
""" | |
pass | |
def _eval_evalf(self, prec): | |
"""Evaluate the expression numerically using ``evalf``.""" | |
return self.doit(deep=False, evaluate=False)._eval_evalf(prec) | |
def doit(self, deep=True, evaluate=True, **hints): | |
"""Evaluate the expression defining the function. | |
Parameters | |
========== | |
deep : bool | |
Whether ``doit`` should be recursively called. Default is ``True``. | |
evaluate : bool. | |
Whether the SymPy expression should be evaluated as it is | |
constructed. If ``False``, then no constant folding will be | |
conducted which will leave the expression in a more numerically- | |
stable for values of ``v_M_tilde`` that correspond to a sensible | |
operating range for a musculotendon. Default is ``True``. | |
**kwargs : dict[str, Any] | |
Additional keyword argument pairs to be recursively passed to | |
``doit``. | |
""" | |
v_M_tilde, *constants = self.args | |
if deep: | |
hints['evaluate'] = evaluate | |
v_M_tilde = v_M_tilde.doit(deep=deep, **hints) | |
c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] | |
else: | |
c0, c1, c2, c3 = constants | |
if evaluate: | |
return c0*log(c1*v_M_tilde + c2 + sqrt((c1*v_M_tilde + c2)**2 + 1)) + c3 | |
return c0*log(c1*v_M_tilde + c2 + sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1)) + c3 | |
def fdiff(self, argindex=1): | |
"""Derivative of the function with respect to a single argument. | |
Parameters | |
========== | |
argindex : int | |
The index of the function's arguments with respect to which the | |
derivative should be taken. Argument indexes start at ``1``. | |
Default is ``1``. | |
""" | |
v_M_tilde, c0, c1, c2, c3 = self.args | |
if argindex == 1: | |
return c0*c1/sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) | |
elif argindex == 2: | |
return log( | |
c1*v_M_tilde + c2 | |
+ sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) | |
) | |
elif argindex == 3: | |
return c0*v_M_tilde/sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) | |
elif argindex == 4: | |
return c0/sqrt(UnevaluatedExpr(c1*v_M_tilde + c2)**2 + 1) | |
elif argindex == 5: | |
return Integer(1) | |
raise ArgumentIndexError(self, argindex) | |
def inverse(self, argindex=1): | |
"""Inverse function. | |
Parameters | |
========== | |
argindex : int | |
Value to start indexing the arguments at. Default is ``1``. | |
""" | |
return FiberForceVelocityInverseDeGroote2016 | |
def _latex(self, printer): | |
"""Print a LaTeX representation of the function defining the curve. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print the LaTeX string representation. | |
""" | |
v_M_tilde = self.args[0] | |
_v_M_tilde = printer._print(v_M_tilde) | |
return r'\operatorname{fv}^M \left( %s \right)' % _v_M_tilde | |
class FiberForceVelocityInverseDeGroote2016(CharacteristicCurveFunction): | |
r"""Inverse muscle fiber force-velocity curve based on De Groote et al., | |
2016 [1]_. | |
Explanation | |
=========== | |
Gives the normalized muscle fiber velocity that produces a specific | |
normalized muscle fiber force. | |
The function is defined by the equation: | |
${fv^M}^{-1} = \frac{\sinh{\frac{fv^M - c_3}{c_0}} - c_2}{c_1}$ | |
with constant values of $c_0 = -0.318$, $c_1 = -8.149$, $c_2 = -0.374$, and | |
$c_3 = 0.886$. This function is the exact analytical inverse of the related | |
muscle fiber force-velocity curve ``FiberForceVelocityDeGroote2016``. | |
While it is possible to change the constant values, these were carefully | |
selected in the original publication to give the characteristic curve | |
specific and required properties. For example, the function produces a | |
normalized muscle fiber force of 1 when the muscle fibers are contracting | |
isometrically (they have an extension rate of 0). | |
Examples | |
======== | |
The preferred way to instantiate :class:`FiberForceVelocityInverseDeGroote2016` | |
is using the :meth:`~.with_defaults` constructor because this will automatically | |
populate the constants within the characteristic curve equation with the | |
floating point values from the original publication. This constructor takes | |
a single argument corresponding to normalized muscle fiber force-velocity | |
component of the muscle fiber force. We'll create a :class:`~.Symbol` called | |
``fv_M`` to represent this. | |
>>> from sympy import Symbol | |
>>> from sympy.physics.biomechanics import FiberForceVelocityInverseDeGroote2016 | |
>>> fv_M = Symbol('fv_M') | |
>>> v_M_tilde = FiberForceVelocityInverseDeGroote2016.with_defaults(fv_M) | |
>>> v_M_tilde | |
FiberForceVelocityInverseDeGroote2016(fv_M, -0.318, -8.149, -0.374, 0.886) | |
It's also possible to populate the four constants with your own values too. | |
>>> from sympy import symbols | |
>>> c0, c1, c2, c3 = symbols('c0 c1 c2 c3') | |
>>> v_M_tilde = FiberForceVelocityInverseDeGroote2016(fv_M, c0, c1, c2, c3) | |
>>> v_M_tilde | |
FiberForceVelocityInverseDeGroote2016(fv_M, c0, c1, c2, c3) | |
To inspect the actual symbolic expression that this function represents, | |
we can call the :meth:`~.doit` method on an instance. We'll use the keyword | |
argument ``evaluate=False`` as this will keep the expression in its | |
canonical form and won't simplify any constants. | |
>>> v_M_tilde.doit(evaluate=False) | |
(-c2 + sinh((-c3 + fv_M)/c0))/c1 | |
The function can also be differentiated. We'll differentiate with respect | |
to fv_M using the ``diff`` method on an instance with the single positional | |
argument ``fv_M``. | |
>>> v_M_tilde.diff(fv_M) | |
cosh((-c3 + fv_M)/c0)/(c0*c1) | |
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 | |
""" | |
def with_defaults(cls, fv_M): | |
r"""Recommended constructor that will use the published constants. | |
Explanation | |
=========== | |
Returns a new instance of the inverse muscle fiber force-velocity | |
function using the four constant values specified in the original | |
publication. | |
These have the values: | |
$c_0 = -0.318$ | |
$c_1 = -8.149$ | |
$c_2 = -0.374$ | |
$c_3 = 0.886$ | |
Parameters | |
========== | |
fv_M : Any (sympifiable) | |
Normalized muscle fiber extension velocity. | |
""" | |
c0 = Float('-0.318') | |
c1 = Float('-8.149') | |
c2 = Float('-0.374') | |
c3 = Float('0.886') | |
return cls(fv_M, c0, c1, c2, c3) | |
def eval(cls, fv_M, c0, c1, c2, c3): | |
"""Evaluation of basic inputs. | |
Parameters | |
========== | |
fv_M : Any (sympifiable) | |
Normalized muscle fiber force as a function of muscle fiber | |
extension velocity. | |
c0 : Any (sympifiable) | |
The first constant in the characteristic equation. The published | |
value is ``-0.318``. | |
c1 : Any (sympifiable) | |
The second constant in the characteristic equation. The published | |
value is ``-8.149``. | |
c2 : Any (sympifiable) | |
The third constant in the characteristic equation. The published | |
value is ``-0.374``. | |
c3 : Any (sympifiable) | |
The fourth constant in the characteristic equation. The published | |
value is ``0.886``. | |
""" | |
pass | |
def _eval_evalf(self, prec): | |
"""Evaluate the expression numerically using ``evalf``.""" | |
return self.doit(deep=False, evaluate=False)._eval_evalf(prec) | |
def doit(self, deep=True, evaluate=True, **hints): | |
"""Evaluate the expression defining the function. | |
Parameters | |
========== | |
deep : bool | |
Whether ``doit`` should be recursively called. Default is ``True``. | |
evaluate : bool. | |
Whether the SymPy expression should be evaluated as it is | |
constructed. If ``False``, then no constant folding will be | |
conducted which will leave the expression in a more numerically- | |
stable for values of ``fv_M`` that correspond to a sensible | |
operating range for a musculotendon. Default is ``True``. | |
**kwargs : dict[str, Any] | |
Additional keyword argument pairs to be recursively passed to | |
``doit``. | |
""" | |
fv_M, *constants = self.args | |
if deep: | |
hints['evaluate'] = evaluate | |
fv_M = fv_M.doit(deep=deep, **hints) | |
c0, c1, c2, c3 = [c.doit(deep=deep, **hints) for c in constants] | |
else: | |
c0, c1, c2, c3 = constants | |
if evaluate: | |
return (sinh((fv_M - c3)/c0) - c2)/c1 | |
return (sinh(UnevaluatedExpr(fv_M - c3)/c0) - c2)/c1 | |
def fdiff(self, argindex=1): | |
"""Derivative of the function with respect to a single argument. | |
Parameters | |
========== | |
argindex : int | |
The index of the function's arguments with respect to which the | |
derivative should be taken. Argument indexes start at ``1``. | |
Default is ``1``. | |
""" | |
fv_M, c0, c1, c2, c3 = self.args | |
if argindex == 1: | |
return cosh((fv_M - c3)/c0)/(c0*c1) | |
elif argindex == 2: | |
return (c3 - fv_M)*cosh((fv_M - c3)/c0)/(c0**2*c1) | |
elif argindex == 3: | |
return (c2 - sinh((fv_M - c3)/c0))/c1**2 | |
elif argindex == 4: | |
return -1/c1 | |
elif argindex == 5: | |
return -cosh((fv_M - c3)/c0)/(c0*c1) | |
raise ArgumentIndexError(self, argindex) | |
def inverse(self, argindex=1): | |
"""Inverse function. | |
Parameters | |
========== | |
argindex : int | |
Value to start indexing the arguments at. Default is ``1``. | |
""" | |
return FiberForceVelocityDeGroote2016 | |
def _latex(self, printer): | |
"""Print a LaTeX representation of the function defining the curve. | |
Parameters | |
========== | |
printer : Printer | |
The printer to be used to print the LaTeX string representation. | |
""" | |
fv_M = self.args[0] | |
_fv_M = printer._print(fv_M) | |
return r'\left( \operatorname{fv}^M \right)^{-1} \left( %s \right)' % _fv_M | |
class CharacteristicCurveCollection: | |
"""Simple data container to group together related characteristic curves.""" | |
tendon_force_length: CharacteristicCurveFunction | |
tendon_force_length_inverse: CharacteristicCurveFunction | |
fiber_force_length_passive: CharacteristicCurveFunction | |
fiber_force_length_passive_inverse: CharacteristicCurveFunction | |
fiber_force_length_active: CharacteristicCurveFunction | |
fiber_force_velocity: CharacteristicCurveFunction | |
fiber_force_velocity_inverse: CharacteristicCurveFunction | |
def __iter__(self): | |
"""Iterator support for ``CharacteristicCurveCollection``.""" | |
yield self.tendon_force_length | |
yield self.tendon_force_length_inverse | |
yield self.fiber_force_length_passive | |
yield self.fiber_force_length_passive_inverse | |
yield self.fiber_force_length_active | |
yield self.fiber_force_velocity | |
yield self.fiber_force_velocity_inverse | |