IR primitives¶
The low-level manta.ir layer — frames, typed values, manifolds, and the
flat-vector state spec. Most users never touch this directly; it is
exported for advanced use (building bare CasADi graphs).
Frames¶
manta.ir.Frame ¶
Marker base class. Subclass to declare a new frame tag.
Frames are never instantiated; they are used purely as types passed to
IR-value generics like Vec3[SomeFrame]. A frame's identity is its
class object.
manta.ir.FrameError ¶
Bases: TypeError
Raised when an IR op's operands have incompatible frame tags.
Carries the operation name, the offending frames, and a source-location
string captured at op-build time so the message points at the user's
.py:line (not at the manta IR internals).
Source code in manta/ir/frames.py
Typed values¶
manta.ir.Vec3 ¶
Bases: _IRValue
A 3-vector tagged with a single frame.
Source code in manta/ir/types.py
manta.ir.Mat3 ¶
Bases: _IRValue
A 3×3 matrix mapping Vec3[B] → Vec3[A] (i.e., from_frame=A, to_frame=B).
Composition: Mat3[A,B] @ Mat3[B,C] → Mat3[A,C].
Application: Mat3[A,B] @ Vec3[B] → Vec3[A].
Source code in manta/ir/types.py
transpose ¶
inv ¶
Mat3[A,A] → Mat3[A,A]. Frames must match (only same-frame matrices are square endomorphisms with sensible inverse semantics).
Source code in manta/ir/types.py
manta.ir.Quat ¶
Bases: _IRValue
Unit quaternion representing a rotation from one frame to another.
Convention: a Quat[From, To],
when applied to a vector in To, returns the vector expressed in From
components::
q : Quat[From, To]
v_in_to : Vec3[To]
v_in_from = q.apply(v_in_to) # → Vec3[From]
Composition Quat[A, B] * Quat[B, C] → Quat[A, C].
Storage: (w, x, y, z) as a 4×1 MX. Normalization is the caller's
responsibility (use .normalize() after numerical updates).
Source code in manta/ir/types.py
conjugate ¶
Quat[A,B] → Quat[B,A]. For unit quaternions this is the inverse.
apply ¶
q.apply(v_in_To) → v_in_From. Standard quaternion-vector product.
Source code in manta/ir/types.py
manta.ir.Scalar ¶
Bases: _IRValue
A scalar IR value. Has no frame tag — scalars are frame-agnostic.
Source code in manta/ir/types.py
coerce
classmethod
¶
value as-is when already a Scalar, else a constant — the
promotable-Parameter idiom (see _ParameterizedConstructor.coerce).
Manifolds¶
manta.ir.Manifold
dataclass
¶
Bases: ABC
Structural descriptor + operations for a state-vector component.
Subclasses set kind (the backend-registry key) and the dims as
class-level constants. Instance attributes carry per-occurrence
context (e.g. a frame tag).
boxplus
abstractmethod
¶
Frame-checked boxplus on tagged values: ambient ⊞ tangent →
ambient. x is a Scalar/Vec3/Quat, delta the matching
tangent value. Returns a value of the same type as x.
boxminus
abstractmethod
¶
boxplus_sym
abstractmethod
¶
Symbolic boxplus: ambient + tangent → ambient. CasADi MX of shape (ambient_dim, 1) and (tangent_dim, 1).
boxminus_sym
abstractmethod
¶
boxplus_num
abstractmethod
¶
default_value
abstractmethod
¶
Python-side default value for fresh state (identity element
for groups, zero for vectors). Returned as a numpy array of
shape storage_shape or a scalar.
ir_input
abstractmethod
¶
ir_zero
abstractmethod
¶
ir_add
abstractmethod
¶
Type-preserving value ⊕ delta_mx (raw MX), used by random-walk
state updates: bias_next = bias + √dt · driver.
manta.ir.ScalarManifold
dataclass
¶
manta.ir.R3Manifold
dataclass
¶
Bases: Manifold
R^3 — 3-vector. Backend kind: "vec" (size carried in
storage_shape, not in the kind string, so a future R6 reuses
the same backend dispatch).
manta.ir.SO3Manifold
dataclass
¶
Bases: Manifold
SO(3) — rotations stored as a unit quaternion (w, x, y, z).
Ambient 4 (quat) / tangent 3 (axis-angle). Backend kind: "quat".
Left-trivialization convention: the tangent vector lives in the
rotation's from_frame, and
q ⊞ δ = exp(δ) ⊗ q
from_frame / to_frame parametrize the underlying Quat — a
Quat[from, to] rotates a vector in to coords into from coords.
Both must be provided when used as a user-declared State manifold
(no Frame default — pick the application's convention; the
framework's rigid-body orientation uses Quat[WorldFrame,
CraftFrame], so an attitude estimator typically matches that).
Codegen consumes only kind / storage_shape; the value-typed ops
derive their frames from the Quat argument, so a frameless
SO3Manifold() is a valid operator for already-frame-tagged values.
boxplus ¶
q ⊞ δ with δ in q's from_frame (left trivialization). Returns a Quat carrying q's frames.
Source code in manta/ir/manifold.py
boxminus ¶
δ = log(a ⊗ b⁻¹), returned in a's from_frame tangent space.
Source code in manta/ir/manifold.py
State spec¶
manta.ir.StateSpec ¶
Layout descriptor for a flat ambient state vector.
Source code in manta/ir/state_spec.py
from_craft
classmethod
¶
Build the canonical state spec for a single-craft world.
Layout:
position (3, R3)
orientation (4, SO3 → tangent 3)
velocity (3, R3)
angular_velocity (3, R3)
Future: multi-craft worlds will concatenate per-craft layouts here.
Source code in manta/ir/state_spec.py
from_world
classmethod
¶
Build the joint state spec for a World — every craft's slots
concatenated, each prefixed with <craft.name>..
Slot ordering: per-craft in world.crafts order; within each
craft, the same per-craft layout as from_craft.
Source code in manta/ir/state_spec.py
from_layout
classmethod
¶
Build a spec from an ordered list of (name, Manifold) pairs.
The world-agnostic constructor: a RecurrenceBlock (PID, Madgwick,
the IMU integrator) declares its internal state this way, with no
craft / world to walk. Offsets densify from 0 in the given order.
Source code in manta/ir/state_spec.py
subset
classmethod
¶
Build a standalone spec over a subset of full_spec's slots.
Slots are kept in their original full_spec order; ambient and
tangent offsets re-densify from 0 so every existing method
(pack/unpack/boxplus/boxminus) works on the result unchanged.
Source code in manta/ir/state_spec.py
slot ¶
Lookup a slot by name.
Exact match wins. As a convenience for single-craft worlds, a
suffix match also works — spec.slot("position") finds
"drone.position" when there's exactly one such slot. The
suffix match raises if ambiguous (the shared resolve_suffix
rule).
Source code in manta/ir/state_spec.py
pack ¶
Flatten a tick-style dict into the ambient vector.
Source code in manta/ir/state_spec.py
unpack ¶
Slice an ambient vector back into a tick-style dict.
Source code in manta/ir/state_spec.py
pack_any ¶
Pack a state given in ANY of the three shapes — nested dict
({owner: {slot: v}}), flat dict ({"owner.slot": v}), or ambient
vector — merged over base (ambient vector or dict), into the
ambient vector. The single owner of shape conversion: every
runtime/analysis call site goes through here instead of hand-rolling
the flatten + merge + filter loop.
Rules:
* state is an ndarray → taken verbatim (length-checked).
* state is a dict → flattened; unknown keys ignored; merged
over base.
* state is None → base packed as-is.
* every slot must be covered by state or base.
Source code in manta/ir/state_spec.py
to_nested ¶
Ambient vector → nested {owner: {slot: v}} dict (the runtime's
user-facing shape). A dotless slot name stays a top-level entry.
Source code in manta/ir/state_spec.py
boxplus_sym ¶
Apply a tangent-space perturbation to an ambient state.
Per-slot manifold operation: R3 / R1 → x_new = x + δ SO3 → q_new = boxplus(q, δ_θ) (quaternion + axis-angle)
Returns an ambient-dimensioned MX. Used by the ESKF to build a symbolic perturbed state for Jacobian extraction:
δ_out = boxminus(f(boxplus(x, δ_in)), f(x))
F = ∂δ_out / ∂δ_in evaluated at δ_in = 0
Source code in manta/ir/state_spec.py
boxminus_sym ¶
Compute the tangent-space delta between two ambient states.
Per-slot: R3 / R1 → δ = x_a − x_b SO3 → δ_θ = boxminus(q_a, q_b) = log(q_a ⊗ q_b⁻¹)
Source code in manta/ir/state_spec.py
boxplus_num ¶
Numeric (numpy) boxplus. Used by the ESKF update step to apply the Kalman correction onto the manifold without going through a compiled symbolic function.
Source code in manta/ir/state_spec.py
manta.ir.StateSlot
dataclass
¶
One named slot in the flat layout.
Attrs:
name — string key in the tick dict (e.g., "position",
"wheel.angle"). Same key the tick function uses.
ambient_offset — start index in the ambient vector.
manifold — Manifold instance describing the slot's storage
and tangent dims. ambient_dim and tangent_dim
are derived. Backends key on manifold.kind.
tangent_offset — start index in the tangent vector.
The ambient pair (ambient_offset/ambient_dim) and the tangent pair
(tangent_offset/tangent_dim) are named symmetrically on purpose:
covariance / Jacobian work indexes by the tangent pair, packing /
unpacking by the ambient pair, and mixing them is a classic bug.
manta.ir.SlotSet ¶
Bases: IntFlag
Aliases for the rigid-body slots an EKF should track.
Combine with |::
EKF(world, track={"drone": POSE | TWIST})
The composites map to the rigid-body slot-name suffixes
(position / orientation / velocity / angular_velocity).
They name ONLY the rigid-body block — part R1/R3 states, RW-bias
slots, and disturbance slots are never selected by an alias; they
enter the tracked set via dynamics closure or because a chosen
sensor observes them.
Wrench¶
manta.ir.Wrench ¶
Force + torque pair sharing a frame.
Construction::
w = Wrench(force=Vec3[F](...), torque=Vec3[F](...))
w = Wrench.zero(F)