Direction Cosine Matrix To Quaternion — Robotics/Orientation 3D
Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Quaternion · 1 input / 1 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.
Direction Cosine Matrix To Quaternion
Robotics / Orientation 3D
Converts a [3,3] passive direction cosine matrix D into the
scalar-first [4,1] quaternion [w x y z]T that represents
the same rotation. It takes the same four-way branch MATLAB's
dcm2quat takes: whichever of four square roots is largest is the one
computed, and the other three components are formed from it. Writing tr
for the trace and d for the diagonal:
- tr > 0 – s = √(tr+1), w = s/2, and each of x, y, z is an antisymmetric pair of entries divided by 2s.
- d₁ largest – s = √(d₁−d₀−d₂+1), y = s/2, the rest scaled by 0.5/s.
- d₂ largest – the same shape with z = s/2.
- otherwise – the same shape with x = s/2.
Branching on the largest root is what keeps the answer accurate near a half turn, where the trace formula divides by something close to zero. Quaternion To Rotation Matrix is the map back – through a transpose, see Notes.
Ports
- D – the direction cosine matrix, a [3,3]. The size is fixed. It is the passive matrix, the aerospace convention.
- q – the quaternion, a [4,1] column [w x y z]T, scalar first. Its size is fixed and does not follow the input's.
Parameters
- 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.
The branch is chosen by the data rather than by a setting, so every generated body carries all four arms; there is nothing here for a tunable parameter to hold.
On the three HDL targets the block is offered as simulation-only:
a Q16.16 datapath has neither a square root nor a reciprocal, so all of it goes
through real arithmetic and only the ports quantize. They also guard the root
against a negative radicand and answer zero there, because VHDL's
MATH_REAL SQRT asserts and an assertion aborts the whole
simulation rather than producing one bad sample – see Notes for what
the other targets do.
Simulink bridge
Both directions, onto
aerolibtransform2/Direction Cosine Matrix to Quaternions in the
Aerospace Blockset. No parameter crosses. The Simulink block's two
dialog parameters, action and tolerance, decide what it
does when the matrix it is handed is not orthonormal; this block validates
nothing and computes the same arithmetic on whatever arrives, so both are listed
as ignored rather than mapped – the same choice
Direction Cosine Matrix To Rodrigues makes.
The block defines no SampleTime parameter, so the rate stays
on this side and a block configured with an explicit positive rate reports that
the rate did not cross.
The Simulink block uses a row at its quaternion port; this one uses a column, as every vector port in this family does.
Notes
- Algebraic, with no state: the output depends only on the current input.
- Feed the PASSIVE matrix. This tree's Quaternion To Rotation Matrix produces the active rotation, which is this matrix's transpose – so a round trip through the two blocks needs a Matrix Operations / Transpose between them. Measured: transposed, the pair returns the original quaternion to the last bit; untransposed, it returns the conjugate, which is a perfectly valid quaternion of the wrong rotation and so cannot be caught by anything downstream.
- No normalization, and no validation. The arithmetic runs on whatever
arrives. A matrix that is not orthonormal produces a quaternion that is not a
unit one, and no warning is raised – that is what the Simulink block's
actionandtoleranceare for, and they do not cross. - The sign is not unique, and the branch decides it. q and
−q name the same rotation; this block returns whichever one the
largest-root arm produces, exactly as
dcm2quatdoes. - A vanishing root gives an all-zero quaternion, not an infinity. The reciprocal is guarded, so the multiplier stays at zero when the root does. Measured: a half turn about y answers [0 0 1 0] exactly.
- A NEGATIVE radicand is not a rotation matrix. The six software targets answer NaN there (MATLAB, a complex number) and the three HDL ones answer zero, for the assertion reason under Code export. It cannot arise from an orthonormal matrix.
- Bit-identical to the reference, deliberately. The trace arm divides by
2s and the other three multiply by 0.5/s, which are different
doubles;
dcm2quatwrites it that way and so does this block. - Deliberately no state space – the map is not linear.
Code facts#
| Fact | Value |
|---|---|
| registered type | Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Quaternion |
| family | Robotics/Orientation_3D |
| solver environment class | ICoreBlock_0_Robotics_1_Orientation_3D_2_Direction_Cosine_Matrix_To_Quaternion |
| source | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Quaternion/ICoreBlock_0_Robotics_1_Orientation_3D_2_Direction_Cosine_Matrix_To_Quaternion.cpp |
| header | src/ICoreBlocks/ICoreBlockLibrary/Blocks/Robotics/Orientation_3D/Direction_Cosine_Matrix_To_Quaternion/ICoreBlock_0_Robotics_1_Orientation_3D_2_Direction_Cosine_Matrix_To_Quaternion.h |
| default size on canvas | 150 × 76 px |
| ports at insert | 1 in, 1 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 | D |
| 2 | out | ICoreDouble | q |
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#
No config variable beyond the Sampling Time (s) every block carries.
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 | aerolibtransform2/Direction Cosine Matrix \nto Quaternions |
| port-count rule | PortsParam::None |
SampleTime parameter | no — the counterpart defines none; the rate stays on the ICore side |
| deliberately not crossed | action, tolerance |
Caveat (shown to the user): MEASURED 2026-09-10: the Simulink block's dialog carries
action(None | Warning | Error) andtolerance(default eps(2)) and nothing else. Neither crosses -- they govern an orthonormality CHECK this block does not perform, exactly as with Direction Cosine Matrix To Rodrigues. It defines no SampleTime parameter, so an ICore rate set explicitly stays on this side and is reported, and it uses a ROW at its quaternion port where this block uses a column
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).
Direction Cosine Matrix To Quaternion — a [3,3] passive DCM to its [4,1] scalar-first quaternion, by the four-way branch MATLAB's dcm2quat takes Whichever of the four square roots is largest is the one computed; the other three components follow from it. Writing tr for the trace and d for the diagonal:
tr > 0 s = sqrt(tr + 1) q0 = s/2, and q1..q3 = (antisymmetric pair) / (2s) d1 > d0 and d1 > d2 s = sqrt(d1 - d0 - d2 + 1), q2 = s/2, k = 0.5/s d2 > d0 s = sqrt(d2 - d0 - d1 + 1), q3 = s/2, k = 0.5/s otherwise s = sqrt(d0 - d1 - d2 + 1), q1 = s/2, k = 0.5/s
⚠ THE FOUR ARMS ARE WRITTEN ONCE, in branchAt() below, and the C++ reference and all TEN generators read that one table. Four arms times ten backends would otherwise be forty transcriptions of a sign-and-index table, which is exactly the shape of thing that goes wrong in one cell and nowhere else.
⚠ THREE DETAILS TAKEN OFF dcm2quat.m RATHER THAN OFF A TEXTBOOK (R2026a, 2026-09-10):
- THE INDEX PAIRS ARE TRANSPOSED from the familiar ones -- the trace arm forms q1 from
D(1,2) - D(2,1), not D(2,1) - D(1,2) -- because the aerospace DCM is the PASSIVE matrix, the transpose of the active rotation this tree's Quaternion To Rotation Matrix produces. MEASURED: quat(R') returns q exactly, and quat(R) returns the conjugate, which is a valid quaternion of the wrong rotation and cannot be caught downstream.
- THE TRACE ARM DIVIDES BY 2s; THE OTHER THREE MULTIPLY BY 0.5/s. Those are different
doubles, and the asymmetry is reproduced on purpose: it is what makes this block bit-identical to the reference rather than merely close to it.
- THE RECIPROCAL IS GUARDED --
if (s ~= 0) s = 0.5/s; end-- so a vanishing root leavesthe multiplier at ZERO and the answer is an all-zero quaternion rather than an infinity.
MEASURED against aerolibtransform2/Direction Cosine Matrix \nto Quaternions (R2026a, 2026-09-10) through its own dcm2quat, on FIVE matrices chosen so that each of the four arms runs at least once -- traces 2.42, -0.967, -0.979, -0.992 and -0.992, largest diagonal at index 1, 1, 2 and 0 respectively. All five agree to the LAST BIT (worst difference exactly 0), and the degenerate half turn about y answers [0 0 1 0].
⚠ SIMULATION-ONLY on the three HDL targets: a square root and a reciprocal per sample. They additionally guard the root against a negative radicand and answer zero there -- VHDL's MATH_REAL SQRT asserts, and an assertion aborts the whole simulation rather than producing one bad sample. A negative radicand means the matrix is not a rotation at all; the six
Sample results#
No stimulus produced a sampled output in this rig — Invalid input size at Direction Cosine Matrix To Quaternion block: ICore Blocks/Home/Direction Cosine Matrix To Quaternion. 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 3c100aff6f27235305db4ad4d572f32e342718ad · produced by docsSample --out <folder> --blocks Direction_Cosine_Matrix_To_Quaternion --steps 60
Sample data: docs/generated/samples/Robotics__Orientation_3D__Direction_Cosine_Matrix_To_Quaternion.json