Skip to content

Commit

Permalink
Algebra comparison and null-signature support
Browse files Browse the repository at this point in the history
Add `MVector.subtype` property to obtain the type of underlying objects.
Add `Cl.__eq__()` and `Cl.__repr__()`.
Update pytests add `test_null_sig()`.
  • Loading branch information
trundev committed Dec 30, 2024
1 parent 3d1d03a commit e46feb2
Show file tree
Hide file tree
Showing 2 changed files with 43 additions and 4 deletions.
28 changes: 24 additions & 4 deletions micro_ga/multivector.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,19 @@ def _add_blades(self, dtype: type|None) -> None:
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 All @@ -105,6 +118,16 @@ def __init__(self, layout: Cl, value: npt.NDArray) -> None:
self.layout = layout
self.value = value.copy()

@property
def subtype(self) -> type:
"""Type of underlying objects"""
# Start with native `numpy` data-type
subtype = self.value.dtype.type
if subtype == np.object_:
# Type of underlying object
subtype = type(self.value.item(0))
return subtype

def _to_string(self, *, tol: float=0, ndigits: int|None=None) -> str:
"""String representation, strips near-zero blades"""
vals = self.value
Expand Down Expand Up @@ -144,10 +167,7 @@ def _get_other_value(self, other: OtherArg) -> npt.NDArray:
return self.layout.scalar.value * other
if not isinstance(other, MVector):
return NotImplemented
if self.layout is other.layout:
return other.value # The layout is identical
# Check signature
if not (self.layout.sig == other.layout.sig).all():
if self.layout != other.layout:
return NotImplemented
return other.value

Expand Down
19 changes: 19 additions & 0 deletions tests/test_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ def test_dimensions(pos_sig, neg_sig, zero_sig):
assert layout.gaDims == 1<<layout.dims
assert len(layout.blades) == layout.gaDims

def test_null_sig():
"""Test scalar-only algebra"""
test_dimensions(0, 0, 0)
layout = micro_ga.Cl(0)
assert layout.scalar is layout.I
assert layout.blades == {'': layout.scalar}

@pytest.fixture
def layout(pos_sig, neg_sig, zero_sig):
return micro_ga.Cl(pos_sig, neg_sig, zero_sig)
Expand All @@ -37,9 +44,21 @@ def test_blades(layout):
assert sum(v.value) == 1 and np.count_nonzero(v.value) == 1, \
'Blade must have a single value set to 1'

def test_comparison(pos_sig, neg_sig):
"""Test algebra and multi-vector comparison operators"""
layout = micro_ga.Cl(pos_sig, neg_sig)
assert layout == micro_ga.Cl(pos_sig, neg_sig)
assert layout != 'BAD'
assert layout.scalar != 'BAD'
# Different layout with different dimensions
layout2 = micro_ga.Cl(neg_sig, pos_sig+1)
assert layout != layout2
assert layout.scalar != layout2.scalar

def test_repr():
"""Test `repr()` and `str()` results"""
layout = micro_ga.Cl(3)
assert str(layout) == f'Cl(sig={[1]*layout.dims}, dtype={np.empty(0).dtype})'
assert str(layout.scalar + 1e-12) == '+1.0'
assert repr(layout.scalar + 1e-12) == '+1.000000000001'
assert str(layout.scalar - layout.scalar) == '0'
Expand Down

0 comments on commit e46feb2

Please sign in to comment.