Skip to content

Commit 9cd5142

Browse files
author
peng.li24
committed
issue/001: rewrite reply — correct sin/cos root cause (glibc not SVML)
1 parent 4e90e3f commit 9cd5142

1 file changed

Lines changed: 55 additions & 32 deletions

File tree

issue/001_rotation_from_euler_alignment.md

Lines changed: 55 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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),
106106
Python 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"
149175
using Rot = scipy::spatial::transform::Rotation<double>;
150176

151-
// 替换闭式 cos/sin 直接计算:
177+
// 替代闭式 std::cos/sin 直接计算,纯 C++ 路径 0-ULP
152178
double m9[9];
153179
Rot::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

Comments
 (0)