Source code for clifford._layout

import functools
from typing import List, Tuple, Optional, Dict, Container
import warnings

import numpy as np
import sparse

# TODO: move some of these functions to this file if they're not useful anywhere
# else
import clifford as cf
from . import (
    get_mult_function,
    val_get_left_gmt_matrix,
    val_get_right_gmt_matrix,
    _numba_val_get_left_gmt_matrix,
    NUMBA_PARALLEL
)
from . import _numba_utils
from .io import read_ga_file
from . import _settings
from ._multivector import MultiVector
from ._layout_helpers import (
    BasisBladeOrder, BasisVectorIds, canonical_reordering_sign_euclidean
)


class _cached_property:
    def __init__(self, getter):
        self.fget = getter
        self.__name__ = getter.__name__
        self.__doc__ = getter.__doc__

    def __get__(self, obj, cls):
        if obj is None:
            return self
        val = self.fget(obj)
        # this entry hides the _cached_property
        setattr(obj, self.__name__, val)
        return val


@_numba_utils.njit(cache=True)
def canonical_reordering_sign(bitmap_a, bitmap_b, metric):
    """
    Computes the sign for the product of bitmap_a and bitmap_b
    given the supplied metric
    """
    bitmap = bitmap_a & bitmap_b
    output_sign = canonical_reordering_sign_euclidean(bitmap_a, bitmap_b)
    i = 0
    while bitmap != 0:
        if (bitmap & 1) != 0:
            output_sign *= metric[i]
        i = i + 1
        bitmap = bitmap >> 1
    return output_sign


@_numba_utils.njit(cache=True)
def gmt_element(bitmap_a, bitmap_b, sig_array):
    """
    Element of the geometric multiplication table given blades a, b.
    The implementation used here is described in :cite:`ga4cs` chapter 19.
    """
    output_sign = canonical_reordering_sign(bitmap_a, bitmap_b, sig_array)
    output_bitmap = bitmap_a^bitmap_b
    return output_bitmap, output_sign


@_numba_utils.njit(cache=True)
def imt_check(grade_v, grade_i, grade_j):
    """
    A check used in imt table generation
    """
    # A_r . B_s = <A_r B_s>_|r-s|
    # if r, s != 0
    return (grade_v == abs(grade_i - grade_j)) and (grade_i != 0) and (grade_j != 0)


@_numba_utils.njit(cache=True)
def omt_check(grade_v, grade_i, grade_j):
    """
    A check used in omt table generation
    """
    # A_r ^ B_s = <A_r B_s>_|r+s|
    return grade_v == (grade_i + grade_j)


@_numba_utils.njit(cache=True)
def lcmt_check(grade_v, grade_i, grade_j):
    """
    A check used in lcmt table generation
    """
    # A_r _| B_s = <A_r B_s>_(s-r) if s-r >= 0
    return grade_v == (grade_j - grade_i)


@_numba_utils.njit(parallel=NUMBA_PARALLEL, nogil=True, cache=True)
def _numba_construct_gmt(
    index_to_bitmap, bitmap_to_index, signature
):
    n = len(index_to_bitmap)
    array_length = int(n * n)
    coords = np.zeros((3, array_length), dtype=np.uint64)
    k_list = coords[0, :]
    l_list = coords[1, :]
    m_list = coords[2, :]

    # use as small a type as possible to minimize type promotion
    mult_table_vals = np.zeros(array_length, dtype=np.int8)

    for i in range(n):
        bitmap_i = index_to_bitmap[i]

        for j in range(n):
            bitmap_j = index_to_bitmap[j]
            bitmap_v, mul = gmt_element(bitmap_i, bitmap_j, signature)
            v = bitmap_to_index[bitmap_v]

            list_ind = i * n + j
            k_list[list_ind] = i
            l_list[list_ind] = v
            m_list[list_ind] = j

            mult_table_vals[list_ind] = mul

    return coords, mult_table_vals


def construct_gmt(
    blade_order: BasisBladeOrder, signature
) -> sparse.COO:
    # wrap the numba one
    coords, mult_table_vals = _numba_construct_gmt(
        blade_order.index_to_bitmap,
        blade_order.bitmap_to_index,
        signature
    )
    dims = len(blade_order.grades)
    return sparse.COO(coords=coords, data=mult_table_vals, shape=(dims, dims, dims))


@_numba_utils.njit(parallel=NUMBA_PARALLEL, nogil=True)
def _numba_construct_graded_mt(
    index_to_grade, coords, gmt_vals, check_func
):
    n_elems = coords.shape[1]

    mask = np.zeros(n_elems, dtype=np.bool_)

    for ind in range(coords.shape[1]):
        k, l, m = coords[:, ind]

        grade_k = index_to_grade[k]
        grade_l = index_to_grade[l]
        grade_m = index_to_grade[m]

        mask[ind] = check_func(grade_l, grade_k, grade_m)

    return coords[:, mask], gmt_vals[mask]


def construct_graded_mt(
    blade_order: BasisBladeOrder, gmt: sparse.COO, check_func
) -> sparse.COO:
    # wrap the numba one
    coords, mult_table_vals = _numba_construct_graded_mt(
        blade_order.grades,
        gmt.coords,
        gmt.data,
        check_func
    )
    dims = len(blade_order.grades)
    return sparse.COO(coords=coords, data=mult_table_vals, shape=(dims, dims, dims))


[docs]class Layout(object): r""" Layout stores information regarding the geometric algebra itself and the internal representation of multivectors. Parameters ---------- sig : List[int] The signature of the vector space. This should be a list of positive and negative numbers where the sign determines the sign of the inner product of the corresponding vector with itself. The values are irrelevant except for sign. This list also determines the dimensionality of the vectors. Examples:: sig=[+1, -1, -1, -1] # Hestenes', et al. Space-Time Algebra sig=[+1, +1, +1] # 3-D Euclidean signature ids : Optional[BasisVectorIds[Any]] A list of ids to associate with each basis vector. These ids are used to generate names (if not passed explicitly), and also used when using tuple-notation to access elements, such as ``mv[(1, 3)] = 1``. Defaults to ``BasisVectorIds.ordered_integers(len(sig))``; that is, integers starting at 1. This supersedes the old `firstIdx` argument. Examples:: ids=BasisVectorIds.ordered_integers(2, first_index=1) ids=BasisVectorIds([10, 20, 30]) .. versionadded:: 1.3.0 order : Optional[BasisBladeOrder] A specification of the memory order to use when storing the basis blades. Defaults to ``BasisBladeOrder.shortlex(len(sig))``. This supersedes the old `bladeTupList` argument. .. warning:: Various tools within clifford assume this default, so do not change this unless you know what you're doing! .. versionadded:: 1.3.0 bladeTupList : List[Tuple[int, ...]] List of tuples corresponding to the blades in the whole algebra. This list determines the order of coefficients in the internal representation of multivectors. The entry for the scalar must be an empty tuple, and the entries for grade-1 vectors must be singleton tuples. Remember, the length of the list will be ``2**dims``. Example:: bladeTupList = [(), (0,), (1,), (0, 1)] # 2-D .. deprecated:: 1.3.0 Use the new `order` and `ids` arguments instead. The above example can be spelt with the slightly longer:: ids = BasisVectorIds([.ordered_integers(2, first_index=0)]) order = ids.order_from_tuples([(), (0,), (1,), (0, 1)]) Layout(sig, ids=ids, order=order) firstIdx : int The index of the first vector. That is, some systems number the base vectors starting with 0, some with 1. .. deprecated:: 1.3.0 Use the new `ids` argument instead, for which the docs show an equivalent replacement names : List[str] List of names of each blade. When pretty-printing multivectors, use these symbols for the blades. names should be in the same order as `order`. You may use an empty string for scalars. By default, the name for each non-scalar blade is 'e' plus the ids of the blade as given in `ids`. Example:: names=['', 's0', 's1', 'i'] # 2-D Attributes ---------- dims : dimensionality of vectors (``len(self.sig)``) sig : normalized signature, with all values ``+1`` or ``-1`` gaDims : 2**dims names : pretty-printing symbols for the blades """ # old signature def __init__(self, sig, bladeTupList, firstIdx=1, names=None): return sig, bladeTupList, firstIdx, names # lgtm [py/explicit-return-in-init] _legacy_init_parser = __init__ # new signature def __init__(self, sig, *, ids=None, order=None, names=None): if ids is not None and not isinstance(ids, BasisVectorIds): raise TypeError("ids must be a BasisVectorIds") if order is not None and not isinstance(order, BasisBladeOrder): raise TypeError("order must be a BasisBladeOrder") return sig, ids, order, names # lgtm [py/explicit-return-in-init] _new_init_parser = __init__ @functools.wraps(_new_init_parser) def __init__(self, *args, **kw): # handle old vs new arguments. Once we drop support for the old we can # eliminate the entire `except` clause here. try: sig, ids, order, names = self._new_init_parser(*args, **kw) except TypeError as e_new: # try the old arguments try: sig, bladeTupList, firstIdx, names = self._legacy_init_parser(*args, **kw) except TypeError: # if both fail, give the error message about the new one raise e_new from None import inspect warnings.warn( "The Layout{} constructor is deprecated, use Layout{} " "instead.".format( inspect.signature(self._legacy_init_parser), inspect.signature(self._new_init_parser) ), DeprecationWarning, stacklevel=2) ids = BasisVectorIds.ordered_integers(len(sig), first_index=firstIdx) del firstIdx # shortcut the lazy property, no need to recompute this order = ids.order_from_tuples(bladeTupList) self.bladeTupList = bladeTupList else: # typically there's no need to override this if ids is None: ids = BasisVectorIds.ordered_integers(len(sig)) if order is None: order = BasisBladeOrder.shortlex(len(sig)) if len(ids.values) != len(sig): raise ValueError( "Length of basis vector ids must match length of signature") self.dims = len(sig) self.sig = np.array(sig).astype(int) self._basis_vector_ids = ids self._basis_blade_order = order self.gaDims = len(order.grades) self._metric = None if names is None or isinstance(names, str): if isinstance(names, str): e = names else: e = 'e' self.names = [ e + ''.join(map(str, tup)) if tup else '' for tup in self.bladeTupList ] elif len(names) == self.gaDims: self.names = names else: raise ValueError( "names list of length %i needs to be of length %i" % (len(names), self.gaDims)) @property def gradeList(self): return list(self._basis_blade_order.grades)
[docs] @_cached_property def gmt(self): r""" Multiplication table for the geometric product. This is a tensor of rank 3 such that :math:`a = b c` can be computed as :math:`a_j = \sum_{i,k} b_i \mathit{M}_{ijk} c_k`.""" return construct_gmt(self._basis_blade_order, self.sig)
[docs] @_cached_property def omt(self): """ Multiplication table for the inner product, stored in the same way as :attr:`gmt` """ return construct_graded_mt(self._basis_blade_order, self.gmt, omt_check)
[docs] @_cached_property def imt(self): """ Multiplication table for the outer product, stored in the same way as :attr:`gmt` """ return construct_graded_mt(self._basis_blade_order, self.gmt, imt_check)
[docs] @_cached_property def lcmt(self): """ Multiplication table for the left-contraction, stored in the same way as :attr:`gmt` """ return construct_graded_mt(self._basis_blade_order, self.gmt, lcmt_check)
[docs] @_cached_property def bladeTupList(self): return self._basis_vector_ids.order_as_tuples(self._basis_blade_order)
@property def firstIdx(self): """ Starting point for vector indices .. deprecated:: 1.3.0 This attribute has been deprecated, to match the deprecation of the matching argument in the constructor. Internal code should be using ``self._basis_vector_ids.values[x]`` instead of ``x + self.firstIdx``. This replacement API is not yet finalized, so if you need it please file an issue on github! """ try: i = self._basis_vector_ids._first_index except AttributeError: raise AttributeError("'Layout' objects no longer always have a 'firstIdx' attribute") from None else: warnings.warn( "Layout.firstIdx is deprecated, and will be removed. If you " "needed access to this, please contact us!", DeprecationWarning, stacklevel=2) return i @classmethod def _from_sig(cls, sig=None, *, firstIdx=1, **kwargs): """ Factory method that uses sorted blade tuples. """ return cls( sig, ids=BasisVectorIds.ordered_integers(len(sig), first_index=firstIdx), order=None, # use the default **kwargs ) @classmethod def _from_Cl(cls, p=0, q=0, r=0, **kwargs): """ Factory method from a :math:`{Cl}_{p,q,r}` notation """ return cls._from_sig([0]*r + [+1]*p + [-1]*q, **kwargs) def __hash__(self): """ hashes the signature of the layout """ return hash(tuple(self.sig))
[docs] @_cached_property def dual_func(self): """ Generates the dual function for the pseudoscalar """ if 0 in self.sig: # We are degenerate, use the right complement return self.right_complement_func else: # Equivalent to but faster than # Iinv = self.pseudoScalar.inv().value II_scalar = self.gmt[-1, 0, -1] inv_II_scalar = 1 / II_scalar if II_scalar in (1, -1): Iinv = np.zeros(self.gaDims, dtype=int) else: Iinv = np.zeros(self.gaDims, dtype=type(inv_II_scalar)) # set the pseudo-scalar part Iinv[-1] = inv_II_scalar gmt_func = self.gmt_func @_numba_utils.njit def dual_func(Xval): return gmt_func(Xval, Iinv) return dual_func
@_cached_property def _grade_invol(self): """ Generates the grade involution function """ signs = np.power(-1, self._basis_blade_order.grades) @_numba_utils.njit def grade_inv_func(mv): newValue = signs * mv.value return self.MultiVector(newValue) return grade_inv_func
[docs] @_cached_property def vee_func(self): """ Generates the vee product function """ # Often, the dual and undual are used here. However, this unecessarily # invokes the metric for a product that is itself non-metric. The # complement functions are faster anyway. rc_func = self.right_complement_func lc_func = self.left_complement_func omt_func = self.omt_func @_numba_utils.njit def vee(aval, bval): return lc_func(omt_func(rc_func(aval), rc_func(bval))) return vee
def __repr__(self): return "{}({!r}, ids={!r}, order={!r}, names={!r})".format( type(self).__name__, list(self.sig), self._basis_vector_ids, self._basis_blade_order, self.names ) def _repr_pretty_(self, p, cycle): if cycle: raise RuntimeError("Should not be cyclic") prefix = '{}('.format(type(self).__name__) with p.group(len(prefix), prefix, ')'): p.text('{},'.format(list(self.sig))) p.breakable() p.text('ids=') p.pretty(self._basis_vector_ids) p.text(',') p.breakable() p.text('order=') p.pretty(self._basis_blade_order) p.text(',') p.breakable() p.text('names={}'.format(self.names)) def __eq__(self, other): if other is self: return True elif isinstance(other, Layout): return np.array_equal(self.sig, other.sig) else: return NotImplemented
[docs] def parse_multivector(self, mv_string: str) -> MultiVector: """ Parses a multivector string into a MultiVector object """ # guarded import in case the parse become heavier weight from ._parser import parse_multivector return parse_multivector(self, mv_string)
[docs] def gmt_func_generator(self, grades_a=None, grades_b=None, filter_mask=None): return get_mult_function( self.gmt, self._basis_blade_order.grades, grades_a=grades_a, grades_b=grades_b, filter_mask=filter_mask )
[docs] def imt_func_generator(self, grades_a=None, grades_b=None, filter_mask=None): return get_mult_function( self.imt, self._basis_blade_order.grades, grades_a=grades_a, grades_b=grades_b, filter_mask=filter_mask )
[docs] def omt_func_generator(self, grades_a=None, grades_b=None, filter_mask=None): return get_mult_function( self.omt, self._basis_blade_order.grades, grades_a=grades_a, grades_b=grades_b, filter_mask=filter_mask )
[docs] def lcmt_func_generator(self, grades_a=None, grades_b=None, filter_mask=None): return get_mult_function( self.lcmt, self._basis_blade_order.grades, grades_a=grades_a, grades_b=grades_b, filter_mask=filter_mask )
[docs] def get_grade_projection_matrix(self, grade): """ Returns the matrix M_g that performs grade projection via left multiplication eg. ``M_g@A.value = A(g).value`` """ diag_mask = 1.0 * (self._basis_blade_order.grades == grade) return np.diag(diag_mask)
def _gen_complement_func(self, omt): """ Generates the function which computes the complement of a multivector. `omt` should be an outer product table. """ dims = self.gaDims signlist = np.zeros(dims) # Since we're working with basis blades, we can use the table directly. # We only care about the pseudo-scalar part of the wedge. omt_ps_part = omt[:, -1, :] for n in range(dims): signlist[n] = (-1)**(omt_ps_part[n, dims-1-n] < 0.001) @_numba_utils.njit def comp_func(Xval): Yval = np.zeros(dims, dtype=Xval.dtype) for i, s in enumerate(signlist): Yval[i] = Xval[dims-1-i]*s return Yval return comp_func @_cached_property def _shirokov_inverse(self): """ See `MultiVector.shirokov_inverse` for documentation """ n = len(self.sig) exponent = (n + 1) // 2 N = 2 ** exponent @_numba_utils.njit def shirokov_inverse(U): Uk = U * 1.0 # cast to float for k in range(1, N): Ck = (N / k) * Uk.value[0] adjU = (Uk - Ck) Uk = U * adjU if Uk.value[0] == 0: raise ValueError('Multivector has no inverse') return adjU / Uk.value[0] return shirokov_inverse @_cached_property def _hitzer_inverse(self): """ See `MultiVector.hitzer_inverse` for documentation """ tot = len(self.sig) @_numba_utils.njit def hitzer_inverse(operand): if tot == 0: numerator = 1 + 0*operand elif tot == 1: # Equation 4.3 mv_invol = operand.gradeInvol() numerator = mv_invol elif tot == 2: # Equation 5.5 mv_conj = operand.conjugate() numerator = mv_conj elif tot == 3: # Equation 6.5 without the rearrangement from 6.4 mv_conj = operand.conjugate() mv_mul_mv_conj = operand * mv_conj numerator = (mv_conj * ~mv_mul_mv_conj) elif tot == 4: # Equation 7.7 mv_conj = operand.conjugate() mv_mul_mv_conj = operand * mv_conj numerator = mv_conj * (mv_mul_mv_conj - 2 * mv_mul_mv_conj(3, 4)) elif tot == 5: # Equation 8.22 without the rearrangement from 8.21 mv_conj = operand.conjugate() mv_mul_mv_conj = operand * mv_conj combo_op = mv_conj * ~mv_mul_mv_conj mv_combo_op = operand * combo_op numerator = combo_op * (mv_combo_op - 2 * mv_combo_op(1, 4)) else: raise NotImplementedError( 'Closed form inverses for algebras with more than 5 dimensions are not implemented') denominator = (operand * numerator).value[0] if denominator == 0: raise ValueError('Multivector has no inverse') return numerator / denominator return hitzer_inverse
[docs] @_cached_property def gmt_func(self): return get_mult_function(self.gmt, self._basis_blade_order.grades)
[docs] @_cached_property def imt_func(self): return get_mult_function(self.imt, self._basis_blade_order.grades)
[docs] @_cached_property def omt_func(self): return get_mult_function(self.omt, self._basis_blade_order.grades)
[docs] @_cached_property def lcmt_func(self): return get_mult_function(self.lcmt, self._basis_blade_order.grades)
[docs] @_cached_property def left_complement_func(self): return self._gen_complement_func(omt=self.omt)
[docs] @_cached_property def right_complement_func(self): return self._gen_complement_func(omt=self.omt.T)
[docs] @_cached_property def adjoint_func(self): ''' This function returns a fast jitted adjoint function ''' grades = self._basis_blade_order.grades signs = np.power(-1, grades*(grades-1)//2) @_numba_utils.njit def adjoint_func(value): return signs * value # elementwise multiplication return adjoint_func
[docs] @_cached_property def inv_func(self): """ Get a function that returns left-inverse using a computational linear algebra method proposed by Christian Perwass. Computes :math:`M^{-1}` where :math:`M^{-1}M = 1`. """ mult_table = self.gmt k_list, l_list, m_list = mult_table.coords mult_table_vals = mult_table.data n_dims = mult_table.shape[1] identity = np.zeros((n_dims,)) identity[self._basis_blade_order.bitmap_to_index[0]] = 1 @_numba_utils.njit def leftLaInvJIT(value): intermed = _numba_val_get_left_gmt_matrix(value, k_list, l_list, m_list, mult_table_vals, n_dims) if abs(np.linalg.det(intermed)) < _settings._eps: raise ValueError("multivector has no left-inverse") sol = np.linalg.solve(intermed, identity.astype(intermed.dtype)) return sol return leftLaInvJIT
[docs] def get_left_gmt_matrix(self, x): """ This produces the matrix X that performs left multiplication with x eg. ``X@b == (x*b).value`` """ return val_get_left_gmt_matrix(self.gmt, x.value)
[docs] def get_right_gmt_matrix(self, x): """ This produces the matrix X that performs right multiplication with x eg. ``X@b == (b*x).value`` """ return val_get_right_gmt_matrix(self.gmt, x.value)
[docs] def load_ga_file(self, filename: str) -> 'cf.MVArray': """ Loads the data from a ga file, checking it matches this layout. """ data_array, metric, basis_names, support = read_ga_file(filename) if not np.allclose(np.diagonal(metric), self.sig): raise ValueError('The signature of the ga file does not match this layout') return cf.MVArray.from_value_array(self, data_array)
[docs] def grade_mask(self, grade: int) -> np.ndarray: return grade == self._basis_blade_order.grades
@property def rotor_mask(self) -> np.ndarray: return self._basis_blade_order.grades % 2 == 0 @property def metric(self) -> np.ndarray: basis_vectors = self.basis_vectors_lst if self._metric is None: self._metric = np.zeros((len(basis_vectors), len(basis_vectors))) for i, v in enumerate(basis_vectors): for j, v2 in enumerate(basis_vectors): self._metric[i, j] = (v | v2)[()] return self._metric.copy() else: return self._metric.copy() @property def scalar(self) -> MultiVector: ''' the scalar of value 1, for this GA (a MultiVector object) useful for forcing a MultiVector type ''' s = self.MultiVector(dtype=int) s[()] = 1 return s @property def pseudoScalar(self) -> MultiVector: ''' The pseudoscalar, :math:`I`. ''' return self.blades_list[-1] I = pseudoScalar
[docs] def randomMV(self, n=1, **kwargs) -> MultiVector: ''' Convenience method to create a random multivector. see `clifford.randomMV` for details ''' return cf.randomMV(layout=self, n=n, **kwargs)
[docs] def randomV(self, n=1, **kwargs) -> MultiVector: ''' generate n random 1-vector s ''' return cf.randomMV(layout=self, n=n, grades=[1], **kwargs)
[docs] def randomRotor(self, **kwargs) -> MultiVector: ''' generate a random Rotor. this is created by muliplying an N unit vectors, where N is the dimension of the algebra if its even; else its one less. ''' n = self.dims if self.dims % 2 == 0 else self.dims - 1 R = functools.reduce(cf.gp, self.randomV(n, normed=True, **kwargs)) return R
# Helpers to get hold of basis blades of various specifications. # For historic reasons, we have a lot of different ways to spell similar ideas. def _basis_blade(self, i, mvClass=MultiVector) -> MultiVector: ''' get a basis blade with only the element at the given storage index set ''' v = np.zeros((self.gaDims,), dtype=int) v[i] = 1 return mvClass(self, v) def _basis_vector_indices(self): for v_id in self._basis_vector_ids.values: v_bitmap = self._basis_vector_ids.id_as_bitmap(v_id) v_index = self._basis_blade_order.bitmap_to_index[v_bitmap] yield v_index @property def basis_vectors(self) -> Dict[str, MultiVector]: '''dictionary of basis vectors''' return dict(zip(self.basis_names, self.basis_vectors_lst)) @property def basis_names(self) -> List[str]: """ Get the names of the basis vectors, in the order they are stored. .. versionchanged:: 1.3.0 Returns a list instead of a numpy array """ return [self.names[i] for i in self._basis_vector_indices()] @property def basis_vectors_lst(self) -> List[MultiVector]: """ Like ``blades_of_grade(1)``, but ordered based on the ``ids`` parameter passed at construction. """ return [self._basis_blade(i) for i in self._basis_vector_indices()]
[docs] def blades_of_grade(self, grade: int) -> List[MultiVector]: ''' return all blades of a given grade, ''' return [ self._basis_blade(i) for i, i_grade in enumerate(self._basis_blade_order.grades) if i_grade == grade ]
@property def blades_list(self) -> List[MultiVector]: ''' List of blades in this layout matching the `order` argument this layout was constructed from. ''' return [self._basis_blade(i) for i in range(self.gaDims)] @property def blades(self): return self.bases()
[docs] def bases(self, mvClass=MultiVector, grades: Optional[Container[int]] = None) -> Dict[str, MultiVector]: """Returns a dictionary mapping basis element names to their MultiVector instances, optionally for specific grades if you are lazy, you might do this to populate your namespace with the variables of a given layout. >>> locals().update(layout.blades()) # doctest: +SKIP .. versionchanged:: 1.1.0 This dictionary includes the scalar """ return { name: self._basis_blade(i, mvClass) for i, (name, grade) in enumerate(zip(self.names, self._basis_blade_order.grades)) if grades is None or grade in grades }
def _sign_and_index_from_tuple(self, blade: Tuple) -> Tuple[int, int]: """ Takes a tuple blade representation and converts it to a canonical tuple blade representation """ s, bitmap = self._basis_vector_ids.tuple_as_sign_and_bitmap(blade) index = self._basis_blade_order.bitmap_to_index[bitmap] return s, index def _index_as_tuple(self, idx: int) -> Tuple: """ Convert an index into a blade tuple """ return self._basis_vector_ids.bitmap_as_tuple( self._basis_blade_order.index_to_bitmap[idx] ) # this needs to be last else it replaces the type for our annotations!
[docs] def MultiVector(self, *args, **kwargs) -> MultiVector: ''' Create a multivector in this layout convenience func to ``MultiVector(layout)`` ''' return MultiVector(self, *args, **kwargs)
if _numba_utils.DISABLE_JIT: def __reduce__(self): data = super().__reduce__() state = data[2] # Workaround for gh-404 - remove all cached properties that look # like jittable functions, as these crash when pickling. # For now, we only do this if jitting is disabled, as it may still # be useful to use pickling in lieu of a proper cache. To this end, # we also leave around the non-function caches like multiplication tables. for k, v in list(state.items()): if isinstance(getattr(type(self), k, None), _cached_property) and callable(v): del state[k] return data