Models with Rotations

In robotics, the state of our robot often depends on one or more arbitrary 3D rotations (a.k.a. orientation, attitude). Effectively representing the non-trivial group structure of rotations has been a topic of study for over 100 years, and as a result many parameterizations exists. RobotDynamics supports the types defined in Rotations.jl.

The LieGroupModel type allows users to abstract away the particular rotation representation used and will automatically create efficient methods to handle the potentially different state dimensions that result. Additionally, it defines methods for operating on the error state, which for rotations is always three-dimensional. See the discussion in the Rotaitons.jl README for more information on the error state.

Compat

RobotDynamics requires v1.0 or higher of Rotations.jl

Defining a LieGroupModel

We define a LieGroupModel very similarly to that of a standard model. For this example, let's assume we are modeling a constellation of 2 satellites and we only care about the attitude dynamics. We will define our state to be [q1, ω1, q2, ω2] where qi and ωi are the orientation and angular velocity of the ith satellite, respectively.

We start by defining our new type and our dynamics function

struct SatellitePair{R,T} <: LieGroupModel
    J1::SMatrix{3,3,T,9}   # inertia of satellite 1
    J2::SMatrix{3,3,T,9}   # inertia of satellite 2
end

function RobotDynamics.dynamics(model::SatellitePair, x, u)
    vs = RobotDynamics.vec_states(model, x)  # extract "vector" states
    qs = RobotDynamics.rot_states(model, x)  # extract attitude states
    ω1 = vs[2]  # offset index by 1 since there are now "vector" states before the first quaternion
    ω2 = vs[3]
    q1 = qs[1]
    q2 = qs[2]

    J1, J2 = model.J1, model.J2
    u1 = u[SA[1,2,3]]
    u2 = u[SA[4,5,6]]
    ω1dot = J1\(u1 - ω1 × (J1 * ω1))
    ω2dot = J2\(u2 - ω2 × (J2 * ω2))
    q1dot = Rotations.kinematics(q1, ω1)
    q2dot = Rotations.kinematics(q2, ω2)
    SA[
        q1dot[1], q1dot[2], q1dot[3], q1dot[4],
        ω1dot[1], ω1dot[2], ω1dot[3],
        q2dot[1], q2dot[2], q2dot[3], q2dot[4],
        ω2dot[1], ω2dot[2], ω2dot[3],
    ]
end

RobotDynamics.control_dim(::SatellitePair) = 6

Before defining the functions vec_states and rot_states, we will define the type LieState, which defines how our state vector is stacked or partitioned. The LieState only needs to know how many "vector" or "non-rotation" states exist, and where the rotations are placed in the state vector. In our example, we have a rotation, followed by 3 "vector" states, followed by a rotation, followed by 3 "vector" states, so we would define our LieState to be

RobotDynamics.LieState(::SatellitePair{R}) where R = RobotDynamics.LieState(R, (0,3,3))

which means we have a state with 0 vector states at the beginning, followed by a rotation, followed by 3 vector states, followed by a rotation, followed by 3 vector states, and the rotation type is R.

With this partitioning in mind, we can now understand the behavior of vec_states and rot_states, which simply extract the vector and attitude parts of the state as tuples of SVectors.

LieGroupModel API

RobotDynamics.LieGroupModelType
LieGroupModel <: AbstractModel

Abstraction of a dynamical system whose state contains at least one arbitrary rotation.

RobotDynamics.LieStateType
LieState{R,P}

Specifies a state with rotational components mixed in with standard vector components. All rotational components are assumed to be parameterizations of 3D rotations.

Parameters

  • R <: Rotation is the rotational representation used in the state vector. Must have

params(::Type{R}) defined, which returns the number of parameters used by the rotation, as well a constructor that takes each parameter as a separate scalar argument.

  • P <: Tuple{Vararg{Int}} is a tuple of integers specifying the partitioning of the state

vector. Each element of P specifies the length of the vector component between the rotational components, and P[1] and P[end] specify the number of vector states at the beginning and end of the state vector.

Examples

If we want to construct a state vector like the following: [v3, q, v2, q, v3] where v2 and v3 and vector components of length 2 and 3, respectively, and q is a 4-dimensional unit quaternion. The LieState for this state vector would be LieState{UnitQuaternion{Float64},3,2,3}. The length should be (3+4+2+4+3) = 16, which can be verified by length(s::LieState).

Constructors

LieState(::Type{R}, P::Tuple{Vararg{Int}})
LieState(::Type{R}, p1::Int, p2::Int, p3::Int...)
RobotDynamics.QuatStateFunction
QuatState(n::Int, Q::StaticVector{<:Any,Int})
QuatState(n::Int, Q::NTuple{<:Any,Int})

Create a n-dimensional LieState assuming R = UnitQuaternion{Float64} and Q[i] is the first index of each quaternion in the state vector.

Example

If we want to construct a state vector like the following: [v3, q, v2, q, v3] where v2 and v3 and vector components of length 2 and 3, respectively, and q is a 4-dimensional unit quaternion. Since the first quaternion starts at index 4, and the second starts at index 10, Q = [4,10]. The entire length of the vector is n = 16 = 3 + 4 + 2 + 4 + 3, so we would call QuatState(16, SA[4,10]).

RobotDynamics.vec_statesFunction
vec_states(model::LieGroupModel, x)
vec_states(s::LieState, x)

Extracts the "vector" states out of the state vector x for a LieGroupModel. Returns a tuple v of SVectors, where length(v[i]) is equal to the length specified by the LieState.

RobotDynamics.rot_statesFunction
vec_states(model::LieGroupModel, x)
vec_states(s::LieState, x)

Extracts the rotations out of the state vector x for a LieGroupModel. Returns a tuple rotations, whose type matches the rotation type specified in the LieState.