transform-tree 0.0.6__tar.gz → 0.0.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {transform_tree-0.0.6 → transform_tree-0.0.7}/Cargo.toml +9 -6
- {transform_tree-0.0.6 → transform_tree-0.0.7}/PKG-INFO +1 -1
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/Cargo.toml +7 -0
- transform_tree-0.0.7/crates/tf_tree_math/src/helicoid_backend.rs +284 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/src/interp.rs +15 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/src/lib.rs +2 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/src/quat.rs +61 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/tests/slerp_public.rs +30 -8
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_py/Cargo.lock +29 -8
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_py/Cargo.toml +4 -4
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_py/src/tree.rs +2 -2
- {transform_tree-0.0.6 → transform_tree-0.0.7}/pyproject.toml +1 -1
- {transform_tree-0.0.6 → transform_tree-0.0.7}/LICENSE-APACHE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/LICENSE-MIT +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/NOTICE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/README.md +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/Cargo.toml +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/LICENSE-APACHE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/LICENSE-MIT +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/NOTICE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/README.md +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/examples/control_loop.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/examples/gen_domain_fixture.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/examples/two_processes.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/bin/rendezvous_child.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/cache.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/cbor.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/frozen.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/lib.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/open.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/tree.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/src/unstable.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/await_frames.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/batch.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/batch_phases.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/behavior.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/clock_offset.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/common/mod.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/construction.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/counters.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/derivatives.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/enumeration.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/error_payloads.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/feature_gates.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/frozen.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/lookup.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/math_reexports.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/owned_writer.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/plan_cache_identity.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/rendezvous.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/tsan.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree/tests/wide_stamps.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/Cargo.toml +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/LICENSE-APACHE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/LICENSE-MIT +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/NOTICE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/README.md +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/check.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/frozen.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/header.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/heap.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/layout.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/lib.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/mapped.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/src/render_test.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_arena/tests/heap_alignment.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/Cargo.toml +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/examples/offer_cost.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/authority.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/clock.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/config.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/discover.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/edgeindex.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/edgemap.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/ingest.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/interner.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/lib.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/names.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/statics.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/src/stats.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_bridge/tests/steady_state_alloc.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/Cargo.toml +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/LICENSE-APACHE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/LICENSE-MIT +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/NOTICE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/README.md +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/arena_view.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/bench_probe.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/buffer.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/counters.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/crash.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/crash_tests.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/edge.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/error.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/frame.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/layout.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/lib.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/loom_tests.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/participant.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/plan.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/sample.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/sync.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/tests.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_core/src/topology.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/Cargo.toml +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/examples/gen_zstd_conformance.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/cdr.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/decompress.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/fixture.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/ingest.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/lib.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/report.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/source.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/spill.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/src/tft.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/testdata/ATTRIBUTION.md +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/testdata/zstd_conformance.mcap +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/tests/anomaly_corpus.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/tests/codec_free.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/tests/frozen_bag.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/tests/ingest.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/tests/memory.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ingest/tests/record_ceiling.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/Cargo.toml +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/LICENSE-APACHE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/LICENSE-MIT +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/NOTICE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/README.md +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/bin/ipc_child.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/client.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/error.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/fork.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/identity.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/lib.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/lockfile.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/ofd.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/open.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/procstat.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/rendezvous.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/runtime_dir.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/server.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/src/wire.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_ipc/tests/multiprocess.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/LICENSE-APACHE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/LICENSE-MIT +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/NOTICE +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/README.md +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/src/dualquat.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/src/iso3.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/src/reference.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/src/twist.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_math/tests/proptests.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_py/src/errors.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_py/src/ingest.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_py/src/lib.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/crates/tf_tree_py/src/offline.rs +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/python/tf_tree/__init__.py +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/python/tf_tree/_core.pyi +0 -0
- {transform_tree-0.0.6 → transform_tree-0.0.7}/python/tf_tree/py.typed +0 -0
|
@@ -20,7 +20,7 @@ exclude = [
|
|
|
20
20
|
|
|
21
21
|
[workspace.package]
|
|
22
22
|
# 0.0.x: every release may break every other (`SUPPORT.md`).
|
|
23
|
-
version = "0.0.
|
|
23
|
+
version = "0.0.7"
|
|
24
24
|
edition = "2021"
|
|
25
25
|
# 1.87 is forced by `ruzstd 0.9.0` (`tf_tree_ingest`). The `msrv` job reads this
|
|
26
26
|
# field; policy in `SUPPORT.md`.
|
|
@@ -57,16 +57,16 @@ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(loom)'] }
|
|
|
57
57
|
|
|
58
58
|
[workspace.dependencies]
|
|
59
59
|
# Intra-workspace crates.
|
|
60
|
-
tf_tree_math = { path = "crates/tf_tree_math", version = "0.0.
|
|
61
|
-
tf_tree_arena = { path = "crates/tf_tree_arena", version = "0.0.
|
|
60
|
+
tf_tree_math = { path = "crates/tf_tree_math", version = "0.0.7" }
|
|
61
|
+
tf_tree_arena = { path = "crates/tf_tree_arena", version = "0.0.7" }
|
|
62
62
|
# `shm` (`docs/PHASE2.md` §2): `linux_raw` backend, no C build; used only by `tf_tree_ipc`.
|
|
63
63
|
libc = "0.2"
|
|
64
64
|
rustix = { version = "1.1", default-features = false, features = ["mm", "fs", "std"] }
|
|
65
65
|
# `default-features = false` is load-bearing: without it `--no-default-features`
|
|
66
66
|
# downstream still enables `counters` (`docs/PHASE5.md` §5.5).
|
|
67
|
-
tf_tree_core = { path = "crates/tf_tree_core", version = "0.0.
|
|
68
|
-
tf_tree_ipc = { path = "crates/tf_tree_ipc", version = "0.0.
|
|
69
|
-
tf_tree = { path = "crates/tf_tree", version = "0.0.
|
|
67
|
+
tf_tree_core = { path = "crates/tf_tree_core", version = "0.0.7", default-features = false }
|
|
68
|
+
tf_tree_ipc = { path = "crates/tf_tree_ipc", version = "0.0.7" }
|
|
69
|
+
tf_tree = { path = "crates/tf_tree", version = "0.0.7", default-features = false }
|
|
70
70
|
# publish = false crates; `default-features = false` as above.
|
|
71
71
|
tf_tree_bench = { path = "crates/tf_tree_bench", default-features = false }
|
|
72
72
|
tf_tree_bridge = { path = "crates/tf_tree_bridge" }
|
|
@@ -76,6 +76,9 @@ tf_tree_ingest = { path = "crates/tf_tree_ingest", default-features = false }
|
|
|
76
76
|
libm = "0.2"
|
|
77
77
|
bytemuck = { version = "1", features = ["derive"] }
|
|
78
78
|
blake3 = { version = "1", default-features = false }
|
|
79
|
+
# `tf_tree_math`'s optional `helicoid` feature (`docs/decisions/0063`): off by default, so D14's budget holds for everyone who does not opt in.
|
|
80
|
+
helicoid = "=0.0.4"
|
|
81
|
+
helicoid-linalg = "=0.0.4"
|
|
79
82
|
# std-side facade / tooling only.
|
|
80
83
|
thiserror = "2"
|
|
81
84
|
anyhow = "1"
|
|
@@ -42,9 +42,16 @@ rustdoc-args = ["--cfg", "docsrs"]
|
|
|
42
42
|
# `sqrt`/`sin`/`cos` on the hot path — with soft floats.
|
|
43
43
|
miri-soft-float = ["libm/force-soft-floats"]
|
|
44
44
|
|
|
45
|
+
# Delegate the SO(3) bodies to `helicoid` (`docs/decisions/0063`), on by default (`0064`);
|
|
46
|
+
# `--no-default-features` runs the old bodies, which stay compiled either way as the adapter twins.
|
|
47
|
+
default = ["helicoid"]
|
|
48
|
+
helicoid = ["dep:helicoid", "dep:helicoid-linalg"]
|
|
49
|
+
|
|
45
50
|
[dependencies]
|
|
46
51
|
libm.workspace = true
|
|
47
52
|
bytemuck.workspace = true
|
|
53
|
+
helicoid = { workspace = true, optional = true }
|
|
54
|
+
helicoid-linalg = { workspace = true, optional = true }
|
|
48
55
|
|
|
49
56
|
[dev-dependencies]
|
|
50
57
|
proptest.workspace = true
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
//! The SO(3) bodies delegated to `helicoid` under the `helicoid` feature (`docs/decisions/0063`).
|
|
2
|
+
//!
|
|
3
|
+
//! Each function converts at the boundary and calls one `helicoid` routine; the arithmetic is
|
|
4
|
+
//! `helicoid`'s. `Quat` and `Vec3` keep their layouts, so nothing here crosses a storage contract.
|
|
5
|
+
//!
|
|
6
|
+
//! A quaternion enters on `helicoid`'s *carried* path, a struct literal moved into
|
|
7
|
+
//! `SO3::from_quat_unchecked`, never through the vouching `Quat::from_wxyz_unchecked`. `Iso3`
|
|
8
|
+
//! composition never normalizes, so this crate's quaternions drift by design. The carried path
|
|
9
|
+
//! asserts nothing, propagates NaN, and states each operation's error up to
|
|
10
|
+
//! `|‖q‖² − 1| ≤ 2^-26.29` (`helicoid`'s `NUMERICS.md` §12).
|
|
11
|
+
|
|
12
|
+
use crate::iso3::Vec3;
|
|
13
|
+
use crate::quat::Quat;
|
|
14
|
+
use helicoid::{LieGroup, Quat as HQuat, SO3Tangent, SO3};
|
|
15
|
+
use helicoid_linalg::{Matrix, Vector};
|
|
16
|
+
|
|
17
|
+
#[inline]
|
|
18
|
+
fn so3(q: Quat) -> SO3<f64> {
|
|
19
|
+
SO3::from_quat_unchecked(HQuat {
|
|
20
|
+
w: q.w,
|
|
21
|
+
x: q.x,
|
|
22
|
+
y: q.y,
|
|
23
|
+
z: q.z,
|
|
24
|
+
})
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
#[inline]
|
|
28
|
+
fn quat(r: SO3<f64>) -> Quat {
|
|
29
|
+
let q = r.quat();
|
|
30
|
+
Quat::new(q.w, q.x, q.y, q.z)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#[inline]
|
|
34
|
+
pub(crate) fn exp_so3(w: Vec3) -> Quat {
|
|
35
|
+
quat(SO3::exp(&SO3Tangent {
|
|
36
|
+
phi: Vector([w.x, w.y, w.z]),
|
|
37
|
+
}))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#[inline]
|
|
41
|
+
pub(crate) fn log_so3(q: Quat) -> Vec3 {
|
|
42
|
+
let [x, y, z] = so3(q).log().phi.0;
|
|
43
|
+
Vec3::new(x, y, z)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Shepperd's quotients in the native body's arithmetic on a rotation, normalized off the sphere,
|
|
47
|
+
/// as `SO3::from_matrix` returns them (`docs/decisions/0063` decision 3).
|
|
48
|
+
#[inline]
|
|
49
|
+
pub(crate) fn quat_from_rot3(r: &[f64; 9]) -> Quat {
|
|
50
|
+
let row = |i: usize| Vector([r[3 * i], r[3 * i + 1], r[3 * i + 2]]);
|
|
51
|
+
quat(SO3::from_matrix(&Matrix::from_rows([
|
|
52
|
+
row(0),
|
|
53
|
+
row(1),
|
|
54
|
+
row(2),
|
|
55
|
+
])))
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
#[inline]
|
|
59
|
+
pub(crate) fn slerp(qa: Quat, qb: Quat, s: f64) -> Quat {
|
|
60
|
+
quat(SO3::geodesic(&so3(qa), &so3(qb), s))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// Bit-identical to the native sandwich, drifted or not: the same products, in the same order.
|
|
64
|
+
#[inline]
|
|
65
|
+
pub(crate) fn rotate(q: Quat, v: Vec3) -> Vec3 {
|
|
66
|
+
let [x, y, z] = so3(q).act(Vector([v.x, v.y, v.z])).0;
|
|
67
|
+
Vec3::new(x, y, z)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// The adapter twins (`docs/decisions/0063` decision 5): each delegated function against the body
|
|
71
|
+
/// that ran before delegation, on a deterministic sweep. Tolerances are measured, not derived: the
|
|
72
|
+
/// worst observed distance is asserted as a band, so an unrelated change does not fail them.
|
|
73
|
+
#[cfg(test)]
|
|
74
|
+
mod twin_tests {
|
|
75
|
+
use super::*;
|
|
76
|
+
use crate::interp::slerp_native;
|
|
77
|
+
use crate::quat::{exp_so3_native, log_so3_native, quat_from_rot3_native};
|
|
78
|
+
|
|
79
|
+
/// Rotation-vector magnitudes: both sides of `EXP_SO3_SMALL`, the series switches, and `π`.
|
|
80
|
+
const THETAS: [f64; 14] = [
|
|
81
|
+
0.0,
|
|
82
|
+
1e-12,
|
|
83
|
+
1e-9,
|
|
84
|
+
1e-8,
|
|
85
|
+
1e-6,
|
|
86
|
+
1e-3,
|
|
87
|
+
0.05,
|
|
88
|
+
0.15,
|
|
89
|
+
0.5,
|
|
90
|
+
1.0,
|
|
91
|
+
2.0,
|
|
92
|
+
3.0,
|
|
93
|
+
3.1,
|
|
94
|
+
core::f64::consts::PI - 1e-9,
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
/// A unit direction, varied across `k` (golden-ratio steps; no `rand`).
|
|
98
|
+
fn axis(k: usize) -> Vec3 {
|
|
99
|
+
let a = 0.618_033_988_749_894_9 * (k as f64 + 1.0);
|
|
100
|
+
let (u, v) = (
|
|
101
|
+
a.fract() * 2.0 - 1.0,
|
|
102
|
+
(a * 7.0).fract() * core::f64::consts::TAU,
|
|
103
|
+
);
|
|
104
|
+
let r = (1.0 - u * u).sqrt();
|
|
105
|
+
Vec3::new(r * v.cos(), r * v.sin(), u)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fn rotation_vectors() -> impl Iterator<Item = Vec3> {
|
|
109
|
+
THETAS
|
|
110
|
+
.iter()
|
|
111
|
+
.enumerate()
|
|
112
|
+
.flat_map(|(i, &t)| (0..8).map(move |k| axis(7 * i + k).scale(t)))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fn dist(a: Quat, b: Quat) -> f64 {
|
|
116
|
+
a.sub(b).norm().min(a.add(b).norm())
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// `q` as the row-major `[f64; 9]` `quat_from_rot3` consumes.
|
|
120
|
+
fn rot3(q: Quat) -> [f64; 9] {
|
|
121
|
+
let (w, x, y, z) = (q.w, q.x, q.y, q.z);
|
|
122
|
+
[
|
|
123
|
+
1.0 - 2.0 * (y * y + z * z),
|
|
124
|
+
2.0 * (x * y - w * z),
|
|
125
|
+
2.0 * (x * z + w * y),
|
|
126
|
+
2.0 * (x * y + w * z),
|
|
127
|
+
1.0 - 2.0 * (x * x + z * z),
|
|
128
|
+
2.0 * (y * z - w * x),
|
|
129
|
+
2.0 * (x * z - w * y),
|
|
130
|
+
2.0 * (y * z + w * x),
|
|
131
|
+
1.0 - 2.0 * (x * x + y * y),
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[test]
|
|
136
|
+
fn exp_so3_agrees_with_the_native_twin() {
|
|
137
|
+
let worst = rotation_vectors()
|
|
138
|
+
.map(|w| exp_so3(w).sub(exp_so3_native(w)).norm())
|
|
139
|
+
.fold(0.0, f64::max);
|
|
140
|
+
assert!(worst < 1e-15, "exp_so3 differs by {worst:e}");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn log_so3_agrees_with_the_native_twin() {
|
|
145
|
+
let worst = rotation_vectors()
|
|
146
|
+
.map(|w| {
|
|
147
|
+
let q = exp_so3_native(w);
|
|
148
|
+
log_so3(q).sub(log_so3_native(q)).norm()
|
|
149
|
+
})
|
|
150
|
+
.fold(0.0, f64::max);
|
|
151
|
+
assert!(worst < 1e-14, "log_so3 differs by {worst:e}");
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/// The recorded behaviour change (`0063` decision 3): the native result is not normalized, so
|
|
155
|
+
/// the twin is compared after `normalize`, and up to the sign of the quaternion.
|
|
156
|
+
#[test]
|
|
157
|
+
fn quat_from_rot3_agrees_with_the_normalized_native_twin() {
|
|
158
|
+
let worst = rotation_vectors()
|
|
159
|
+
.map(|w| {
|
|
160
|
+
let r = rot3(exp_so3_native(w));
|
|
161
|
+
dist(quat_from_rot3(&r), quat_from_rot3_native(&r).normalize())
|
|
162
|
+
})
|
|
163
|
+
.fold(0.0, f64::max);
|
|
164
|
+
assert!(worst < 1e-14, "quat_from_rot3 differs by {worst:e}");
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#[test]
|
|
168
|
+
fn slerp_agrees_with_the_native_twin() {
|
|
169
|
+
let qs: [Quat; 4] =
|
|
170
|
+
core::array::from_fn(|i| exp_so3_native(axis(i).scale(0.7 * i as f64 + 0.01)));
|
|
171
|
+
let mut worst = 0.0f64;
|
|
172
|
+
for (i, &qa) in qs.iter().enumerate() {
|
|
173
|
+
for &qb in &qs[i + 1..] {
|
|
174
|
+
for s in [0.0, 0.1, 0.25, 0.5, 0.9, 1.0] {
|
|
175
|
+
worst = worst.max(dist(slerp(qa, qb, s), slerp_native(qa, qb, s)));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
assert!(worst < 1e-14, "slerp differs by {worst:e}");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn slerp_endpoints_agree_with_the_native_twin() {
|
|
184
|
+
let qa = exp_so3_native(axis(3).scale(0.4));
|
|
185
|
+
for theta in [1e-8, 1e-3, 0.9] {
|
|
186
|
+
let qb = qa * exp_so3_native(axis(5).scale(theta));
|
|
187
|
+
assert!(
|
|
188
|
+
dist(slerp(qa, qb, 0.0), qa) < 4e-16,
|
|
189
|
+
"s = 0, theta {theta:e}"
|
|
190
|
+
);
|
|
191
|
+
assert!(
|
|
192
|
+
dist(slerp(qa, qb, 1.0), qb) < 4e-16,
|
|
193
|
+
"s = 1, theta {theta:e}"
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[test]
|
|
199
|
+
fn slerp_of_identical_inputs_is_qa_to_a_few_ulp() {
|
|
200
|
+
for k in 0..16 {
|
|
201
|
+
let qa = exp_so3_native(axis(k).scale(0.3 * k as f64 + 0.05));
|
|
202
|
+
for j in 0..=10 {
|
|
203
|
+
let d = dist(slerp(qa, qa, j as f64 / 10.0), qa);
|
|
204
|
+
assert!(d < 1e-15, "k={k} j={j}: {d:e}");
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// The native series collapses outside `[0, 1]`; `helicoid`'s geodesic is `x0 ⊕ s d`, so it holds.
|
|
210
|
+
#[test]
|
|
211
|
+
fn slerp_extrapolates_off_the_segment() {
|
|
212
|
+
let mut worst = 0.0f64;
|
|
213
|
+
for k in 0..16 {
|
|
214
|
+
let qa = exp_so3_native(axis(k).scale(0.3 * k as f64 + 0.05));
|
|
215
|
+
let ax = axis(k + 40);
|
|
216
|
+
for theta in [0.02, 0.1, 0.5] {
|
|
217
|
+
let qb = qa * exp_so3_native(ax.scale(theta));
|
|
218
|
+
for s in [-20.0, -5.0, 2.0, 5.0, 20.0] {
|
|
219
|
+
let want = qa * exp_so3_native(ax.scale(s * theta));
|
|
220
|
+
worst = worst.max(dist(slerp(qa, qb, s), want));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
assert!(worst < 1e-13, "extrapolation differs by {worst:e}");
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/// `q` scaled so that `‖q‖² − 1 = eta`: the drift `Iso3` composition carries.
|
|
228
|
+
fn drifted(q: Quat, eta: f64) -> Quat {
|
|
229
|
+
q.scale((1.0 + eta).sqrt())
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/// Unit, at `helicoid`'s vouched bound `2^-40`, and out to the edge of its drift band.
|
|
233
|
+
const DRIFTS: [f64; 5] = [0.0, 9.1e-13, -9.1e-13, 1.2e-8, -1.2e-8];
|
|
234
|
+
|
|
235
|
+
#[test]
|
|
236
|
+
fn rotate_is_the_native_twin_to_the_bit_drifted_or_not() {
|
|
237
|
+
let v = [
|
|
238
|
+
Vec3::new(1.0, -2.0, 0.5),
|
|
239
|
+
Vec3::new(-3e-9, 7.0, 1e6),
|
|
240
|
+
Vec3::new(0.0, 0.0, 1.0),
|
|
241
|
+
];
|
|
242
|
+
let bits = |a: Vec3| [a.x, a.y, a.z].map(f64::to_bits);
|
|
243
|
+
for w in rotation_vectors() {
|
|
244
|
+
for eta in DRIFTS {
|
|
245
|
+
let q = drifted(exp_so3_native(w), eta);
|
|
246
|
+
for &v in &v {
|
|
247
|
+
assert_eq!(
|
|
248
|
+
bits(rotate(q, v)),
|
|
249
|
+
bits(q.rotate_native(v)),
|
|
250
|
+
"{w:?} {eta:e}"
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/// A drifted quaternion reaches every delegated function without a debug panic, and `log_so3`
|
|
258
|
+
/// stays the native twin's: both are scale-invariant.
|
|
259
|
+
#[test]
|
|
260
|
+
fn a_drifted_quaternion_is_carried_through_every_delegated_function() {
|
|
261
|
+
let mut worst = 0.0f64;
|
|
262
|
+
for w in rotation_vectors() {
|
|
263
|
+
for eta in DRIFTS {
|
|
264
|
+
let q = drifted(exp_so3_native(w), eta);
|
|
265
|
+
worst = worst.max(log_so3(q).sub(log_so3_native(q)).norm());
|
|
266
|
+
let other = drifted(exp_so3_native(w.scale(0.5)), -eta);
|
|
267
|
+
let _ = slerp(q, other, 0.3);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
assert!(worst < 1e-14, "log_so3 of a drifted q differs by {worst:e}");
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#[test]
|
|
274
|
+
fn nan_propagates_through_every_delegated_function() {
|
|
275
|
+
let q = Quat::new(f64::NAN, 0.1, 0.2, 0.3);
|
|
276
|
+
let one = exp_so3_native(Vec3::new(0.3, -0.2, 0.1));
|
|
277
|
+
let log = log_so3(q);
|
|
278
|
+
assert!(log.x.is_nan() && log.y.is_nan() && log.z.is_nan());
|
|
279
|
+
assert!(rotate(q, Vec3::new(1.0, 2.0, 3.0)).x.is_nan());
|
|
280
|
+
assert!(slerp(q, one, 0.5).w.is_nan());
|
|
281
|
+
assert!(slerp(one, q, 0.5).w.is_nan());
|
|
282
|
+
assert!(slerp(one, one, f64::NAN).w.is_nan());
|
|
283
|
+
}
|
|
284
|
+
}
|
|
@@ -159,6 +159,21 @@ impl Iso3 {
|
|
|
159
159
|
#[inline]
|
|
160
160
|
#[must_use]
|
|
161
161
|
pub fn slerp(qa: Quat, qb: Quat, s: f64) -> Quat {
|
|
162
|
+
#[cfg(feature = "helicoid")]
|
|
163
|
+
{
|
|
164
|
+
crate::helicoid_backend::slerp(qa, qb, s)
|
|
165
|
+
}
|
|
166
|
+
#[cfg(not(feature = "helicoid"))]
|
|
167
|
+
{
|
|
168
|
+
slerp_native(qa, qb, s)
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// The body that ran before delegation, kept as [`slerp`]'s adapter twin (`docs/decisions/0063`).
|
|
173
|
+
#[inline]
|
|
174
|
+
#[must_use]
|
|
175
|
+
#[cfg_attr(all(feature = "helicoid", not(test)), allow(dead_code))]
|
|
176
|
+
pub(crate) fn slerp_native(qa: Quat, qb: Quat, s: f64) -> Quat {
|
|
162
177
|
let dot = qa.dot(qb);
|
|
163
178
|
let qb = if dot < 0.0 { qb.neg() } else { qb };
|
|
164
179
|
|
|
@@ -174,6 +174,22 @@ impl Quat {
|
|
|
174
174
|
#[inline]
|
|
175
175
|
#[must_use]
|
|
176
176
|
pub fn rotate(self, v: Vec3) -> Vec3 {
|
|
177
|
+
#[cfg(feature = "helicoid")]
|
|
178
|
+
{
|
|
179
|
+
crate::helicoid_backend::rotate(self, v)
|
|
180
|
+
}
|
|
181
|
+
#[cfg(not(feature = "helicoid"))]
|
|
182
|
+
{
|
|
183
|
+
self.rotate_native(v)
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/// The body that ran before delegation, kept as [`Quat::rotate`]'s adapter twin
|
|
188
|
+
/// (`docs/decisions/0063`).
|
|
189
|
+
#[inline]
|
|
190
|
+
#[must_use]
|
|
191
|
+
#[cfg_attr(all(feature = "helicoid", not(test)), allow(dead_code))]
|
|
192
|
+
pub(crate) fn rotate_native(self, v: Vec3) -> Vec3 {
|
|
177
193
|
let u = self.vector();
|
|
178
194
|
let t = u.cross(v).scale(2.0);
|
|
179
195
|
v.add(t.scale(self.w)).add(u.cross(t))
|
|
@@ -205,6 +221,21 @@ impl Mul for Quat {
|
|
|
205
221
|
#[inline]
|
|
206
222
|
#[must_use]
|
|
207
223
|
pub fn exp_so3(w: Vec3) -> Quat {
|
|
224
|
+
#[cfg(feature = "helicoid")]
|
|
225
|
+
{
|
|
226
|
+
crate::helicoid_backend::exp_so3(w)
|
|
227
|
+
}
|
|
228
|
+
#[cfg(not(feature = "helicoid"))]
|
|
229
|
+
{
|
|
230
|
+
exp_so3_native(w)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/// The body that ran before delegation, kept as [`exp_so3`]'s adapter twin (`docs/decisions/0063`).
|
|
235
|
+
#[inline]
|
|
236
|
+
#[must_use]
|
|
237
|
+
#[cfg_attr(all(feature = "helicoid", not(test)), allow(dead_code))]
|
|
238
|
+
pub(crate) fn exp_so3_native(w: Vec3) -> Quat {
|
|
208
239
|
exp_so3_theta(w, w.norm())
|
|
209
240
|
}
|
|
210
241
|
|
|
@@ -238,6 +269,21 @@ pub(crate) fn exp_so3_theta(w: Vec3, theta: f64) -> Quat {
|
|
|
238
269
|
#[inline]
|
|
239
270
|
#[must_use]
|
|
240
271
|
pub fn log_so3(q: Quat) -> Vec3 {
|
|
272
|
+
#[cfg(feature = "helicoid")]
|
|
273
|
+
{
|
|
274
|
+
crate::helicoid_backend::log_so3(q)
|
|
275
|
+
}
|
|
276
|
+
#[cfg(not(feature = "helicoid"))]
|
|
277
|
+
{
|
|
278
|
+
log_so3_native(q)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// The body that ran before delegation, kept as [`log_so3`]'s adapter twin (`docs/decisions/0063`).
|
|
283
|
+
#[inline]
|
|
284
|
+
#[must_use]
|
|
285
|
+
#[cfg_attr(all(feature = "helicoid", not(test)), allow(dead_code))]
|
|
286
|
+
pub(crate) fn log_so3_native(q: Quat) -> Vec3 {
|
|
241
287
|
// w ≥ 0 puts the result in the principal branch.
|
|
242
288
|
let (w, x, y, z) = if q.w < 0.0 {
|
|
243
289
|
(-q.w, -q.x, -q.y, -q.z)
|
|
@@ -269,6 +315,21 @@ pub fn log_so3(q: Quat) -> Vec3 {
|
|
|
269
315
|
#[inline]
|
|
270
316
|
#[must_use]
|
|
271
317
|
pub fn quat_from_rot3(r: &[f64; 9]) -> Quat {
|
|
318
|
+
#[cfg(feature = "helicoid")]
|
|
319
|
+
{
|
|
320
|
+
crate::helicoid_backend::quat_from_rot3(r)
|
|
321
|
+
}
|
|
322
|
+
#[cfg(not(feature = "helicoid"))]
|
|
323
|
+
{
|
|
324
|
+
quat_from_rot3_native(r)
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// The body that ran before delegation, kept as [`quat_from_rot3`]'s adapter twin (`docs/decisions/0063`).
|
|
329
|
+
#[inline]
|
|
330
|
+
#[must_use]
|
|
331
|
+
#[cfg_attr(all(feature = "helicoid", not(test)), allow(dead_code))]
|
|
332
|
+
pub(crate) fn quat_from_rot3_native(r: &[f64; 9]) -> Quat {
|
|
272
333
|
let (r00, r11, r22) = (r[0], r[4], r[8]);
|
|
273
334
|
let trace = r00 + r11 + r22;
|
|
274
335
|
if trace > 0.0 {
|
|
@@ -111,7 +111,11 @@ fn endpoints_are_bit_exact_above_the_lerp_fallback() {
|
|
|
111
111
|
/// `s = 0` differs from `qa` by a couple of ulp unless `qa` is already unit to
|
|
112
112
|
/// the bit. That ulp is the observable: it appears below the threshold and
|
|
113
113
|
/// disappears above it with the same `qa`.
|
|
114
|
+
///
|
|
115
|
+
/// Native arm only: `helicoid`'s geodesic has no LERP fallback to pin, and its endpoints are
|
|
116
|
+
/// held by `helicoid_backend`'s `slerp_endpoints_agree_with_the_native_twin`.
|
|
114
117
|
#[test]
|
|
118
|
+
#[cfg(not(feature = "helicoid"))]
|
|
115
119
|
fn endpoints_lose_bit_exactness_only_in_the_lerp_fallback() {
|
|
116
120
|
let mut discriminating = 0usize;
|
|
117
121
|
let mut worst = 0.0f64;
|
|
@@ -262,7 +266,10 @@ fn the_arc_is_the_short_one_and_the_sign_of_qb_is_irrelevant() {
|
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
/// Identical inputs short-circuit, and `s` cannot perturb the answer.
|
|
269
|
+
///
|
|
270
|
+
/// Native arm only: `helicoid`'s geodesic returns `qa` to a few ulp, not the bit; `slerp_of_identical_inputs_is_qa_to_a_few_ulp` pins that arm.
|
|
265
271
|
#[test]
|
|
272
|
+
#[cfg(not(feature = "helicoid"))]
|
|
266
273
|
fn numerically_identical_inputs_return_qa_at_every_s() {
|
|
267
274
|
for k in 0..16 {
|
|
268
275
|
let qa = sample_rotation(k);
|
|
@@ -274,11 +281,13 @@ fn numerically_identical_inputs_return_qa_at_every_s() {
|
|
|
274
281
|
}
|
|
275
282
|
|
|
276
283
|
/// The exact great-circle point at parameter `s`, valid off the segment too.
|
|
284
|
+
#[cfg(not(feature = "helicoid"))]
|
|
277
285
|
fn geodesic(qa: Quat, theta: f64, s: f64) -> Quat {
|
|
278
286
|
step(qa, s * theta)
|
|
279
287
|
}
|
|
280
288
|
|
|
281
289
|
/// The double cover: the distance that counts is the smaller of the two.
|
|
290
|
+
#[cfg(not(feature = "helicoid"))]
|
|
282
291
|
fn rot_dist(a: Quat, b: Quat) -> f64 {
|
|
283
292
|
a.sub(b).norm().min(a.add(b).norm())
|
|
284
293
|
}
|
|
@@ -287,7 +296,10 @@ fn rot_dist(a: Quat, b: Quat) -> f64 {
|
|
|
287
296
|
/// branches degrade differently (closed form holds, series collapses, the
|
|
288
297
|
/// fallback's chord is fine until the arc grows), so the bands are wide and pin
|
|
289
298
|
/// the shape, not a libm revision.
|
|
299
|
+
///
|
|
300
|
+
/// Native arm only: `helicoid` does not collapse out of range; `slerp_extrapolates_off_the_segment` pins that arm.
|
|
290
301
|
#[test]
|
|
302
|
+
#[cfg(not(feature = "helicoid"))]
|
|
291
303
|
fn out_of_range_s_extrapolates_and_only_the_closed_form_holds() {
|
|
292
304
|
// Closed form: as true off the segment as on it.
|
|
293
305
|
let mut worst_closed = 0.0f64;
|
|
@@ -364,15 +376,14 @@ fn out_of_range_s_extrapolates_and_only_the_closed_form_holds() {
|
|
|
364
376
|
);
|
|
365
377
|
}
|
|
366
378
|
|
|
367
|
-
/// `NaN` propagates through every branch
|
|
368
|
-
/// before `s` is read: `slerp(qa, qa, NaN)` returns `qa`.
|
|
379
|
+
/// `NaN` propagates through every branch, on both arms.
|
|
369
380
|
///
|
|
370
381
|
/// Do not "harden" the closed form. A `NaN` component reaches the closed form,
|
|
371
382
|
/// where `acos(NaN.min(1.0)) = 0` makes `sin_angle` zero and the output `NaN` only
|
|
372
383
|
/// via `0.0/0.0`; a `sin_angle == 0.0` guard would turn a `NaN` input into a
|
|
373
384
|
/// plausible pose. `dot.clamp(-1.0, 1.0)` would be safe; see `slerp`'s docs.
|
|
374
385
|
#[test]
|
|
375
|
-
fn
|
|
386
|
+
fn nan_propagates_through_every_branch() {
|
|
376
387
|
let all_nan = |q: Quat| q.w.is_nan() && q.x.is_nan() && q.y.is_nan() && q.z.is_nan();
|
|
377
388
|
for k in 0..16 {
|
|
378
389
|
let qa = sample_rotation(k);
|
|
@@ -397,18 +408,29 @@ fn nan_propagates_except_through_the_identical_input_return() {
|
|
|
397
408
|
before relaxing this."
|
|
398
409
|
);
|
|
399
410
|
}
|
|
400
|
-
// `h` is `0`, so nothing downstream reads `s`.
|
|
401
|
-
assert_eq!(bits(slerp(qa, qa, f64::NAN)), bits(qa), "k={k}");
|
|
402
|
-
assert_eq!(bits(slerp(qa, qa, f64::INFINITY)), bits(qa), "k={k}");
|
|
403
|
-
// A NaN component cannot reach it: `h` is NaN, not `<= 0.0`.
|
|
404
411
|
let qnan = Quat::new(f64::NAN, qa.x, qa.y, qa.z);
|
|
405
412
|
assert!(
|
|
406
413
|
all_nan(slerp(qnan, qnan, 0.5)),
|
|
407
|
-
"
|
|
414
|
+
"a NaN component was eaten: k={k}"
|
|
408
415
|
);
|
|
409
416
|
}
|
|
410
417
|
}
|
|
411
418
|
|
|
419
|
+
/// The native early return answers before `s` is read: `slerp(qa, qa, NaN)` returns `qa`. A NaN
|
|
420
|
+
/// component cannot reach it, which `nan_propagates_through_every_branch` checks: `h` is NaN, not
|
|
421
|
+
/// `<= 0.0`.
|
|
422
|
+
///
|
|
423
|
+
/// Native arm only: `helicoid`'s geodesic has no early return, so a NaN `s` gives NaN there.
|
|
424
|
+
#[test]
|
|
425
|
+
#[cfg(not(feature = "helicoid"))]
|
|
426
|
+
fn the_identical_input_return_answers_before_s_is_read() {
|
|
427
|
+
for k in 0..16 {
|
|
428
|
+
let qa = sample_rotation(k);
|
|
429
|
+
assert_eq!(bits(slerp(qa, qa, f64::NAN)), bits(qa), "k={k}");
|
|
430
|
+
assert_eq!(bits(slerp(qa, qa, f64::INFINITY)), bits(qa), "k={k}");
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
412
434
|
/// What dropping the `Iso3` round trip costs: as rotations `slerp` and
|
|
413
435
|
/// `LerpSlerp::eval` never disagree (worst ~2.7e-16, the renormalization); the
|
|
414
436
|
/// bits differ only at `s = 0` or `s = 1`, where `eval` answers from a shortcut:
|
|
@@ -231,6 +231,25 @@ version = "0.5.0"
|
|
|
231
231
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
232
232
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
233
233
|
|
|
234
|
+
[[package]]
|
|
235
|
+
name = "helicoid"
|
|
236
|
+
version = "0.0.4"
|
|
237
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
238
|
+
checksum = "5d8d5abfd68550c667ec6721da48a1d1fb852b435db673a8fda7093195411a7d"
|
|
239
|
+
dependencies = [
|
|
240
|
+
"helicoid-linalg",
|
|
241
|
+
"libm",
|
|
242
|
+
]
|
|
243
|
+
|
|
244
|
+
[[package]]
|
|
245
|
+
name = "helicoid-linalg"
|
|
246
|
+
version = "0.0.4"
|
|
247
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
248
|
+
checksum = "994bdd3e24ef9a226b96904499395071be521cf75a4a4234198bc71a627794d2"
|
|
249
|
+
dependencies = [
|
|
250
|
+
"libm",
|
|
251
|
+
]
|
|
252
|
+
|
|
234
253
|
[[package]]
|
|
235
254
|
name = "hermit-abi"
|
|
236
255
|
version = "0.5.2"
|
|
@@ -553,7 +572,7 @@ checksum = "adb6935a6f5c20170eeceb1a3835a49e12e19d792f6dd344ccc76a985ca5a6ca"
|
|
|
553
572
|
|
|
554
573
|
[[package]]
|
|
555
574
|
name = "tf_tree"
|
|
556
|
-
version = "0.0.
|
|
575
|
+
version = "0.0.7"
|
|
557
576
|
dependencies = [
|
|
558
577
|
"tf_tree_arena",
|
|
559
578
|
"tf_tree_core",
|
|
@@ -564,7 +583,7 @@ dependencies = [
|
|
|
564
583
|
|
|
565
584
|
[[package]]
|
|
566
585
|
name = "tf_tree_arena"
|
|
567
|
-
version = "0.0.
|
|
586
|
+
version = "0.0.7"
|
|
568
587
|
dependencies = [
|
|
569
588
|
"bytemuck",
|
|
570
589
|
"rustix",
|
|
@@ -572,14 +591,14 @@ dependencies = [
|
|
|
572
591
|
|
|
573
592
|
[[package]]
|
|
574
593
|
name = "tf_tree_bridge"
|
|
575
|
-
version = "0.0.
|
|
594
|
+
version = "0.0.7"
|
|
576
595
|
dependencies = [
|
|
577
596
|
"tf_tree",
|
|
578
597
|
]
|
|
579
598
|
|
|
580
599
|
[[package]]
|
|
581
600
|
name = "tf_tree_core"
|
|
582
|
-
version = "0.0.
|
|
601
|
+
version = "0.0.7"
|
|
583
602
|
dependencies = [
|
|
584
603
|
"blake3",
|
|
585
604
|
"bytemuck",
|
|
@@ -590,7 +609,7 @@ dependencies = [
|
|
|
590
609
|
|
|
591
610
|
[[package]]
|
|
592
611
|
name = "tf_tree_ingest"
|
|
593
|
-
version = "0.0.
|
|
612
|
+
version = "0.0.7"
|
|
594
613
|
dependencies = [
|
|
595
614
|
"blake3",
|
|
596
615
|
"crc32fast",
|
|
@@ -604,7 +623,7 @@ dependencies = [
|
|
|
604
623
|
|
|
605
624
|
[[package]]
|
|
606
625
|
name = "tf_tree_ipc"
|
|
607
|
-
version = "0.0.
|
|
626
|
+
version = "0.0.7"
|
|
608
627
|
dependencies = [
|
|
609
628
|
"libc",
|
|
610
629
|
"rustix",
|
|
@@ -612,15 +631,17 @@ dependencies = [
|
|
|
612
631
|
|
|
613
632
|
[[package]]
|
|
614
633
|
name = "tf_tree_math"
|
|
615
|
-
version = "0.0.
|
|
634
|
+
version = "0.0.7"
|
|
616
635
|
dependencies = [
|
|
617
636
|
"bytemuck",
|
|
637
|
+
"helicoid",
|
|
638
|
+
"helicoid-linalg",
|
|
618
639
|
"libm",
|
|
619
640
|
]
|
|
620
641
|
|
|
621
642
|
[[package]]
|
|
622
643
|
name = "tf_tree_py"
|
|
623
|
-
version = "0.0.
|
|
644
|
+
version = "0.0.7"
|
|
624
645
|
dependencies = [
|
|
625
646
|
"numpy",
|
|
626
647
|
"pyo3",
|