コンテンツにスキップ

SpacecraftDynamics

このコンテンツはまだ日本語訳がありません。

Defined in: dynamics.rs:33

pub struct SpacecraftDynamics\<G: [GravityField](../../traits/gravityfield/), F: [Eci](../../../../arika/api/traits/eci/) = [arika::frame::SimpleEci](../../../../arika/api/type-aliases/simpleeci/)\>

Coupled orbit-attitude dynamics for a rigid spacecraft.

Composes a gravitational field, inertia tensor, external load models, and state effectors (e.g. reaction wheels) into a DynamicalSystem for the augmented spacecraft state.

Parameterized by the inertial frame F (default SimpleEci), matching SpacecraftState<F>.

The state type is AugmentedState<SpacecraftState<F>> — the 14D plant state (orbit 6D + attitude 7D + mass 1D) plus concatenated auxiliary variables from registered StateEffectors (e.g. RW angular momentum). When no effectors are registered, aux is empty and the dynamics are equivalent to the pre-effector version.

Equations of motion:

  • Translation: dr/dt = v, dv/dt = a_gravity + Σ a_loads
  • Rotation: dq/dt = ½ q ⊗ (0,ω), dω/dt = I⁻¹(τ − ω × Iω)
  • Auxiliary: daux/dt from registered effectors

fn new(mu: f64, gravity: G, inertia: Matrix3<f64>) -> Self

Create with gravitational parameter, gravity model, and inertia tensor.

Panics if inertia is singular (not invertible).


fn with_model(self, model: impl [Model<SpacecraftState<F>>](../../traits/model/) + ‘static) -> Self

Add an external model (builder pattern).


fn with_propellant(self, pool: PropellantPool) -> Self

The propellant this spacecraft carries, as a floor under its mass.

One pool per spacecraft: every model registered with with_propulsion(Self::with_propulsion) draws from it, and what is left is the mass the state carries above the floor.

The pool is registered as an effector, which is how running dry becomes a boundary the propagation locates: it carries no auxiliary state of its own — the mass is the plant’s — but it carries the mode that says whether there is anything left to burn, and it declares the floor as a boundary.

Panics if a pool is already registered. Two pools would be two floors, which is what this exists to prevent.


fn with_propulsion(self, model: impl [Model<SpacecraftState<F>>](../../traits/model/) + ‘static) -> Self

Add a model that burns propellant (builder pattern).

Its loads are evaluated exactly as with_model(Self::with_model)‘s are, until the pool is empty — then this one is not asked at all, and neither is any other that draws on the same propellant. A propulsion model registered through with_model would keep thrusting on an empty tank.

The pool’s mode is what stops the burn, and only a propagation that handles boundaries moves it: a group, the CLI’s controlled path, or walk_to_target(crate::boundary::walk_to_target) directly. Stepping this system through Integrator(utsuroi::Integrator) yourself runs no search and settles nothing, so the mode stays Free(crate::effector::ConstraintMode::Free) and the burn continues below the floor — the same limitation the momentum limit of RwAssembly(crate::spacecraft::RwAssembly) has.

Panics without a pool to draw from: the floor is what says when the spacecraft is empty, and thrust with no floor has no end.


fn pool(&self) -> [Option<PropellantPool>](https://doc.rust-lang.org/std/option/enum.Option.html)

The propellant pool, if this spacecraft carries any.


fn declared_aux_bounds(&self) -> Vec<(f64, f64)>

The projection bounds every registered effector declared, concatenated in registration order.

What initial_augmented_state(Self::initial_augmented_state) puts in a new state, and what the start-state check measures a handed-over state against.


fn with_effector(self, effector: impl [StateEffector<SpacecraftState<F>>](../../traits/stateeffector/) + ‘static) -> Self

Add a state effector (builder pattern).

A PropellantPool registered here is registered as the pool, exactly as with_propellant(Self::with_propellant) would: it declares a boundary and carries the mode the propulsion is gated on, and a pool the system did not know about would declare a floor that stops nothing.

Effectors have auxiliary state (e.g. RW angular momentum) that is integrated alongside the plant state. The effector must produce its loads in this system’s inertial frame F (its StateEffector::derivatives returns ExternalLoads<F>), so the dynamics never re-tag coordinates. See issue #103.


fn with_epoch(self, epoch: Epoch) -> Self

Set the initial epoch corresponding to integration time t = 0.


fn with_body_radius(self, radius: f64) -> Self

Set the central body radius for event detection.


fn initial_augmented_state(&self, plant: SpacecraftState<F>) -> [AugmentedState<SpacecraftState<F>>](../augmentedstate/)

Create an initial augmented state with the given plant state.

Auxiliary state is initialized to zeros; bounds are collected from all registered effectors.


fn effector<T: [StateEffector<SpacecraftState<F>>](../../traits/stateeffector/) + ‘static>(&self, index: usize) -> Option<&T>

Downcast a state effector by index (immutable).


fn effector_mut<T: [StateEffector<SpacecraftState<F>>](../../traits/stateeffector/) + ‘static>(&mut self, index: usize) -> Option<&mut T>

Downcast a state effector by index (mutable).


fn effector_by_name<T: [StateEffector<SpacecraftState<F>>](../../traits/stateeffector/) + ‘static>(&self, name: &str) -> Option<&T>

Find and downcast a state effector by name (immutable).


fn effector_by_name_mut<T: [StateEffector<SpacecraftState<F>>](../../traits/stateeffector/) + ‘static>(&mut self, name: &str) -> Option<&mut T>

Find and downcast a state effector by name (mutable).


fn registry(&self) -> &AuxRegistry

Get the auxiliary state registry.


fn inertia(&self) -> &Matrix3<f64>

Get the inertia tensor.


fn body_radius(&self) -> Option<f64>

Get the central body radius (if set).


fn replace_model(&mut self, name: &str, new_model: [Box<dyn [Model<SpacecraftState<F>>](../../traits/model/)>](https://doc.rust-lang.org/std/boxed/struct.Box.html)) -> [Option<[Box<dyn [Model<SpacecraftState<F>>](../../traits/model/)>](https://doc.rust-lang.org/std/boxed/struct.Box.html)>](https://doc.rust-lang.org/std/option/enum.Option.html)

Replace a model by name, returning the old one (if found).

This is used to swap in a model with updated commanded state between integration segments (e.g., MtqAssembly with a new command).

A propulsion model is replaced where it was registered, so a rebuilt thruster assembly stays propulsion and the pool it draws from is untouched: the propellant is the spacecraft’s, and a command does not refill it.


fn model_names(&self) -> Vec<&str>

Names of the registered models, propulsion last.

Every one of them, whether it acts at a given state or not: this takes no state, so it cannot say whether a tank still has propellant. The breakdowns do — they skip evaluating a propulsion model the pool has switched off, and keep its entry as a zero, so a record has one column per name here whatever the tank holds.


fn model_breakdown(&self, t: f64, state: &[AugmentedState<SpacecraftState<F>>](../augmentedstate/)) -> [Vec<(&str, ExternalLoads<F>)>](https://doc.rust-lang.org/std/vec/struct.Vec.html)

Per-model load breakdown at the given state.

The augmented state rather than the plant alone, because whether the propulsion burns is the pool’s mode — the same value the right-hand side gates on. Reading the mass here instead would make a record disagree with the trajectory on a path that settles no boundaries: the mode stays Free and the thrust keeps being integrated, while a mass-based comparison would report none.

A state whose mass is not positive gets the same treatment it gets in the right-hand side, for the same reason: the acceleration a model reports there is an infinity, and a record of an infinity says nothing the trajectory did.

Every registered model gets an entry, in the order model_names(Self::model_names) lists them. A propulsion model the pool has switched off is not evaluated — that is the point of the mode — but its entry is there, a zero: the telemetry that reads this builds one column per model, and a row that drops an entry after depletion leaves that column sparse where a reader wants to see the thrust go to zero.

If the state’s mode vector is not the length the registry says (see check_modes(Self::check_modes)).


fn torque_breakdown(&self, t: f64, state: &[AugmentedState<SpacecraftState<F>>](../augmentedstate/)) -> [Vec<(&str, [arika::frame::Vec3<arika::frame::Body>](../../../../arika/api/structs/vec3/))>](https://doc.rust-lang.org/std/vec/struct.Vec.html)

Per-model disturbance torque in the body frame [N·m], for telemetry.

The vector rather than a magnitude, because a magnitude carries neither the sign nor the axis, and those are what a torque is read for: an attitude disturbance that turns the wrong way looks the same size as one that turns the right way.

The gravity field is absent. It acts on the centre of mass, so it exerts no torque about it; a gravity-gradient torque is a Model(crate::model::Model) and answers under its own name. This is why the list is one entry per model, where acceleration_breakdown(Self::acceleration_breakdown) leads with "gravity".

Every model answers, not only the environmental ones. A magnetorquer and a thruster assembly are Models and are installed as such, so their commanded torques appear here under their own names. A reaction wheel is a StateEffector — it carries its own state — and is absent; its momentum is read from the effector.


fn load_breakdown(&self, t: f64, state: &[AugmentedState<SpacecraftState<F>>](../augmentedstate/)) -> LoadBreakdown<’_>

Both breakdowns from one evaluation of every model.

ExternalLoads carries the acceleration and the torque together, so a caller that wants both — telemetry reporting one sample — has no reason to evaluate every model twice. The panel models make that visible: the shadow geometry is most of their cost, and a 22-panel spacecraft is 50 µs an evaluation.

The two halves are what acceleration_breakdown(Self::acceleration_breakdown) and torque_breakdown(Self::torque_breakdown) answer. Each of those projects from model_breakdown on its own, so a caller wanting one half does not build the other; this exists for the caller wanting both.


fn acceleration_breakdown(&self, t: f64, state: &[AugmentedState<SpacecraftState<F>>](../augmentedstate/)) -> Vec<(&str, f64)>

Acceleration breakdown for telemetry.