Skip to content

Commit

Permalink
Split MVector and Cl implementation
Browse files Browse the repository at this point in the history
Move existing `Cl` implementation in a separate 'layout.py' module.
  • Loading branch information
trundev committed Jan 3, 2025
1 parent 39da000 commit ba12029
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 103 deletions.
3 changes: 2 additions & 1 deletion micro_ga/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Minimal/generic geometric algebra implementation"""
from .multivector import Cl, MVector
from .layout import Cl
from .multivector import MVector

__version__ = "0.0.1.dev0"

Expand Down
105 changes: 105 additions & 0 deletions micro_ga/layout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""Geometric algebra multi-vector basic implementation"""
import numpy as np
import numpy.typing as npt
from .multivector import MVector

#
# Geometric algebra signature elements (0, +1, or -1)
# Use minimum if 8-bits
#
SigType = np.int8
#
# Multiplication/sign table (0, +1, or -1)
# Combined signature and euclidean-sign swap
#
MultTableType = np.int8
#
# Bit-mask to represent the basis-vectors, included in a multi-vector blade
# 16-bits allow max of 16 basis-vectors (2**16 == 65536 blades) - ought to be enough!
#
BasisBitmaskType = np.uint16

NDSigType = npt.NDArray[SigType]
NDMultTableType = npt.NDArray[MultTableType]
NDResultIdxType = npt.NDArray[np.int_]

class Cl:
"""Clifford algebra generator (similar to `clifford.Cl()`)"""
#
# Algebra signature, similar to `clifford.Layout.sig`
#
sig: NDSigType
# Basis-vector dimensions, similar to `clifford.Layout.dims`
dims: int
# Multi-vector dimensions, similar to `clifford.Layout.gaDims`
gaDims: int
# Blade-name to multi-vector map
blades: dict[str, MVector]
# Individual blades, also include 'e1', 'e2', etc.
scalar: MVector
I: MVector
#
# Bit-masks for basis-vectors in each multi-vector blade
#
_blade_basis_masks: npt.NDArray[BasisBitmaskType]

def __init__(self, pos_sig: int, neg_sig: int=0, zero_sig: int=0, *,
dtype: type|None=None) -> None:
# Build signature
self.sig = np.array([0] * zero_sig + [1] * pos_sig + [-1] * neg_sig,
dtype=SigType)
self.dims = self.sig.size
self.gaDims = 1<<self.dims # pylint: disable=C0103 #HACK: match `clifford` naming
#
# Select bit-masks for all available blades
#
blade_masks = np.arange(1<<self.dims, dtype=BasisBitmaskType)
# Sort by grades - number of set-bits, which is the number of basis-vectors
# like: 000b; 001b, 010b, 100b; 011b, 101b, 110b; 111b
# Then, by the smallest basis vector: `e14` (mask 9) is before `e23` (mask 6)
argsort = np.lexsort(list(-(blade_masks & 1<<np.arange(self.dims)[:, np.newaxis]))[::-1]
+ [np.bitwise_count(blade_masks)])
self._blade_basis_masks = blade_masks[argsort]
# Update blade names, add object attributes
self._add_blades(dtype)

def _add_blades(self, dtype: type|None) -> None:
"""Assign blade-names as the object attributes"""
#
# Select blade names
#
blade_names = np.where(
self._blade_basis_masks[:, np.newaxis] & 1<<np.arange(self.dims),
np.arange(self.dims) + 1, '').astype(object).sum(-1)
self.blades = {}
blade_val = np.empty(shape=self.gaDims, dtype=dtype)
# Create 0 and 1 objects of type `dtype`
zero, one = (0, 1) if dtype in (None, object) else (dtype(0), dtype(1))
for idx, n in enumerate(blade_names):
# Create multi-vector for this blade
blade_val[...] = zero
blade_val[idx] = one
blade_mvec = MVector(self, blade_val)
# Add to `blades` map, the scalar is ''
name = 'e'+n if n else ''
self.blades[name] = blade_mvec
# Add it as object attribute, the scalar is 'scalar'
if name == '':
name = 'scalar'
setattr(self, name, blade_mvec)
# Extra pseudo-scalar property from the last blade
if idx == self.gaDims - 1:
setattr(self, 'I', blade_mvec)

def __repr__(self) -> str:
"""String representation"""
dtype_str = self.scalar.subtype.__name__
return f'{type(self).__name__}(sig={self.sig.tolist()}, dtype={dtype_str})'

def __eq__(self, other) -> bool:
"""Algebra comparison"""
if self is other: # The algebra-objects are often identical
return True
if not isinstance(other, type(self)):
return False
return np.array_equal(self.sig, other.sig)
108 changes: 6 additions & 102 deletions micro_ga/multivector.py
Original file line number Diff line number Diff line change
@@ -1,113 +1,17 @@
"""Geometric algebra multi-vector basic implementation"""
import numbers
from typing import Union, Callable
from typing import Union, Callable, ForwardRef, TYPE_CHECKING
import numpy as np
import numpy.typing as npt

#
# Geometric algebra signature elements (0, +1, or -1)
# Use minimum if 8-bits
#
SigType = np.int8
#
# Multiplication/sign table (0, +1, or -1)
# Combined signature and euclidean-sign swap
#
MultTableType = np.int8
#
# Bit-mask to represent the basis-vectors, included in a multi-vector blade
# 16-bits allow max of 16 basis-vectors (2**16 == 65536 blades) - ought to be enough!
#
BasisBitmaskType = np.uint16

NDSigType = npt.NDArray[SigType]
NDMultTableType = npt.NDArray[MultTableType]
NDResultIdxType = npt.NDArray[np.int_]
# Avoid circular imports, while type checking works
if TYPE_CHECKING:
from .layout import Cl # pragma: no cover # pylint: disable=C0401
else:
Cl = ForwardRef('Cl')

# Multi-vector operation `other` argument type
OtherArg = Union['MVector', int, complex, numbers.Number]

class Cl:
"""Clifford algebra generator (similar to `clifford.Cl()`)"""
#
# Algebra signature, similar to `clifford.Layout.sig`
#
sig: NDSigType
# Basis-vector dimensions, similar to `clifford.Layout.dims`
dims: int
# Multi-vector dimensions, similar to `clifford.Layout.gaDims`
gaDims: int
# Blade-name to multi-vector map
blades: dict[str, 'MVector']
# Individual blades, also include 'e1', 'e2', etc.
scalar: 'MVector'
I: 'MVector'
#
# Bit-masks for basis-vectors in each multi-vector blade
#
_blade_basis_masks: npt.NDArray[BasisBitmaskType]

def __init__(self, pos_sig: int, neg_sig: int=0, zero_sig: int=0, *,
dtype: type|None=None) -> None:
# Build signature
self.sig = np.array([0] * zero_sig + [1] * pos_sig + [-1] * neg_sig,
dtype=SigType)
self.dims = self.sig.size
self.gaDims = 1<<self.dims # pylint: disable=C0103 #HACK: match `clifford` naming
#
# Select bit-masks for all available blades
#
blade_masks = np.arange(1<<self.dims, dtype=BasisBitmaskType)
# Sort by grades - number of set-bits, which is the number of basis-vectors
# like: 000b; 001b, 010b, 100b; 011b, 101b, 110b; 111b
# Then, by the smallest basis vector: `e14` (mask 9) is before `e23` (mask 6)
argsort = np.lexsort(list(-(blade_masks & 1<<np.arange(self.dims)[:, np.newaxis]))[::-1]
+ [np.bitwise_count(blade_masks)])
self._blade_basis_masks = blade_masks[argsort]
# Update blade names, add object attributes
self._add_blades(dtype)

def _add_blades(self, dtype: type|None) -> None:
"""Assign blade-names as the object attributes"""
#
# Select blade names
#
blade_names = np.where(
self._blade_basis_masks[:, np.newaxis] & 1<<np.arange(self.dims),
np.arange(self.dims) + 1, '').astype(object).sum(-1)
self.blades = {}
blade_val = np.empty(shape=self.gaDims, dtype=dtype)
# Create 0 and 1 objects of type `dtype`
zero, one = (0, 1) if dtype in (None, object) else (dtype(0), dtype(1))
for idx, n in enumerate(blade_names):
# Create multi-vector for this blade
blade_val[...] = zero
blade_val[idx] = one
blade_mvec = MVector(self, blade_val)
# Add to `blades` map, the scalar is ''
name = 'e'+n if n else ''
self.blades[name] = blade_mvec
# Add it as object attribute, the scalar is 'scalar'
if name == '':
name = 'scalar'
setattr(self, name, blade_mvec)
# Extra pseudo-scalar property from the last blade
if idx == self.gaDims - 1:
setattr(self, 'I', blade_mvec)

def __repr__(self) -> str:
"""String representation"""
dtype_str = self.scalar.subtype.__name__
return f'{type(self).__name__}(sig={self.sig.tolist()}, dtype={dtype_str})'

def __eq__(self, other) -> bool:
"""Algebra comparison"""
if self is other: # The algebra-objects are often identical
return True
if not isinstance(other, type(self)):
return False
return np.array_equal(self.sig, other.sig)

class MVector:
"""Multi-vector representation"""
layout: Cl
Expand Down

0 comments on commit ba12029

Please sign in to comment.