Skip to content

Full conformal (N3) operators: Rotors, Motors, Inversions

Keywords: N3 · conformal · Rotor · Motor · Inversion · Dilator

Showcases all operator (versor) types in the conformal geometric algebra: - 7 operator types: Reflection, Inversion, Rotor, Translator, Dilator, Motor, GeneralRotor - Versor decomposition via blade_factorize_versor() - Entity/Operator distinction: sphere analyzed as Sphere vs Inversion - Factor-count-based classification

Uses the Geometry class to bind the N3 algebra. Plain functions remain available as an alternative (see the last section).

Entities are covered separately in n3_entities.py.

Prerequisite: n3_entities.py

Run

uv run python py/examples/ga/geometry/n3_operators.py

Source

ga/geometry/n3_operators.py

Code

# SPDX-License-Identifier: Apache-2.0
# Copyright 2021 Christian Perwass

"""n3_operators.py — Full conformal (N3) operators: Rotors, Motors, Inversions.

Showcases all operator (versor) types in the conformal geometric algebra:
  - 7 operator types: Reflection, Inversion, Rotor, Translator, Dilator,
    Motor, GeneralRotor
  - Versor decomposition via blade_factorize_versor()
  - Entity/Operator distinction: sphere analyzed as Sphere vs Inversion
  - Factor-count-based classification

Uses the ``Geometry`` class to bind the N3 algebra.  Plain functions remain
available as an alternative (see the last section).

Entities are covered separately in n3_entities.py.

Prerequisite: n3_entities.py
Run with:  uv run python py/examples/ga/geometry/n3_operators.py

Keywords: N3, conformal, Rotor, Motor, Inversion, Dilator
"""

import math

from pytanga.algebra import MV
from pytanga.basis import BasisN3
from pytanga.geometry import (
    Dilator,
    Direction,
    Geometry,
    Inversion,
    Motor,
    Point,
    Reflection,
    Rotor,
    Sphere,
    Translator,
)

n3 = BasisN3()
geo = Geometry(n3)  # defaults to OPNS


def hr(title: str) -> None:
    print(f"\n{'=' * 60}")
    print(f"  {title}")
    print("=" * 60)


# ── 1. Reflection ──────────────────────────────────────────
hr("1. Reflection — grade-1 versor, no null components")

refl = Reflection(Direction(0, 0, 1))
# geo(...) creates for Entity/Operator args; analyzes for MV args
mv_ref = geo.create(refl)
mv_ref.show("Reflection in plane with normal (0,0,1)")
# geo(...) creates for Entity/Operator args; analyzes for MV args
result = geo.which_operator(mv_ref)
print(f"  analyze → {result}")

# ── 2. Inversion ───────────────────────────────────────────
hr("2. Inversion — grade-1 versor with eo component")

inv = Inversion(center=Point(2, 0, 0))
# geo(...) creates for Entity/Operator args; analyzes for MV args
mv_inv = geo.create(inv)
mv_inv.show("Inversion at origin (2,0,0)")
# geo(...) creates for Entity/Operator args; analyzes for MV args
result = geo.which_operator(mv_inv)
assert isinstance(result, Inversion), "an inversion MV analyses to Inversion"
print(f"  analyze → {result}")
print(
    f"  center: ({result.center.x:.1f}, {result.center.y:.1f}, {result.center.z:.1f})"
)

# ── 3. Rotor ───────────────────────────────────────────────
hr("3. Rotor — two Euclidean reflectors")

rot = Rotor(angle=math.pi / 3, axis=Direction(0, 0, 1))
# geo(...) creates for Entity/Operator args; analyzes for MV args
mv_rot = geo.create(rot)
mv_rot.show("Rotor: 60° about z-axis")
# geo(...) creates for Entity/Operator args; analyzes for MV args
result = geo.which_operator(mv_rot)
print(f"  analyze → {result}")

# ── 4. Translator ──────────────────────────────────────────
hr("4. Translator — two einf reflectors, direct coefficient extraction")

t = Translator(vector=Direction(3, 1, 0))
# geo(...) creates for Entity/Operator args; analyzes for MV args
mv_t = geo.create(t)
assert isinstance(mv_t, MV), "a Translator materialises one MV in N3"
mv_t.show("Translator by (3, 1, 0)")
# geo(...) creates for Entity/Operator args; analyzes for MV args
result = geo.which_operator(mv_t)
assert isinstance(result, Translator), "a translation MV analyses to Translator"
print(f"  analyze → {result}")
print(
    f"  vector = ({result.vector.x:.3f}, {result.vector.y:.3f}, {result.vector.z:.3f})"
)

# ── 5. Dilator ─────────────────────────────────────────────
hr("5. Dilator — two eo reflectors, uniform scaling")

d = Dilator(factor=2.0)
# geo(...) creates for Entity/Operator args; analyzes for MV args
mv_d = geo.create(d)
mv_d.show("Dilator: factor = 2.0")
# geo(...) creates for Entity/Operator args; analyzes for MV args
result = geo.which_operator(mv_d)
print(f"  analyze → {result}")
if isinstance(result, Dilator):
    print(f"  factor = {result.factor:.3f}")

# ── 6. Motor — combined rotation + translation ─────────────
hr("6. Motor — rigid body motion (4 factors)")

motor = Motor(
    rotor=Rotor(angle=math.pi / 2, axis=Direction(0, 0, 1)),
    translator=Translator(vector=Direction(1, 0, 0)),
)
# geo(...) creates for Entity/Operator args; analyzes for MV args
mv_m = geo.create(motor)
mv_m.show("Motor: 90° around z + shift along x")
# geo(...) creates for Entity/Operator args; analyzes for MV args
result = geo.which_operator(mv_m)
print(f"  analyze → {result}")
if isinstance(result, Motor):
    print(f"    rotor angle = {result.rotor.angle:.3f} rad")
    print(
        f"    translator  = ({result.translator.vector.x:.1f}, "
        f"{result.translator.vector.y:.1f}, "
        f"{result.translator.vector.z:.1f})"
    )

# ── 7. Operator coverage summary ───────────────────────────
hr("7. Operator coverage — all N3 operator types")

ops = [
    ("Reflection", Reflection(Direction(0, 0, 1))),
    ("Inversion", Inversion(center=Point(1, 0, 0))),
    ("Rotor", Rotor(angle=0.5, axis=Direction(0, 0, 1))),
    ("Translator", Translator(vector=Direction(3, 0, 0))),
    ("Dilator", Dilator(factor=2.0)),
]

for name, op in ops:
    # geo(...) creates for Entity/Operator args; analyzes for MV args
    mv_op = geo.create(op)
    # geo(...) creates for Entity/Operator args; analyzes for MV args
    result = geo.which_operator(mv_op)
    print(f"  {name}: {type(result).__name__} ✓")

# Motor via combined dispatcher
# geo(...) creates for Entity/Operator args; analyzes for MV args
result = geo.analyze(geo.create(motor))
print(f"  Motor (via analyze): {type(result).__name__} ✓")

# ── 8. Entity/Operator distinction ─────────────────────────
hr("8. Entity vs Operator — same blade, different interpretation")

sphere = Sphere(center=Point(0, 0, 0), radius=2.0)
# geo(...) creates for Entity/Operator args; analyzes for MV args
mv_sph = geo.create(sphere)

# which_entity sees the geometric entity
# geo(...) creates for Entity/Operator args; analyzes for MV args
entity_result = geo.which_entity(mv_sph)
print(f"  which_entity → {entity_result}")
print("    (sphere is a geometric entity)")

# which_operator sees the same blade as an Inversion
try:
    # geo(...) creates for Entity/Operator args; analyzes for MV args
    op_result = geo.which_operator(mv_sph)
    print(f"  which_operator → {op_result}")
    print("    (the same blade is also an inversion operator)")
except (ValueError, NotImplementedError):
    print("  which_operator → (grade-4 versor analysis not yet available)")

# ── 9. Plain Functions (alternative) ───────────────────────
hr("9. Plain functions — no Geometry wrapper needed")

from pytanga.geometry import analyze_operator, create_operator  # noqa: E402

mv_op = create_operator(n3, Reflection(Direction(1, 0, 0)))
result = analyze_operator(mv_op)
print(f"  plain create + analyze → {result}")

print("\nDone — N3 operators demo complete.")