CogniPilot

MODELICA LIBRARY · ENGINEERING REFERENCE

Tests/RuleChecks/so3JacobianFamilyResiduals.mo

Download original .moGitHub source

Documentation: Tests.RuleChecks.so3JacobianFamilyResiduals

within Tests.RuleChecks;
function so3JacobianFamilyResiduals
  "How far the rule-local SO(3) Jacobian family is from its own identities"
  input Real angleScales[:] "Rotation magnitudes to sweep";
  input Integer trials "Randomized directions per magnitude";
  output Real worst[3]
    "1: |J_r J_r^-1 - I|, 2: |J_l - transpose(J_r)|, 3: |J_l^-1 - transpose(J_r^-1)|";
protected
  Real draw[3];
  Real axis[3];
  Real axisNorm;
  Real v[3];
  Real forward[3, 3];
  Real inverted[3, 3];
  // Scalar accumulators: see the note in so3RuleResiduals.
  Real worstInverse;
  Real worstLeft;
  Real worstLeftInverse;
algorithm
  worstInverse := 0.0;
  worstLeft := 0.0;
  worstLeftInverse := 0.0;
  for i in 1:size(angleScales, 1) loop
    for trial in 1:trials loop
      // Same seeds as so3RuleResiduals, so the two checks look at the same
      // rotation vectors and a failure of one can be read against the other.
      draw := Tests.RuleChecks.pseudoRandom(7919 * trial + 13, 3);
      axis := draw;
      axisNorm := sqrt(axis[1]^2 + axis[2]^2 + axis[3]^2);
      v := (angleScales[i] / max(axisNorm, 1.0e-12)) * axis;

      forward := LieGroups.SO3.Quat.right_jacobian_exact(v);
      inverted := LieGroups.SO3.Quat.right_jacobian_inv_exact(v);
      worstInverse := max(worstInverse, Tests.Assertions.maxAbsMatrix(
        forward * inverted - identity(3)));
      worstLeft := max(worstLeft, Tests.Assertions.maxAbsMatrix(
        LieGroups.SO3.Quat.left_jacobian_exact(v) - transpose(forward)));
      worstLeftInverse := max(worstLeftInverse, Tests.Assertions.maxAbsMatrix(
        LieGroups.SO3.Quat.left_jacobian_inv_exact(v) - transpose(inverted)));
    end for;
  end for;
  worst := {worstInverse, worstLeft, worstLeftInverse};
  annotation(Documentation(info="<html>
    <p>Three identities the rule-local SO(3) Jacobian family satisfies exactly,
    checked without a finite difference so that the measurement is limited by
    the functions rather than by an oracle.</p>
    <ul>
      <li>J_r(v) J_r^-1(v) = I. This is the identity
      <code>exp_map_jacobian</code> and <code>log_map_jacobian</code> rely on
      when a chain composes one against the other, and it is the sharpest
      statement about the coefficients: it holds to 4.5e-16 across 1e-6 to
      3 rad, where the two primitives it replaces depart by 1.1e-8 at
      0.0999 rad. Both of those keep two series terms, but the residual is not
      symmetric in them: J_r's dropped term sits on the coefficient of [v]x and
      reaches the product multiplied by |v|, giving |v|^5/720, while J_r^-1's
      sits on the coefficient of [v]x^2 and arrives multiplied by |v|^2, giving
      |v|^6/30240, some four hundred times smaller at that magnitude.</li>
      <li>J_l(v) = J_r(v)^T. Transposition flips the sign of the [v]x term and
      leaves the [v]x^2 term, which is exactly the difference between the two
      Jacobians, while the implementation reaches the same matrix by evaluating
      J_r at -v. Both routes negate one skew matrix and leave its square, so the
      identity holds bitwise and measures zero; the entry is a guard against a
      sign or a slot going wrong, not a measurement of accuracy.</li>
      <li>J_l^-1(v) = J_r^-1(v)^T, for the same reason and with the same
      zero.</li>
    </ul>
  </html>"));
end so3JacobianFamilyResiduals;