Spacecraft Dynamics — Robotics/Spacecraft Dynamics
Robotics/Spacecraft_Dynamics/Spacecraft_Dynamics · 2 input / 4 output port(s) at insert · exports to Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text
Description#
The block's own DESCRIPTION_HTML, rendered verbatim — the same text the config dialog's info panel and the library navigator show. Fix a wrong sentence in the block's .cpp (R-D9), never here.
Spacecraft Dynamics
Robotics / Spacecraft Dynamics
Integrates a spacecraft's orbit and attitude together, coupled by gravity, from a body-axis force and moment:
r′ = v
v′ = R(q)·F/m + g(r)
q′ = ½ Ω(w) q, renormalised every substep
w′ = I−1(M + 3μ/|r|5 (rb
× I rb) − w × I w)
Thirteen states, integrated by the same fixed-step RK4 the rest of the Equations of Motion family uses.
⚠ Both halves already exist in this library and neither can do this. Orbit Propagator Kepler is closed form and takes no force at all; Attitude Dynamics takes the position on a port rather than integrating it. So the two can be wired together for a coasting spacecraft and cannot represent a thrusting one – and thrust is what makes the coupling run both ways, because the force is applied in body axes and so depends on the attitude, while the gravity-gradient torque depends on the position.
Ports
- F – the applied force in body axes, [3,1], newtons. ⚠ Body axes, not inertial: a thruster is bolted to the spacecraft.
- M – the applied moment in body axes, [3,1], newton metres.
- r – position in ICRF, [3,1], metres.
- v – velocity in ICRF, [3,1], metres per second.
- q – the attitude quaternion, [4,1], scalar first, taking body vectors to ICRF. It is renormalised every substep, so it stays unit length for as long as the run does.
- w – the body angular rate, [3,1], degrees per second.
Parameters
- Mass (kg) – positive. It divides the force and nothing else: the mass is fixed, so it does not enter the rotation.
- Inertia – the body inertia tensor, a 3×3, kg·m². It must be invertible; it need not be diagonal or symmetric, and the block does not symmetrise it.
- Initial Position / Initial Velocity – the ICRF state at t = 0, three entries each, metres and metres per second.
- Initial Attitude – the quaternion at t = 0, four entries, scalar first. It is normalised at load.
- Initial Attitude Rate – the body rate at t = 0, three entries, in degrees per second.
- Gravity Model – Point-mass or None. See Notes for why those two and not the other two.
- Gravity Gradient – on or off. With it on the attitude feels the torque the position generates, which is the coupling that makes this one block rather than two.
- Central Body – Earth (μ = 3.986004418×1014 m³/s²), Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Sun or Custom. The same measured table Attitude Dynamics and the Orbit Propagator carry.
- Custom Gravitational Parameter – μ in m³/s², read only when Central Body is Custom.
- Integration Substeps – RK4 substeps per sample, 1 to 100000, default 100. This is how the block integrates a sample; the Simulink block is continuous and has no counterpart, so it does not cross the bridge.
- Sampling Time (s) – zero or less inherits the solver's rate; a positive value runs the block at that period.
Code export
All ten targets: Python, MATLAB, Java, Rust,
C, C++, VHDL, Verilog, SystemVerilog and
PLC Structured Text, from one description of the derivative, so every
target runs the same arithmetic in the same order as the simulation. The state is
carried across samples; each sample is Integration Substeps classical RK4
steps with the inputs held, which is the map Simulink's fixed-step
ode4 runs.
The three HDL targets are simulation-only real
arithmetic, quantized at the port, and a Q16.16 port cannot carry this block's
position or velocity at all: it saturates past about 32767, while a low Earth
orbit is around 6.9 million metres. No configuration escapes that, because this
state is SI metres by the definition of the Simulink block it mirrors, and a
32 km orbit is not one — measured, the three HDL cores return the position
gate as zero, a residual of 100%. The real arithmetic itself is
correct, so a hardware export remains meaningful for the quaternion and
body-rate outputs; the suite reports the three HDL targets as not comparable and
gives that reason, rather than counting them red.
Simulink bridge
Import and export, mapped to Aerospace Blockset's
aerolibsatdyn/Spacecraft Dynamics, with the shape fixed to the
subset below. Every parameter above crosses one for one: Mass (kg) →
mass, Inertia → inertia, Initial Position →
inertialPosition, Initial Velocity →
inertialVelocity, Initial Attitude → attitude,
Initial Attitude Rate → attitudeRate, Gravity Model →
gravityModel, Gravity Gradient → useGravGrad,
Central Body → centralBody, Custom Gravitational Parameter
→ customMu; every option list is Simulink's own spelling, so
lossless. Always written: stateFormatNum ICRF state vector,
attitudeFrame and outportFrame ICRF,
attitudeFormat Quaternion, massType Fixed,
forcesIn and momentsIn on, accelIn,
dateOut, outputAccel, outputAngAccel and
outputFuelStatus off, units Metric (m/s) and
angleUnits Degrees – which is what every measurement behind
this block was taken at. Integration Substeps does not cross, because it
is how this block integrates a sample and the Simulink block is continuous with
no counterpart. That block defines no SampleTime, so the rate stays on
the ICore side – measured.
Notes
- ⚠ THE FORCE IS IN BODY AXES. With gravity off, v′ is R(q)·F/m and not F/m – measured, the body reading matches to 1e-9 where the inertial one is out by 80 %. A force applied in ICRF is the user's to rotate.
- ⚠ Only two of the four gravity models, and the reason is a measurement. Point-mass reproduces the Simulink block to the measurement's own error. Oblate ellipsoid (J2) does not match the classic −1.5 J₂μRe²/r5 form – the best fit is 4.3e-6 relative, where point-mass fits at 5e-8 – so its form is something else and shipping a guess would have been worse than refusing it. Spherical harmonics is EGM2008, a coefficient set no export target can carry. Both are refused with that reason rather than approximated.
- ⚠ The quaternion is renormalised after EVERY substep, and that is not a tidy-up. It is the term that makes the block match: integrating q′ alone leaves the state 2.2e-8 from Simulink's after 10000 substeps, normalising only the output gets 6e-14, and normalising every substep gets one ulp.
- The rate is in degrees per second at the port and radians inside. The initial rate is read in degrees too.
- The inertia is not symmetrised. An asymmetric tensor is integrated as given, which is what the Simulink block does; if that is not what you meant, it is your tensor that is wrong.
- No state space: the dynamics are nonlinear, so model reduction correctly declines the block.
- ⚠ This block does not replace Orbit Propagator Kepler, which is closed form and therefore exact and cheap for a coasting orbit. Reach for this one when there is a force.
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Spacecraft_Dynamics/Spacecraft_Dynamics |
| family | Robotics/Spacecraft_Dynamics |
| solver environment class | ICoreBlock_0_Robotics_1_Spacecraft_Dynamics_2_Spacecraft_Dynamics |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Spacecraft_Dynamics/Spacecraft_Dynamics/ICoreBlock_0_Robotics_1_Spacecraft_Dynamics_2_Spacecraft_Dynamics.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Spacecraft_Dynamics/Spacecraft_Dynamics/ICoreBlock_0_Robotics_1_Spacecraft_Dynamics_2_Spacecraft_Dynamics.h |
| default size on canvas | 180 × 120 px |
| ports at insert | 2 in, 4 out |
| code generators implemented | Python, MATLAB, Java, Rust, C, C++, VHDL, Verilog, SystemVerilog, PLC Structured Text |
Ports#
| # | Direction | Signal type | Description label |
|---|---|---|---|
| 1 | in | ICoreDouble | F |
| 2 | in | ICoreDouble | M |
| 3 | out | ICoreDouble | r |
| 4 | out | ICoreDouble | v |
| 5 | out | ICoreDouble | q |
| 6 | out | ICoreDouble | w |
Ports the constructor creates. A block whose port list changes with its configuration adds or removes ports at load time; the count above is the one a freshly inserted block has.
Configuration variables#
| Config variable | Default | Simulink parameter |
|---|---|---|
Mass (kg) | 4.0 | mass |
Inertia | [0.2273 0 0; 0 0.2273 0; 0 0 0.0040] | inertia |
Initial Position | [6878137; 0; 0] | inertialPosition |
Initial Velocity | [0; 6000; 4000] | inertialVelocity |
Initial Attitude | [1; 0; 0; 0] | attitude |
Initial Attitude Rate | [0; 0; 0] | attitudeRate |
Gravity Model | Point-mass%~%None~~Point-mass | gravityModel |
Gravity Gradient | on%~%off~~on | useGravGrad |
Central Body | comboOf(names, 11, "Earth") | centralBody |
Custom Gravitational Parameter | 4.2828314258067e13 | customMu |
Integration Substeps | 100 | not crossed |
Every block also carries Sampling Time (s) from ICoreBlockSolverEnvironment: zero or less inherits the solver's rate, a positive value runs the block at that period.
Simulink bridge#
| support | Support::Both |
| Simulink path | aerolibsatdyn/Spacecraft Dynamics |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| deliberately not crossed | Integration Substeps |
| always set | stateFormatNum = ICRF state vector, attitudeFrame = ICRF, attitudeFormat = Quaternion, outportFrame = ICRF, massType = Fixed, forcesIn = on, momentsIn = on, accelIn = off, dateOut = off, outputAccel = off, outputAngAccel = off, outputFuelStatus = off, units = Metric (m/s), angleUnits = Degrees |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Mass (kg) | mass | passes through |
Inertia | inertia | passes through |
Initial Position | inertialPosition | passes through |
Initial Velocity | inertialVelocity | passes through |
Initial Attitude | attitude | passes through |
Initial Attitude Rate | attitudeRate | passes through |
Gravity Model | gravityModel | None → None, Point-mass → Point-mass |
Gravity Gradient | useGravGrad | on → on, off → off |
Central Body | centralBody | Earth → Earth, Moon → Moon, Mercury → Mercury, Venus → Venus, Mars → Mars, Jupiter → Jupiter, Saturn → Saturn, Uranus → Uranus, Neptune → Neptune, Sun → Sun, Custom → Custom |
Custom Gravitational Parameter | customMu | passes through |
Caveat (shown to the user): the SUBSET that is exact, and the subset is chosen by measurement rather than by taste. An ICRF Cartesian initial state, a fixed mass, a quaternion attitude in ICRF and ICRF outputs; forces and moments in, r/v/q/w out, no date, no acceleration and no fuel port. Gravity is Point-mass or None: "Oblate ellipsoid (J2)" does NOT match the classic -1.5 J2 mu Re^2/r^5 form (best fit 4.3e-6 relative, where point-mass fits at 5e-8), so its form is something else and is refused rather than guessed, and "Spherical harmonics" is EGM2008, a coefficient set no export target can carry. Units are Metric (m/s) and angles Degrees, which is what every measurement behind this block was taken at. "Integration Substeps" is how this block integrates a sample and has no counterpart. The Simulink block is continuous and has no SampleTime
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The checker has a blind spot here — it could not resolve something (a grouped port bullet, a computed config name), which is reported and never counted as a pass. A reader has to settle it:
B0every stimulus in the sample errored — cross-checks skipped
The verdict above is
tools/docs/check_block_descriptions.py(P7.1), which compares LISTS. It cannot read a sentence: "stateless" on a block with a state, an initial-value semantic the recursion does not implement, a "not synthesizable" caveat the HDL banner contradicts. That is the agent audit (P7.3) on BLOCK_DESCRIPTION_AUDIT.md, and this tool's green is not a substitute for one.
File banner (developer view)#
The top comment of the block's .cpp — the maths, the realization and the export strategy, addressed to whoever changes it. It must not contradict the description above (P7.5).
Spacecraft Dynamics -- the orbit and the attitude integrated together, coupled by gravity r' = v v' = R(q) F/m + g(r) g = -mu r/|r|^3, or nothing q' = 1/2 Omega(w) q, RENORMALISED after every substep w' = I^-1 ( M + 3mu/|r|^5 (r_b x I r_b) - w x I w ), r_b = R(q)' r
Thirteen states on the Equations_Of_Motion family's RK4 scaffold (ICoreEomRk4), whose association reproduces Simulink's fixed-step ode4 bit for bit.
⚠ WHY THIS BLOCK EXISTS WHEN BOTH HALVES ALREADY DO.
Orbit_Propagator_Kepleris CLOSED FORM and takes no force at all;Attitude_Dynamicstakes the position on a PORT rather than integrating it. So the pair can be wired together for a COASTING spacecraft and cannot represent a THRUSTING one -- and thrust is exactly what makes the coupling two-way, because the force is applied in BODY axes and therefore depends on the attitude. Board rule 4 was checked against both blocks before this one was written.⚠ EVERY TERM WAS IDENTIFIED BY MEASUREMENT AGAINST THE SIMULINK BLOCK, not read off a textbook, and each identification is a number:
- THE FORCE IS IN BODY AXES. With gravity off, v' is R(q)(F/m) and not F/m -- the two
differ by a factor of the attitude, and the body-axis reading matches to 1e-9 where the inertial one is out by 80 %.
- mu IS 3.986004418e14 for Earth, the same table Attitude_Dynamics and the Orbit
Propagator carry: -mu r/|r|^3 matches the measured point-mass acceleration to 5e-8, which is the finite difference's own error (3.986005e14 is three times worse).
- THE GRAVITY-GRADIENT TORQUE IS 3mu/|r|^5 (r_b x I r_b) AND THE RATE PORT IS IN DEGREES.
Measured against that formula on an asymmetric inertia the ratio was 57.295821, 57.295740 and 57.295869 on the three components -- 180/pi to five digits, on all three.
- THE QUATERNION IS HAMILTON, BODY TO INERTIAL, WITH THE RATE ON THE RIGHT:
q' = 1/2 q (x) [0, w] matches to 3e-6 where 1/2 [0, w] (x) q is out by 62 %.
- ⚠ AND THE QUATERNION IS RENORMALISED, WHICH IS THE ONE TERM A DERIVATION WOULD MISS.
Simulink's answer after 10000 substeps has |q| = 1.0 EXACTLY. Integrating q' alone leaves |q| adrift by 2e-8 over that run and the state 2.2e-8 away from Simulink's; renormalising after every substep closes it to ONE ULP. Dividing only the OUTPUT by |q| gets 6e-14 -- better, and still not it.
⚠ THE WHOLE MODEL, END TO END, AGAINST A REAL SIMULINK RUN: 10000 RK4 substeps of 1e-4 s with force and moment held, an asymmetric inertia and a non-identity attitude --
r relative error EXACTLY 0 v relative error EXACTLY 0
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at: ICore Blocks/Home/Spacecraft Dynamics. That is a fact about the single-block rig, not a verdict on the block: an offline batch fit, a block whose output only appears at onSolverFinish, or one that needs a driven environment cannot be exercised alone.
Category unsampled · sample time 0.1 · 60 steps · commit 223010ca1 · produced by docsSample --out <folder> --blocks Robotics/Spacecraft_Dynamics/Spacecraft_Dynamics --steps 60
Sample data: docs/generated/samples/Robotics__Spacecraft_Dynamics__Spacecraft_Dynamics.json