Parts¶
A Part is an atomic unit of behavior on a craft. The declaration
sentinels (Parameter, State, Input, Output, Noise) are
documented on the base classes; the stock parts below are what you add to
a craft.
Declaration model¶
manta.parts.Part ¶
Bases: DeclarationHost
Base class for all parts.
Subclasses declare their interface via class-attribute Parameter
(and later Input/State) entries, then implement update(ctx) to
contribute a Wrench per tick.
Every Part has a static mount pose relative to its parent's output
frame: mount_offset, an (x, y, z) position, and
mount_orientation, a wxyz quaternion rotating the part's OWN axes
into the parent's output frame.
Both carry the mount_ prefix deliberately. It separates the
STATIC installation from the DYNAMIC kinematics that share the same
vocabulary — ctx.position and ctx.orientation are what the part
is doing this tick, self.mount_offset and self.mount_orientation
are where it was bolted — and it leaves orientation free for the
thing parts actually output (an AHRS reports one). The framework uses the pose to express the part's
kinematics and to roll its wrench up into the parent's frame
(force-at-offset → torque contribution at parent origin).
The two compose in the order you would bolt the thing on: the
offset is measured in the PARENT's frame ("put it here"), then the
rotation turns the part in place ("pointing that way"). So a
thruster canted 30° outboard is one mount_orientation, not a hand-
rotated thrust vector, and a sensor mounted on its side reports in
a frame that is genuinely rotated rather than one the measurement
function has to correct after the fact.
Every Part also has a parent attribute — either another Part
(typically a CompositePart like the craft's RootPart or a joint)
or None for the unattached state. Parents are set by
CompositePart.add(child) when a child is attached. The craft's
part tree is rooted at Craft.root.
Construction signature::
class Mass(Part):
mass: Scalar = Parameter(1.0)
Mass("body") # at origin of parent
Mass("battery", mass=2.0,
mount_offset=(0.0, 0.0, -0.5)) # 0.5 m below parent origin
Source code in manta/parts/base.py
mounted_upright
property
¶
True when this part's static mount rotation is identity — the common case, and the fast path the kinematic pass takes.
noise_R ¶
Measurement-noise covariance for a declared Noise slot.
Reads the per-instance <name>_sigma attribute (set at
construction time, default from the declaration). Returns:
* σ² (float) for scalar noise.
* σ²·I_d (np.ndarray, d×d) for d-vector noise (sized
off signal_manifold.ambient_dim).
User-facing convenience: the framework's own EKF wiring reads
<name>_sigma directly, but hand-driven filter code can size a
measurement update without restating σ²:
ekf.update(h, z, R=imu.noise_R("gyro_noise")).
Source code in manta/parts/base.py
update ¶
Compute this part's wrench contribution for the current tick.
ctx is the manta.craft.TickContext. Subclasses must override
and return a Wrench or PartUpdate.
Source code in manta/parts/base.py
on_world_resolve ¶
Compile-time resolution hook — called once per part by snapshot resolution, after planets and field sources have registered their disturbances, with the world and this part's craft. The generic slot for anything a part can only do against the finished model:
- resolve cross-craft wiring (a camera collecting the optical ellipsoids it can see),
- validate structural invariants that need the complete part tree (a thermal link's same-craft check, a root-mount requirement)
so a user-authored part gets the same compile-time treatment as
the stock ones — no isinstance ladder in World resolution.
Default: no-op. Raise to reject a bad configuration at
resolution time (before any tracing) rather than mid-trace.
Source code in manta/parts/base.py
manta.parts.CompositePart ¶
Bases: Part
A Part that hosts other Parts as children.
Children mount on this part's output frame. For a non-joint
CompositePart the output frame is identical to the part's own
frame — which is its parent's output frame displaced by
mount_offset and turned by mount_orientation. An ArticulatedJoint
overrides this — a RevoluteJoint's output frame additionally
rotates by the joint angle (a PrismaticJoint's translates by its
displacement).
add(child) appends a child Part, sets its parent to self, and
returns the child (so chained construction reads naturally):
gimbal = pan.add(RevoluteJoint("tilt", axis=(0, 1, 0)))
gimbal.add(Mass("camera", mass=0.05, mount_offset=(0.1, 0, 0)))
Source code in manta/parts/base.py
remove ¶
Detach one direct child by instance or name and return it.
Source code in manta/parts/base.py
walk ¶
DFS over this part's subtree, yielding self then each descendant.
update ¶
CompositePart has no intrinsic wrench contribution by default — subclasses (RootPart, joints, etc.) override if they need to. PartFrame, like every part's update: the tick rolls each wrench up from the part's own frame, so a CraftFrame zero here would FrameError the compile for any non-root composite.
Source code in manta/parts/base.py
manta.parts.Parameter ¶
Bases: _Declaration
Frozen-at-config-time value. Set when the user constructs a Part, used as a constant during graph tracing.
Concrete attribute types are deduced from the default value at init
time — a Parameter(1.0) becomes a Python float; a
Parameter((1.0, 0.0, 0.0)) stays a tuple until the part's update()
promotes it to an IR vector (Vec3[F].constant / Vec3[F].coerce).
Args:
manifold — optional Manifold instance or shortcut string
("R1", "R3"; same vocabulary as Noise).
Declaring it makes the parameter promotable: a
transform constructed with parameters=[...] (system
identification — see manta.fit) can promote it from
a baked graph constant to a live graph input named
<craft>.<part>.<param>. Inside update() a promoted
parameter reads as an IR value (the trace binds it),
so parts consume promotable parameters through the
.coerce factory, which accepts both forms.
None (default) — a plain Python config value.
frame — Frame tag, consumed when manifold is a shortcut
resolving to a vector manifold. The promoted input's
frame; must match what update() composes it with.
numeric — whether the declaration participates in the framework's
finite-number validation. Set false only for typed object
configuration whose protocol the owning Part validates.
Source code in manta/parts/_declarations.py
manta.parts.State ¶
Bases: _Declaration
Per-tick state slot.
Declared at class scope. The framework:
* Creates a graph input named "update(), so self.<state_name> reads the symbolic current value.
* Reads the new value from PartUpdate.new_state["<state_name>"]
and emits it as a graph output of the same name. Omitted states
pass through unchanged.
Args:
init Python value (default initial value across compiles).
For R1 a float; for R3 a length-3 tuple / ndarray; for
SO(3) a length-4 quaternion (w, x, y, z).
manifold String shortcut ('R1', 'R3') or a Manifold instance.
SO(3) state is fully supported — pass an explicit
SO3Manifold(from_frame=..., to_frame=...) instance
(the string shortcut is intentionally disallowed because
SO(3) needs the dual-frame parametrization). The slot
then evolves on the manifold: the part integrates it
with manifold.boxplus(q, ω·dt), the framework keeps it
unit-normalized, and the EKF/LQR linearization gives it
a 3-dim tangent automatically. See tests/test_so3_state.
frame Frame tag for R3 state. Default CraftFrame. Ignored
for R1 and SO(3) (the latter's frames live on the
manifold). Folded into the Manifold instance.
state.manifold always reads back as a Manifold instance; the
string form is normalized at construction.
Source code in manta/parts/_declarations.py
manta.parts.Input ¶
Bases: _Declaration
Per-tick external value.
Declared at class scope on a Part. The framework:
* Creates a graph input named "update(), so self.<input_name> reads the current value.
* Initial state from Craft.initial_state() includes the input slot
seeded with the declaration's default (or the construction-time
override if the user passed one).
* Inputs pass through Sim.step's merge — they persist
between steps until the user overrides. This makes per-tick
commands ergonomic: set once, tick repeatedly, change when you
want.
Args:
default — Python value used to seed the initial state. May be
overridden at construction (Motor("m", torque_cmd=0.5))
in which case the override becomes the seed.
The semantic distinction from Parameter: Parameter values are
frozen into the compiled graph as constants; Input values are
re-evaluated each tick from the state dict.
Source code in manta/parts/_declarations.py
manta.parts.Output ¶
Bases: _Declaration
Per-tick value produced by a part (sensor reading, derived quantity, telemetry signal).
Declared at class scope. The part writes its computed value via
PartUpdate.outputs["<name>"] = <Vec3 | Scalar | …>. The framework
emits the value as a graph output named "
Source code in manta/parts/_declarations.py
manta.parts.Noise ¶
Bases: _Declaration
Abstract base for noise-channel declarations.
Subclasses set class-level metadata (kind, contributes_state)
and implement synthesize() (the per-tick IR plumbing). Backends
key on signal_manifold.kind via their own registry — no
isinstance(WhiteNoise) dispatch anywhere in the codebase.
Concrete subclasses:
-
WhiteNoise— per-tick i.i.d. Gaussian. The framework creates a graph input named<part>.<noise_name>, rebinds the part attribute to that input, and the part adds it directly into its sensor reading (or process expression). σ is the per-tick measurement stddev.kind = "white". -
RandomWalkNoise— random-walk bias. The framework synthesizes:- A state slot
<part>.<noise_name>holding the bias. - A driver noise input
<part>.<noise_name>_driver. - A state update each tick:
bias_next = bias + sqrt(dt) · driver, driver ~ N(0, σ²).
Inside
update(),self.<noise_name>reads the bias state (the slowly-drifting current value). σ has continuous σ/√Hz semantics; per-tick bias variance is dt·σ².kind = "random_walk".
- A state slot
-
GaussMarkovNoise— first-order Gauss–Markov (exponentially correlated) error with correlation time τ and stationary variance σ². Same state-slot / driver plumbing as the random walk, with the exact discrete transition φ = exp(-dt/τ), e_next = φ · e + sqrt(1 − φ²) · driver, driver ~ N(0, σ²), so the slot's variance stays at σ² in steady state and the auto-assembled process noise is(1 − φ²)·σ²per tick — no Euler approximation anywhere.kind = "gauss_markov".
Args:
signal_manifold — Manifold instance OR shortcut string. The
manifold of the symbol user code reads as
self.<name>. Shortcuts: "R1" (scalar),
"R3" (combine with frame=). Default
"R3". Same vocabulary as State(manifold=).
frame — Frame class, only consumed when
signal_manifold is a shortcut and resolves
to a vector-typed manifold. Ignored otherwise.
sigma — 1-σ standard deviation, scalar (isotropic
across axes). See subclass docstrings for
unit conventions.
Source code in manta/parts/_declarations.py
resolved_signal_manifold ¶
Return self.signal_manifold with any unresolved frame
substituted from default_frame. Used at IR synthesis time;
the unresolved form keeps R3Manifold(frame=None) legal so
a part can declare a noise without committing to a frame
until the compiler knows which one it's in (CraftFrame for
parts, WorldFrame for disturbances).
Source code in manta/parts/_declarations.py
state_manifold ¶
Manifold of the synthesized state slot, or None. For RW the state lives in the same space as the per-tick signal.
Source code in manta/parts/_declarations.py
runtime_attributes ¶
The per-instance attributes this channel exposes on its owner,
attr -> (default, allow_zero). DeclarationHost seeds them from
the declaration and accepts constructor overrides of the same
names; is_active / synthesize read them back at compile time.
Every channel has <name>_sigma; subclasses add their own
(GaussMarkovNoise adds <name>_tau).
Source code in manta/parts/_declarations.py
is_active ¶
Is this channel currently producing nonzero output? Reads
the runtime <name>_sigma attribute on the owner.
driver_input_name ¶
The name of this channel's per-tick stochastic input. For
White noise the signal IS the driver (same name); for RW the
driver is a separate <name>_driver input distinct from the
bias state name.
Source code in manta/parts/_declarations.py
initial_state_entries ¶
Names → zero values this channel contributes to the seed state dict (state_spec.unpack-compatible). Inert RW channels return an empty dict; everyone else seeds at least the signal slot.
Source code in manta/parts/_declarations.py
synthesize ¶
Build one tick's worth of IR plumbing for this channel. Subclasses implement; the world-tick compiler calls this once per noise declaration per owner.
Source code in manta/parts/_declarations.py
manta.parts.WhiteNoise ¶
Bases: Noise
Per-tick i.i.d. Gaussian noise channel. σ is the per-tick stddev.
Source code in manta/parts/_declarations.py
manta.parts.RandomWalkNoise ¶
Bases: Noise
Random-walk bias channel. σ has σ/√Hz drift-density units.
Source code in manta/parts/_declarations.py
manta.parts.GaussMarkovNoise ¶
Bases: Noise
First-order Gauss–Markov error channel: correlation time tau
(seconds, > 0) and stationary 1-σ sigma (signal units).
The synthesized state slot holds the correlated error itself; the
exact discrete transition φ = exp(-dt/τ) keeps the slot's variance
at σ² for any tick length. With sigma == 0 the channel is inert
(no slot, no driver), exactly like RandomWalkNoise.
Source code in manta/parts/_declarations.py
manta.parts.PartUpdate ¶
Bundle returned by Part.update(ctx) describing this tick's
contributions: a wrench (force + torque on parent in CraftFrame), new
values for any declared State slots, any declared Output values the
part produces, and the rates its I/O runs at.
Construction::
return PartUpdate(wrench, {"angle": a})
return PartUpdate(wrench=w, new_state={"angle": a, "rate": r})
return PartUpdate(wrench=w, outputs={"gyro": gyro_vec},
rates={"gyro": self.rate})
rates maps this part's Output slots and/or Input attribute names to
a positive rate in Hz (None ⇒ every tick). The symbolic world tick
stays a pure function: Sim.module() uses Output rates to partition
measurement-only expressions into independently scheduled kernels, and
the simulation runtime holds each reading between acquisitions. An Input
rate remains an intake-contract annotation; the command transport owns
its ZOH policy because an input that contributes force or state must
remain in every plant integration step. Deploy estimator kernels remain
continuous measurement functions and are invoked only when an external
observation arrives.
Stateless parts can return a bare Wrench instead — the framework
wraps it as PartUpdate(wrench=w) automatically.
Source code in manta/parts/_declarations.py
Structure¶
manta.parts.Mass ¶
Bases: Part
A lump of mass with diagonal inertia tensor.
Parameters:
mass — kilograms. Promotable (system-ID target).
moi — 3-tuple, diagonal MOI tensor (Ixx, Iyy, Izz) about the
part's own COM, in part frame. Defaults to zero (point
mass). Promotable, like mass: a tunable transform
(Sim(world, parameters=[...]) / Fit) promotes it to
a live R3 input and the inertia rollup keeps it
symbolic.
Gravity contribution is applied automatically whenever a
GravityField is registered on the world: F = m · g(p_world),
sampled at the part's anchor position. With no GravityField
registered the contribution is explicitly zero (gravity_at
branches on ctx.has_field) — a free-space world is legitimate,
not a configuration error.
The part's spatial location is set via its mount_offset parameter
(inherited from Part). Aggregation at the Craft level rolls these
individual contributions into total mass, COM, and MOI about craft
origin via parallel-axis lifts.
Source code in manta/parts/structure/mass.py
manta.parts.PointBuoy ¶
Bases: Part
Single-point buoyancy displacing a fixed volume.
Parameters: volume — m³ displaced by the buoyancy element. Default 1e-3.
Force = ρ(p_world) · V · (a_fluid - g) at the part's mount point, rotated from anchor to craft frame, applied at the offset (so the framework lifts force-at-offset → body-frame torque for tilt response).
Source code in manta/parts/structure/point_buoy.py
manta.parts.DisplacementHull ¶
DisplacementHull(name, *, dimensions, displacement_volume=None, hydrostatic_offset=(0, 0, 0), drag_coefficients=(0.2, 0.8, 1.0), reference_areas=None, sample_resolution=(5, 2, 8), **mount_overrides)
Bases: CompositePart
Low-speed surface-piercing displacement hull.
Args:
dimensions:
(length, beam, height) of the ellipsoidal hydrostatic sample
envelope, metres, in the hull part frame.
displacement_volume:
Full-submersion displaced volume in m³. Defaults to the exact
volume pi/6 * length * beam * height of the envelope. Set it
from a measured displacement calibration when available.
hydrostatic_offset:
Translation of the sample cloud in the hull frame, metres. This
is the explicit centre-of-buoyancy calibration knob.
drag_coefficients:
Per-axis non-negative quadratic (Cx, Cy, Cz). Body x is
normally longitudinal, y lateral, and z vertical.
reference_areas:
Optional per-axis drag reference areas in m². Defaults to the
ellipsoid's projected frontal, lateral, and planform areas.
sample_resolution:
(axial, radial, circumferential) product-quadrature counts.
The circumferential count must be even. (5, 2, 8) gives 80
buoy/drag pairs and is the practical default; convergence should
be checked for the craft's draft and sea-state bandwidth.
mount_offset and mount_orientation are the normal Part mount
pose and apply to the entire generated sample cloud.
Source code in manta/parts/structure/displacement_hull.py
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | |
displaced_volume_below ¶
Discrete displaced volume below a flat hull-frame waterline.
This calibration helper uses a hard horizontal cut through sample centres. Runtime physics does not: each child queries the world's smooth, possibly moving fluid boundary. Use this helper only to compare resolutions or choose an initial calm-water draft.
Source code in manta/parts/structure/displacement_hull.py
manta.parts.Collider ¶
Bases: Part
Point contact element backed by the registered CollisionField.
Parameters: stiffness — N/m. Spring constant of the contact normal-force. Bigger = stiffer contact. Default 5e3. damping — N·s/m. Damper coefficient for the relative velocity along the outward normal direction. Bigger = more energy dissipation per bounce. Default 50.0. friction — N·s/m. Viscous TANGENTIAL friction: opposes the contact point's slip in the contact plane, gated smoothly by penetration (a smooth, EKF-friendly stand-in for Coulomb friction — grips a resting contact against sliding). ANISOTROPIC: a per-BODY-axis 3-vector of coefficients, so a wheel can roll free along one axis and grip the others (e.g. (0, c, 0) — free fore–aft, grips sideways). A scalar is accepted as shorthand for isotropic (c, c, c). Default 0 → frictionless contact (the prior behaviour).
Source code in manta/parts/structure/collider.py
manta.parts.ThermalMass ¶
Bases: Part
A lumped thermal node: heat capacity + temperature state + conduction/boundary/generation heat flows.
Parameters: heat_capacity — C, J/K. Promotable (a classic sysid target). Default 100. ambient_conductance — g_amb, W/K leak to the ambient boundary. 0 (default) = insulated: no ambient term, and the ambient input is not plumbed. ambient — where the boundary temperature comes from: "input" (the ambient_temperature Input) or "fluid" (the world FluidField's temperature at the part's position — requires a registered FluidField whose regimes declare temperatures).
Inputs: heat_input — external heat into the node, W (signed). ambient_temperature — boundary temperature the node leaks to, K. Only bound when ambient_conductance > 0 AND ambient="input". Default 293.15; rewrite per tick from the driving loop to script an environment.
State:
temperature — node temperature, K. Init 293.15; set a
per-run value via
add_craft(..., **{"node.temperature": T0}).
Noise: heat_noise — white heat-flow noise, W (σ default 0 — inert). Gives the EKF an auto-Q channel for the temperature slot.
Construction:
source — optional Part implementing
dissipated_heat() -> MX (W); its loss is
added to this node's balance each tick.
connect(other, conductance=k) links two nodes with a k W/K
conductance (symmetric; both must ride the same craft).
Source code in manta/parts/thermal/thermal_mass.py
input_declarations ¶
The ambient_temperature input exists only when it is the live boundary: an insulated node (ambient_conductance == 0) has no ambient term, and a fluid-coupled node reads the FluidField instead — drop it so it never reaches the u port.
Source code in manta/parts/thermal/thermal_mass.py
connect ¶
Register a symmetric conduction link to other (W/K). One
call wires BOTH directions — do not also call
other.connect(self, ...); the natural-looking reciprocal call
would silently double the conductance, so it raises instead.
Deliberate parallel heat paths are still expressed by calling
connect again from the same endpoint. Returns self for
chaining.
Source code in manta/parts/thermal/thermal_mass.py
on_world_resolve ¶
Validate the thermal network on the resolved snapshot — before any tracing — instead of erroring mid-trace: every linked node and the heat source must ride this node's craft.
Source code in manta/parts/thermal/thermal_mass.py
Electrical¶
manta.parts.ElectricalNode ¶
Bases: ElectricalPort, Part
Base contract for one node in a radial DC network.
The diagnostic outputs have identical meanings on every node:
voltage
The node terminal/rail voltage (V).
input_current / input_power
Flow entering from the upstream edge (or ideal reservoir for a
source), in A/W.
output_current / output_power
Flow delivered to children, or consumed as useful endpoint power by
a load, in A/W.
loss_power
Electrical conversion/series loss (W), suitable for thermal coupling.
brownout / open / tripped
Unit-valued status signals. Brownout is 1 below the lower voltage and
0 above the recovery voltage. open and tripped are explicit;
ordinary buses and loads report zero.
kcl_residual / energy_residual
Equation diagnostics (A and W). They are symbolically zero for a
correctly assembled tick, including capacitor storage and injected
current noise.
Use :meth:connect on the upstream node. Connectivity need not resemble
mechanical mounting, but every connected node must ride the same craft.
Source code in manta/parts/electrical/core.py
manta.parts.DCSource ¶
Bases: _CapacitiveRail
Current-limited Thevenin DC source with terminal capacitance.
open_circuit_voltage and source_resistance describe the ideal
reservoir. Current flows only out of the reservoir; A1 deliberately does
not model charging. enabled is a normalized contact command.
Source code in manta/parts/electrical/core.py
manta.parts.ExternalDCSupply ¶
Bases: ElectricalNode
Runtime boundary for a simulation-only or hardware DC source.
supplied_voltage enters the Manta tick as an ordinary input and the
aggregate downstream demand leaves as output_current. A battery
plant may therefore keep cell, thermal, and fault state in a
non-differentiable simulator while powered mechanical parts retain their
normal compiled model. Source-internal heat remains owned by that plant;
endpoint conversion loss is still available through each load's
dissipated_heat().
Source code in manta/parts/electrical/core.py
manta.parts.ElectricalBus ¶
Bases: _CapacitiveRail
Capacitive DC bus fed through a current-limited series edge.
Source code in manta/parts/electrical/core.py
manta.parts.DCConverter ¶
Bases: _CapacitiveRail
Regulated DC converter with dropout, efficiency and hard ratings.
A proportional internal regulator charges the output capacitor toward
output_voltage through control_resistance. Delivery is bounded by
output current, output power and available input power. Below
minimum_input_voltage it fades out through a C1 gate; above that
voltage the stated efficiency relates input and rail-injection power.
Source code in manta/parts/electrical/core.py
manta.parts.Contactor ¶
Bases: ElectricalBus
Commanded series contactor with downstream hold-up capacitance.
Source code in manta/parts/electrical/core.py
manta.parts.Fuse ¶
Bases: ElectricalBus
Latching I²t fuse with a continuous overload accumulator.
trip_fraction integrates normalized I² above the rated current and
latches at one. The electrical edge remains closed until the threshold,
then opens. This is a deterministic hybrid event and therefore only
piecewise differentiable at the exact trip surface.
Source code in manta/parts/electrical/core.py
manta.parts.ElectricalLoad ¶
Bases: ElectricalNode
Base endpoint load with brownout and enable semantics.
Source code in manta/parts/electrical/core.py
manta.parts.ResistiveLoad ¶
Bases: ElectricalLoad
Constant-resistance endpoint load.
Source code in manta/parts/electrical/core.py
manta.parts.ConstantCurrentLoad ¶
Bases: ElectricalLoad
Constant-current endpoint load above its brownout recovery voltage.
Source code in manta/parts/electrical/core.py
manta.parts.ConstantPowerLoad ¶
Bases: ElectricalLoad
Bounded constant-power endpoint load with a low-voltage floor.
Source code in manta/parts/electrical/core.py
manta.parts.ConstantPowerElectronicsLoad ¶
Bases: ConstantPowerLoad
Compute/electronics load whose consumed power ultimately becomes heat.
Source code in manta/parts/electrical/core.py
manta.parts.PoweredMotor ¶
Bases: PoweredLoadMixin, Motor
A direct-voltage :class:Motor supplied by an A1 electrical rail.
The existing signed voltage input remains the requested winding
voltage. The drive clips it to available rail magnitude and fades it
through brownout. Reverse power is not returned to A1's unidirectional
network; regenerative/dynamic braking energy is reported as heat.
Source code in manta/parts/electrical/powered.py
manta.parts.PoweredThruster ¶
Bases: _CalibratedPoweredActuator, Thruster
Voltage-derated polynomial thruster with a calibrated power map.
Source code in manta/parts/electrical/powered.py
manta.parts.PoweredDuctedPropeller ¶
Bases: _CalibratedPoweredActuator, DuctedPropeller
Voltage-derated ducted propeller with calibrated shaft power.
Source code in manta/parts/electrical/powered.py
manta.parts.PoweredControlSurface ¶
Bases: _CalibratedPoweredActuator, ControlSurface
Control surface whose servo torque and speed fade with rail voltage.
Source code in manta/parts/electrical/powered.py
Actuation¶
manta.parts.Thruster ¶
Bases: Part
Polynomial-in-throttle thruster (linear + quadratic).
Coefficients are 3-vectors in the thruster's own frame. For a thruster
mounted directly on the craft root that frame is CraftFrame, so
Thruster("t", force=(0,0,1)) is a pure +z thrust in body coords.
Mounted on a joint's rotor, the thruster's frame spins with the rotor
and the framework rotates the emitted wrench into body coords — a
gimballed thruster's thrust direction tracks the joint angle
automatically, with no frame handling here. Any unset coefficient
defaults to zero.
Input: throttle — scalar control input. Units depend on the scaling of the coefficients.
Process-noise channels (the actuator analogue of a sensor's noise —
set σ to engage, default 0 = a perfectly clean actuator):
force_noise : per-tick white force (N) added to the thrust, in
the thruster frame. Because it enters the wrench
(not an Output) it propagates through the dynamics
into the next state, so the EKF auto-builds Q
from it (just as σ on a sensor auto-builds R),
and a NoiseDriver jitters the truth thrust by it.
torque_noise : per-tick white torque (N·m) added to the reaction
torque, same frame and same role for attitude.
Source code in manta/parts/base.py
manta.parts.Motor ¶
Bases: RevoluteDOF
Voltage-commanded DC motor on a revolute DOF.
Parameters: axis — input-frame unit vector along the rotation axis. Default (0, 0, 1). torque_constant — SI motor constant k: torque constant (N·m/A) and back-EMF constant (V·s/rad). Promotable. Default 0.05. resistance — winding resistance R (Ω). Promotable. Default 1.0. current_limit — armature-current clamp magnitude (A), the driver/thermal limit. Default inf (no limit). gear_ratio — motor-shaft turns per output-shaft turn (reduction ratio ≥ 1 gears torque up). Default 1.0 (direct drive). damping — viscous friction at the output shaft (N·m·s/rad). Default 0.
Inputs: voltage — terminal voltage V (signed; negative reverses).
State: angle — output-shaft angle, rad. rate — output-shaft rate relative to the mounting body, rad/s.
Source code in manta/parts/articulation/motor.py
applied_dof_force ¶
Electrical shaft torque on top of the base viscous damping, as a raw MX scalar — this joint's generalized-force row entry (see the base class for the reaction-bookkeeping argument).
Source code in manta/parts/articulation/motor.py
dissipated_heat ¶
Winding copper loss i²·R (W) — the ThermalMass heat-source
protocol (ThermalMass("winding", source=motor)). Everything
the electrical model wastes: at stall the full V²/R, at no-load
speed ~0.
Source code in manta/parts/articulation/motor.py
Articulation¶
manta.parts.RevoluteJoint ¶
Bases: RevoluteDOF, CommandedDOF
1-DOF revolute joint with an axial rotor (set of Mass children).
Parameters: axis — input-frame unit vector along the rotation axis. Default (0, 0, 1). mode — "passive" or "saturating". Default "passive". stall_torque — saturating-mode torque clamp magnitude (N·m). Ignored in passive mode. Default 1.0. damping — viscous joint friction (N·m·s/rad). Default 0.
Inputs:
torque_cmd — commanded torque about axis. Clamped to
±stall_torque in saturating mode; ignored
entirely in passive mode.
State: angle — joint angle, rad. rate — joint angular rate (rotor spin relative to body), rad/s.
Source code in manta/parts/articulation/joint.py
manta.parts.PrismaticJoint ¶
Bases: CommandedDOF
1-DOF prismatic (sliding) joint carrying a subtree of Mass children.
Parameters: axis — input-frame unit vector along the slide axis. Default (0, 0, 1). mode — "passive" or "saturating". Default "passive". stall_force — saturating-mode force clamp magnitude (N). Ignored in passive mode. Default 1.0. damping — viscous slide friction (N·s/m). Default 0.
Inputs:
force_cmd — commanded force along axis. Clamped to
±stall_force in saturating mode; ignored
entirely in passive mode.
State:
displacement — slide displacement along axis, m.
rate — slide rate (relative to the mount), m/s.
Source code in manta/parts/articulation/joint.py
Aerodynamics¶
manta.parts.DragSurface ¶
Bases: Part
Polynomial drag/lift surface.
Parameters:
linear_coefficient_areas — positive diagonal coefficient-area vector
for -rho * k1 * v. This is the compact,
physically constrained system-identification form.
quadratic_coefficient_areas — positive diagonal coefficient-area
vector for -rho * k2 * v*abs(v). For the
conventional drag law, k2 = 0.5 * Cd * A.
force_tensors — list of 3×3 matrices [A_1, A_2, …, A_N], CraftFrame.
Default is a single zero matrix (no drag).
moment_tensors — same shape: the surface's FLOW-INDUCED moment
about the mount point (τ = ρ·Σ B_k·v_rel^(k);
not spin damping — see the module docstring).
Convenience args (mutually exclusive with the *_tensors form): force=(x,y,z) — sets A_1 = diag(x, y, z) (per-axis linear drag). moment=(x,y,z) — sets B_1 = diag(x, y, z) (per-axis, linear in flow).
Source code in manta/parts/aero/drag_surface.py
isotropic_quadratic
classmethod
¶
Single-Cd quadratic hull/sphere drag: F = -½·ρ·A·Cd · v_rel^(2) (element-wise square per body axis) Identical to the v1 isotropic model, just expressed in tensor form so the user can mix it with other polynomial orders.
Source code in manta/parts/aero/drag_surface.py
directional_quadratic
classmethod
¶
Anisotropic quadratic drag — a per-body-axis reference area:
F_i = -½·ρ·areas_i·Cd · v_i·|v_i| (diagonal A_2)
Use it for a slender body: a cylindrical fuselage along, say, body
+z is areas=(side, side, frontal) with frontal ≪ side —
low drag nose-on, high drag broadside (and an off-axis flow gets a
restoring body torque through the standard force-at-offset lift).
Source code in manta/parts/aero/drag_surface.py
manta.parts.RotationalDrag ¶
Bases: Part
Damping torque polynomial in the part-frame angular velocity.
Parameters: torque — (kx, ky, kz): the one-order shortcut, τ_i = ρ·k_i·ω_i (linear damping; k_i < 0). torque_tensors — full per-order 3×3 tensor list instead.
Tensors are per unit fluid density, like DragSurface's.
Source code in manta/parts/aero/rotational_drag.py
manta.parts.AddedMass ¶
Bases: Part
Diagonal added mass (kg) and added rotational inertia (kg·m²), in the part's own frame, about the craft's COM.
Parameters: translational — (Ax, Ay, Az) kg: extra effective mass per part-frame axis. Slender body along +x: Ax ≪ Ay ≈ Az. rotational — (Bx, By, Bz) kg·m²: extra effective rotational inertia per part-frame axis. Bx ≈ 0 for a hull of revolution (fluid slips around the roll axis).
Source code in manta/parts/aero/added_mass.py
manta.parts.FossenDamping ¶
Bases: Part
Full 6×6 damping wrench, polynomial in ν = [v_rel; ω].
Parameters: damping — (kvx, kvy, kvz, kwx, kwy, kwz): the diagonal one-order shortcut (dissipative entries are < 0). tensors — full per-order 6×6 tensor list instead.
Tensors are per unit fluid density and applied additively — see the module docstring's sign-convention note before pasting a textbook D in here.
Source code in manta/parts/aero/fossen_damping.py
manta.parts.Aerofoil ¶
Bases: Part
A cambered, Reynolds-aware lifting surface.
Parameters:
area — m². Planform (reference) area.
chord — m. Mean aerodynamic chord; the Reynolds reference
length (and, for a ControlSurface, the wing chord
a flap is measured against).
chord_axis — unit vector along the chord (leading → trailing
edge) in the part's OWN frame. Default (1, 0, 0);
leave it there — see "Orientation" below.
normal_axis — unit vector normal to the chord, in the foil
plane; the +lift side. Default (0, 0, 1); leave
it there.
alpha_0 — zero-lift angle of attack, rad. 0 for a symmetric
foil; negative for positive camber. From the camber
line (Re-independent); naca(...) fills it.
CL_alpha — lift-curve slope, per rad. Thin-airfoil ≈ 2π.
Cm_ac — moment coefficient about the aerodynamic centre
(constant; nose-down negative for positive camber).
CL_max — reference (high-Re) stall lift coefficient. Scaled
DOWN at low Reynolds number.
CD_0 — reference (at Re ≈ 5·10⁵) zero-lift drag coefficient.
Scaled by the local Reynolds number.
induced_k — induced-drag factor: CD gains induced_k·CL², so
induced_k ≈ 1/(π·AR·e) (≈0.05 for an AR-6 wing).
Orientation: use mount_orientation, not the axis pair.
chord_axis / normal_axis define this foil's CANONICAL frame —
chord along +x, lift along +z — and should stay at their defaults.
How the surface is INSTALLED is a mount rotation: rigging
incidence, dihedral, a vertical stabiliser's 90° roll, an all-moving
fin's station around a hull. All of those belong in
Part.mount_orientation.
The axis pair predates static mount rotations, and encoding installation angles in it has three costs. One physical rotation gets spread across two hand-derived vectors that must stay mutually perpendicular (the constructor checks, because they can silently stop being so). A left/right mirrored pair becomes a sign buried in a vector component rather than the sign of a roll angle. And only the aerodynamics end up rotated — every other quantity the part sees is still in the unrotated frame, so the part's own idea of "its frame" and the framework's disagree.
The parameters remain for the models written before mount
rotations existed (examples/vehicles/airplane.py); new ones
should not touch them.
Source code in manta/parts/aero/aerofoil.py
manta.parts.naca ¶
Build an Aerofoil for a NACA 4-digit section (e.g. "2412",
"0012").
The first digit is max camber in % chord, the second its chordwise
position in tenths, the last two the thickness in % chord. The
Re-independent invariants — zero-lift angle alpha_0 and moment
Cm_ac — are derived from the camber line by thin-airfoil theory;
the reference CL_max and CD_0 are estimated from thickness (and
are themselves rescaled by Reynolds number at run time). Any of these
may be overridden, along with the geometry (area, chord,
chord_axis, normal_axis, induced_k), via keyword.
Example::
a.add(naca("2412", "wing", area=0.72, chord=0.3))
Source code in manta/parts/aero/aerofoil.py
manta.parts.ControlSurface ¶
Bases: Aerofoil
A wing section with a deflectable trailing-edge flap.
Inherits all Aerofoil geometry/aero parameters (these describe the
WING section — area, chord, chord_axis, normal_axis, alpha_0,
Cm_ac, CL_max, …); the flap is parameterised by its chord fraction,
not a separate foil. The deflection is a single state driven by a
commanded angle through a saturating first-order servo.
Flap + servo parameters: flap_chord_fraction — c_flap / c, in (0, 1). Sets the flap effectiveness τ and moment slope Cm_δ. servo_gain — K_servo, hinge restoring torque per rad of command error (N·m/rad). stall_torque — τ_stall, the servo's saturation torque (N·m). Aero hinge moment beyond this blows the surface back. hinge_damping — b, hinge viscous damping (N·m·s/rad); with servo_gain it sets the lag bandwidth K/b. max_deflection — travel limit, rad (the state saturates here). Ch_alpha, Ch_delta — hinge-moment coefficient slopes wrt angle of attack and deflection (both restoring, < 0). Engineering values; tune per surface.
Input: deflection_cmd — commanded deflection, rad. State: deflection — actual deflection δ, rad.
Source code in manta/parts/aero/control_surface.py
Sensors¶
manta.parts.IMU ¶
Bases: Part
Inertial-measurement unit with Kalibr-style 4-parameter noise.
Channels (override sigmas via construction): gyro_noise — vec3 white, per-tick rad/s. accel_noise — vec3 white, per-tick m/s². gyro_bias — vec3 RW, rad/s²/√Hz drift density. accel_bias — vec3 RW, m/s³/√Hz drift density.
The two RW channels add bias state slots that the EKF can estimate; skip them by leaving sigma at 0.
Source code in manta/parts/base.py
manta.parts.ModelForce ¶
Bases: Part
Model-predicted sensor-frame specific force.
Args:
imu — the colocated IMU whose raw accelerometer sample is the
observation for specific_force (its cadence is the
default rate).
evidence — FitEvidence for that IMU's accel channel. Builds
the complete error model (see module docstring) and
refuses any hand-set error override alongside it.
model_error_sigma — isotropic shorthand for the three white
model_error_<axis>_sigma values (evidence-less use).
Per-axis channels (1-σ, m/s²): model_error_{x,y,z} (white) and
model_error_correlated_{x,y,z} (Gauss–Markov, with
model_error_correlated_<axis>_tau seconds). residual_bias is the
deterministic held-out bias correction in the sensor frame.
Source code in manta/parts/sensor/model_force.py
manta.parts.VelocitySensor ¶
Bases: Part
Body-frame linear-velocity sensor.
Outputs: velocity : Vec3[PartFrame] — the craft's inertial (ground- relative) velocity in the sensor's own case frame (R^T·v_anchor). For a root-mounted sensor that frame coincides with CraftFrame; on a rotor it spins with the joint.
Noise channel (set σ to engage):
velocity_noise — vec3 white, per-tick m/s. Becomes the EKF's
measurement R, exactly as PositionSensor's
position_noise. Defaults to 0 (an ideal read).
Source code in manta/parts/base.py
manta.parts.Magnetometer ¶
Bases: Part
3-axis magnetometer.
Outputs: B : Vec3[PartFrame] — magnetic flux density at the sensor position, in the sensor's own frame. SI units (Tesla). For a sensor mounted directly on the craft root that frame coincides with CraftFrame; on a joint rotor it spins with the rotor.
Noise channel (set σ to engage):
B_noise — vec3 white, per-tick Tesla. Becomes the EKF's
measurement R for a heading/attitude fix, exactly as
PositionSensor's position_noise does. Defaults to 0
(a clean reading).
Source code in manta/parts/base.py
manta.parts.PositionSensor ¶
Bases: Part
Outputs the sensor's world-frame position each tick.
Outputs: position : Vec3[WorldFrame] — sensor mount-point position in world frame; exactly what a GPS or mocap marker reads.
Noise channel (set σ to engage — leave at 0 for a noiseless oracle):
position_noise : world-frame white noise on the reading. Engage
it (PositionSensor("gps", position_noise_sigma=0.5))
to give the EKF an auto-built R for this sensor,
e.g. when driving the filter through step().
Rate (Hz):
rate : measurement rate. None (default) ⇒ a fresh fix every
tick. Set it (PositionSensor("gps", rate=1.0)) to model a
slow sensor: the Sim publishes a new reading once per
1/rate window and holds it in between, and the EKF folds
each fix in exactly once. Pure metadata — the tick stays a
smooth function (the estimator sees the continuous model).
Source code in manta/parts/base.py
manta.parts.Barometer ¶
Bases: Part
Outputs the local fluid pressure at the sensor each tick.
Outputs: pressure : scalar — absolute pressure (Pa) of the fluid at the sensor's world-frame position, read from the FluidField.
Noise channel (set σ to engage — leave at 0 for a noiseless oracle):
pressure_noise : scalar white noise (Pa) on the reading. Engage it
(Barometer("baro", pressure_noise_sigma=50.0))
to give the EKF an auto-built R for this sensor.
Rate (Hz):
rate : measurement rate. None (default) ⇒ a fresh reading every
tick. Set it (Barometer("baro", rate=10.0)) to model a
slow sensor: the Sim publishes a new reading once per
1/rate window and holds it in between, and the EKF folds
each in exactly once. Pure metadata — the tick stays a
smooth function.
Source code in manta/parts/base.py
manta.parts.ProjectiveCamera ¶
ProjectiveCamera(name, *, width=None, height=None, hfov_deg=70.0, fx=None, fy=None, cx=None, cy=None, rate=None, noise_sigma=0.0, mount_offset=(0.0, 0.0, 0.0), mount_orientation=(1.0, 0.0, 0.0, 0.0))
Bases: Part
Base for a pinhole camera that measures OpticalField ellipsoids.
Construct with the image size and horizontal field of view (the focal
length and a centred principal point are derived), or override any
intrinsic explicitly. Subclasses set _COMPONENTS (the per-target scalar
measurement names, excluding vis) and implement _project.
Parameters:
width, height — image size in pixels.
fx, fy, cx, cy — intrinsics (focal lengths + principal point, px).
rate — optional capture rate (Hz); None ⇒ every tick.
noise_sigma — per-component pixel measurement σ (subclasses expose it
under a friendlier name). 0 ⇒ noiseless oracle, no noise channels
(byte-identical to a camera with none), and not EKF-usable.
Source code in manta/parts/sensor/camera.py
on_world_resolve ¶
Point the camera at every optical ellipsoid it can see — all
but its own craft's. A camera with no OpticalField registered
fails the requires_fields check at transform build; here we
just skip target wiring.
Source code in manta/parts/sensor/camera.py
set_targets ¶
Point the camera: fix the compile-time set of ellipsoids it measures. Called once during snapshot resolution before the tick is traced; the output/noise declarations follow it.
Also materializes the per-channel <name>_sigma attributes the
framework reads off the instance (Noise.is_active, the tick-
signature walk, Craft.sample_noise) — this is the one mutation
point; noise_declarations() stays a pure read.
Source code in manta/parts/sensor/camera.py
manta.parts.BBoxCamera ¶
Bases: ProjectiveCamera
Pinhole camera emitting per-object image-frame bounding boxes.
Outputs per visible source S: <S>_xmin/_ymin/_xmax/_ymax (pixel
box, clamped to the image) and <S>_vis (1 when in front and a real
ellipse, else 0). The box size encodes range given the target's semi-axes.
BBoxCamera("cam", width=640, height=480, hfov_deg=70)
BBoxCamera("cam", width=1280, height=720, bbox_sigma=2.0) # EKF-usable
Source code in manta/parts/sensor/camera.py
manta.parts.CentroidCamera ¶
Bases: ProjectiveCamera
Pinhole camera emitting per-object image-frame CENTROIDS (u, v).
A centroid is the projection of the target's centre — a pure bearing,
independent of the target's size. Outputs per visible source S:
<S>_u, <S>_v (pixels) and <S>_vis. One camera fixes a ray;
space several apart and select their _u/_v as EKF sensors and the
filter triangulates the target's 3-D position (the wider the baseline, the
better the range — size never enters).
CentroidCamera("c0", width=1280, height=720, hfov_deg=40,
pixel_sigma=1.0)
Source code in manta/parts/sensor/camera.py
manta.parts.Antenna ¶
Bases: Part
A kinematic marker for an RF antenna phase center and local frame.
Outputs:
position : Vec3[WorldFrame]
Antenna phase-center position in world coordinates.
orientation : Quat[WorldFrame, PartFrame]
World-from-antenna unit quaternion in (w, x, y, z) order.
angular_velocity : Vec3[WorldFrame]
Absolute angular velocity of the antenna frame, expressed in
world coordinates, in rad/s.
These are ideal kinematic outputs. Radiation patterns, propagation, tracking, link quality, and packet behavior belong to downstream users of the model, not to this marker.
Source code in manta/parts/base.py
Attachment and disturbance¶
manta.parts.TetherEndpoint ¶
Bases: Part
Marker Part for one end of a tether. No wrench contribution; the Tether coupling applies the actual force using this part's transform as the attachment offset.
Source code in manta/parts/base.py
manta.parts.TrajectoryEndpoint ¶
Bases: Part
Spring-damper that slews its craft along a reference pose path.
Parameters:
trajectory — callable f(t) -> TrajectorySample taking the
symbolic clock (a Scalar) and returning the
reference at that time. Required.
kp_pos, kd_pos — position spring / damping gains (N per m, N per
m/s). With mass set, critical damping is
kd_pos = 2·sqrt(kp_pos·mass).
kp_att, kd_att — attitude spring / damping gains (N·m per rad,
N·m per rad/s).
mass — craft mass (kg). When > 0, enables gravity + linear-
acceleration feedforward for tight tracking. 0 (the
default) is a pure spring — fine for light craft and
a zero-gravity world, but it will droop under gravity.
Mount it on the craft root at the origin (the default transform). An off-origin mount would inject a force×lever-arm torque that corrupts the attitude channel.
Source code in manta/parts/attachment/trajectory_endpoint.py
manta.parts.ProcessNoise ¶
Bases: Part
White force/torque wrench — model uncertainty as Langevin forcing.
Set σ to engage a channel (force_noise_sigma= / torque_noise_sigma=
on construction); both default to 0 (a perfectly modeled craft). The
EKF assembles Q from the engaged channels and NoiseDriver excites
the truth identically.
Source code in manta/parts/base.py
Field sources¶
manta.parts.GravitySource ¶
Bases: FieldSource
Adds a point-mass gravity disturbance that rides the carrying craft.
Parameters: GM — gravitational parameter G·M (m³/s²). Earth ≈ 3.986e14, Moon ≈ 4.903e12, a 1000-ton asteroid ≈ 6.7e-5. eps — softening length (m) capping the singularity at the source.
Source code in manta/parts/base.py
manta.parts.MagneticSource ¶
Bases: FieldSource
Adds a magnetic dipole disturbance that rides the carrying craft.
Parameters: moment — (mx, my, mz) dipole moment in the craft BODY frame, A·m². A small hobby motor magnet is ~1e-2–1e-1. eps — softening length (m) at the dipole position.
Source code in manta/parts/base.py
manta.parts.OpticalSource ¶
Bases: FieldSource
Adds a semantic ellipsoid disturbance that rides the carrying craft.
Parameters: semi_axes — (a, b, c) half-extents of the bounding ellipsoid along the craft's body axes, m. Roughly half the vehicle's length/width/height. label — integer class id the camera reports with each box.