EOM 3DOF Simple Variable Mass Body Axes — Robotics/Equations Of Motion
Robotics/Equations_Of_Motion/EOM_3DOF_Simple_Variable_Mass_Body_Axes · 4 input / 5 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.
EOM 3DOF Simple Variable Mass Body Axes
Robotics / Equations Of Motion
The longitudinal equations of motion of a rigid body that burns fuel, carried in body axes. The mass is a state driven by the mass flow rate and held between the empty and full masses; the pitch inertia follows it linearly between its empty and full values: Iyy(m) = Ie + (m − me)·(If − Ie)/(mf − me).
- u' = Fx/m − q·w − g·sin θ, w' = Fz/m + q·u + g·cos θ
- m' = dm/dt, except zero while the tank is empty and draining or full and filling; q' = (M − dIyy/dt·q)/Iyy(m) with dIyy/dt = m'·(If − Ie)/(mf − me); θ' = q
- xe' = u·cos θ + w·sin θ, ze' = −u·sin θ + w·cos θ
Ports
- Fx – the force along the body x axis, a scalar [1,1].
- Fz – the force along the body z axis (positive down), a scalar [1,1].
- M – the pitching moment about the centre of gravity, a scalar [1,1].
- m_dot – the mass flow rate dm/dt (negative while burning), a scalar [1,1].
- theta – the pitch attitude θ in radians, [1,1].
- q – the pitch rate in rad/s, [1,1].
- Xe – the position [xe; ze] in the flat-earth frame, [2,1].
- Vb – the body velocity [u; w], [2,1].
- Fuel – the fuel tank status: 1 full (m ≥ mf), −1 empty (m ≤ me), 0 otherwise, [1,1].
Parameters
- Units – Metric (MKS) (the default) or English (velocity in ft/s). The two are the same arithmetic – the gravity is a parameter – so the choice only says which units the numbers are in.
- Initial Airspeed – V0, a scalar. Defaults to 100.
- Initial Pitch Attitude – θ0 in radians. Defaults to 0.
- Initial Body Rotation Rate – q0 in rad/s. Defaults to 0.
- Initial Incidence – α0 in radians: the initial body velocity is V0·[cos α0; sin α0]. Defaults to 0.
- Initial Position [x z] – the initial [xe ze], two values. Defaults to [0 0].
- Initial Mass – m0, from the empty to the full mass. Defaults to 1.
- Empty Mass – me, the lower limit. Defaults to 0.5.
- Full Mass – mf, the upper limit, greater than the empty mass. Defaults to 3.
- Empty Inertia – Ie, the pitch inertia at the empty mass, > 0. Defaults to 0.5.
- Full Inertia – If, the pitch inertia at the full mass, > 0. Defaults to 3.
- Gravity – g, a scalar. Defaults to 9.81.
- Integration Substeps – M, how many fourth-order Runge-Kutta steps a sample is integrated with on the discrete solver and in exported code, a whole number of 1 or more. Defaults to 100. No Simulink counterpart.
- 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. A core holds the seven states, publishes the outputs from them, and integrates one sample with the inputs held and M Runge-Kutta substeps – with M = 100, the same computation as Simulink's fixed-step ode4 at one hundredth of the sample time, the fuel limit applied at every stage and the mass clamped after every substep exactly as the Simulink block does it.
The three HDL targets are simulation-only real arithmetic, quantized at the port: a sine of a state has no Q16.16 form. The cores simulate correctly and are not offered as synthesizable.
Simulink bridge
Import and export, mapped to Aerospace Blockset's aerolib3dof2/Simple Variable Mass 3dof (Body Axes): Units → units (the two offered values 1:1), Initial Airspeed → v_ini, Initial Pitch Attitude → theta_ini, Initial Body Rotation Rate → q_ini, Initial Incidence → alpha_ini, Initial Position [x z] → pos_ini, Initial Mass → mass, Empty Mass → mass_e, Full Mass → mass_f, Empty Inertia → Iyy_e, Full Inertia → Iyy_f, Gravity → g. Always emitted with axes = Body, mtype = Simple Variable, g_in = Internal and vre_flag and mass_flag off: each of those moves that block's port list, and this block has one; mdot_flag moves none and is emitted at its default, on. The knots unit system is not offered – it converts velocities inside the integration – and an imported one is reported. The dialog's Iyy is not read by the Simulink block at this mass type (measured) and is not offered. "Sampling Time (s)" does not cross: the Simulink block is continuous and defines no SampleTime.
Notes
- Stateful, continuous and nonlinear: seven continuous states. The outputs are states, so the block has no direct feedthrough and a loop through it is not an algebraic loop.
- At a limit the mass stops and the inertia rate with it, so the q' equation loses its dIyy/dt term the moment the tank empties or fills. On the continuous solver the mass is held by the zero rate alone and can overshoot a limit by one solver step; the discrete solver and every exported core also clamp it, as Simulink does.
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Equations_Of_Motion/EOM_3DOF_Simple_Variable_Mass_Body_Axes |
| family | Robotics/Equations_Of_Motion |
| solver environment class | ICoreBlock_0_Robotics_1_Equations_Of_Motion_2_EOM_3DOF_Simple_Variable_Mass_Body_Axes |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Equations_Of_Motion/EOM_3DOF_Simple_Variable_Mass_Body_Axes/ICoreBlock_0_Robotics_1_Equations_Of_Motion_2_EOM_3DOF_Simple_Variable_Mass_Body_Axes.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Equations_Of_Motion/EOM_3DOF_Simple_Variable_Mass_Body_Axes/ICoreBlock_0_Robotics_1_Equations_Of_Motion_2_EOM_3DOF_Simple_Variable_Mass_Body_Axes.h |
| default size on canvas | 150 × 130 px |
| ports at insert | 4 in, 5 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 | Fx |
| 2 | in | ICoreDouble | Fz |
| 3 | in | ICoreDouble | M |
| 4 | in | ICoreDouble | m_dot |
| 5 | out | ICoreDouble | theta |
| 6 | out | ICoreDouble | q |
| 7 | out | ICoreDouble | Xe |
| 8 | out | ICoreDouble | Vb |
| 9 | out | ICoreDouble | Fuel |
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 |
|---|---|---|
Units | Metric (MKS)%~%English (velocity in ft/s)~~Metric (MKS) | units |
Initial Airspeed | 100 | v_ini |
Initial Pitch Attitude | 0 | theta_ini |
Initial Body Rotation Rate | 0 | q_ini |
Initial Incidence | 0 | alpha_ini |
Initial Position [x z] | [0 0] | pos_ini |
Initial Mass | 1.0 | mass |
Empty Mass | 0.5 | mass_e |
Full Mass | 3.0 | mass_f |
Empty Inertia | 0.5 | Iyy_e |
Full Inertia | 3.0 | Iyy_f |
Gravity | 9.81 | g |
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 | aerolib3dof2/Simple Variable Mass 3dof (Body Axes) |
| 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 | axes = Body, mtype = Simple Variable, g_in = Internal, vre_flag = off, mdot_flag = on, mass_flag = off |
| ICore config | Simulink parameter | Value translation |
|---|---|---|
Units | units | Metric (MKS) → Metric (MKS), English (velocity in ft/s) → English (velocity in ft/s) |
Initial Airspeed | v_ini | passes through |
Initial Pitch Attitude | theta_ini | passes through |
Initial Body Rotation Rate | q_ini | passes through |
Initial Incidence | alpha_ini | passes through |
Initial Position [x z] | pos_ini | passes through |
Initial Mass | mass | passes through |
Empty Mass | mass_e | passes through |
Full Mass | mass_f | passes through |
Empty Inertia | Iyy_e | passes through |
Full Inertia | Iyy_f | passes through |
Gravity | g | passes through |
Caveat (shown to the user): aerolib3dof2/Simple Variable Mass 3dof (Body Axes) is continuous and has NO SampleTime parameter (verified against the R2026a block dialog). axes, mtype, g_in, vre_flag and mass_flag are pinned because each moves that block's port list (measured); mdot_flag moves none and is pinned at its measured default; the kts unit system is not offered because it converts velocities inside the integration; the dialog's Iyy is not read at this mass type (measured); "Integration Substeps" is how this block integrates a sample and has no counterpart
Catalog contract: src/ICoreBlocks/ICoreCoder/ICoreCommandSystem/SimulinkBridge/ICoreSimulinkBlockCatalog.h
Description vs code#
The lists agree. check_block_descriptions.py finds no disagreement between the description's Ports, Parameters, Code export and Simulink bridge lists and the code's.
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).
Simple Variable Mass 3dof (Body Axes) -- longitudinal rigid-body equations of motion, simple variable mass State [u w q theta xe ze m]. Inputs Fx, Fz, M and the mass flow rate dm/dt. The mass is a STATE limited to [m_e, m_f], and the inertia is interpolated between its empty and full values:
Iyy(m) = Iyy_e + (m - m_e)*k, k = (Iyy_f - Iyy_e)/(m_f - m_e) m' = dm/dt, except 0 while m <= m_e with dm/dt < 0 or m >= m_f with dm/dt > 0 q' = (M - (k*m')*q)/Iyy(m) u' = (Fx/m - q*w) - g*sin(theta) w' = (Fz/m + q*u) + g*cos(theta) and m is clamped to [m_e, m_f] after every step.
⚠ MEASURED AGAINST R2026a (a compiled EOM3DOFNoAccel block), and the LIMIT is where the candidates split: inside the limits the equations reproduce the block to the last printed digit, and through a limit crossing in EITHER direction exactly one of sixteen candidate limit rules did -- the gate tested on the RAW mass at every Runge-Kutta stage, the raw stage mass in F/m and in Iyy(m), and the state clamped after the step. A gate decided once per step (a Simulink limited Integrator's rule) is out by 4e-6; a gate on the clamped mass by 9e-10. The rest of what the measurement settled:
- The dialog's Iyy is NOT read: the inertia is Iyy(m) from the empty and full values alone.
- The last output is the fuel tank status: +1 at m >= m_f, -1 at m <= m_e, 0 between.
- "Metric" and "English (velocity in ft/s)" are the same arithmetic; kts is not offered (it
converts velocities inside the integration). axes, mtype, g_in, vre_flag and mass_flag move the Simulink block's port list and are pinned; mdot_flag moves none and is pinned at its measured default.
A continuous block with a real derivative, like the fixed-mass pair; the discrete path and every exported core run ICoreEomRk4's map -- M RK4 substeps in ode4's association, the clamp as its post-step -- which at M = 100 is Simulink's fixed-step ode4 at Ts/100, step for step.
Sample results#
The same rig also ran:
| Stimulus | What it is | Output range |
|---|---|---|
impulse | Impulse: one sample of 1 at k = 5, 0 elsewhere (Repeating Sequence Stair) | 0 … 0.4865 |
ramp | Ramp: slope 1 from t = 0 | 0 … 11.25 |
sine | Sine Wave: amplitude 1, 2 rad/s, no phase, no bias | 0 … 1.824 |
table | Repeating Sequence Stair: [-2 -1 -0.5 0 0.5 1 2 3], one entry per sample | -0.2405 … 1.927 |
Plotted: step — Step: 0 -> 1 at t = 1 s
Category dynamic · sample time 0.1 · 60 steps · commit 9a488f18c · produced by docsSample --out <folder> --blocks EOM_3DOF_Custom_Variable_Mass_Body_Axes EOM_3DOF_Custom_Variable_Mass_Wind_Axes EOM_3DOF_Simple_Variable_Mass_Body_Axes EOM_3DOF_Simple_Variable_Mass_Wind_Axes --steps 60 · data docs/generated/samples/Robotics__Equations_Of_Motion__EOM_3DOF_Simple_Variable_Mass_Body_Axes.json · the SVG is generated from those numbers by tools/docs/plot_svg.py, so it is a run and not a drawing (R-D10).