Duals and Complements in pytanga¶
pytanga exposes three operations for blade complementation and dualization:
| Method | Formula | dual-of-dual | Uses inverse? |
|---|---|---|---|
mv.complement() |
blade_id XOR pseudoscalar_id (no sign) | A always |
N/A — bitwise |
mv.dual() |
★A = A · I⁺ (right dual) | ±A | Yes (pseudoinverse) |
mv.ldual() |
I · A (left dual) | ±A | No — uses I directly |
Complement — mv.complement()¶
The complement maps each basis blade to its bitwise complement within the algebra. No sign changes are applied to coefficients:
from pytanga import Algebra
from pytanga.basis import BasisE3
alg = Algebra(3, 0) # E3
a = alg({"e1": 2.0, "e12": 3.0})
b = a.complement()
print(b) # 2.0 e23 + 3.0 e3 (no sign changes)
print(b.complement()) # recovers a exactly
This is an involution for all dimensions and signatures:
The complement is a purely combinatorial operation — a simple
blade_id XOR pseudoscalar_id at the bitmask level. It is NOT the
Clifford dual and does not satisfy geometric dual identities
like ★(a ∧ b) = a × b.
Use complement() for:
- Bitmask-based algorithms
- Blade mask trajectory tracking
- Index gymnastics where sign is irrelevant or handled separately
Do not use complement() for geometric entity dualization
(OPNS ↔ IPNS conversion). Use dual() for that — it correctly accounts
for the permutation parity between a blade and its complement within
the pseudoscalar.
Signed Dual — mv.dual()¶
The signed dual implements the standard Clifford algebra dual:
where I⁺ is the pseudoinverse of the pseudoscalar I. The blade mask is
the same bitwise complement as complement(), but the coefficient
receives a sign correction accounting for the geometric product with the
inverse pseudoscalar:
alg = Algebra(3, 0) # E3
a = alg({"e1": 2.0})
b = a.dual()
# b = 2.0 * e23 (with correct sign — ± depends on metric and dimension)
Why dual() is the "correct" dual¶
In G(3,0) with pseudoscalar I = e₁₂₃ (I² = −1, I⁻¹ = −I = e₃₂₁), the signed dual satisfies:
This is the standard vector cross product identity. The complement does not satisfy this because it misses the sign from reordering the blade basis vectors with the pseudoscalar. Concretely:
| Blade B | complement(B) | dual(B) | Reason |
|---|---|---|---|
| e₁₂ | e₃ | e₃ | 0 swaps — even |
| e₁₃ | e₂ | −e₂ | 1 swap — odd |
| e₂₃ | e₁ | e₁ | 2 swaps — even |
The sign arises from the permutation parity counted by GPSign() in the
C++ backend — swapping basis vectors of the blade with those of the
pseudoscalar to bring them into canonical order.
Dual-of-Dual Sign¶
The dual-of-dual sign depends on dimension D and the number s of
negative-signature basis vectors:
| D | s | sign(★★A) | Example |
|---|---|---|---|
| 1 | 0 | +1 | G(1) |
| 2 | 0 | −1 | G(2) |
| 3 | 0 | −1 | E3 |
| 4 | 0 | +1 | G(4) |
| 4 | 1 | −1 | Spacetime G(3,1) |
Left Dual — mv.ldual()¶
The left dual multiplies by the pseudoscalar from the left:
Unlike dual() (right multiplication with the pseudoinverse I⁺),
ldual() uses I directly — no inverse is needed. This makes it simpler
and more robust for algebras where the pseudoscalar is not invertible
(e.g. PGA, where I² = 0 and I has no proper inverse).
alg = Algebra(3, 0) # E3
a = alg({"e1": 2.0})
b = a.ldual()
# b = -2.0 * e23 (I·e₁ = −e₂₃ in G(3,0) since I = e₁₂₃)
Relation in G(3,0):
Since I⁻¹ = −I in Cl(3):
ldual(A) = I · A = −(A · I⁻¹) = −dual(A) for odd-grade A
ldual(A) = I · A = A · I⁻¹ = dual(A) for even-grade A (since I² = −1)
Equivalently: ldual(A) = (−1)^k · dual(A) for grade‑k elements.
Operation Summary¶
| Operation | Formula | In G(3,0) on a∧b | Use case |
|---|---|---|---|
complement(A) |
bitwise XOR, no sign | wrong sign on e₂ component | Bitmask gymnastics |
dual(A) |
A · I⁺ | a × b ✓ | Standard geometry dual (OPNS↔IPNS) |
ldual(A) |
I · A | −(a × b) = −dual(A) | When I is non-invertible, or left convention |
Implementation Reference¶
| Layer | Location | Details |
|---|---|---|
| C++ blade | CBlade::GetComplement(), CBlade::GetDual(), CBlade::GetLeftDual() |
Bitmask XOR and sign computation |
| C++ MV | GA::Complement(), GA::Dual(), GA::LDual() |
Per-blade iteration |
| Python | MV.complement(), MV.dual(), MV.ldual(), plus Algebra counterparts |
User-facing API |