activation.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. r"""Activation dynamics for musclotendon models.
  2. Musculotendon models are able to produce active force when they are activated,
  3. which is when a chemical process has taken place within the muscle fibers
  4. causing them to voluntarily contract. Biologically this chemical process (the
  5. diffusion of :math:`\textrm{Ca}^{2+}` ions) is not the input in the system,
  6. electrical signals from the nervous system are. These are termed excitations.
  7. Activation dynamics, which relates the normalized excitation level to the
  8. normalized activation level, can be modeled by the models present in this
  9. module.
  10. """
  11. from abc import ABC, abstractmethod
  12. from functools import cached_property
  13. from sympy.core.symbol import Symbol
  14. from sympy.core.numbers import Float, Integer, Rational
  15. from sympy.functions.elementary.hyperbolic import tanh
  16. from sympy.matrices.dense import MutableDenseMatrix as Matrix, zeros
  17. from sympy.physics.biomechanics._mixin import _NamedMixin
  18. from sympy.physics.mechanics import dynamicsymbols
  19. __all__ = [
  20. 'ActivationBase',
  21. 'FirstOrderActivationDeGroote2016',
  22. 'ZerothOrderActivation',
  23. ]
  24. class ActivationBase(ABC, _NamedMixin):
  25. """Abstract base class for all activation dynamics classes to inherit from.
  26. Notes
  27. =====
  28. Instances of this class cannot be directly instantiated by users. However,
  29. it can be used to created custom activation dynamics types through
  30. subclassing.
  31. """
  32. def __init__(self, name):
  33. """Initializer for ``ActivationBase``."""
  34. self.name = str(name)
  35. # Symbols
  36. self._e = dynamicsymbols(f"e_{name}")
  37. self._a = dynamicsymbols(f"a_{name}")
  38. @classmethod
  39. @abstractmethod
  40. def with_defaults(cls, name):
  41. """Alternate constructor that provides recommended defaults for
  42. constants."""
  43. pass
  44. @property
  45. def excitation(self):
  46. """Dynamic symbol representing excitation.
  47. Explanation
  48. ===========
  49. The alias ``e`` can also be used to access the same attribute.
  50. """
  51. return self._e
  52. @property
  53. def e(self):
  54. """Dynamic symbol representing excitation.
  55. Explanation
  56. ===========
  57. The alias ``excitation`` can also be used to access the same attribute.
  58. """
  59. return self._e
  60. @property
  61. def activation(self):
  62. """Dynamic symbol representing activation.
  63. Explanation
  64. ===========
  65. The alias ``a`` can also be used to access the same attribute.
  66. """
  67. return self._a
  68. @property
  69. def a(self):
  70. """Dynamic symbol representing activation.
  71. Explanation
  72. ===========
  73. The alias ``activation`` can also be used to access the same attribute.
  74. """
  75. return self._a
  76. @property
  77. @abstractmethod
  78. def order(self):
  79. """Order of the (differential) equation governing activation."""
  80. pass
  81. @property
  82. @abstractmethod
  83. def state_vars(self):
  84. """Ordered column matrix of functions of time that represent the state
  85. variables.
  86. Explanation
  87. ===========
  88. The alias ``x`` can also be used to access the same attribute.
  89. """
  90. pass
  91. @property
  92. @abstractmethod
  93. def x(self):
  94. """Ordered column matrix of functions of time that represent the state
  95. variables.
  96. Explanation
  97. ===========
  98. The alias ``state_vars`` can also be used to access the same attribute.
  99. """
  100. pass
  101. @property
  102. @abstractmethod
  103. def input_vars(self):
  104. """Ordered column matrix of functions of time that represent the input
  105. variables.
  106. Explanation
  107. ===========
  108. The alias ``r`` can also be used to access the same attribute.
  109. """
  110. pass
  111. @property
  112. @abstractmethod
  113. def r(self):
  114. """Ordered column matrix of functions of time that represent the input
  115. variables.
  116. Explanation
  117. ===========
  118. The alias ``input_vars`` can also be used to access the same attribute.
  119. """
  120. pass
  121. @property
  122. @abstractmethod
  123. def constants(self):
  124. """Ordered column matrix of non-time varying symbols present in ``M``
  125. and ``F``.
  126. Only symbolic constants are returned. If a numeric type (e.g. ``Float``)
  127. has been used instead of ``Symbol`` for a constant then that attribute
  128. will not be included in the matrix returned by this property. This is
  129. because the primary use of this property attribute is to provide an
  130. ordered sequence of the still-free symbols that require numeric values
  131. during code generation.
  132. Explanation
  133. ===========
  134. The alias ``p`` can also be used to access the same attribute.
  135. """
  136. pass
  137. @property
  138. @abstractmethod
  139. def p(self):
  140. """Ordered column matrix of non-time varying symbols present in ``M``
  141. and ``F``.
  142. Only symbolic constants are returned. If a numeric type (e.g. ``Float``)
  143. has been used instead of ``Symbol`` for a constant then that attribute
  144. will not be included in the matrix returned by this property. This is
  145. because the primary use of this property attribute is to provide an
  146. ordered sequence of the still-free symbols that require numeric values
  147. during code generation.
  148. Explanation
  149. ===========
  150. The alias ``constants`` can also be used to access the same attribute.
  151. """
  152. pass
  153. @property
  154. @abstractmethod
  155. def M(self):
  156. """Ordered square matrix of coefficients on the LHS of ``M x' = F``.
  157. Explanation
  158. ===========
  159. The square matrix that forms part of the LHS of the linear system of
  160. ordinary differential equations governing the activation dynamics:
  161. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  162. """
  163. pass
  164. @property
  165. @abstractmethod
  166. def F(self):
  167. """Ordered column matrix of equations on the RHS of ``M x' = F``.
  168. Explanation
  169. ===========
  170. The column matrix that forms the RHS of the linear system of ordinary
  171. differential equations governing the activation dynamics:
  172. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  173. """
  174. pass
  175. @abstractmethod
  176. def rhs(self):
  177. """
  178. Explanation
  179. ===========
  180. The solution to the linear system of ordinary differential equations
  181. governing the activation dynamics:
  182. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  183. """
  184. pass
  185. def __eq__(self, other):
  186. """Equality check for activation dynamics."""
  187. if type(self) != type(other):
  188. return False
  189. if self.name != other.name:
  190. return False
  191. return True
  192. def __repr__(self):
  193. """Default representation of activation dynamics."""
  194. return f'{self.__class__.__name__}({self.name!r})'
  195. class ZerothOrderActivation(ActivationBase):
  196. """Simple zeroth-order activation dynamics mapping excitation to
  197. activation.
  198. Explanation
  199. ===========
  200. Zeroth-order activation dynamics are useful in instances where you want to
  201. reduce the complexity of your musculotendon dynamics as they simple map
  202. exictation to activation. As a result, no additional state equations are
  203. introduced to your system. They also remove a potential source of delay
  204. between the input and dynamics of your system as no (ordinary) differential
  205. equations are involved.
  206. """
  207. def __init__(self, name):
  208. """Initializer for ``ZerothOrderActivation``.
  209. Parameters
  210. ==========
  211. name : str
  212. The name identifier associated with the instance. Must be a string
  213. of length at least 1.
  214. """
  215. super().__init__(name)
  216. # Zeroth-order activation dynamics has activation equal excitation so
  217. # overwrite the symbol for activation with the excitation symbol.
  218. self._a = self._e
  219. @classmethod
  220. def with_defaults(cls, name):
  221. """Alternate constructor that provides recommended defaults for
  222. constants.
  223. Explanation
  224. ===========
  225. As this concrete class doesn't implement any constants associated with
  226. its dynamics, this ``classmethod`` simply creates a standard instance
  227. of ``ZerothOrderActivation``. An implementation is provided to ensure
  228. a consistent interface between all ``ActivationBase`` concrete classes.
  229. """
  230. return cls(name)
  231. @property
  232. def order(self):
  233. """Order of the (differential) equation governing activation."""
  234. return 0
  235. @property
  236. def state_vars(self):
  237. """Ordered column matrix of functions of time that represent the state
  238. variables.
  239. Explanation
  240. ===========
  241. As zeroth-order activation dynamics simply maps excitation to
  242. activation, this class has no associated state variables and so this
  243. property return an empty column ``Matrix`` with shape (0, 1).
  244. The alias ``x`` can also be used to access the same attribute.
  245. """
  246. return zeros(0, 1)
  247. @property
  248. def x(self):
  249. """Ordered column matrix of functions of time that represent the state
  250. variables.
  251. Explanation
  252. ===========
  253. As zeroth-order activation dynamics simply maps excitation to
  254. activation, this class has no associated state variables and so this
  255. property return an empty column ``Matrix`` with shape (0, 1).
  256. The alias ``state_vars`` can also be used to access the same attribute.
  257. """
  258. return zeros(0, 1)
  259. @property
  260. def input_vars(self):
  261. """Ordered column matrix of functions of time that represent the input
  262. variables.
  263. Explanation
  264. ===========
  265. Excitation is the only input in zeroth-order activation dynamics and so
  266. this property returns a column ``Matrix`` with one entry, ``e``, and
  267. shape (1, 1).
  268. The alias ``r`` can also be used to access the same attribute.
  269. """
  270. return Matrix([self._e])
  271. @property
  272. def r(self):
  273. """Ordered column matrix of functions of time that represent the input
  274. variables.
  275. Explanation
  276. ===========
  277. Excitation is the only input in zeroth-order activation dynamics and so
  278. this property returns a column ``Matrix`` with one entry, ``e``, and
  279. shape (1, 1).
  280. The alias ``input_vars`` can also be used to access the same attribute.
  281. """
  282. return Matrix([self._e])
  283. @property
  284. def constants(self):
  285. """Ordered column matrix of non-time varying symbols present in ``M``
  286. and ``F``.
  287. Only symbolic constants are returned. If a numeric type (e.g. ``Float``)
  288. has been used instead of ``Symbol`` for a constant then that attribute
  289. will not be included in the matrix returned by this property. This is
  290. because the primary use of this property attribute is to provide an
  291. ordered sequence of the still-free symbols that require numeric values
  292. during code generation.
  293. Explanation
  294. ===========
  295. As zeroth-order activation dynamics simply maps excitation to
  296. activation, this class has no associated constants and so this property
  297. return an empty column ``Matrix`` with shape (0, 1).
  298. The alias ``p`` can also be used to access the same attribute.
  299. """
  300. return zeros(0, 1)
  301. @property
  302. def p(self):
  303. """Ordered column matrix of non-time varying symbols present in ``M``
  304. and ``F``.
  305. Only symbolic constants are returned. If a numeric type (e.g. ``Float``)
  306. has been used instead of ``Symbol`` for a constant then that attribute
  307. will not be included in the matrix returned by this property. This is
  308. because the primary use of this property attribute is to provide an
  309. ordered sequence of the still-free symbols that require numeric values
  310. during code generation.
  311. Explanation
  312. ===========
  313. As zeroth-order activation dynamics simply maps excitation to
  314. activation, this class has no associated constants and so this property
  315. return an empty column ``Matrix`` with shape (0, 1).
  316. The alias ``constants`` can also be used to access the same attribute.
  317. """
  318. return zeros(0, 1)
  319. @property
  320. def M(self):
  321. """Ordered square matrix of coefficients on the LHS of ``M x' = F``.
  322. Explanation
  323. ===========
  324. The square matrix that forms part of the LHS of the linear system of
  325. ordinary differential equations governing the activation dynamics:
  326. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  327. As zeroth-order activation dynamics have no state variables, this
  328. linear system has dimension 0 and therefore ``M`` is an empty square
  329. ``Matrix`` with shape (0, 0).
  330. """
  331. return Matrix([])
  332. @property
  333. def F(self):
  334. """Ordered column matrix of equations on the RHS of ``M x' = F``.
  335. Explanation
  336. ===========
  337. The column matrix that forms the RHS of the linear system of ordinary
  338. differential equations governing the activation dynamics:
  339. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  340. As zeroth-order activation dynamics have no state variables, this
  341. linear system has dimension 0 and therefore ``F`` is an empty column
  342. ``Matrix`` with shape (0, 1).
  343. """
  344. return zeros(0, 1)
  345. def rhs(self):
  346. """Ordered column matrix of equations for the solution of ``M x' = F``.
  347. Explanation
  348. ===========
  349. The solution to the linear system of ordinary differential equations
  350. governing the activation dynamics:
  351. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  352. As zeroth-order activation dynamics have no state variables, this
  353. linear has dimension 0 and therefore this method returns an empty
  354. column ``Matrix`` with shape (0, 1).
  355. """
  356. return zeros(0, 1)
  357. class FirstOrderActivationDeGroote2016(ActivationBase):
  358. r"""First-order activation dynamics based on De Groote et al., 2016 [1]_.
  359. Explanation
  360. ===========
  361. Gives the first-order activation dynamics equation for the rate of change
  362. of activation with respect to time as a function of excitation and
  363. activation.
  364. The function is defined by the equation:
  365. .. math::
  366. \frac{da}{dt} = \left(\frac{\frac{1}{2} + a0}{\tau_a \left(\frac{1}{2}
  367. + \frac{3a}{2}\right)} + \frac{\left(\frac{1}{2}
  368. + \frac{3a}{2}\right) \left(\frac{1}{2} - a0\right)}{\tau_d}\right)
  369. \left(e - a\right)
  370. where
  371. .. math::
  372. a0 = \frac{\tanh{\left(b \left(e - a\right) \right)}}{2}
  373. with constant values of :math:`tau_a = 0.015`, :math:`tau_d = 0.060`, and
  374. :math:`b = 10`.
  375. References
  376. ==========
  377. .. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation
  378. of direct collocation optimal control problem formulations for
  379. solving the muscle redundancy problem, Annals of biomedical
  380. engineering, 44(10), (2016) pp. 2922-2936
  381. """
  382. def __init__(self,
  383. name,
  384. activation_time_constant=None,
  385. deactivation_time_constant=None,
  386. smoothing_rate=None,
  387. ):
  388. """Initializer for ``FirstOrderActivationDeGroote2016``.
  389. Parameters
  390. ==========
  391. activation time constant : Symbol | Number | None
  392. The value of the activation time constant governing the delay
  393. between excitation and activation when excitation exceeds
  394. activation.
  395. deactivation time constant : Symbol | Number | None
  396. The value of the deactivation time constant governing the delay
  397. between excitation and activation when activation exceeds
  398. excitation.
  399. smoothing_rate : Symbol | Number | None
  400. The slope of the hyperbolic tangent function used to smooth between
  401. the switching of the equations where excitation exceed activation
  402. and where activation exceeds excitation. The recommended value to
  403. use is ``10``, but values between ``0.1`` and ``100`` can be used.
  404. """
  405. super().__init__(name)
  406. # Symbols
  407. self.activation_time_constant = activation_time_constant
  408. self.deactivation_time_constant = deactivation_time_constant
  409. self.smoothing_rate = smoothing_rate
  410. @classmethod
  411. def with_defaults(cls, name):
  412. r"""Alternate constructor that will use the published constants.
  413. Explanation
  414. ===========
  415. Returns an instance of ``FirstOrderActivationDeGroote2016`` using the
  416. three constant values specified in the original publication.
  417. These have the values:
  418. :math:`tau_a = 0.015`
  419. :math:`tau_d = 0.060`
  420. :math:`b = 10`
  421. """
  422. tau_a = Float('0.015')
  423. tau_d = Float('0.060')
  424. b = Float('10.0')
  425. return cls(name, tau_a, tau_d, b)
  426. @property
  427. def activation_time_constant(self):
  428. """Delay constant for activation.
  429. Explanation
  430. ===========
  431. The alias ```tau_a`` can also be used to access the same attribute.
  432. """
  433. return self._tau_a
  434. @activation_time_constant.setter
  435. def activation_time_constant(self, tau_a):
  436. if hasattr(self, '_tau_a'):
  437. msg = (
  438. f'Can\'t set attribute `activation_time_constant` to '
  439. f'{repr(tau_a)} as it is immutable and already has value '
  440. f'{self._tau_a}.'
  441. )
  442. raise AttributeError(msg)
  443. self._tau_a = Symbol(f'tau_a_{self.name}') if tau_a is None else tau_a
  444. @property
  445. def tau_a(self):
  446. """Delay constant for activation.
  447. Explanation
  448. ===========
  449. The alias ``activation_time_constant`` can also be used to access the
  450. same attribute.
  451. """
  452. return self._tau_a
  453. @property
  454. def deactivation_time_constant(self):
  455. """Delay constant for deactivation.
  456. Explanation
  457. ===========
  458. The alias ``tau_d`` can also be used to access the same attribute.
  459. """
  460. return self._tau_d
  461. @deactivation_time_constant.setter
  462. def deactivation_time_constant(self, tau_d):
  463. if hasattr(self, '_tau_d'):
  464. msg = (
  465. f'Can\'t set attribute `deactivation_time_constant` to '
  466. f'{repr(tau_d)} as it is immutable and already has value '
  467. f'{self._tau_d}.'
  468. )
  469. raise AttributeError(msg)
  470. self._tau_d = Symbol(f'tau_d_{self.name}') if tau_d is None else tau_d
  471. @property
  472. def tau_d(self):
  473. """Delay constant for deactivation.
  474. Explanation
  475. ===========
  476. The alias ``deactivation_time_constant`` can also be used to access the
  477. same attribute.
  478. """
  479. return self._tau_d
  480. @property
  481. def smoothing_rate(self):
  482. """Smoothing constant for the hyperbolic tangent term.
  483. Explanation
  484. ===========
  485. The alias ``b`` can also be used to access the same attribute.
  486. """
  487. return self._b
  488. @smoothing_rate.setter
  489. def smoothing_rate(self, b):
  490. if hasattr(self, '_b'):
  491. msg = (
  492. f'Can\'t set attribute `smoothing_rate` to {b!r} as it is '
  493. f'immutable and already has value {self._b!r}.'
  494. )
  495. raise AttributeError(msg)
  496. self._b = Symbol(f'b_{self.name}') if b is None else b
  497. @property
  498. def b(self):
  499. """Smoothing constant for the hyperbolic tangent term.
  500. Explanation
  501. ===========
  502. The alias ``smoothing_rate`` can also be used to access the same
  503. attribute.
  504. """
  505. return self._b
  506. @property
  507. def order(self):
  508. """Order of the (differential) equation governing activation."""
  509. return 1
  510. @property
  511. def state_vars(self):
  512. """Ordered column matrix of functions of time that represent the state
  513. variables.
  514. Explanation
  515. ===========
  516. The alias ``x`` can also be used to access the same attribute.
  517. """
  518. return Matrix([self._a])
  519. @property
  520. def x(self):
  521. """Ordered column matrix of functions of time that represent the state
  522. variables.
  523. Explanation
  524. ===========
  525. The alias ``state_vars`` can also be used to access the same attribute.
  526. """
  527. return Matrix([self._a])
  528. @property
  529. def input_vars(self):
  530. """Ordered column matrix of functions of time that represent the input
  531. variables.
  532. Explanation
  533. ===========
  534. The alias ``r`` can also be used to access the same attribute.
  535. """
  536. return Matrix([self._e])
  537. @property
  538. def r(self):
  539. """Ordered column matrix of functions of time that represent the input
  540. variables.
  541. Explanation
  542. ===========
  543. The alias ``input_vars`` can also be used to access the same attribute.
  544. """
  545. return Matrix([self._e])
  546. @property
  547. def constants(self):
  548. """Ordered column matrix of non-time varying symbols present in ``M``
  549. and ``F``.
  550. Only symbolic constants are returned. If a numeric type (e.g. ``Float``)
  551. has been used instead of ``Symbol`` for a constant then that attribute
  552. will not be included in the matrix returned by this property. This is
  553. because the primary use of this property attribute is to provide an
  554. ordered sequence of the still-free symbols that require numeric values
  555. during code generation.
  556. Explanation
  557. ===========
  558. The alias ``p`` can also be used to access the same attribute.
  559. """
  560. constants = [self._tau_a, self._tau_d, self._b]
  561. symbolic_constants = [c for c in constants if not c.is_number]
  562. return Matrix(symbolic_constants) if symbolic_constants else zeros(0, 1)
  563. @property
  564. def p(self):
  565. """Ordered column matrix of non-time varying symbols present in ``M``
  566. and ``F``.
  567. Explanation
  568. ===========
  569. Only symbolic constants are returned. If a numeric type (e.g. ``Float``)
  570. has been used instead of ``Symbol`` for a constant then that attribute
  571. will not be included in the matrix returned by this property. This is
  572. because the primary use of this property attribute is to provide an
  573. ordered sequence of the still-free symbols that require numeric values
  574. during code generation.
  575. The alias ``constants`` can also be used to access the same attribute.
  576. """
  577. constants = [self._tau_a, self._tau_d, self._b]
  578. symbolic_constants = [c for c in constants if not c.is_number]
  579. return Matrix(symbolic_constants) if symbolic_constants else zeros(0, 1)
  580. @property
  581. def M(self):
  582. """Ordered square matrix of coefficients on the LHS of ``M x' = F``.
  583. Explanation
  584. ===========
  585. The square matrix that forms part of the LHS of the linear system of
  586. ordinary differential equations governing the activation dynamics:
  587. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  588. """
  589. return Matrix([Integer(1)])
  590. @property
  591. def F(self):
  592. """Ordered column matrix of equations on the RHS of ``M x' = F``.
  593. Explanation
  594. ===========
  595. The column matrix that forms the RHS of the linear system of ordinary
  596. differential equations governing the activation dynamics:
  597. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  598. """
  599. return Matrix([self._da_eqn])
  600. def rhs(self):
  601. """Ordered column matrix of equations for the solution of ``M x' = F``.
  602. Explanation
  603. ===========
  604. The solution to the linear system of ordinary differential equations
  605. governing the activation dynamics:
  606. ``M(x, r, t, p) x' = F(x, r, t, p)``.
  607. """
  608. return Matrix([self._da_eqn])
  609. @cached_property
  610. def _da_eqn(self):
  611. HALF = Rational(1, 2)
  612. a0 = HALF * tanh(self._b * (self._e - self._a))
  613. a1 = (HALF + Rational(3, 2) * self._a)
  614. a2 = (HALF + a0) / (self._tau_a * a1)
  615. a3 = a1 * (HALF - a0) / self._tau_d
  616. activation_dynamics_equation = (a2 + a3) * (self._e - self._a)
  617. return activation_dynamics_equation
  618. def __eq__(self, other):
  619. """Equality check for ``FirstOrderActivationDeGroote2016``."""
  620. if type(self) != type(other):
  621. return False
  622. self_attrs = (self.name, self.tau_a, self.tau_d, self.b)
  623. other_attrs = (other.name, other.tau_a, other.tau_d, other.b)
  624. if self_attrs == other_attrs:
  625. return True
  626. return False
  627. def __repr__(self):
  628. """Representation of ``FirstOrderActivationDeGroote2016``."""
  629. return (
  630. f'{self.__class__.__name__}({self.name!r}, '
  631. f'activation_time_constant={self.tau_a!r}, '
  632. f'deactivation_time_constant={self.tau_d!r}, '
  633. f'smoothing_rate={self.b!r})'
  634. )