You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AbstractDynamicalSystem: AbstractPlant implements a general vector_field method, which aggregates the vector_field methods of all the AbstractDynamicalSystem instances referred to by the abstract property AbstractPlant.dynamics_spec. Final subclasses of AbstractPlant must implement this property.
AbstractStagedModel: final subclasses of AbstractPlant must implement a model_spec property that defines the sequence of kinematic/static updates.
On the other hand, a Mechanics instance wraps an AbstractPlant instance in order to discretize it, associate it with a Diffrax solver, and execute all its kinematic and dynamic updates. Currently, it also handles the effector states.
In this issue I discuss this arrangement in more detail. At the end, I raise a couple of potential issues:
Should the vector fields be aggregated, and updated through a single Diffrax solver step?
Which class should manage the effector states?
AbstractPlant
AbstractPlant objects provide most of the components of a biomechanical model. In particular, they aggregate the differential equations as well as the kinematic updates the mechanical components.
a differential equation describing the physics of skeletal movement due to torques generated by muscles, and belonging to an AbstractSkeleton object;
another differential equation describing the dynamics of muscle activation, belonging to an AbstractMuscle object;
some kinematic methods belonging to the AbstractPlant object itself, that directly calculate the muscle length and contraction velocity given the current state of the skeleton;
a method of the same AbstractMuscle object as in (2) that determines the forces generated by the muscle, given its current length and velocity. These are converted into torques after being passed back to the AbstractPlant object.
AbstractPlant subclasses must implement the property dynamics_spec, which aggregates all the AbstractDynamicalSystem components that should be included in the dynamics step for the entire plant. For example, MuscledArm includes a first-order filter for the muscle activation, as well as an ODE describing the skeletal dynamics, and both of these are referred to in its dynamics_spec:
The entries in dynamics_spec are automatically aggregated into a single field by the method AbstractPlant.vector_field, which fulfils AbstractPlant's implementation of AbstractDynamicalSystem:
AbstractPlant also inherits from AbstractStagedModel and therefore defines a model_spec, which composes the various static operations that are performed on the plant state (e.g. calculating muscle length and velocity, or clipping states to bounds).
Mechanics
An instance of AbstractPlant describes continuous dynamics, as do the vector fields defined in dynamics_spec and aggregated in vector_field.
Mechanics associates the continuous dynamics of an AbstractPlant instance with a Diffrax solver and a time step, thereby discretizing them. It includes a property _term which returns diffrax.ODETerm(self.plant.vector_field), which is used in the Diffrax solver call:
Mechanics inherits solely from AbstractStagedModel[MechanicsState], and when called it:
updates the plant state by 1) calling the AbstractPlant instance (i.e. the kinematic operations in its model_spec), then 2) executing a step of the dynamics solver.
syncs the effector state with the configuration state. For example, the configuration state of a typical arm model consists of joint angles and angular velocities, whereas the effector state is typically the Cartesian state of some point on the arm -- the very end of it. The actual methods for doing these calculations are part of the skeleton object.
Here is how these operations are defined in Mechanics.model_spec:
Aggregation of vector fields -> single solver step
Should all the component vector fields be explicitly aggregated in AbstractPlant objects, to be associated with a single Diffrax solver step in Mechanics? It's not generally the case that the fields are coupled, and in principle they could be stepped separately.
Sometimes aggregating the dynamics can lead to confusing situations. For example, see the admonition in the docstring for AbstractMuscle.__call__ -- it's a little conceptually weird to have to include the muscle's ActivationFilter (which models something that would really occur inside a muscle fibre) as part of the AbstractPlant, and not the AbstractMuscle. One potential solution is to have AbstractMuscle inherit from both AbstractStagedModel and AbstractDynamicalSystem, just like AbstractPlant does.
Defining and updating the effector
The loss function is generally calculated in effector (or operational) coordinates. For example, a reaching task defines its reach goals in Cartesian coordinates. Similarly, force perturbations may be applied to the effector as linear forces, which must be converted to torques. However, only the configuration state (e.g. joint angles/angular velocities) is part of the ODE describing the skeleton dynamics. It does not make sense to return derivatives for the effector state through the Diffrax solver.
That is why the effector state is currently part of MechanicsState and not of AbstractSkeletonState. It may be best to change that, as all the relevant kinematic methods belong to an AbstractSkeleton, and the effector state is rightly just an alternative representation of the skeleton state specifically (assuming we won't be modeling soft tissue). Of course, AbstractSkeletonState could be separated into configuration and effector fields, and dynamics_spec could ensure that only state.configuration is passed to the skeleton's vector_field.
Once effector state is removed from MechanicsState, it will only contain two fields: plant and solver. I think it's kind of annoying that the user has to refer to state.mechanics.plant.skeleton instead of state.mechanics.skeleton, and I'd prefer if Mechanics could work as a kind of wrapper that operates on PlantState directly. However, the solver state needs to be passed from each time step to the next. The solver could be moved to PlantState, but that creates a bit of an asymmetry between the model and state hierarchies, given that numerical solution is the responsibility of of Mechanics and not AbstractPlant.
Which point on the skeleton is considered the effector may vary between tasks or experiments. So far, it's always been the endpoint of an arm, or (trivially) a point mass. There may also be models with multiple effectors. One way to deal with complexity here is to 1) keep all the Cartesian states for the skeleton, and 2) use generalized target specifications (#10) so the user can define losses for arbitrary parts of those states.
This discussion was converted from issue #1 on March 04, 2024 13:42.
Heading
Bold
Italic
Quote
Code
Link
Numbered list
Unordered list
Task list
Attach files
Mention
Reference
Menu
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
I'm uncertain about the design of
AbstractPlant
andMechanics
.In brief, an
AbstractPlant
is a subclass ofAbstractDynamicalSystem
:AbstractPlant
implements a generalvector_field
method, which aggregates thevector_field
methods of all theAbstractDynamicalSystem
instances referred to by the abstract propertyAbstractPlant.dynamics_spec
. Final subclasses ofAbstractPlant
must implement this property.AbstractStagedModel
: final subclasses ofAbstractPlant
must implement amodel_spec
property that defines the sequence of kinematic/static updates.On the other hand, a
Mechanics
instance wraps anAbstractPlant
instance in order to discretize it, associate it with a Diffrax solver, and execute all its kinematic and dynamic updates. Currently, it also handles the effector states.In this issue I discuss this arrangement in more detail. At the end, I raise a couple of potential issues:
AbstractPlant
AbstractPlant
objects provide most of the components of a biomechanical model. In particular, they aggregate the differential equations as well as the kinematic updates the mechanical components.For example, an
AbstractPlant
object that models a muscled arm (e.g.feedbax.mechanics.plant.MuscledArm
) may bring together:AbstractSkeleton
object;AbstractMuscle
object;AbstractPlant
object itself, that directly calculate the muscle length and contraction velocity given the current state of the skeleton;AbstractMuscle
object as in (2) that determines the forces generated by the muscle, given its current length and velocity. These are converted into torques after being passed back to theAbstractPlant
object.AbstractPlant
subclasses must implement the propertydynamics_spec
, which aggregates all theAbstractDynamicalSystem
components that should be included in the dynamics step for the entire plant. For example,MuscledArm
includes a first-order filter for the muscle activation, as well as an ODE describing the skeletal dynamics, and both of these are referred to in itsdynamics_spec
:feedbax/feedbax/mechanics/plant.py
Lines 387 to 404 in 147fb42
The entries in
dynamics_spec
are automatically aggregated into a single field by the methodAbstractPlant.vector_field
, which fulfilsAbstractPlant
's implementation ofAbstractDynamicalSystem
:feedbax/feedbax/mechanics/plant.py
Lines 101 to 112 in 147fb42
AbstractPlant
also inherits fromAbstractStagedModel
and therefore defines amodel_spec
, which composes the various static operations that are performed on the plant state (e.g. calculating muscle length and velocity, or clipping states to bounds).Mechanics
An instance of
AbstractPlant
describes continuous dynamics, as do the vector fields defined indynamics_spec
and aggregated invector_field
.Mechanics
associates the continuous dynamics of anAbstractPlant
instance with a Diffrax solver and a time step, thereby discretizing them. It includes a property_term
which returnsdiffrax.ODETerm(self.plant.vector_field)
, which is used in the Diffrax solver call:https://github.com/mlprt/feedbax/blob/147fb42f8793db55d06c8e432e4627a3679a801a/feedbax/mechanics/mechanics.py#L127C1-L135C10
Mechanics
inherits solely fromAbstractStagedModel[MechanicsState]
, and when called it:AbstractPlant
instance (i.e. the kinematic operations in itsmodel_spec
), then 2) executing a step of the dynamics solver.Here is how these operations are defined in
Mechanics.model_spec
:feedbax/feedbax/mechanics/mechanics.py
Lines 83 to 112 in 147fb42
Potential issues
Aggregation of vector fields -> single solver step
Should all the component vector fields be explicitly aggregated in
AbstractPlant
objects, to be associated with a single Diffrax solver step inMechanics
? It's not generally the case that the fields are coupled, and in principle they could be stepped separately.Sometimes aggregating the dynamics can lead to confusing situations. For example, see the admonition in the docstring for
AbstractMuscle.__call__
-- it's a little conceptually weird to have to include the muscle'sActivationFilter
(which models something that would really occur inside a muscle fibre) as part of theAbstractPlant
, and not theAbstractMuscle
. One potential solution is to haveAbstractMuscle
inherit from bothAbstractStagedModel
andAbstractDynamicalSystem
, just likeAbstractPlant
does.Defining and updating the effector
The loss function is generally calculated in effector (or operational) coordinates. For example, a reaching task defines its reach goals in Cartesian coordinates. Similarly, force perturbations may be applied to the effector as linear forces, which must be converted to torques. However, only the configuration state (e.g. joint angles/angular velocities) is part of the ODE describing the skeleton dynamics. It does not make sense to return derivatives for the effector state through the Diffrax solver.
That is why the effector state is currently part of
MechanicsState
and not ofAbstractSkeletonState
. It may be best to change that, as all the relevant kinematic methods belong to anAbstractSkeleton
, and the effector state is rightly just an alternative representation of the skeleton state specifically (assuming we won't be modeling soft tissue). Of course,AbstractSkeletonState
could be separated intoconfiguration
andeffector
fields, anddynamics_spec
could ensure that onlystate.configuration
is passed to the skeleton'svector_field
.Once effector state is removed from
MechanicsState
, it will only contain two fields:plant
andsolver
. I think it's kind of annoying that the user has to refer tostate.mechanics.plant.skeleton
instead ofstate.mechanics.skeleton
, and I'd prefer ifMechanics
could work as a kind of wrapper that operates onPlantState
directly. However, the solver state needs to be passed from each time step to the next. Thesolver
could be moved toPlantState
, but that creates a bit of an asymmetry between the model and state hierarchies, given that numerical solution is the responsibility of ofMechanics
and notAbstractPlant
.Which point on the skeleton is considered the effector may vary between tasks or experiments. So far, it's always been the endpoint of an arm, or (trivially) a point mass. There may also be models with multiple effectors. One way to deal with complexity here is to 1) keep all the Cartesian states for the skeleton, and 2) use generalized target specifications (#10) so the user can define losses for arbitrary parts of those states.
Beta Was this translation helpful? Give feedback.
All reactions