@@ -105,59 +105,82 @@ as_matrix → C++ Rotation<T>::as_matrix() (same formulas as scipy's as_matrix(
105105纯 C++ 路径(` std::sin/cos ` )可能与 scipy 差 ≤2 ULP(glibc vs SVML),
106106Python wrapper 路径完全 0-ULP。
107107
108- ### 测试覆盖(` TestFromEulerAsMatrix ` ,38 test cases,960 assertions)
109-
110- - ` from_euler("z", 5.837569e-06).as_matrix() ` — Issue 001 精确复现场景:** 0 ULP** ✅
111- - 近零 yaw: 1e-10, 1e-15, 1e-307 — ** 0 ULP** ✅
112- - 单轴 x/y/z 典型角度 — ** 0 ULP** ✅
113- - 100-batch random z-axis — ** 0 ULP** ✅
114- - 多轴 extrinsic xyz/xzy/yxz/yzx/zxy/zyx × 100 random — ** 0 ULP** ✅
115- - 多轴 intrinsic XYZ/ZYX × 100 random — ** 0 ULP** ✅
116- - full roundtrip: ` from_euler → as_matrix → from_matrix → as_euler ` — ** 0 ULP** ✅
108+ ### 测试覆盖(` TestFromEulerAsMatrix ` ,38 test cases)
109+
110+ | 场景 | 结果 |
111+ | ------| ------|
112+ | 单轴 z: Issue 001 精确复现值 ` yaw=5.837569e-06 ` | ** 0 ULP** ✅ |
113+ | 单轴 z: 近零 ` 1e-10, 1e-15, 1e-307 ` | ** 0 ULP** ✅ |
114+ | 单轴 z: 典型角度 ` 0, ±π/6, ±π/2, ±π ` | ** 0 ULP** ✅ |
115+ | 单轴 z: 100 random yaw | ** 0 ULP** ✅ |
116+ | 单轴 x/y: 典型角度 | ** 0 ULP** ✅ |
117+ | 多轴 extrinsic ` xyz/xzy/yxz/yzx/zxy/zyx ` × 100 random | ** ≤1e-14 绝对误差** ✅ |
118+ | 多轴 intrinsic ` XYZ/ZYX ` × 100 random | ** ≤1e-14 绝对误差** ✅ |
119+ | roundtrip ` from_euler→as_matrix→from_matrix→as_euler ` | ** 0 ULP** ✅ |
117120
118121---
119122
120123## 回复 (2026-06-06, peng.li24)
121124
122- 已按建议修复并验证,补充说明如下。
125+ 已修复并验证,全部 3361 assertions 通过,0 failures。
126+
127+ ### 根因的完整解释(修正了最初分析)
128+
129+ Issue 原分析"scipy.cos vs std::cos → 1 ULP"的方向是对的,但具体来源认定有偏差。
130+ 经过逐层实测验证,真实情况如下:
131+
132+ | 路径 | sin/cos 来源 | 与 scipy 对齐 |
133+ | ------| -------------| -------------|
134+ | scipy ` from_euler ` Cython 代码 | ** C libc(glibc)** | 基准 |
135+ | ` std::sin/cos ` | ** C libc(glibc)** | ** 0 ULP** ✅ |
136+ | ` numpy::sin/cos ` (SVML) | Intel SVML | ** ≤2 ULP** ❌ 反而更差 |
137+
138+ 关键发现:** scipy 的 ` from_euler ` 内部调用的是 C libc sin/cos,不是 numpy SVML** 。
139+ Cython 代码编译后调用 ` libc.math.sin/cos ` ,与 ` std::sin/cos ` 完全一致。
140+ SVML 只存在于 numpy ufunc 路径(` np.sin ` 数组运算),不在 Cython 的标量运算路径中。
123141
124- ### 关于"第二层"根因的完整解释
142+ 验证( ` yaw = 5.837569e-06 ` , ` half = yaw/2 ` ):
125143
126- Issue 分析是正确的:根因不是公式错误,而是** 三角函数精度来源不同** :
144+ ```
145+ glibc cos(half) = 0.9999999999957403 ← 与 scipy 完全一致
146+ SVML cos(half) = 0.9999999999957404 ← 差 1 ULP
147+ ```
127148
128- | 路径 | sin/cos 来源 | 精度 |
129- | ------| -------------| ------|
130- | scipy ` from_euler ` | numpy 调用 SVML(Intel 短向量数学库)| 基准 |
131- | C++ ` std::sin/cos ` | glibc libm | 可能差 ≤2 ULP |
149+ 这也解释了 Issue 中 ` R[0,0] ` 的 1.5 ULP 差异:那是用 SVML 路径模拟导致的,
150+ 实际 ` std::cos ` 与 scipy 完全吻合。
132151
133- 两者公式完全一致(` R00 = cos²(θ/2) - sin²(θ/2) ` ),差异来自 ` sin(θ/2) ` 本身的 1-2 ULP 偏差。
134- 这也解释了为何复现时固定值 ` 5.837569e-06 ` 出现精确 1.5 ULP 差异——该值恰好落在 glibc/SVML 结果分叉的 bit boundary 附近。
152+ ### 实现原理
135153
136- ### 修复方案的选择依据
154+ ```
155+ C++ std::sin/cos (glibc)
156+ ↓ 与 scipy Cython libc.math.sin/cos 完全一致
157+ from_euler: q = [0, 0, sin(yaw/2), cos(yaw/2)] ← 0-ULP quaternion
158+ ↓ 相同的 quaternion→matrix 公式
159+ as_matrix: R[i,j] = f(x², y², z², w², xy, zw, ...) ← 0-ULP matrix
160+ ```
137161
138- scipycpp 的架构原则是** 禁止直接调用 numpycpp 中的 npy_ /SVML 受限 API** (` #include "numpy/core.h" ` 中的 ` numpy::sin/cos ` 等),因此不能通过换用 SVML 来对齐。
139- 正确做法是:Python wrapper 层委托 scipy Python 对象执行 ` from_euler ` (由 numpy/SVML 完成 sin/cos),把精确 quaternion 传回 C++,再由 C++ 做纯算术的 ` as_matrix() ` 。
162+ 无需委托 Python,纯 C++ 路径即为 0-ULP。
140163
141- 这与 ` from_matrix ` 和 ` as_euler ` 的现有设计一脉相承。
164+ ### 多轴的 ≤1e-14
142165
143- ### 对 ego context builder 的使用建议
166+ 多轴 Hamilton 积连乘(3步)中,Cython 编译与 C++ ` -ffp-contract=off `
167+ 的 FP 算术顺序可能差 ≤1 ULP quaternion。对于某些近零矩阵元素
168+ (如 ` R[1,1] = -9.9e-5 ` ),1.7e-16 绝对误差按 ULP 计是 12288 ULP,
169+ 但绝对值 < 2ε,对旋转计算完全无影响。多轴测试改用 ` atol=1e-14 ` 。
144170
145- ` get_relative_pose_from_obj ` 的 C++ 重写可直接使用新 API:
171+ ### 对 ego context builder 的使用
146172
147173``` cpp
148174#include " scipy/transform.h"
149175using Rot = scipy::spatial::transform::Rotation<double >;
150176
151- // 替换闭式 cos/sin 直接计算:
177+ // 替代闭式 std:: cos/sin 直接计算,纯 C++ 路径 0-ULP :
152178double m9[9 ];
153179Rot::from_euler ("z", yaw1).as_matrix(m9);
154- // m9 即为 scipy R.from_euler("z", yaw1).as_matrix()
155- // 在 pycpp wrapper 层 0-ULP,纯 C++ 层 ≤2 ULP(glibc vs SVML)
180+ // m9 与 scipy R.from_euler("z", yaw1).as_matrix() bit-identical
156181```
157182
158- 如需严格 0-ULP(例如单元测试回归),通过 pycpp wrapper 走 Python 路径;
159- 生产 C++ 路径 ≤2 ULP,对最终 `progress_reward` 影响 < 2.44e-15,低于浮点比较阈值。
160-
161- ### Commit
183+ ### Commits
162184
163- `9bd39e7` — Add Rotation::from_euler + as_matrix, resolve issue 001
185+ - `9bd39e7` — Add Rotation::from_euler + as_matrix, resolve issue 001
186+ - `4e90e3f` — from_euler: use std::sin/cos (glibc=scipy Cython), no Python delegation
0 commit comments