within Tests.JacobianRuleTests; model So3 "SO(3) rules against central differences over a dense magnitude sweep" constant Real step = 1.0e-6 "Central-difference step: the O(h^2) truncation and the O(eps/h) rounding of a binary64 central difference cross near 1e-6 for quantities of order one"; constant Integer trials = 6 "Randomized points per magnitude"; constant Real magnitudes[32] = { 1.0e-6, 3.0e-6, 1.0e-5, 3.0e-5, 1.0e-4, 3.0e-4, 1.0e-3, 3.0e-3, 0.01, 0.03, 0.05, 0.07, 0.08, 0.085, 0.09, 0.092, 0.094, 0.096, 0.098, 0.0999, 0.1, 0.1001, 0.102, 0.105, 0.11, 0.2, 0.3, 0.5, 1.0, 1.5, 2.0, 3.0} "Dense from 1e-6 to 3 rad. 1e-4 rad is the radius where exp_map and the rule-local Jacobians switch between series and closed form, and 0.09 to 0.11 brackets the radius the SE(3) and SE_2(3) building blocks switch at"; constant Real tolerance[7] = { 1.0e-9, 2.0e-9, 1.5e-9, 1.5e-9, 1.0e-9, 5.0e-9, 2.5e-9} "One budget per rule: exp_map, log_map, product left, product right, inverse, rotate rotation, rotate vector. Each is about five times what that rule measures over the whole sweep, which is its own difference floor: eps times the magnitude of the quantity being differenced, divided by the step, so the rules that carry a 5 m target vector sit higher than the rules that carry only rotations"; constant Real familyTolerance = 1.0e-14 "The three identities are exact: the first measures 4.4e-16 and the other two measure zero, so this budget is rounding, not accuracy"; Real sweep[7] "Worst over the whole magnitude list, one entry per rule"; Real family[3] "J_r J_r^-1 - I, J_l - J_r^T, J_l^-1 - (J_r^-1)^T"; Real series[7] "0.05 rad"; Real belowBranch[7] "0.095 rad"; Real atBranch[7] "0.0999 rad, the last magnitude below 0.1"; Real large[7] "2.0 rad"; equation sweep = Tests.RuleChecks.so3RuleResidualsSweep(magnitudes, step, trials); family = Tests.RuleChecks.so3JacobianFamilyResiduals(magnitudes, trials); series = Tests.RuleChecks.so3RuleResiduals(0.05, step, trials); belowBranch = Tests.RuleChecks.so3RuleResiduals(0.095, step, trials); atBranch = Tests.RuleChecks.so3RuleResiduals(0.0999, step, trials); large = Tests.RuleChecks.so3RuleResiduals(2.0, step, trials); assert(sweep[1] < tolerance[1], "exp_map_jacobian disagrees with a central difference of exp_map"); assert(sweep[2] < tolerance[2], "log_map_jacobian disagrees with a central difference of log_map"); assert(sweep[3] < tolerance[3], "product_left_factor_jacobian disagrees with a central difference of product"); assert(sweep[4] < tolerance[4], "product_right_factor_jacobian disagrees with a central difference of product"); assert(sweep[5] < tolerance[5], "inverse_jacobian disagrees with a central difference of inverse"); assert(sweep[6] < tolerance[6], "rotate_rotation_jacobian disagrees with a central difference of rotate"); assert(sweep[7] < tolerance[7], "rotate_vector_jacobian disagrees with a central difference of rotate"); assert(family[1] < familyTolerance, "right_jacobian_exact and right_jacobian_inv_exact are not inverse"); assert(family[2] < familyTolerance, "left_jacobian_exact is not the transpose of right_jacobian_exact"); assert(family[3] < familyTolerance, "left_jacobian_inv_exact is not the transpose of right_jacobian_inv_exact"); // The named rows are inside the sweep and are recorded so a failure can be // placed. 0.095 and 0.0999 rad are where a rule that carried the SE(3) // building blocks' 0.1 rad series radius reports its own truncation, of // order |v|^5/720, as a disagreement with exp_map: 1.0e-8 and 1.3e-8, both // over the 1e-8 budget this model used to share between all seven rules. assert(Tests.Assertions.maxAbsVector(belowBranch) < 5.0e-9, "SO(3) rules disagree with central differences at 0.095 rad"); assert(Tests.Assertions.maxAbsVector(atBranch) < 5.0e-9, "SO(3) rules disagree with central differences at 0.0999 rad"); annotation(Documentation(info="
Each vector entry is one rule: exp_map, log_map, product in its left
factor, product in its right factor, inverse, rotate in its rotation slot,
and rotate in its vector slot. Budgets are per rule rather than per model:
one shared budget hides a rule sitting a hundred times worse than its
neighbours, which is how the two-term series in right_jacobian
reached 1.3e-8 at 0.0999 rad while its six neighbours measured 1e-11 to
8e-10 and no assertion noticed.
The sweep is dense and includes 0.1 rad itself. The central differences
here call only exp_map, log_map,
product, inverse and rotate, none of
which branches at 0.1 rad, so no magnitude in this model straddles a branch
of its own oracle. The rules and their oracle now branch at the same
1e-4 rad, where the two sides differ by about 1e-33 rather than the 1.4e-7
that a 0.1 rad radius produces.
family is not a difference. It is three exact identities of
the rule-local Jacobian family, measured directly, so it stays sharp where a
finite difference bottoms out at 1e-10.