1764 lines
62 KiB
Python
1764 lines
62 KiB
Python
"""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."""
|
|
|
|
@classmethod
|
|
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
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
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)
|
|
|
|
@classmethod
|
|
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
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
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
|