from dataclasses import dataclass, field
from functools import cached_property, partial, partialmethod
import re
[docs]
@dataclass(init=False)
class TapeRecorder:
#TODO: Create common baseclass for TapeRecorder and Multivector?
algebra: "Algebra"
expr: str
_keys: tuple = field(default_factory=tuple)
def __new__(cls, algebra, expr, keys):
obj = object.__new__(cls)
obj.algebra = algebra
obj.expr = expr
obj._keys = keys
return obj
[docs]
def keys(self):
return self._keys
@cached_property
def type_number(self) -> int:
return int(''.join('1' if i in self.keys() else '0' for i in reversed(self.algebra.canon2bin.values())), 2)
def __getattr__(self, basis_blade):
if not re.match(r'^e[0-9a-fA-Z]*$', basis_blade):
raise AttributeError(f'{self.__class__.__name__} object has no attribute or basis blade {basis_blade}')
if basis_blade not in self.algebra.canon2bin:
return self.__class__(
algebra=self.algebra,
expr=f"(0,)",
keys=(0,)
)
try:
idx = self.keys().index(self.algebra.canon2bin[basis_blade])
except ValueError:
return self.__class__(
algebra=self.algebra,
expr=f"(0,)",
keys=(0,)
)
else:
return self.__class__(
algebra=self.algebra,
expr=f"({self.expr}[{idx}],)",
keys=(self.keys()[idx],)
)
[docs]
def grade(self, *grades):
if len(grades) == 1 and isinstance(grades[0], tuple):
grades = grades[0]
basis_blades = tuple(self.algebra.indices_for_grades(grades))
indices_keys = [(idx, k) for idx, k in enumerate(self.keys()) if k in basis_blades]
indices, keys = zip(*indices_keys) if indices_keys else (tuple(), tuple())
expr = f"[{self.expr}[idx] for idx in {indices}]"
return self.__class__(
algebra=self.algebra,
expr=expr,
keys=keys,
)
def __str__(self):
return self.expr
[docs]
def binary_operator(self, other, operator: str):
if not isinstance(other, self.__class__):
# Assume scalar
keys_out, func = getattr(self.algebra, operator)[self.keys(), (0,)]
expr = f'{func.__name__}({self.expr}, ({other},))'
else:
keys_out, func = getattr(self.algebra, operator)[self.keys(), other.keys()]
expr = f'{func.__name__}({self.expr}, {other.expr})'
return self.__class__(algebra=self.algebra, expr=expr, keys=keys_out)
[docs]
def unary_operator(self, operator: str):
keys_out, func = getattr(self.algebra, operator)[self.keys()]
expr = f'{func.__name__}({self.expr})'
return self.__class__(algebra=self.algebra, expr=expr, keys=keys_out)
# Binary operators
gp = __mul__ = __rmul__ = partialmethod(binary_operator, operator='gp')
sw = __rshift__ = partialmethod(binary_operator, operator='sw')
cp = partialmethod(binary_operator, operator='cp')
acp = partialmethod(binary_operator, operator='acp')
ip = __or__ = partialmethod(binary_operator, operator='ip')
sp = partialmethod(binary_operator, operator='sp')
lc = partialmethod(binary_operator, operator='lc')
rc = partialmethod(binary_operator, operator='rc')
op = __xor__ = __rxor__ = partialmethod(binary_operator, operator='op')
rp = __and__ = partialmethod(binary_operator, operator='rp')
proj = __matmul__ = partialmethod(binary_operator, operator='proj')
add = __add__ = __radd__ = partialmethod(binary_operator, operator='add')
sub = __sub__ = partialmethod(binary_operator, operator='sub')
def __rsub__(self, other): return other + (-self)
__truediv__ = div = partialmethod(binary_operator, operator='div')
def __pow__(self, power, modulo=None):
if power == 0:
return self.__class__(self.algebra, expr='(1,)', keys=(0,))
res = self
for i in range(1, power):
res = res.gp(self)
return res
# Unary operators
inv = partialmethod(unary_operator, operator='inv')
neg = __neg__ = partialmethod(unary_operator, operator='neg')
reverse = __invert__ = partialmethod(unary_operator, operator='reverse')
involute = partialmethod(unary_operator, operator='involute')
conjugate = partialmethod(unary_operator, operator='conjugate')
sqrt = partialmethod(unary_operator, operator='sqrt')
polarity = partialmethod(unary_operator, operator='polarity')
unpolarity = partialmethod(unary_operator, operator='unpolarity')
hodge = partialmethod(unary_operator, operator='hodge')
unhodge = partialmethod(unary_operator, operator='unhodge')
normsq = partialmethod(unary_operator, operator='normsq')
outerexp = partialmethod(unary_operator, operator='outerexp')
outersin = partialmethod(unary_operator, operator='outersin')
outercos = partialmethod(unary_operator, operator='outercos')
outertan = partialmethod(unary_operator, operator='outertan')
[docs]
def dual(self, kind='auto'):
if kind == 'polarity' or kind == 'auto' and self.algebra.r == 0:
return self.polarity()
elif kind == 'hodge' or kind == 'auto' and self.algebra.r == 1:
return self.hodge()
elif kind == 'auto':
raise Exception('Cannot select a suitable dual in auto mode for this algebra.')
else:
raise ValueError(f'No dual found for kind={kind}.')
[docs]
def undual(self, kind='auto'):
if kind == 'polarity' or kind == 'auto' and self.algebra.r == 0:
return self.unpolarity()
elif kind == 'hodge' or kind == 'auto' and self.algebra.r == 1:
return self.unhodge()
elif kind == 'auto':
raise Exception('Cannot select a suitable undual in auto mode for this algebra.')
else:
raise ValueError(f'No undual found for kind={kind}.')
[docs]
def norm(self):
normsq = self.normsq()
return normsq.sqrt()
[docs]
def normalized(self):
""" Normalized version of this multivector. """
return self / self.norm()