Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions galgebra/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@
"""

from ._version import __version__ # noqa: F401
from .interop import Cl # noqa: F401
22 changes: 22 additions & 0 deletions galgebra/interop/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""
Interoperability interfaces for creating geometric algebras using
conventions from other libraries.

The ``Cl(p, q, r)`` signature interface was popularized by ganja.js and
adopted by kingdon. Two flavours are provided:

- ``galgebra.interop.Cl`` uses galgebra defaults (no surprises for
existing users).
- ``galgebra.interop.kingdon.Cl`` uses kingdon conventions
(``dual_mode='Iinv+'``, 0-indexed basis names).

Known incompatibilities between galgebra and kingdon:

- **Basis naming**: galgebra numbers from 1 (``e_1, e_2, ...``);
kingdon defaults to 0-indexed for PGA (``e0, e1, e2, e3``).
- **Dual convention**: galgebra's default is ``'I+'`` (``dual(A) = I * A``);
kingdon uses ``'Iinv+'`` (``dual(A) = A * I^{-1}``) via its
``codegen_polarity``.
"""

from ._cl import Cl # noqa: F401
64 changes: 64 additions & 0 deletions galgebra/interop/_cl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"""
Core ``Cl(p, q, r)`` factory with galgebra defaults.
"""

from ..ga import Ga


def Cl(p: int, q: int = 0, r: int = 0, root: str = 'e', **kwargs):
r"""
Construct a Clifford algebra :math:`Cl(p,q,r)` from its signature.

This interface was popularized by ganja.js and adopted by kingdon.
It provides a concise way to create a geometric algebra without
specifying a full metric tensor.

Uses galgebra defaults (basis numbered from 1, dual mode ``'I+'``).
For kingdon conventions, use ``galgebra.interop.kingdon.Cl`` instead.

Parameters
----------
p : int
Number of basis vectors that square to +1.
q : int
Number of basis vectors that square to -1.
r : int
Number of basis vectors that square to 0 (degenerate).
root : str
Root name for basis vectors (default ``'e'``).
**kwargs :
Additional keyword arguments passed to :class:`Ga`.

Returns
-------
tuple
``(ga, e_1, ..., e_n)`` -- the geometric algebra and basis vectors.

Examples
--------
3D Euclidean algebra::

>>> ga, e1, e2, e3 = Cl(3)

Spacetime algebra (Minkowski, +---)::

>>> ga, e1, e2, e3, e4 = Cl(1, 3)

Projective Geometric Algebra::

>>> ga, e1, e2, e3 = Cl(2, 0, 1)
"""
n = p + q + r
if n == 0:
raise ValueError("Total dimension p + q + r must be positive.")

# Build diagonal metric: p ones, q negative ones, r zeros
g = [1] * p + [-1] * q + [0] * r

# Build basis name string (1-indexed)
if n <= 9:
basis = root + '*' + '|'.join(str(i + 1) for i in range(n))
else:
basis = ' '.join(root + '_' + str(i + 1) for i in range(n))

return Ga.build(basis, g=g, **kwargs)
81 changes: 81 additions & 0 deletions galgebra/interop/kingdon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""
Kingdon-compatible ``Cl(p, q, r)`` factory.

Uses kingdon conventions:
- Dual mode ``'Iinv+'`` (polarity dual: ``dual(A) = A * I^{-1}``)
- 0-indexed basis names for PGA (``e0, e1, ...``)
"""

from ..ga import Ga


def Cl(p: int, q: int = 0, r: int = 0, root: str = 'e', **kwargs):
r"""
Construct a Clifford algebra :math:`Cl(p,q,r)` using kingdon conventions.

Differences from ``galgebra.interop.Cl``:

- **Dual mode**: sets ``'Iinv+'`` globally so that
``dual(A) = A * I^{-1}``, matching kingdon's ``codegen_polarity``.
- **Basis naming**: uses 0-indexed names (``e_0, e_1, ...``) to match
kingdon's default for PGA algebras.

.. warning::

The dual mode change is session-wide. If you mix kingdon and
galgebra conventions in the same session, save and restore
``Ga.dual_mode_value`` around the kingdon block::

saved = Ga.dual_mode_value
try:
ga, *basis = Cl(3, 0, 1)
# ... kingdon-convention code ...
finally:
Ga.dual_mode(saved)

Parameters
----------
p : int
Number of basis vectors that square to +1.
q : int
Number of basis vectors that square to -1.
r : int
Number of basis vectors that square to 0 (degenerate).
root : str
Root name for basis vectors (default ``'e'``).
**kwargs :
Additional keyword arguments passed to :class:`Ga`.

Returns
-------
tuple
``(ga, e_0, ..., e_{n-1})`` -- the geometric algebra and basis vectors.

Examples
--------
3D Euclidean algebra::

>>> from galgebra.interop.kingdon import Cl
>>> ga, e1, e2, e3 = Cl(3)

3D PGA (0-indexed, degenerate metric)::

>>> ga, e0, e1, e2, e3 = Cl(3, 0, 1)
"""
n = p + q + r
if n == 0:
raise ValueError("Total dimension p + q + r must be positive.")

# Set kingdon dual convention
Ga.dual_mode('Iinv+')

# Build diagonal metric: p ones, q negative ones, r zeros
g = [1] * p + [-1] * q + [0] * r

# Build basis name string (0-indexed, matching kingdon)
if n <= 10:
basis = root + '*' + '|'.join(str(i) for i in range(n))
else:
basis = ' '.join(root + '_' + str(i) for i in range(n))

return Ga.build(basis, g=g, **kwargs)
47 changes: 47 additions & 0 deletions test/test_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,3 +687,50 @@ def test_deprecations(self):
assert ga_ortho.dot_product_basis_blades((e_1.obj, e_12.obj), mode='<') == (e_1 < e_12).obj
with pytest.warns(DeprecationWarning):
assert ga_ortho.dot_product_basis_blades((e_1.obj, e_12.obj), mode='>') == (e_1 > e_12).obj

def test_Cl(self):
"""Test the Cl(p, q, r) interface (issue 524)."""
from galgebra.interop import Cl

# 3D Euclidean: Cl(3)
ga3, e1, e2, e3 = Cl(3)
assert e1 * e1 == ga3.mv(1)
assert e2 * e2 == ga3.mv(1)
assert e3 * e3 == ga3.mv(1)

# 2D Minkowski: Cl(1, 1)
ga11, e1, e2 = Cl(1, 1)
assert e1 * e1 == ga11.mv(1)
assert e2 * e2 == ga11.mv(-1)

# Degenerate: Cl(2, 0, 1)
ga201, e1, e2, e3 = Cl(2, 0, 1)
assert e1 * e1 == ga201.mv(1)
assert e2 * e2 == ga201.mv(1)
assert e3 * e3 == ga201.mv(0)

# Import from top-level package
from galgebra import Cl as Cl2
ga_top, e1_top, e2_top = Cl2(2)
assert e1_top * e1_top == ga_top.mv(1)

# Error on zero dimension
with pytest.raises(ValueError):
Cl(0)

def test_Cl_kingdon(self):
"""Test the kingdon-convention Cl (0-indexed, Iinv+ dual)."""
from galgebra.interop.kingdon import Cl as KCl
from galgebra.ga import Ga

# Save and restore global dual mode
saved = Ga.dual_mode_value
try:
# 3D PGA: Cl(3, 0, 1) with 0-indexed basis
ga, e0, e1, e2, e3 = KCl(3, 0, 1)
assert e0 * e0 == ga.mv(1)
assert e3 * e3 == ga.mv(0)
# Dual mode should be Iinv+
assert Ga.dual_mode_value == 'Iinv+'
finally:
Ga.dual_mode(saved)
Loading