| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763 |
- """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(-(l_M/l_M_opt
- - 1.06)**2/(2*(0.0633*l_M/l_M_opt + 0.162)**2))
- + 0.433*exp(-(l_M/l_M_opt - 0.717)**2/(2*(0.2*l_M/l_M_opt - 0.0299)**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)
- + (0.433*(-l_M/l_M_opt + 0.717)/(0.2*l_M/l_M_opt - 0.0299)**2
- + 0.0866*(l_M/l_M_opt - 0.717)**2/(0.2*l_M/l_M_opt
- - 0.0299)**3)*exp(-(l_M/l_M_opt - 0.717)**2/(2*(0.2*l_M/l_M_opt - 0.0299)**2))
- + (0.814*(-l_M/l_M_opt + 1.06)/(0.0633*l_M/l_M_opt
- + 0.162)**2 + 0.0515262*(l_M/l_M_opt
- - 1.06)**2/(0.0633*l_M/l_M_opt
- + 0.162)**3)*exp(-(l_M/l_M_opt
- - 1.06)**2/(2*(0.0633*l_M/l_M_opt + 0.162)**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
|