|
|
r"""Activation dynamics for musclotendon models. |
|
|
|
|
|
Musculotendon models are able to produce active force when they are activated, |
|
|
which is when a chemical process has taken place within the muscle fibers |
|
|
causing them to voluntarily contract. Biologically this chemical process (the |
|
|
diffusion of :math:`\textrm{Ca}^{2+}` ions) is not the input in the system, |
|
|
electrical signals from the nervous system are. These are termed excitations. |
|
|
Activation dynamics, which relates the normalized excitation level to the |
|
|
normalized activation level, can be modeled by the models present in this |
|
|
module. |
|
|
|
|
|
""" |
|
|
|
|
|
from abc import ABC, abstractmethod |
|
|
from functools import cached_property |
|
|
|
|
|
from sympy.core.symbol import Symbol |
|
|
from sympy.core.numbers import Float, Integer, Rational |
|
|
from sympy.functions.elementary.hyperbolic import tanh |
|
|
from sympy.matrices.dense import MutableDenseMatrix as Matrix, zeros |
|
|
from sympy.physics.biomechanics._mixin import _NamedMixin |
|
|
from sympy.physics.mechanics import dynamicsymbols |
|
|
|
|
|
|
|
|
__all__ = [ |
|
|
'ActivationBase', |
|
|
'FirstOrderActivationDeGroote2016', |
|
|
'ZerothOrderActivation', |
|
|
] |
|
|
|
|
|
|
|
|
class ActivationBase(ABC, _NamedMixin): |
|
|
"""Abstract base class for all activation dynamics classes to inherit from. |
|
|
|
|
|
Notes |
|
|
===== |
|
|
|
|
|
Instances of this class cannot be directly instantiated by users. However, |
|
|
it can be used to created custom activation dynamics types through |
|
|
subclassing. |
|
|
|
|
|
""" |
|
|
|
|
|
def __init__(self, name): |
|
|
"""Initializer for ``ActivationBase``.""" |
|
|
self.name = str(name) |
|
|
|
|
|
|
|
|
self._e = dynamicsymbols(f"e_{name}") |
|
|
self._a = dynamicsymbols(f"a_{name}") |
|
|
|
|
|
@classmethod |
|
|
@abstractmethod |
|
|
def with_defaults(cls, name): |
|
|
"""Alternate constructor that provides recommended defaults for |
|
|
constants.""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
def excitation(self): |
|
|
"""Dynamic symbol representing excitation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``e`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return self._e |
|
|
|
|
|
@property |
|
|
def e(self): |
|
|
"""Dynamic symbol representing excitation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``excitation`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return self._e |
|
|
|
|
|
@property |
|
|
def activation(self): |
|
|
"""Dynamic symbol representing activation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``a`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return self._a |
|
|
|
|
|
@property |
|
|
def a(self): |
|
|
"""Dynamic symbol representing activation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``activation`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return self._a |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def order(self): |
|
|
"""Order of the (differential) equation governing activation.""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def state_vars(self): |
|
|
"""Ordered column matrix of functions of time that represent the state |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``x`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def x(self): |
|
|
"""Ordered column matrix of functions of time that represent the state |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``state_vars`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def input_vars(self): |
|
|
"""Ordered column matrix of functions of time that represent the input |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``r`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def r(self): |
|
|
"""Ordered column matrix of functions of time that represent the input |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``input_vars`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def constants(self): |
|
|
"""Ordered column matrix of non-time varying symbols present in ``M`` |
|
|
and ``F``. |
|
|
|
|
|
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) |
|
|
has been used instead of ``Symbol`` for a constant then that attribute |
|
|
will not be included in the matrix returned by this property. This is |
|
|
because the primary use of this property attribute is to provide an |
|
|
ordered sequence of the still-free symbols that require numeric values |
|
|
during code generation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``p`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def p(self): |
|
|
"""Ordered column matrix of non-time varying symbols present in ``M`` |
|
|
and ``F``. |
|
|
|
|
|
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) |
|
|
has been used instead of ``Symbol`` for a constant then that attribute |
|
|
will not be included in the matrix returned by this property. This is |
|
|
because the primary use of this property attribute is to provide an |
|
|
ordered sequence of the still-free symbols that require numeric values |
|
|
during code generation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``constants`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def M(self): |
|
|
"""Ordered square matrix of coefficients on the LHS of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The square matrix that forms part of the LHS of the linear system of |
|
|
ordinary differential equations governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@property |
|
|
@abstractmethod |
|
|
def F(self): |
|
|
"""Ordered column matrix of equations on the RHS of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The column matrix that forms the RHS of the linear system of ordinary |
|
|
differential equations governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
@abstractmethod |
|
|
def rhs(self): |
|
|
""" |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The solution to the linear system of ordinary differential equations |
|
|
governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
""" |
|
|
pass |
|
|
|
|
|
def __eq__(self, other): |
|
|
"""Equality check for activation dynamics.""" |
|
|
if type(self) != type(other): |
|
|
return False |
|
|
if self.name != other.name: |
|
|
return False |
|
|
return True |
|
|
|
|
|
def __repr__(self): |
|
|
"""Default representation of activation dynamics.""" |
|
|
return f'{self.__class__.__name__}({self.name!r})' |
|
|
|
|
|
|
|
|
class ZerothOrderActivation(ActivationBase): |
|
|
"""Simple zeroth-order activation dynamics mapping excitation to |
|
|
activation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
Zeroth-order activation dynamics are useful in instances where you want to |
|
|
reduce the complexity of your musculotendon dynamics as they simple map |
|
|
exictation to activation. As a result, no additional state equations are |
|
|
introduced to your system. They also remove a potential source of delay |
|
|
between the input and dynamics of your system as no (ordinary) differential |
|
|
equations are involved. |
|
|
|
|
|
""" |
|
|
|
|
|
def __init__(self, name): |
|
|
"""Initializer for ``ZerothOrderActivation``. |
|
|
|
|
|
Parameters |
|
|
========== |
|
|
|
|
|
name : str |
|
|
The name identifier associated with the instance. Must be a string |
|
|
of length at least 1. |
|
|
|
|
|
""" |
|
|
super().__init__(name) |
|
|
|
|
|
|
|
|
|
|
|
self._a = self._e |
|
|
|
|
|
@classmethod |
|
|
def with_defaults(cls, name): |
|
|
"""Alternate constructor that provides recommended defaults for |
|
|
constants. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
As this concrete class doesn't implement any constants associated with |
|
|
its dynamics, this ``classmethod`` simply creates a standard instance |
|
|
of ``ZerothOrderActivation``. An implementation is provided to ensure |
|
|
a consistent interface between all ``ActivationBase`` concrete classes. |
|
|
|
|
|
""" |
|
|
return cls(name) |
|
|
|
|
|
@property |
|
|
def order(self): |
|
|
"""Order of the (differential) equation governing activation.""" |
|
|
return 0 |
|
|
|
|
|
@property |
|
|
def state_vars(self): |
|
|
"""Ordered column matrix of functions of time that represent the state |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
As zeroth-order activation dynamics simply maps excitation to |
|
|
activation, this class has no associated state variables and so this |
|
|
property return an empty column ``Matrix`` with shape (0, 1). |
|
|
|
|
|
The alias ``x`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return zeros(0, 1) |
|
|
|
|
|
@property |
|
|
def x(self): |
|
|
"""Ordered column matrix of functions of time that represent the state |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
As zeroth-order activation dynamics simply maps excitation to |
|
|
activation, this class has no associated state variables and so this |
|
|
property return an empty column ``Matrix`` with shape (0, 1). |
|
|
|
|
|
The alias ``state_vars`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return zeros(0, 1) |
|
|
|
|
|
@property |
|
|
def input_vars(self): |
|
|
"""Ordered column matrix of functions of time that represent the input |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
Excitation is the only input in zeroth-order activation dynamics and so |
|
|
this property returns a column ``Matrix`` with one entry, ``e``, and |
|
|
shape (1, 1). |
|
|
|
|
|
The alias ``r`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return Matrix([self._e]) |
|
|
|
|
|
@property |
|
|
def r(self): |
|
|
"""Ordered column matrix of functions of time that represent the input |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
Excitation is the only input in zeroth-order activation dynamics and so |
|
|
this property returns a column ``Matrix`` with one entry, ``e``, and |
|
|
shape (1, 1). |
|
|
|
|
|
The alias ``input_vars`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return Matrix([self._e]) |
|
|
|
|
|
@property |
|
|
def constants(self): |
|
|
"""Ordered column matrix of non-time varying symbols present in ``M`` |
|
|
and ``F``. |
|
|
|
|
|
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) |
|
|
has been used instead of ``Symbol`` for a constant then that attribute |
|
|
will not be included in the matrix returned by this property. This is |
|
|
because the primary use of this property attribute is to provide an |
|
|
ordered sequence of the still-free symbols that require numeric values |
|
|
during code generation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
As zeroth-order activation dynamics simply maps excitation to |
|
|
activation, this class has no associated constants and so this property |
|
|
return an empty column ``Matrix`` with shape (0, 1). |
|
|
|
|
|
The alias ``p`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return zeros(0, 1) |
|
|
|
|
|
@property |
|
|
def p(self): |
|
|
"""Ordered column matrix of non-time varying symbols present in ``M`` |
|
|
and ``F``. |
|
|
|
|
|
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) |
|
|
has been used instead of ``Symbol`` for a constant then that attribute |
|
|
will not be included in the matrix returned by this property. This is |
|
|
because the primary use of this property attribute is to provide an |
|
|
ordered sequence of the still-free symbols that require numeric values |
|
|
during code generation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
As zeroth-order activation dynamics simply maps excitation to |
|
|
activation, this class has no associated constants and so this property |
|
|
return an empty column ``Matrix`` with shape (0, 1). |
|
|
|
|
|
The alias ``constants`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return zeros(0, 1) |
|
|
|
|
|
@property |
|
|
def M(self): |
|
|
"""Ordered square matrix of coefficients on the LHS of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The square matrix that forms part of the LHS of the linear system of |
|
|
ordinary differential equations governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
As zeroth-order activation dynamics have no state variables, this |
|
|
linear system has dimension 0 and therefore ``M`` is an empty square |
|
|
``Matrix`` with shape (0, 0). |
|
|
|
|
|
""" |
|
|
return Matrix([]) |
|
|
|
|
|
@property |
|
|
def F(self): |
|
|
"""Ordered column matrix of equations on the RHS of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The column matrix that forms the RHS of the linear system of ordinary |
|
|
differential equations governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
As zeroth-order activation dynamics have no state variables, this |
|
|
linear system has dimension 0 and therefore ``F`` is an empty column |
|
|
``Matrix`` with shape (0, 1). |
|
|
|
|
|
""" |
|
|
return zeros(0, 1) |
|
|
|
|
|
def rhs(self): |
|
|
"""Ordered column matrix of equations for the solution of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The solution to the linear system of ordinary differential equations |
|
|
governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
As zeroth-order activation dynamics have no state variables, this |
|
|
linear has dimension 0 and therefore this method returns an empty |
|
|
column ``Matrix`` with shape (0, 1). |
|
|
|
|
|
""" |
|
|
return zeros(0, 1) |
|
|
|
|
|
|
|
|
class FirstOrderActivationDeGroote2016(ActivationBase): |
|
|
r"""First-order activation dynamics based on De Groote et al., 2016 [1]_. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
Gives the first-order activation dynamics equation for the rate of change |
|
|
of activation with respect to time as a function of excitation and |
|
|
activation. |
|
|
|
|
|
The function is defined by the equation: |
|
|
|
|
|
.. math:: |
|
|
|
|
|
\frac{da}{dt} = \left(\frac{\frac{1}{2} + a0}{\tau_a \left(\frac{1}{2} |
|
|
+ \frac{3a}{2}\right)} + \frac{\left(\frac{1}{2} |
|
|
+ \frac{3a}{2}\right) \left(\frac{1}{2} - a0\right)}{\tau_d}\right) |
|
|
\left(e - a\right) |
|
|
|
|
|
where |
|
|
|
|
|
.. math:: |
|
|
|
|
|
a0 = \frac{\tanh{\left(b \left(e - a\right) \right)}}{2} |
|
|
|
|
|
with constant values of :math:`tau_a = 0.015`, :math:`tau_d = 0.060`, and |
|
|
:math:`b = 10`. |
|
|
|
|
|
References |
|
|
========== |
|
|
|
|
|
.. [1] De Groote, F., Kinney, A. L., Rao, A. V., & Fregly, B. J., Evaluation |
|
|
of direct collocation optimal control problem formulations for |
|
|
solving the muscle redundancy problem, Annals of biomedical |
|
|
engineering, 44(10), (2016) pp. 2922-2936 |
|
|
|
|
|
""" |
|
|
|
|
|
def __init__(self, |
|
|
name, |
|
|
activation_time_constant=None, |
|
|
deactivation_time_constant=None, |
|
|
smoothing_rate=None, |
|
|
): |
|
|
"""Initializer for ``FirstOrderActivationDeGroote2016``. |
|
|
|
|
|
Parameters |
|
|
========== |
|
|
activation time constant : Symbol | Number | None |
|
|
The value of the activation time constant governing the delay |
|
|
between excitation and activation when excitation exceeds |
|
|
activation. |
|
|
deactivation time constant : Symbol | Number | None |
|
|
The value of the deactivation time constant governing the delay |
|
|
between excitation and activation when activation exceeds |
|
|
excitation. |
|
|
smoothing_rate : Symbol | Number | None |
|
|
The slope of the hyperbolic tangent function used to smooth between |
|
|
the switching of the equations where excitation exceed activation |
|
|
and where activation exceeds excitation. The recommended value to |
|
|
use is ``10``, but values between ``0.1`` and ``100`` can be used. |
|
|
|
|
|
""" |
|
|
super().__init__(name) |
|
|
|
|
|
|
|
|
self.activation_time_constant = activation_time_constant |
|
|
self.deactivation_time_constant = deactivation_time_constant |
|
|
self.smoothing_rate = smoothing_rate |
|
|
|
|
|
@classmethod |
|
|
def with_defaults(cls, name): |
|
|
r"""Alternate constructor that will use the published constants. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
Returns an instance of ``FirstOrderActivationDeGroote2016`` using the |
|
|
three constant values specified in the original publication. |
|
|
|
|
|
These have the values: |
|
|
|
|
|
:math:`tau_a = 0.015` |
|
|
:math:`tau_d = 0.060` |
|
|
:math:`b = 10` |
|
|
|
|
|
""" |
|
|
tau_a = Float('0.015') |
|
|
tau_d = Float('0.060') |
|
|
b = Float('10.0') |
|
|
return cls(name, tau_a, tau_d, b) |
|
|
|
|
|
@property |
|
|
def activation_time_constant(self): |
|
|
"""Delay constant for activation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ```tau_a`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return self._tau_a |
|
|
|
|
|
@activation_time_constant.setter |
|
|
def activation_time_constant(self, tau_a): |
|
|
if hasattr(self, '_tau_a'): |
|
|
msg = ( |
|
|
f'Can\'t set attribute `activation_time_constant` to ' |
|
|
f'{repr(tau_a)} as it is immutable and already has value ' |
|
|
f'{self._tau_a}.' |
|
|
) |
|
|
raise AttributeError(msg) |
|
|
self._tau_a = Symbol(f'tau_a_{self.name}') if tau_a is None else tau_a |
|
|
|
|
|
@property |
|
|
def tau_a(self): |
|
|
"""Delay constant for activation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``activation_time_constant`` can also be used to access the |
|
|
same attribute. |
|
|
|
|
|
""" |
|
|
return self._tau_a |
|
|
|
|
|
@property |
|
|
def deactivation_time_constant(self): |
|
|
"""Delay constant for deactivation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``tau_d`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return self._tau_d |
|
|
|
|
|
@deactivation_time_constant.setter |
|
|
def deactivation_time_constant(self, tau_d): |
|
|
if hasattr(self, '_tau_d'): |
|
|
msg = ( |
|
|
f'Can\'t set attribute `deactivation_time_constant` to ' |
|
|
f'{repr(tau_d)} as it is immutable and already has value ' |
|
|
f'{self._tau_d}.' |
|
|
) |
|
|
raise AttributeError(msg) |
|
|
self._tau_d = Symbol(f'tau_d_{self.name}') if tau_d is None else tau_d |
|
|
|
|
|
@property |
|
|
def tau_d(self): |
|
|
"""Delay constant for deactivation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``deactivation_time_constant`` can also be used to access the |
|
|
same attribute. |
|
|
|
|
|
""" |
|
|
return self._tau_d |
|
|
|
|
|
@property |
|
|
def smoothing_rate(self): |
|
|
"""Smoothing constant for the hyperbolic tangent term. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``b`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return self._b |
|
|
|
|
|
@smoothing_rate.setter |
|
|
def smoothing_rate(self, b): |
|
|
if hasattr(self, '_b'): |
|
|
msg = ( |
|
|
f'Can\'t set attribute `smoothing_rate` to {b!r} as it is ' |
|
|
f'immutable and already has value {self._b!r}.' |
|
|
) |
|
|
raise AttributeError(msg) |
|
|
self._b = Symbol(f'b_{self.name}') if b is None else b |
|
|
|
|
|
@property |
|
|
def b(self): |
|
|
"""Smoothing constant for the hyperbolic tangent term. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``smoothing_rate`` can also be used to access the same |
|
|
attribute. |
|
|
|
|
|
""" |
|
|
return self._b |
|
|
|
|
|
@property |
|
|
def order(self): |
|
|
"""Order of the (differential) equation governing activation.""" |
|
|
return 1 |
|
|
|
|
|
@property |
|
|
def state_vars(self): |
|
|
"""Ordered column matrix of functions of time that represent the state |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``x`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return Matrix([self._a]) |
|
|
|
|
|
@property |
|
|
def x(self): |
|
|
"""Ordered column matrix of functions of time that represent the state |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``state_vars`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return Matrix([self._a]) |
|
|
|
|
|
@property |
|
|
def input_vars(self): |
|
|
"""Ordered column matrix of functions of time that represent the input |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``r`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return Matrix([self._e]) |
|
|
|
|
|
@property |
|
|
def r(self): |
|
|
"""Ordered column matrix of functions of time that represent the input |
|
|
variables. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``input_vars`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
return Matrix([self._e]) |
|
|
|
|
|
@property |
|
|
def constants(self): |
|
|
"""Ordered column matrix of non-time varying symbols present in ``M`` |
|
|
and ``F``. |
|
|
|
|
|
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) |
|
|
has been used instead of ``Symbol`` for a constant then that attribute |
|
|
will not be included in the matrix returned by this property. This is |
|
|
because the primary use of this property attribute is to provide an |
|
|
ordered sequence of the still-free symbols that require numeric values |
|
|
during code generation. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The alias ``p`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
constants = [self._tau_a, self._tau_d, self._b] |
|
|
symbolic_constants = [c for c in constants if not c.is_number] |
|
|
return Matrix(symbolic_constants) if symbolic_constants else zeros(0, 1) |
|
|
|
|
|
@property |
|
|
def p(self): |
|
|
"""Ordered column matrix of non-time varying symbols present in ``M`` |
|
|
and ``F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
Only symbolic constants are returned. If a numeric type (e.g. ``Float``) |
|
|
has been used instead of ``Symbol`` for a constant then that attribute |
|
|
will not be included in the matrix returned by this property. This is |
|
|
because the primary use of this property attribute is to provide an |
|
|
ordered sequence of the still-free symbols that require numeric values |
|
|
during code generation. |
|
|
|
|
|
The alias ``constants`` can also be used to access the same attribute. |
|
|
|
|
|
""" |
|
|
constants = [self._tau_a, self._tau_d, self._b] |
|
|
symbolic_constants = [c for c in constants if not c.is_number] |
|
|
return Matrix(symbolic_constants) if symbolic_constants else zeros(0, 1) |
|
|
|
|
|
@property |
|
|
def M(self): |
|
|
"""Ordered square matrix of coefficients on the LHS of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The square matrix that forms part of the LHS of the linear system of |
|
|
ordinary differential equations governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
""" |
|
|
return Matrix([Integer(1)]) |
|
|
|
|
|
@property |
|
|
def F(self): |
|
|
"""Ordered column matrix of equations on the RHS of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The column matrix that forms the RHS of the linear system of ordinary |
|
|
differential equations governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
""" |
|
|
return Matrix([self._da_eqn]) |
|
|
|
|
|
def rhs(self): |
|
|
"""Ordered column matrix of equations for the solution of ``M x' = F``. |
|
|
|
|
|
Explanation |
|
|
=========== |
|
|
|
|
|
The solution to the linear system of ordinary differential equations |
|
|
governing the activation dynamics: |
|
|
|
|
|
``M(x, r, t, p) x' = F(x, r, t, p)``. |
|
|
|
|
|
""" |
|
|
return Matrix([self._da_eqn]) |
|
|
|
|
|
@cached_property |
|
|
def _da_eqn(self): |
|
|
HALF = Rational(1, 2) |
|
|
a0 = HALF * tanh(self._b * (self._e - self._a)) |
|
|
a1 = (HALF + Rational(3, 2) * self._a) |
|
|
a2 = (HALF + a0) / (self._tau_a * a1) |
|
|
a3 = a1 * (HALF - a0) / self._tau_d |
|
|
activation_dynamics_equation = (a2 + a3) * (self._e - self._a) |
|
|
return activation_dynamics_equation |
|
|
|
|
|
def __eq__(self, other): |
|
|
"""Equality check for ``FirstOrderActivationDeGroote2016``.""" |
|
|
if type(self) != type(other): |
|
|
return False |
|
|
self_attrs = (self.name, self.tau_a, self.tau_d, self.b) |
|
|
other_attrs = (other.name, other.tau_a, other.tau_d, other.b) |
|
|
if self_attrs == other_attrs: |
|
|
return True |
|
|
return False |
|
|
|
|
|
def __repr__(self): |
|
|
"""Representation of ``FirstOrderActivationDeGroote2016``.""" |
|
|
return ( |
|
|
f'{self.__class__.__name__}({self.name!r}, ' |
|
|
f'activation_time_constant={self.tau_a!r}, ' |
|
|
f'deactivation_time_constant={self.tau_d!r}, ' |
|
|
f'smoothing_rate={self.b!r})' |
|
|
) |
|
|
|