Program Listing for File state.h¶

↰ Return to documentation for file (states/state.h)

#pragma once
#include <fmt/format.h>
#include <spdlog/spdlog.h>

#include "lupnt/conversions/frame_converter.h"
#include "lupnt/core/definitions.h"
#include "lupnt/core/error.h"

namespace lupnt {

  using StateType = std::string_view;

  // State *********************************************************************

  class State : public VecX {
  private:
    std::string name_ = "State";
    StateType type_ = UNDEFINED;
    std::vector<std::string> names_;
    std::vector<std::string> units_;
    Frame frame_;

    void SetNamesAndUnits(int n);

  public:
    State();

    State(int n);

    State(int rows, int cols);

    State(const VecX& vec, const std::vector<std::string>& names,
          const std::vector<std::string>& units);

    template <typename T, int N> State(const State& state, const Vector<T, N>& vec);

    State(const State& other);

    template <typename Derived, typename = std::enable_if_t<(Derived::ColsAtCompileTime == 1
                                                             || Derived::RowsAtCompileTime == 1)>>
    State(const Eigen::DenseBase<Derived>& x) {
      if (x.cols() == 1) {
        VecX::operator=(x);
        SetNamesAndUnits(x.rows());
      } else if (x.rows() == 1) {
        VecX::operator=(x.transpose());
        SetNamesAndUnits(x.cols());
      } else {
        static_assert(Derived::ColsAtCompileTime == 1 || Derived::RowsAtCompileTime == 1,
                      "State can only be constructed from vectors");
      }
    }

    void SetFrame(Frame frame);

    void SetType(StateType type);

    void SetName(const std::string& name);

    void SetNames(const std::vector<std::string>& names);

    void SetUnits(const std::vector<std::string>& units);

    Frame GetFrame() const;

    StateType GetType() const;

    std::string GetName() const;

    std::vector<std::string> GetNames() const;

    std::vector<std::string> GetUnits() const;

    State& operator=(const State& other);

    template <typename Derived, typename = std::enable_if_t<(Derived::ColsAtCompileTime == 1
                                                             || Derived::RowsAtCompileTime == 1)>>
    State& operator=(const Eigen::DenseBase<Derived>& x) {
      if (x.cols() == 1) {
        VecX::operator=(x);
      } else if (x.rows() == 1) {
        VecX::operator=(x.transpose());
      } else {
        static_assert(Derived::ColsAtCompileTime == 1 || Derived::RowsAtCompileTime == 1,
                      "State can only be assigned from vectors");
      }
      return *this;
    }

    static constexpr StateType TYPE = UNDEFINED;

    std::string ToString() const;
  };

  template <typename T, int N> State::State(const State& state, const Vector<T, N>& vec)
      : VecX(vec) {
    type_ = state.type_;
    names_ = state.names_;
    units_ = state.units_;
    frame_ = state.frame_;
  }

  // Cart6 *********************************************************************
  // - $r_x$ [m] (position x-component)
  // - $r_y$ [m] (position y-component)
  // - $r_z$ [m] (position z-component)
  // - $v_x$ [m/s] (velocity x-component)
  // - $v_y$ [m/s] (velocity y-component)
  // - $v_z$ [m/s] (velocity z-component)
  class Cart6 : public State {
  private:
    void Init() {
      SetType(Cart6::TYPE);
      SetNames({"r_x", "r_y", "r_z", "v_x", "v_y", "v_z"});
      SetUnits({"m", "m", "m", "m/s", "m/s", "m/s"});
    }

  public:
    static constexpr StateType TYPE = "Cart6";

    Cart6(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 6, "State size must be 6", "Cart6");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be Cart6", "Cart6");
      Init();
    }

    Cart6(const Vec3& r, const Vec3& v, const Frame frame = Frame::MOON_CI) : State() {
      Init();
      SetFrame(frame);
      resize(6);
      head(3) = r;
      tail(3) = v;
    }

    Cart6(const Vec6& x = Vec6::Zero(), const Frame frame = Frame::MOON_CI) : State(x) {
      Init();
      SetFrame(frame);
    }

    Eigen::Ref<Vec3> r() { return head(3); }

    Eigen::Ref<Vec3> v() { return tail(3); }

    Vec3 r() const { return head(3); }

    Vec3 v() const { return tail(3); }
  };

  // AttitudeState ***************************************************************
  // - $q_0$ [-] (scalar part)
  // - $q_1$ [-] (x-component)
  // - $q_2$ [-] (y-component)
  // - $q_3$ [-] (z-component)
  // - $w_x$ [rad/s] (angular velocity x-component)
  // - $w_y$ [rad/s] (angular velocity y-component)
  // - $w_z$ [rad/s] (angular velocity z-component)
  class Attitude : public State {
  private:
    void Init() {
      resize(7);
      head(4) = Vec4(1.0, 0.0, 0.0, 0.0);
      tail(3) = Vec3(0.0, 0.0, 0.0);
      SetType("AttitudeState");
      SetNames({"q0", "q1", "q2", "q3", "wx", "wy", "wz"});
      SetUnits({"-", "-", "-", "-", "rad/s", "rad/s", "rad/s"});
    }

  public:
    static constexpr StateType TYPE = "AttitudeState";

    Attitude() : State() { Init(); }

    Attitude(int rows, int cols) : State(rows, cols) { Init(); }

    Attitude(const State& x) : Attitude() {
      LUPNT_CHECK(x.size() == 7, "State size must be 7", "AttitudeState");
      LUPNT_CHECK(x.GetType() == "AttitudeState", "State type must be AttitudeState",
                  "AttitudeState");
      head(7) = x.head(7);
      SetFrame(x.GetFrame());
    }

    Attitude(const Vec7& qw) : Attitude() { head(7) = qw; }

    Attitude(const Vec4& q, const Vec3& w, Frame f) : Attitude() {
      head(4) = q;
      tail(3) = w;
      SetFrame(f);
    }

    Eigen::Ref<Vec4> q() { return head(4); }

    Eigen::Ref<Vec3> w() { return tail(3); }

    Vec4 q() const { return head(4); }

    Vec3 w() const { return tail(3); }
  };

  // RelCart6 *******************************************************************
  // - $\delta r_x$ [m] (position x-component)
  // - $\delta r_y$ [m] (position y-component)
  // - $\delta r_z$ [m] (position z-component)
  // - $\delta v_x$ [m/s] (velocity x-component)
  // - $\delta v_y$ [m/s] (velocity y-component)
  // - $\delta v_z$ [m/s] (velocity z-component)
  class RelCart6 : public State {
  private:
    void Init() {
      resize(6);
      SetType(Cart6::TYPE);
      SetNames({"dr_x", "dr_y", "dr_z", "dv_x", "dv_y", "dv_z"});
      SetUnits({"m", "m", "m", "m/s", "m/s", "m/s"});
    }

  public:
    static constexpr StateType TYPE = "RelCart6";

    RelCart6(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 6, "State size must be 6", "RelCart6");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be RelCart6", "RelCart6");
      Init();
    }

    RelCart6(const Vec3& dr, const Vec3& dv, const Frame frame = Frame::MOON_CI) : State() {
      Init();
      SetFrame(frame);
      resize(6);
      head(3) = dr;
      tail(3) = dv;
    }

    RelCart6(const Vec6& x = Vec6::Zero(), const Frame frame = Frame::MOON_CI) : State(x) {
      Init();
      SetFrame(frame);
    }

    Eigen::Ref<Vec3> dr() { return head(3); }

    Eigen::Ref<Vec3> dv() { return tail(3); }

    Vec3 dr() const { return head(3); }

    Vec3 dv() const { return tail(3); }
  };

  // SurfaceState2D *************************************************************
  // - $x$ [m] (x-coordinate)
  // - $y$ [m] (y-coordinate)
  // - $\theta$ [rad] (angle)
  class SurfaceState2D : public State {
  private:
    void Init() {
      resize(3);
      SetType(SurfaceState2D::TYPE);
      SetNames({"x", "y", "theta"});
      SetUnits({"m", "m", "rad"});
    }

  public:
    SurfaceState2D(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 3, "State size must be 3", "Surface2DState");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be Surface2D", "Surface2DState");
      Init();
    }

    SurfaceState2D(const Vec3& x = Vec3::Zero(), const Frame frame = Frame::MOON_CI) : State(x) {
      Init();
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "Surface2D";
  };

  // Cart3 **********************************************************************
  // - $r_x$ [m] (position x-component)
  // - $r_y$ [m] (position y-component)
  // - $r_z$ [m] (position z-component)
  class Cart3 : public State {
  private:
    void Init() {
      resize(3);
      SetType(Cart3::TYPE);
      SetNames({"r_x", "r_y", "r_z"});
      SetUnits({"m", "m", "m"});
    }

  public:
    Cart3(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 3, "State size must be 3", "Cart3");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be Cart3", "Cart3");
      Init();
    }

    Cart3(const Vec3& x = Vec3::Zero(), const Frame frame = Frame::MOON_CI) : State(x) {
      Init();
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "Cart3";
  };

  // Cart2 **********************************************************************
  // - $x$ [m] (x-coordinate)
  // - $y$ [m] (y-coordinate)
  class Cart2 : public State {
  private:
    void Init() {
      resize(2);
      SetType(Cart2::TYPE);
      SetNames({"x", "y"});
      SetUnits({"m", "m"});
    }

  public:
    Cart2(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 2, "State size must be 2", "CartTo");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be CartTo", "CartTo");
      Init();
    }

    Cart2(const Vec2& x = Vec2::Zero(), const Frame frame = Frame::MOON_CI) : State(x) {
      Init();
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "CartTo";

    Eigen::Ref<Vec2> r() { return head(2); }

    Vec2 r() const { return head(2); }
  };

  // AzElRange ******************************************************************
  // - $az$ [rad] (azimuth)
  // - $el$ [rad] (elevation)
  // - $range$ [m] (range)
  class AzElRange : public State {
  private:
    void Init() {
      resize(3);
      SetType(AzElRange::TYPE);
      SetNames({"az", "el", "range"});
      SetUnits({"rad", "rad", "m"});
    }

  public:
    AzElRange(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 3, "State size must be 3", "AzElRange");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be AzElRange", "AzElRange");
      Init();
    }

    AzElRange(const Vec3& x = Vec3::Zero(), const Frame frame = Frame::MOON_CI) : State() {
      Init();
      head(3) = x;
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "AzElRange";
  };  // namespace lupnt

  // LatLonAlt ******************************************************************
  // - $lat$ [rad] (latitude)
  // - $lon$ [rad] (longitude)
  // - $alt$ [m] (altitude)
  class LatLonAlt : public State {
  private:
    void Init() {
      resize(3);
      SetType(LatLonAlt::TYPE);
      SetNames({"lat", "lon", "alt"});
      SetUnits({"rad", "rad", "m"});
    }

  public:
    LatLonAlt(const State& x) : State() {
      LUPNT_CHECK(x.size() == 3, "State size must be 3", "LatLonAlt");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be LatLonAlt", "LatLonAlt");
      Init();
      head(3) = x.head(3);
    }

    LatLonAlt(const Vec3& x = Vec3::Zero(), const Frame frame = Frame::MOON_CI) : State() {
      Init();
      head(3) = x;
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "LatLonAlt";
  };

  // ClassicalOE *****************************************************************
  // Singular at $e \in {0,1}$, and $i \in {0, \pi}$
  // - $a$ [m] (semi-major axis)
  // - $e$ [-] (eccentricity)
  // - $i$ [rad] (inclination)
  // - $\Omega$ [rad] (right ascension of the ascending node)
  // - $\omega$ [rad] (argument of periapsis)
  // - $M$ [rad] (mean anomaly)
  class ClassicalOE : public State {
  private:
    void Init() {
      SetType(ClassicalOE::TYPE);
      SetNames({"a", "e", "i", "Omega", "w", "M"});
      SetUnits({"m", "-", "rad", "rad", "rad", "rad"});
    }

  public:
    ClassicalOE(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 6, "State size must be 6", "ClassicalOE");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be ClassicalOE", "ClassicalOE");
      Init();
    }

    ClassicalOE(const Vec6& x = Vec6::Zero(), const Frame frame = Frame::MOON_CI) : State(x) {
      Init();
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "ClassicalOE";

    Real& a() { return this->coeffRef(0); }
    Real& e() { return this->coeffRef(1); }
    Real& i() { return this->coeffRef(2); }
    Real& Omega() { return this->coeffRef(3); }
    Real& w() { return this->coeffRef(4); }
    Real& M() { return this->coeffRef(5); }

    Real a() const { return this->coeff(0); }
    Real e() const { return this->coeff(1); }
    Real i() const { return this->coeff(2); }
    Real Omega() const { return this->coeff(3); }
    Real w() const { return this->coeff(4); }
    Real M() const { return this->coeff(5); }
  };

  // QuasiNonsingularOE **********************************************************
  // - $a$ [m] (semi-major axis)
  // - $u$ [-] (mean argument of latitude)
  // - $e_x$ [-] (eccentricity x-component)
  // - $e_y$ [-] (eccentricity y-component)
  // - $i$ [rad] (inclination)
  // - $\Omega$ [rad] (right ascension of the ascending node)
  // - $\omega$ [rad] (argument of periapsis)
  class QuasiNonsingularOE : public State {
  private:
    void Init() {
      SetType(QuasiNonsingularOE::TYPE);
      SetNames({"a", "u", "ex", "ey", "i", "Omega"});
      SetUnits({"m", "-", "-", "-", "rad", "rad"});
    }

  public:
    QuasiNonsingularOE(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 6, "State size must be 6", "QuasiNonsingularOE");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be QuasiNonsingularOE",
                  "QuasiNonsingularOE");
      Init();
    }

    QuasiNonsingularOE(const Vec6& x = Vec6::Zero()) : State(x) { Init(); }

    QuasiNonsingularOE(const Vec6& x, const Frame frame) : QuasiNonsingularOE(x) {
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "QuasiNonsingularOE";

    Real& a() { return this->coeffRef(0); }
    Real& u() { return this->coeffRef(1); }
    Real& ex() { return this->coeffRef(2); }
    Real& ey() { return this->coeffRef(3); }
    Real& i() { return this->coeffRef(4); }
    Real& Omega() { return this->coeffRef(5); }

    Real a() const { return this->coeff(0); }
    Real u() const { return this->coeff(1); }
    Real ex() const { return this->coeff(2); }
    Real ey() const { return this->coeff(3); }
    Real i() const { return this->coeff(4); }
    Real Omega() const { return this->coeff(5); }

    StateType GetType() const { return "QuasiNonsingularOE"; }
  };

  // DelaunayOE ******************************************************************
  // - $l$ [rad] (mean longitude)
  // - $g$ [rad] (longitude of periapsis)
  // - $h$ [rad] (longitude of ascending node)
  // - $L$ [rad]
  // - $G$ [rad]
  // - $H$ [rad]
  class DelaunayOE : public State {
  private:
    void Init() {
      SetType(DelaunayOE::TYPE);
      SetNames({"l", "g", "h", "L", "G", "H"});
      SetUnits({"rad", "rad", "rad", "rad", "rad", "rad"});
    }

  public:
    DelaunayOE(const Vec6& x = Vec6::Zero()) : State(x) { Init(); }

    DelaunayOE(const Vec6& x, const Frame frame) : DelaunayOE(x) {
      Init();
      SetFrame(frame);
    }
    static constexpr StateType TYPE = "DelaunayOE";

    Real& l() { return this->coeffRef(0); }
    Real& g() { return this->coeffRef(1); }
    Real& h() { return this->coeffRef(2); }
    Real& L() { return this->coeffRef(3); }
    Real& G() { return this->coeffRef(4); }
    Real& H() { return this->coeffRef(5); }

    Real l() const { return this->coeff(0); }
    Real g() const { return this->coeff(1); }
    Real h() const { return this->coeff(2); }
    Real L() const { return this->coeff(3); }
    Real G() const { return this->coeff(4); }
    Real H() const { return this->coeff(5); }
  };

  // EquinoctialOE ***************************************************************
  // - $a$ [m] (semi-major axis)
  // - $h$ [-]
  // - $k$ [-]
  // - $p$ [-]
  // - $q$ [-]
  // - $\lambda$ [rad]
  class EquinoctialOE : public State {
  public:
    EquinoctialOE(const Vec6& x = Vec6::Zero()) : State(x) {
      SetType(TYPE);
      SetNames({"a", "h", "k", "p", "q", "lon"});
      SetUnits({"m", "-", "-", "-", "-", "rad"});
      SetFrame(Frame::MOON_CI);
    }

    EquinoctialOE(const Vec6& x, const Frame frame) : EquinoctialOE(x) { SetFrame(frame); }
    static constexpr StateType TYPE = "EquinoctialOE";

    Real& a() { return this->coeffRef(0); }
    Real& h() { return this->coeffRef(1); }
    Real& k() { return this->coeffRef(2); }
    Real& p() { return this->coeffRef(3); }
    Real& q() { return this->coeffRef(4); }
    Real& lon() { return this->coeffRef(5); }

    Real a() const { return this->coeff(0); }
    Real h() const { return this->coeff(1); }
    Real k() const { return this->coeff(2); }
    Real p() const { return this->coeff(3); }
    Real q() const { return this->coeff(4); }
    Real lon() const { return this->coeff(5); }
  };

  // SingularROE *****************************************************************
  // - $a\delta a$ [m] (semi-major axis)
  // - $a\delta M$ [m] (mean anomaly)
  // - $a\delta e$ [m] (eccentricity)
  // - $a\delta \omega$ [m] (argument of periapsis)
  // - $a\delta i$ [m] (inclination)
  // - $a\delta \Omega$ [m] (right ascension of the ascending node)
  class SingularROE : public State {
  public:
    SingularROE(const Vec6& x = Vec6::Zero()) : State(x) {
      SetType(TYPE);
      SetNames({"ada", "adM", "ade", "adw", "adi", "adOmega"});
      SetUnits({"m", "m", "m", "m", "m", "m"});
      SetFrame(Frame::MOON_CI);
    }

    SingularROE(const Vec6& x, const Frame frame) : SingularROE(x) { SetFrame(frame); }
    static constexpr StateType TYPE = "SingularROE";

    Real& ada() { return this->coeffRef(0); }
    Real& adM() { return this->coeffRef(1); }
    Real& ade() { return this->coeffRef(2); }
    Real& adw() { return this->coeffRef(3); }
    Real& adi() { return this->coeffRef(4); }
    Real& adOmega() { return this->coeffRef(5); }

    Real ada() const { return this->coeff(0); }
    Real adM() const { return this->coeff(1); }
    Real ade() const { return this->coeff(2); }
    Real adw() const { return this->coeff(3); }
    Real adi() const { return this->coeff(4); }
    Real adOmega() const { return this->coeff(5); }
  };

  // QuasiNonsingROE ************************************************************
  // - $a\delta a$ [m] (semi-major axis)
  // - $a\delta l$ [m] (mean longitude)
  // - $a\delta e_x$ [m] (eccentricity x-component)
  // - $a\delta e_y$ [m] (eccentricity y-component)
  // - $a\delta i_x$ [m] (inclination x-component)
  // - $a\delta i_y$ [m] (inclination y-component)
  class QuasiNonsingROE : public State {
  public:
    QuasiNonsingROE(const Vec6& x = Vec6::Zero()) : State(x) {
      SetType(TYPE);
      SetNames({"ada", "adl", "adex", "adey", "adix", "adiy"});
      SetUnits({"m", "m", "m", "m", "m", "m"});
      SetFrame(Frame::MOON_CI);
    }

    QuasiNonsingROE(const Vec6& x, const Frame frame) : QuasiNonsingROE(x) { SetFrame(frame); }
    static constexpr StateType TYPE = "QuasiNonsingROE";

    Real& ada() { return this->coeffRef(0); }
    Real& adl() { return this->coeffRef(1); }
    Real& adex() { return this->coeffRef(2); }
    Real& adey() { return this->coeffRef(3); }
    Real& adix() { return this->coeffRef(4); }
    Real& adiy() { return this->coeffRef(5); }

    Real ada() const { return this->coeff(0); }
    Real adl() const { return this->coeff(1); }
    Real adex() const { return this->coeff(2); }
    Real adey() const { return this->coeff(3); }
    Real adix() const { return this->coeff(4); }
    Real adiy() const { return this->coeff(5); }
  };

  // RollPitchYaw ****************************************************************
  // - $roll$ [rad] (roll)
  // - $pitch$ [rad] (pitch)
  // - $yaw$ [rad] (yaw)
  class RollPitchYaw : public State {
  public:
    RollPitchYaw(const Vec3& x = Vec3::Zero()) : State(x) {
      SetType(TYPE);
      SetNames({"roll", "pitch", "yaw"});
      SetUnits({"rad", "rad", "rad"});
    }
    static constexpr StateType TYPE = "RollPitchYaw";

    Real& roll() { return this->coeffRef(0); }
    Real& pitch() { return this->coeffRef(1); }
    Real& yaw() { return this->coeffRef(2); }

    Real roll() const { return this->coeff(0); }
    Real pitch() const { return this->coeff(1); }
    Real yaw() const { return this->coeff(2); }
  };

  // Quaternion ****************************************************************
  // - $q_0$ [-] (scalar part)
  // - $q_1$ [-] (x-component)
  // - $q_2$ [-] (y-component)
  // - $q_3$ [-] (z-component)
  class Quaternion : public State {
  public:
    Quaternion(const Vec4& x = Vec4::Zero()) : State(x) {
      SetType(TYPE);
      SetNames({"q_0", "q_1", "q_2", "q_3"});
      SetUnits({"-", "-", "-", "-"});
    }
    static constexpr StateType TYPE = "Quaternion";

    Real& q0() { return this->coeffRef(0); }
    Real& q1() { return this->coeffRef(1); }
    Real& q2() { return this->coeffRef(2); }
    Real& q3() { return this->coeffRef(3); }
  };

  // ImuState *******************************************************************
  // - $b\omega_x$ [rad/s] (body frame angular velocity x-component)
  // - $b\omega_y$ [rad/s] (body frame angular velocity y-component)
  // - $b\omega_z$ [rad/s] (body frame angular velocity z-component)
  // - $a_x$ [m/s^2] (body frame acceleration x-component)
  // - $a_y$ [m/s^2] (body frame acceleration y-component)
  class ImuState : public State {
  private:
    void Init() {
      resize(SIZE);
      setZero();
      SetType(TYPE);
      SetNames({"b_w_x", "b_w_y", "b_w_z", "b_a_x", "b_a_y", "b_a_z"});
      SetUnits({"rad/s", "rad/s", "rad/s", "m/s^2", "m/s^2", "m/s^2"});
    }

    void Check(const State& x) {
      LUPNT_CHECK(x.size() == SIZE, "State size must be 6", "ImuState");
      LUPNT_CHECK(x.GetType() == TYPE, "State type must be ImuState", "ImuState");
    }

  public:
    ImuState(const Vec6& x = Vec6::Zero()) : State() {
      Init();
      head(SIZE) = x;
    }

    ImuState(const State& x) : State() {
      Check(x);
      Init();
      head(SIZE) = x.head(SIZE);
    }

    ImuState& operator=(const State& x) {
      if (this != &x) {
        Check(x);
        Init();
        head(SIZE) = x.head(SIZE);
      }
      return *this;
    }

    Eigen::Ref<Vec3> b_w() { return head(3); }
    Eigen::Ref<Vec3> b_a() { return tail(3); }
    Vec3 b_w() const { return head(3); }
    Vec3 b_a() const { return tail(3); }

    static constexpr StateType TYPE = "ImuState";
    static constexpr int SIZE = 6;
  };

  // ImuMeasurement *************************************************************
  // - $w_x$ [rad/s] (angular velocity x-component)
  // - $w_y$ [rad/s] (angular velocity y-component)
  // - $w_z$ [rad/s] (angular velocity z-component)
  class AngularVelocity : public State {
  public:
    AngularVelocity(const Vec3& x = Vec3::Zero()) : State(x) {
      SetType(TYPE);
      SetNames({"w_x", "w_y", "w_z"});
      SetUnits({"rad/s", "rad/s", "rad/s"});
    }
    static constexpr StateType TYPE = "AngularVelocity";

    Real& wx() { return this->coeffRef(0); }
    Real& wy() { return this->coeffRef(1); }
    Real& wz() { return this->coeffRef(2); }

    Real wx() const { return this->coeff(0); }
    Real wy() const { return this->coeff(1); }
    Real wz() const { return this->coeff(2); }
  };

  class Acceleration : public State {
  public:
    Acceleration(const Vec3& x = Vec3::Zero()) : State(x) {
      SetType(TYPE);
      SetNames({"a_x", "a_y", "a_z"});
      SetUnits({"m/s^2", "m/s^2", "m/s^2"});
    }
    static constexpr StateType TYPE = "Acceleration";

    Real& ax() { return this->coeffRef(0); }
    Real& ay() { return this->coeffRef(1); }
    Real& az() { return this->coeffRef(2); }

    Real ax() const { return this->coeff(0); }
    Real ay() const { return this->coeff(1); }
    Real az() const { return this->coeff(2); }
  };

  // ClockState ******************************************************************
  class ClockState3 : public State {
  private:
    void Init() {
      resize(3);
      setZero();
      SetType("ClockState");
      SetNames({"b", "d", "dr"});
      SetUnits({"s", "s/s", "s/s^2"});
      SetFrame(Frame::MOON_CI);
    }

  public:
    ClockState3() : State() { Init(); }

    ClockState3(int rows, int cols) : State(rows, cols) { Init(); }

    ClockState3(const State& x) : State() {
      LUPNT_CHECK(x.size() == 3, "State must be of size 2 or 3", "ClockState");
      Init();
      head(3) = x.head(3);
    }

    ClockState3& operator=(const State& x) {
      if (this != &x) {
        LUPNT_CHECK(x.size() == 3, "State must be of size 2 or 3", "ClockState");
        head(3) = x.head(3);
      }
      return *this;
    }

    Real& b() { return this->coeffRef(0); }
    Real& d() { return this->coeffRef(1); }
    Real& dr() { return this->coeffRef(2); }

    Real b() const { return this->coeff(0); }
    Real d() const { return this->coeff(1); }
    Real dr() const { return this->coeff(2); }
  };

  // ClockState ******************************************************************
  class ClockState2 : public State {
  private:
    void Init() {
      resize(2);
      setZero();
      SetType("ClockState");
      SetNames({"b", "d"});
      SetUnits({"s", "s/s"});
      SetFrame(Frame::MOON_CI);
    }

  public:
    ClockState2() : State() { Init(); }

    ClockState2(int rows, int cols) : State(rows, cols) { Init(); }

    ClockState2(const State& x) : State() {
      LUPNT_CHECK(x.size() == 2, "State must be of size 2 or 3", "ClockState");
      Init();
      head(2) = x.head(2);
    }

    ClockState2& operator=(const State& x) {
      if (this != &x) {
        LUPNT_CHECK(x.size() == 2, "State must be of size 2 or 3", "ClockState");
        head(2) = x.head(2);
      }
      return *this;
    }

    Real& b() { return this->coeffRef(0); }
    Real& d() { return this->coeffRef(1); }

    Real b() const { return this->coeff(0); }
    Real d() const { return this->coeff(1); }
  };

  // JointOrbitClockState ********************************************************
  // - Cartesian orbit state followed by a 2- or 3-state clock model.

  class JointOrbitClockState : public State {
  private:
    static constexpr int ORBIT_STATE_SIZE = 6;
    int clock_state_size_ = 3;

    void Init(int clock_size, const std::vector<std::string>& clock_units) {
      LUPNT_CHECK(clock_size == 2 || clock_size == 3, "Clock state size must be 2 or 3",
                  "JointOrbitClockState");
      clock_state_size_ = clock_size;
      resize(ORBIT_STATE_SIZE + clock_state_size_);
      SetType(JointOrbitClockState::TYPE);
      std::vector<std::string> names = {"r_x", "r_y", "r_z", "v_x", "v_y", "v_z", "b", "d"};
      if (clock_state_size_ == 3) names.push_back("dr");
      SetNames(names);

      std::vector<std::string> units = {"m", "m", "m", "m/s", "m/s", "m/s"};
      units.insert(units.end(), clock_units.begin(), clock_units.end());
      SetUnits(units);
    }

    std::vector<std::string> GetClockUnitsFromState(const State& clock_state) const {
      std::vector<std::string> units = clock_state.GetUnits();
      if (units.size() == static_cast<size_t>(clock_state.size())) return units;
      return clock_state.size() == 2 ? std::vector<std::string>{"s", "s/s"}
                                     : std::vector<std::string>{"s", "s/s", "s/s^2"};
    }

  public:
    static constexpr StateType TYPE = "JointOrbitClock";

    JointOrbitClockState() : State() {
      setZero(9);
      Init(3, {"s", "s/s", "s/s^2"});
      SetFrame(Frame::MOON_CI);
    }

    explicit JointOrbitClockState(int clock_size) : State() {
      setZero(ORBIT_STATE_SIZE + clock_size);
      Init(clock_size, clock_size == 2 ? std::vector<std::string>{"s", "s/s"}
                                       : std::vector<std::string>{"s", "s/s", "s/s^2"});
      SetFrame(Frame::MOON_CI);
    }

    JointOrbitClockState(const State& x) : State(x) {
      LUPNT_CHECK(x.size() == 8 || x.size() == 9, "State size must be 8 or 9",
                  "JointOrbitClockState");
      clock_state_size_ = x.size() - ORBIT_STATE_SIZE;
      SetType(JointOrbitClockState::TYPE);
      if (x.GetNames().size() != static_cast<size_t>(x.size())
          || x.GetUnits().size() != static_cast<size_t>(x.size())) {
        Init(clock_state_size_, clock_state_size_ == 2
                                    ? std::vector<std::string>{"s", "s/s"}
                                    : std::vector<std::string>{"s", "s/s", "s/s^2"});
        head(x.size()) = x.head(x.size());
        SetFrame(x.GetFrame());
      }
    }

    JointOrbitClockState(const State& orbit_state, const State& clock_state) : State() {
      LUPNT_CHECK(orbit_state.size() == ORBIT_STATE_SIZE, "Orbit state size must be 6",
                  "JointOrbitClockState");
      LUPNT_CHECK(clock_state.size() == 2 || clock_state.size() == 3,
                  "Clock state size must be 2 or 3", "JointOrbitClockState");
      Init(clock_state.size(), GetClockUnitsFromState(clock_state));
      head(ORBIT_STATE_SIZE) = orbit_state.head(ORBIT_STATE_SIZE);
      tail(clock_state.size()) = clock_state.head(clock_state.size());
      SetFrame(orbit_state.GetFrame());
    }

    JointOrbitClockState& operator=(const State& x) {
      LUPNT_CHECK(x.size() == 8 || x.size() == 9, "State size must be 8 or 9",
                  "JointOrbitClockState");
      if (this != &x) {
        clock_state_size_ = x.size() - ORBIT_STATE_SIZE;
        State::operator=(x);
        SetType(JointOrbitClockState::TYPE);
      }
      return *this;
    }

    int GetOrbitStateSize() const { return ORBIT_STATE_SIZE; }

    int GetClockStateSize() const { return clock_state_size_; }

    Cart6 GetOrbitState() const {
      Cart6 orbit(head<ORBIT_STATE_SIZE>(), GetFrame());
      return orbit;
    }

    State GetClockState() const {
      State clock(clock_state_size_);
      clock.head(clock_state_size_) = tail(clock_state_size_);
      clock.SetType("ClockState");
      clock.SetNames(clock_state_size_ == 2 ? std::vector<std::string>{"b", "d"}
                                            : std::vector<std::string>{"b", "d", "dr"});
      std::vector<std::string> joint_units = GetUnits();
      std::vector<std::string> units(joint_units.begin() + ORBIT_STATE_SIZE, joint_units.end());
      clock.SetUnits(units);
      clock.SetFrame(GetFrame());
      return clock;
    }

    void SetOrbitState(const State& orbit_state) {
      LUPNT_CHECK(orbit_state.size() == ORBIT_STATE_SIZE, "Orbit state size must be 6",
                  "JointOrbitClockState");
      head(ORBIT_STATE_SIZE) = orbit_state.head(ORBIT_STATE_SIZE);
      SetFrame(orbit_state.GetFrame());
    }

    void SetClockState(const State& clock_state) {
      LUPNT_CHECK(clock_state.size() == clock_state_size_, "Clock state size mismatch",
                  "JointOrbitClockState");
      tail(clock_state_size_) = clock_state.head(clock_state_size_);
      std::vector<std::string> units = GetUnits();
      std::vector<std::string> clock_units = GetClockUnitsFromState(clock_state);
      std::copy(clock_units.begin(), clock_units.end(), units.begin() + ORBIT_STATE_SIZE);
      SetUnits(units);
    }

    Eigen::Ref<Vec3> r() { return head(3); }
    Eigen::Ref<Vec3> v() { return segment(3, 3); }
    Real& b() { return this->coeffRef(ORBIT_STATE_SIZE); }
    Real& d() { return this->coeffRef(ORBIT_STATE_SIZE + 1); }

    Real& dr() {
      LUPNT_CHECK(clock_state_size_ == 3, "Clock state does not contain drift rate",
                  "JointOrbitClockState");
      return this->coeffRef(ORBIT_STATE_SIZE + 2);
    }

    Vec3 r() const { return head(3); }
    Vec3 v() const { return segment(3, 3); }
    Real b() const { return this->coeff(ORBIT_STATE_SIZE); }
    Real d() const { return this->coeff(ORBIT_STATE_SIZE + 1); }

    Real dr() const {
      LUPNT_CHECK(clock_state_size_ == 3, "Clock state does not contain drift rate",
                  "JointOrbitClockState");
      return this->coeff(ORBIT_STATE_SIZE + 2);
    }
  };

}  // namespace lupnt

template <> struct fmt::formatter<lupnt::State> : fmt::formatter<std::string> {
  template <typename FormatContext>
  auto format(const lupnt::State& state, FormatContext& ctx) const {
    std::ostringstream oss;
    oss << state.GetType() << "(" << state.transpose().format(lupnt::FMT_COMPACT) << ", "
        << enum_name(state.GetFrame()) << ")";
    return fmt::formatter<std::string>::format(oss.str(), ctx);
  }
};

template <> struct fmt::formatter<lupnt::Cart6> : fmt::formatter<std::string> {
  template <typename FormatContext>
  auto format(const lupnt::Cart6& state, FormatContext& ctx) const {
    std::ostringstream oss;
    oss << state.GetType() << "(" << state.transpose().format(lupnt::FMT_COMPACT) << ", "
        << enum_name(state.GetFrame()) << ")";
    return formatter<std::string>::format(oss.str(), ctx);
  }
};