test_musculotendon.py 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. """Tests for the ``sympy.physics.biomechanics.musculotendon.py`` module."""
  2. import abc
  3. import pytest
  4. from sympy.core.expr import UnevaluatedExpr
  5. from sympy.core.numbers import Float, Integer, Rational
  6. from sympy.core.symbol import Symbol
  7. from sympy.functions.elementary.exponential import exp
  8. from sympy.functions.elementary.hyperbolic import tanh
  9. from sympy.functions.elementary.miscellaneous import sqrt
  10. from sympy.functions.elementary.trigonometric import sin
  11. from sympy.matrices.dense import MutableDenseMatrix as Matrix, eye, zeros
  12. from sympy.physics.biomechanics.activation import (
  13. FirstOrderActivationDeGroote2016
  14. )
  15. from sympy.physics.biomechanics.curve import (
  16. CharacteristicCurveCollection,
  17. FiberForceLengthActiveDeGroote2016,
  18. FiberForceLengthPassiveDeGroote2016,
  19. FiberForceLengthPassiveInverseDeGroote2016,
  20. FiberForceVelocityDeGroote2016,
  21. FiberForceVelocityInverseDeGroote2016,
  22. TendonForceLengthDeGroote2016,
  23. TendonForceLengthInverseDeGroote2016,
  24. )
  25. from sympy.physics.biomechanics.musculotendon import (
  26. MusculotendonBase,
  27. MusculotendonDeGroote2016,
  28. MusculotendonFormulation,
  29. )
  30. from sympy.physics.biomechanics._mixin import _NamedMixin
  31. from sympy.physics.mechanics.actuator import ForceActuator
  32. from sympy.physics.mechanics.pathway import LinearPathway
  33. from sympy.physics.vector.frame import ReferenceFrame
  34. from sympy.physics.vector.functions import dynamicsymbols
  35. from sympy.physics.vector.point import Point
  36. from sympy.simplify.simplify import simplify
  37. class TestMusculotendonFormulation:
  38. @staticmethod
  39. def test_rigid_tendon_member():
  40. assert MusculotendonFormulation(0) == 0
  41. assert MusculotendonFormulation.RIGID_TENDON == 0
  42. @staticmethod
  43. def test_fiber_length_explicit_member():
  44. assert MusculotendonFormulation(1) == 1
  45. assert MusculotendonFormulation.FIBER_LENGTH_EXPLICIT == 1
  46. @staticmethod
  47. def test_tendon_force_explicit_member():
  48. assert MusculotendonFormulation(2) == 2
  49. assert MusculotendonFormulation.TENDON_FORCE_EXPLICIT == 2
  50. @staticmethod
  51. def test_fiber_length_implicit_member():
  52. assert MusculotendonFormulation(3) == 3
  53. assert MusculotendonFormulation.FIBER_LENGTH_IMPLICIT == 3
  54. @staticmethod
  55. def test_tendon_force_implicit_member():
  56. assert MusculotendonFormulation(4) == 4
  57. assert MusculotendonFormulation.TENDON_FORCE_IMPLICIT == 4
  58. class TestMusculotendonBase:
  59. @staticmethod
  60. def test_is_abstract_base_class():
  61. assert issubclass(MusculotendonBase, abc.ABC)
  62. @staticmethod
  63. def test_class():
  64. assert issubclass(MusculotendonBase, ForceActuator)
  65. assert issubclass(MusculotendonBase, _NamedMixin)
  66. assert MusculotendonBase.__name__ == 'MusculotendonBase'
  67. @staticmethod
  68. def test_cannot_instantiate_directly():
  69. with pytest.raises(TypeError):
  70. _ = MusculotendonBase()
  71. @pytest.mark.parametrize('musculotendon_concrete', [MusculotendonDeGroote2016])
  72. class TestMusculotendonRigidTendon:
  73. @pytest.fixture(autouse=True)
  74. def _musculotendon_rigid_tendon_fixture(self, musculotendon_concrete):
  75. self.name = 'name'
  76. self.N = ReferenceFrame('N')
  77. self.q = dynamicsymbols('q')
  78. self.origin = Point('pO')
  79. self.insertion = Point('pI')
  80. self.insertion.set_pos(self.origin, self.q*self.N.x)
  81. self.pathway = LinearPathway(self.origin, self.insertion)
  82. self.activation = FirstOrderActivationDeGroote2016(self.name)
  83. self.e = self.activation.excitation
  84. self.a = self.activation.activation
  85. self.tau_a = self.activation.activation_time_constant
  86. self.tau_d = self.activation.deactivation_time_constant
  87. self.b = self.activation.smoothing_rate
  88. self.formulation = MusculotendonFormulation.RIGID_TENDON
  89. self.l_T_slack = Symbol('l_T_slack')
  90. self.F_M_max = Symbol('F_M_max')
  91. self.l_M_opt = Symbol('l_M_opt')
  92. self.v_M_max = Symbol('v_M_max')
  93. self.alpha_opt = Symbol('alpha_opt')
  94. self.beta = Symbol('beta')
  95. self.instance = musculotendon_concrete(
  96. self.name,
  97. self.pathway,
  98. self.activation,
  99. musculotendon_dynamics=self.formulation,
  100. tendon_slack_length=self.l_T_slack,
  101. peak_isometric_force=self.F_M_max,
  102. optimal_fiber_length=self.l_M_opt,
  103. maximal_fiber_velocity=self.v_M_max,
  104. optimal_pennation_angle=self.alpha_opt,
  105. fiber_damping_coefficient=self.beta,
  106. )
  107. self.da_expr = (
  108. (1/(self.tau_a*(Rational(1, 2) + Rational(3, 2)*self.a)))
  109. *(Rational(1, 2) + Rational(1, 2)*tanh(self.b*(self.e - self.a)))
  110. + ((Rational(1, 2) + Rational(3, 2)*self.a)/self.tau_d)
  111. *(Rational(1, 2) - Rational(1, 2)*tanh(self.b*(self.e - self.a)))
  112. )*(self.e - self.a)
  113. def test_state_vars(self):
  114. assert hasattr(self.instance, 'x')
  115. assert hasattr(self.instance, 'state_vars')
  116. assert self.instance.x == self.instance.state_vars
  117. x_expected = Matrix([self.a])
  118. assert self.instance.x == x_expected
  119. assert self.instance.state_vars == x_expected
  120. assert isinstance(self.instance.x, Matrix)
  121. assert isinstance(self.instance.state_vars, Matrix)
  122. assert self.instance.x.shape == (1, 1)
  123. assert self.instance.state_vars.shape == (1, 1)
  124. def test_input_vars(self):
  125. assert hasattr(self.instance, 'r')
  126. assert hasattr(self.instance, 'input_vars')
  127. assert self.instance.r == self.instance.input_vars
  128. r_expected = Matrix([self.e])
  129. assert self.instance.r == r_expected
  130. assert self.instance.input_vars == r_expected
  131. assert isinstance(self.instance.r, Matrix)
  132. assert isinstance(self.instance.input_vars, Matrix)
  133. assert self.instance.r.shape == (1, 1)
  134. assert self.instance.input_vars.shape == (1, 1)
  135. def test_constants(self):
  136. assert hasattr(self.instance, 'p')
  137. assert hasattr(self.instance, 'constants')
  138. assert self.instance.p == self.instance.constants
  139. p_expected = Matrix(
  140. [
  141. self.l_T_slack,
  142. self.F_M_max,
  143. self.l_M_opt,
  144. self.v_M_max,
  145. self.alpha_opt,
  146. self.beta,
  147. self.tau_a,
  148. self.tau_d,
  149. self.b,
  150. Symbol('c_0_fl_T_name'),
  151. Symbol('c_1_fl_T_name'),
  152. Symbol('c_2_fl_T_name'),
  153. Symbol('c_3_fl_T_name'),
  154. Symbol('c_0_fl_M_pas_name'),
  155. Symbol('c_1_fl_M_pas_name'),
  156. Symbol('c_0_fl_M_act_name'),
  157. Symbol('c_1_fl_M_act_name'),
  158. Symbol('c_2_fl_M_act_name'),
  159. Symbol('c_3_fl_M_act_name'),
  160. Symbol('c_4_fl_M_act_name'),
  161. Symbol('c_5_fl_M_act_name'),
  162. Symbol('c_6_fl_M_act_name'),
  163. Symbol('c_7_fl_M_act_name'),
  164. Symbol('c_8_fl_M_act_name'),
  165. Symbol('c_9_fl_M_act_name'),
  166. Symbol('c_10_fl_M_act_name'),
  167. Symbol('c_11_fl_M_act_name'),
  168. Symbol('c_0_fv_M_name'),
  169. Symbol('c_1_fv_M_name'),
  170. Symbol('c_2_fv_M_name'),
  171. Symbol('c_3_fv_M_name'),
  172. ]
  173. )
  174. assert self.instance.p == p_expected
  175. assert self.instance.constants == p_expected
  176. assert isinstance(self.instance.p, Matrix)
  177. assert isinstance(self.instance.constants, Matrix)
  178. assert self.instance.p.shape == (31, 1)
  179. assert self.instance.constants.shape == (31, 1)
  180. def test_M(self):
  181. assert hasattr(self.instance, 'M')
  182. M_expected = Matrix([1])
  183. assert self.instance.M == M_expected
  184. assert isinstance(self.instance.M, Matrix)
  185. assert self.instance.M.shape == (1, 1)
  186. def test_F(self):
  187. assert hasattr(self.instance, 'F')
  188. F_expected = Matrix([self.da_expr])
  189. assert self.instance.F == F_expected
  190. assert isinstance(self.instance.F, Matrix)
  191. assert self.instance.F.shape == (1, 1)
  192. def test_rhs(self):
  193. assert hasattr(self.instance, 'rhs')
  194. rhs_expected = Matrix([self.da_expr])
  195. rhs = self.instance.rhs()
  196. assert isinstance(rhs, Matrix)
  197. assert rhs.shape == (1, 1)
  198. assert simplify(rhs - rhs_expected) == zeros(1)
  199. @pytest.mark.parametrize(
  200. 'musculotendon_concrete, curve',
  201. [
  202. (
  203. MusculotendonDeGroote2016,
  204. CharacteristicCurveCollection(
  205. tendon_force_length=TendonForceLengthDeGroote2016,
  206. tendon_force_length_inverse=TendonForceLengthInverseDeGroote2016,
  207. fiber_force_length_passive=FiberForceLengthPassiveDeGroote2016,
  208. fiber_force_length_passive_inverse=FiberForceLengthPassiveInverseDeGroote2016,
  209. fiber_force_length_active=FiberForceLengthActiveDeGroote2016,
  210. fiber_force_velocity=FiberForceVelocityDeGroote2016,
  211. fiber_force_velocity_inverse=FiberForceVelocityInverseDeGroote2016,
  212. ),
  213. )
  214. ],
  215. )
  216. class TestFiberLengthExplicit:
  217. @pytest.fixture(autouse=True)
  218. def _musculotendon_fiber_length_explicit_fixture(
  219. self,
  220. musculotendon_concrete,
  221. curve,
  222. ):
  223. self.name = 'name'
  224. self.N = ReferenceFrame('N')
  225. self.q = dynamicsymbols('q')
  226. self.origin = Point('pO')
  227. self.insertion = Point('pI')
  228. self.insertion.set_pos(self.origin, self.q*self.N.x)
  229. self.pathway = LinearPathway(self.origin, self.insertion)
  230. self.activation = FirstOrderActivationDeGroote2016(self.name)
  231. self.e = self.activation.excitation
  232. self.a = self.activation.activation
  233. self.tau_a = self.activation.activation_time_constant
  234. self.tau_d = self.activation.deactivation_time_constant
  235. self.b = self.activation.smoothing_rate
  236. self.formulation = MusculotendonFormulation.FIBER_LENGTH_EXPLICIT
  237. self.l_T_slack = Symbol('l_T_slack')
  238. self.F_M_max = Symbol('F_M_max')
  239. self.l_M_opt = Symbol('l_M_opt')
  240. self.v_M_max = Symbol('v_M_max')
  241. self.alpha_opt = Symbol('alpha_opt')
  242. self.beta = Symbol('beta')
  243. self.instance = musculotendon_concrete(
  244. self.name,
  245. self.pathway,
  246. self.activation,
  247. musculotendon_dynamics=self.formulation,
  248. tendon_slack_length=self.l_T_slack,
  249. peak_isometric_force=self.F_M_max,
  250. optimal_fiber_length=self.l_M_opt,
  251. maximal_fiber_velocity=self.v_M_max,
  252. optimal_pennation_angle=self.alpha_opt,
  253. fiber_damping_coefficient=self.beta,
  254. with_defaults=True,
  255. )
  256. self.l_M_tilde = dynamicsymbols('l_M_tilde_name')
  257. l_MT = self.pathway.length
  258. l_M = self.l_M_tilde*self.l_M_opt
  259. l_T = l_MT - sqrt(l_M**2 - (self.l_M_opt*sin(self.alpha_opt))**2)
  260. fl_T = curve.tendon_force_length.with_defaults(l_T/self.l_T_slack)
  261. fl_M_pas = curve.fiber_force_length_passive.with_defaults(self.l_M_tilde)
  262. fl_M_act = curve.fiber_force_length_active.with_defaults(self.l_M_tilde)
  263. v_M_tilde = curve.fiber_force_velocity_inverse.with_defaults(
  264. ((((fl_T*self.F_M_max)/((l_MT - l_T)/l_M))/self.F_M_max) - fl_M_pas)
  265. /(self.a*fl_M_act)
  266. )
  267. self.dl_M_tilde_expr = (self.v_M_max/self.l_M_opt)*v_M_tilde
  268. self.da_expr = (
  269. (1/(self.tau_a*(Rational(1, 2) + Rational(3, 2)*self.a)))
  270. *(Rational(1, 2) + Rational(1, 2)*tanh(self.b*(self.e - self.a)))
  271. + ((Rational(1, 2) + Rational(3, 2)*self.a)/self.tau_d)
  272. *(Rational(1, 2) - Rational(1, 2)*tanh(self.b*(self.e - self.a)))
  273. )*(self.e - self.a)
  274. def test_state_vars(self):
  275. assert hasattr(self.instance, 'x')
  276. assert hasattr(self.instance, 'state_vars')
  277. assert self.instance.x == self.instance.state_vars
  278. x_expected = Matrix([self.l_M_tilde, self.a])
  279. assert self.instance.x == x_expected
  280. assert self.instance.state_vars == x_expected
  281. assert isinstance(self.instance.x, Matrix)
  282. assert isinstance(self.instance.state_vars, Matrix)
  283. assert self.instance.x.shape == (2, 1)
  284. assert self.instance.state_vars.shape == (2, 1)
  285. def test_input_vars(self):
  286. assert hasattr(self.instance, 'r')
  287. assert hasattr(self.instance, 'input_vars')
  288. assert self.instance.r == self.instance.input_vars
  289. r_expected = Matrix([self.e])
  290. assert self.instance.r == r_expected
  291. assert self.instance.input_vars == r_expected
  292. assert isinstance(self.instance.r, Matrix)
  293. assert isinstance(self.instance.input_vars, Matrix)
  294. assert self.instance.r.shape == (1, 1)
  295. assert self.instance.input_vars.shape == (1, 1)
  296. def test_constants(self):
  297. assert hasattr(self.instance, 'p')
  298. assert hasattr(self.instance, 'constants')
  299. assert self.instance.p == self.instance.constants
  300. p_expected = Matrix(
  301. [
  302. self.l_T_slack,
  303. self.F_M_max,
  304. self.l_M_opt,
  305. self.v_M_max,
  306. self.alpha_opt,
  307. self.beta,
  308. self.tau_a,
  309. self.tau_d,
  310. self.b,
  311. ]
  312. )
  313. assert self.instance.p == p_expected
  314. assert self.instance.constants == p_expected
  315. assert isinstance(self.instance.p, Matrix)
  316. assert isinstance(self.instance.constants, Matrix)
  317. assert self.instance.p.shape == (9, 1)
  318. assert self.instance.constants.shape == (9, 1)
  319. def test_M(self):
  320. assert hasattr(self.instance, 'M')
  321. M_expected = eye(2)
  322. assert self.instance.M == M_expected
  323. assert isinstance(self.instance.M, Matrix)
  324. assert self.instance.M.shape == (2, 2)
  325. def test_F(self):
  326. assert hasattr(self.instance, 'F')
  327. F_expected = Matrix([self.dl_M_tilde_expr, self.da_expr])
  328. assert self.instance.F == F_expected
  329. assert isinstance(self.instance.F, Matrix)
  330. assert self.instance.F.shape == (2, 1)
  331. def test_rhs(self):
  332. assert hasattr(self.instance, 'rhs')
  333. rhs_expected = Matrix([self.dl_M_tilde_expr, self.da_expr])
  334. rhs = self.instance.rhs()
  335. assert isinstance(rhs, Matrix)
  336. assert rhs.shape == (2, 1)
  337. assert simplify(rhs - rhs_expected) == zeros(2, 1)
  338. @pytest.mark.parametrize(
  339. 'musculotendon_concrete, curve',
  340. [
  341. (
  342. MusculotendonDeGroote2016,
  343. CharacteristicCurveCollection(
  344. tendon_force_length=TendonForceLengthDeGroote2016,
  345. tendon_force_length_inverse=TendonForceLengthInverseDeGroote2016,
  346. fiber_force_length_passive=FiberForceLengthPassiveDeGroote2016,
  347. fiber_force_length_passive_inverse=FiberForceLengthPassiveInverseDeGroote2016,
  348. fiber_force_length_active=FiberForceLengthActiveDeGroote2016,
  349. fiber_force_velocity=FiberForceVelocityDeGroote2016,
  350. fiber_force_velocity_inverse=FiberForceVelocityInverseDeGroote2016,
  351. ),
  352. )
  353. ],
  354. )
  355. class TestTendonForceExplicit:
  356. @pytest.fixture(autouse=True)
  357. def _musculotendon_tendon_force_explicit_fixture(
  358. self,
  359. musculotendon_concrete,
  360. curve,
  361. ):
  362. self.name = 'name'
  363. self.N = ReferenceFrame('N')
  364. self.q = dynamicsymbols('q')
  365. self.origin = Point('pO')
  366. self.insertion = Point('pI')
  367. self.insertion.set_pos(self.origin, self.q*self.N.x)
  368. self.pathway = LinearPathway(self.origin, self.insertion)
  369. self.activation = FirstOrderActivationDeGroote2016(self.name)
  370. self.e = self.activation.excitation
  371. self.a = self.activation.activation
  372. self.tau_a = self.activation.activation_time_constant
  373. self.tau_d = self.activation.deactivation_time_constant
  374. self.b = self.activation.smoothing_rate
  375. self.formulation = MusculotendonFormulation.TENDON_FORCE_EXPLICIT
  376. self.l_T_slack = Symbol('l_T_slack')
  377. self.F_M_max = Symbol('F_M_max')
  378. self.l_M_opt = Symbol('l_M_opt')
  379. self.v_M_max = Symbol('v_M_max')
  380. self.alpha_opt = Symbol('alpha_opt')
  381. self.beta = Symbol('beta')
  382. self.instance = musculotendon_concrete(
  383. self.name,
  384. self.pathway,
  385. self.activation,
  386. musculotendon_dynamics=self.formulation,
  387. tendon_slack_length=self.l_T_slack,
  388. peak_isometric_force=self.F_M_max,
  389. optimal_fiber_length=self.l_M_opt,
  390. maximal_fiber_velocity=self.v_M_max,
  391. optimal_pennation_angle=self.alpha_opt,
  392. fiber_damping_coefficient=self.beta,
  393. with_defaults=True,
  394. )
  395. self.F_T_tilde = dynamicsymbols('F_T_tilde_name')
  396. l_T_tilde = curve.tendon_force_length_inverse.with_defaults(self.F_T_tilde)
  397. l_MT = self.pathway.length
  398. v_MT = self.pathway.extension_velocity
  399. l_T = l_T_tilde*self.l_T_slack
  400. l_M = sqrt((l_MT - l_T)**2 + (self.l_M_opt*sin(self.alpha_opt))**2)
  401. l_M_tilde = l_M/self.l_M_opt
  402. cos_alpha = (l_MT - l_T)/l_M
  403. F_T = self.F_T_tilde*self.F_M_max
  404. F_M = F_T/cos_alpha
  405. F_M_tilde = F_M/self.F_M_max
  406. fl_M_pas = curve.fiber_force_length_passive.with_defaults(l_M_tilde)
  407. fl_M_act = curve.fiber_force_length_active.with_defaults(l_M_tilde)
  408. fv_M = (F_M_tilde - fl_M_pas)/(self.a*fl_M_act)
  409. v_M_tilde = curve.fiber_force_velocity_inverse.with_defaults(fv_M)
  410. v_M = v_M_tilde*self.v_M_max
  411. v_T = v_MT - v_M/cos_alpha
  412. v_T_tilde = v_T/self.l_T_slack
  413. self.dF_T_tilde_expr = (
  414. Float('0.2')*Float('33.93669377311689')*exp(
  415. Float('33.93669377311689')*UnevaluatedExpr(l_T_tilde - Float('0.995'))
  416. )*v_T_tilde
  417. )
  418. self.da_expr = (
  419. (1/(self.tau_a*(Rational(1, 2) + Rational(3, 2)*self.a)))
  420. *(Rational(1, 2) + Rational(1, 2)*tanh(self.b*(self.e - self.a)))
  421. + ((Rational(1, 2) + Rational(3, 2)*self.a)/self.tau_d)
  422. *(Rational(1, 2) - Rational(1, 2)*tanh(self.b*(self.e - self.a)))
  423. )*(self.e - self.a)
  424. def test_state_vars(self):
  425. assert hasattr(self.instance, 'x')
  426. assert hasattr(self.instance, 'state_vars')
  427. assert self.instance.x == self.instance.state_vars
  428. x_expected = Matrix([self.F_T_tilde, self.a])
  429. assert self.instance.x == x_expected
  430. assert self.instance.state_vars == x_expected
  431. assert isinstance(self.instance.x, Matrix)
  432. assert isinstance(self.instance.state_vars, Matrix)
  433. assert self.instance.x.shape == (2, 1)
  434. assert self.instance.state_vars.shape == (2, 1)
  435. def test_input_vars(self):
  436. assert hasattr(self.instance, 'r')
  437. assert hasattr(self.instance, 'input_vars')
  438. assert self.instance.r == self.instance.input_vars
  439. r_expected = Matrix([self.e])
  440. assert self.instance.r == r_expected
  441. assert self.instance.input_vars == r_expected
  442. assert isinstance(self.instance.r, Matrix)
  443. assert isinstance(self.instance.input_vars, Matrix)
  444. assert self.instance.r.shape == (1, 1)
  445. assert self.instance.input_vars.shape == (1, 1)
  446. def test_constants(self):
  447. assert hasattr(self.instance, 'p')
  448. assert hasattr(self.instance, 'constants')
  449. assert self.instance.p == self.instance.constants
  450. p_expected = Matrix(
  451. [
  452. self.l_T_slack,
  453. self.F_M_max,
  454. self.l_M_opt,
  455. self.v_M_max,
  456. self.alpha_opt,
  457. self.beta,
  458. self.tau_a,
  459. self.tau_d,
  460. self.b,
  461. ]
  462. )
  463. assert self.instance.p == p_expected
  464. assert self.instance.constants == p_expected
  465. assert isinstance(self.instance.p, Matrix)
  466. assert isinstance(self.instance.constants, Matrix)
  467. assert self.instance.p.shape == (9, 1)
  468. assert self.instance.constants.shape == (9, 1)
  469. def test_M(self):
  470. assert hasattr(self.instance, 'M')
  471. M_expected = eye(2)
  472. assert self.instance.M == M_expected
  473. assert isinstance(self.instance.M, Matrix)
  474. assert self.instance.M.shape == (2, 2)
  475. def test_F(self):
  476. assert hasattr(self.instance, 'F')
  477. F_expected = Matrix([self.dF_T_tilde_expr, self.da_expr])
  478. assert self.instance.F == F_expected
  479. assert isinstance(self.instance.F, Matrix)
  480. assert self.instance.F.shape == (2, 1)
  481. def test_rhs(self):
  482. assert hasattr(self.instance, 'rhs')
  483. rhs_expected = Matrix([self.dF_T_tilde_expr, self.da_expr])
  484. rhs = self.instance.rhs()
  485. assert isinstance(rhs, Matrix)
  486. assert rhs.shape == (2, 1)
  487. assert simplify(rhs - rhs_expected) == zeros(2, 1)
  488. class TestMusculotendonDeGroote2016:
  489. @staticmethod
  490. def test_class():
  491. assert issubclass(MusculotendonDeGroote2016, ForceActuator)
  492. assert issubclass(MusculotendonDeGroote2016, _NamedMixin)
  493. assert MusculotendonDeGroote2016.__name__ == 'MusculotendonDeGroote2016'
  494. @staticmethod
  495. def test_instance():
  496. origin = Point('pO')
  497. insertion = Point('pI')
  498. insertion.set_pos(origin, dynamicsymbols('q')*ReferenceFrame('N').x)
  499. pathway = LinearPathway(origin, insertion)
  500. activation = FirstOrderActivationDeGroote2016('name')
  501. l_T_slack = Symbol('l_T_slack')
  502. F_M_max = Symbol('F_M_max')
  503. l_M_opt = Symbol('l_M_opt')
  504. v_M_max = Symbol('v_M_max')
  505. alpha_opt = Symbol('alpha_opt')
  506. beta = Symbol('beta')
  507. instance = MusculotendonDeGroote2016(
  508. 'name',
  509. pathway,
  510. activation,
  511. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  512. tendon_slack_length=l_T_slack,
  513. peak_isometric_force=F_M_max,
  514. optimal_fiber_length=l_M_opt,
  515. maximal_fiber_velocity=v_M_max,
  516. optimal_pennation_angle=alpha_opt,
  517. fiber_damping_coefficient=beta,
  518. )
  519. assert isinstance(instance, MusculotendonDeGroote2016)
  520. @pytest.fixture(autouse=True)
  521. def _musculotendon_fixture(self):
  522. self.name = 'name'
  523. self.N = ReferenceFrame('N')
  524. self.q = dynamicsymbols('q')
  525. self.origin = Point('pO')
  526. self.insertion = Point('pI')
  527. self.insertion.set_pos(self.origin, self.q*self.N.x)
  528. self.pathway = LinearPathway(self.origin, self.insertion)
  529. self.activation = FirstOrderActivationDeGroote2016(self.name)
  530. self.l_T_slack = Symbol('l_T_slack')
  531. self.F_M_max = Symbol('F_M_max')
  532. self.l_M_opt = Symbol('l_M_opt')
  533. self.v_M_max = Symbol('v_M_max')
  534. self.alpha_opt = Symbol('alpha_opt')
  535. self.beta = Symbol('beta')
  536. def test_with_defaults(self):
  537. origin = Point('pO')
  538. insertion = Point('pI')
  539. insertion.set_pos(origin, dynamicsymbols('q')*ReferenceFrame('N').x)
  540. pathway = LinearPathway(origin, insertion)
  541. activation = FirstOrderActivationDeGroote2016('name')
  542. l_T_slack = Symbol('l_T_slack')
  543. F_M_max = Symbol('F_M_max')
  544. l_M_opt = Symbol('l_M_opt')
  545. v_M_max = Float('10.0')
  546. alpha_opt = Float('0.0')
  547. beta = Float('0.1')
  548. instance = MusculotendonDeGroote2016.with_defaults(
  549. 'name',
  550. pathway,
  551. activation,
  552. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  553. tendon_slack_length=l_T_slack,
  554. peak_isometric_force=F_M_max,
  555. optimal_fiber_length=l_M_opt,
  556. )
  557. assert instance.tendon_slack_length == l_T_slack
  558. assert instance.peak_isometric_force == F_M_max
  559. assert instance.optimal_fiber_length == l_M_opt
  560. assert instance.maximal_fiber_velocity == v_M_max
  561. assert instance.optimal_pennation_angle == alpha_opt
  562. assert instance.fiber_damping_coefficient == beta
  563. @pytest.mark.parametrize(
  564. 'l_T_slack, expected',
  565. [
  566. (None, Symbol('l_T_slack_name')),
  567. (Symbol('l_T_slack'), Symbol('l_T_slack')),
  568. (Rational(1, 2), Rational(1, 2)),
  569. (Float('0.5'), Float('0.5')),
  570. ],
  571. )
  572. def test_tendon_slack_length(self, l_T_slack, expected):
  573. instance = MusculotendonDeGroote2016(
  574. self.name,
  575. self.pathway,
  576. self.activation,
  577. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  578. tendon_slack_length=l_T_slack,
  579. peak_isometric_force=self.F_M_max,
  580. optimal_fiber_length=self.l_M_opt,
  581. maximal_fiber_velocity=self.v_M_max,
  582. optimal_pennation_angle=self.alpha_opt,
  583. fiber_damping_coefficient=self.beta,
  584. )
  585. assert instance.l_T_slack == expected
  586. assert instance.tendon_slack_length == expected
  587. @pytest.mark.parametrize(
  588. 'F_M_max, expected',
  589. [
  590. (None, Symbol('F_M_max_name')),
  591. (Symbol('F_M_max'), Symbol('F_M_max')),
  592. (Integer(1000), Integer(1000)),
  593. (Float('1000.0'), Float('1000.0')),
  594. ],
  595. )
  596. def test_peak_isometric_force(self, F_M_max, expected):
  597. instance = MusculotendonDeGroote2016(
  598. self.name,
  599. self.pathway,
  600. self.activation,
  601. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  602. tendon_slack_length=self.l_T_slack,
  603. peak_isometric_force=F_M_max,
  604. optimal_fiber_length=self.l_M_opt,
  605. maximal_fiber_velocity=self.v_M_max,
  606. optimal_pennation_angle=self.alpha_opt,
  607. fiber_damping_coefficient=self.beta,
  608. )
  609. assert instance.F_M_max == expected
  610. assert instance.peak_isometric_force == expected
  611. @pytest.mark.parametrize(
  612. 'l_M_opt, expected',
  613. [
  614. (None, Symbol('l_M_opt_name')),
  615. (Symbol('l_M_opt'), Symbol('l_M_opt')),
  616. (Rational(1, 2), Rational(1, 2)),
  617. (Float('0.5'), Float('0.5')),
  618. ],
  619. )
  620. def test_optimal_fiber_length(self, l_M_opt, expected):
  621. instance = MusculotendonDeGroote2016(
  622. self.name,
  623. self.pathway,
  624. self.activation,
  625. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  626. tendon_slack_length=self.l_T_slack,
  627. peak_isometric_force=self.F_M_max,
  628. optimal_fiber_length=l_M_opt,
  629. maximal_fiber_velocity=self.v_M_max,
  630. optimal_pennation_angle=self.alpha_opt,
  631. fiber_damping_coefficient=self.beta,
  632. )
  633. assert instance.l_M_opt == expected
  634. assert instance.optimal_fiber_length == expected
  635. @pytest.mark.parametrize(
  636. 'v_M_max, expected',
  637. [
  638. (None, Symbol('v_M_max_name')),
  639. (Symbol('v_M_max'), Symbol('v_M_max')),
  640. (Integer(10), Integer(10)),
  641. (Float('10.0'), Float('10.0')),
  642. ],
  643. )
  644. def test_maximal_fiber_velocity(self, v_M_max, expected):
  645. instance = MusculotendonDeGroote2016(
  646. self.name,
  647. self.pathway,
  648. self.activation,
  649. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  650. tendon_slack_length=self.l_T_slack,
  651. peak_isometric_force=self.F_M_max,
  652. optimal_fiber_length=self.l_M_opt,
  653. maximal_fiber_velocity=v_M_max,
  654. optimal_pennation_angle=self.alpha_opt,
  655. fiber_damping_coefficient=self.beta,
  656. )
  657. assert instance.v_M_max == expected
  658. assert instance.maximal_fiber_velocity == expected
  659. @pytest.mark.parametrize(
  660. 'alpha_opt, expected',
  661. [
  662. (None, Symbol('alpha_opt_name')),
  663. (Symbol('alpha_opt'), Symbol('alpha_opt')),
  664. (Integer(0), Integer(0)),
  665. (Float('0.1'), Float('0.1')),
  666. ],
  667. )
  668. def test_optimal_pennation_angle(self, alpha_opt, expected):
  669. instance = MusculotendonDeGroote2016(
  670. self.name,
  671. self.pathway,
  672. self.activation,
  673. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  674. tendon_slack_length=self.l_T_slack,
  675. peak_isometric_force=self.F_M_max,
  676. optimal_fiber_length=self.l_M_opt,
  677. maximal_fiber_velocity=self.v_M_max,
  678. optimal_pennation_angle=alpha_opt,
  679. fiber_damping_coefficient=self.beta,
  680. )
  681. assert instance.alpha_opt == expected
  682. assert instance.optimal_pennation_angle == expected
  683. @pytest.mark.parametrize(
  684. 'beta, expected',
  685. [
  686. (None, Symbol('beta_name')),
  687. (Symbol('beta'), Symbol('beta')),
  688. (Integer(0), Integer(0)),
  689. (Rational(1, 10), Rational(1, 10)),
  690. (Float('0.1'), Float('0.1')),
  691. ],
  692. )
  693. def test_fiber_damping_coefficient(self, beta, expected):
  694. instance = MusculotendonDeGroote2016(
  695. self.name,
  696. self.pathway,
  697. self.activation,
  698. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  699. tendon_slack_length=self.l_T_slack,
  700. peak_isometric_force=self.F_M_max,
  701. optimal_fiber_length=self.l_M_opt,
  702. maximal_fiber_velocity=self.v_M_max,
  703. optimal_pennation_angle=self.alpha_opt,
  704. fiber_damping_coefficient=beta,
  705. )
  706. assert instance.beta == expected
  707. assert instance.fiber_damping_coefficient == expected
  708. def test_excitation(self):
  709. instance = MusculotendonDeGroote2016(
  710. self.name,
  711. self.pathway,
  712. self.activation,
  713. )
  714. assert hasattr(instance, 'e')
  715. assert hasattr(instance, 'excitation')
  716. e_expected = dynamicsymbols('e_name')
  717. assert instance.e == e_expected
  718. assert instance.excitation == e_expected
  719. assert instance.e is instance.excitation
  720. def test_excitation_is_immutable(self):
  721. instance = MusculotendonDeGroote2016(
  722. self.name,
  723. self.pathway,
  724. self.activation,
  725. )
  726. with pytest.raises(AttributeError):
  727. instance.e = None
  728. with pytest.raises(AttributeError):
  729. instance.excitation = None
  730. def test_activation(self):
  731. instance = MusculotendonDeGroote2016(
  732. self.name,
  733. self.pathway,
  734. self.activation,
  735. )
  736. assert hasattr(instance, 'a')
  737. assert hasattr(instance, 'activation')
  738. a_expected = dynamicsymbols('a_name')
  739. assert instance.a == a_expected
  740. assert instance.activation == a_expected
  741. def test_activation_is_immutable(self):
  742. instance = MusculotendonDeGroote2016(
  743. self.name,
  744. self.pathway,
  745. self.activation,
  746. )
  747. with pytest.raises(AttributeError):
  748. instance.a = None
  749. with pytest.raises(AttributeError):
  750. instance.activation = None
  751. def test_repr(self):
  752. instance = MusculotendonDeGroote2016(
  753. self.name,
  754. self.pathway,
  755. self.activation,
  756. musculotendon_dynamics=MusculotendonFormulation.RIGID_TENDON,
  757. tendon_slack_length=self.l_T_slack,
  758. peak_isometric_force=self.F_M_max,
  759. optimal_fiber_length=self.l_M_opt,
  760. maximal_fiber_velocity=self.v_M_max,
  761. optimal_pennation_angle=self.alpha_opt,
  762. fiber_damping_coefficient=self.beta,
  763. )
  764. expected = (
  765. 'MusculotendonDeGroote2016(\'name\', '
  766. 'pathway=LinearPathway(pO, pI), '
  767. 'activation_dynamics=FirstOrderActivationDeGroote2016(\'name\', '
  768. 'activation_time_constant=tau_a_name, '
  769. 'deactivation_time_constant=tau_d_name, '
  770. 'smoothing_rate=b_name), '
  771. 'musculotendon_dynamics=0, '
  772. 'tendon_slack_length=l_T_slack, '
  773. 'peak_isometric_force=F_M_max, '
  774. 'optimal_fiber_length=l_M_opt, '
  775. 'maximal_fiber_velocity=v_M_max, '
  776. 'optimal_pennation_angle=alpha_opt, '
  777. 'fiber_damping_coefficient=beta)'
  778. )
  779. assert repr(instance) == expected