Skip to content

Commit

Permalink
Merge pull request #277 from eric-wieser/tidy-basis_vectors
Browse files Browse the repository at this point in the history
General cleanup around clifford.bases
  • Loading branch information
eric-wieser committed Mar 11, 2020
2 parents ce8fe99 + 35bf49a commit e4412b0
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 72 deletions.
32 changes: 4 additions & 28 deletions clifford/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
:toctree: generated/
grade_obj
bases
randomMV
"""
Expand All @@ -65,7 +64,7 @@
import os
import itertools
import warnings
from typing import List, Tuple, Set, Container, Dict, Optional
from typing import List, Tuple, Set

# Major library imports.
import numpy as np
Expand Down Expand Up @@ -347,35 +346,12 @@ def Cl(p=0, q=0, r=0, sig=None, names=None, firstIdx=1, mvClass=MultiVector):
return layout, layout.bases(mvClass=mvClass)


def bases(layout, 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())
.. versionchanged:: 1.1.0
This dictionary includes the scalar
"""

dict = {}
for i in range(layout.gaDims):
grade = layout.gradeList[i]
if grades is not None and grade not in grades:
continue
v = np.zeros((layout.gaDims,), dtype=int)
v[i] = 1
dict[layout.names[i]] = mvClass(layout, v)
return dict
def bases(layout, *args, **kwargs):
return layout.bases(*args, **kwargs)


def basis_vectors(layout):
'''
dictionary of basis vectors
'''
return bases(layout=layout, grades=[1])
return layout.basis_vectors


def randomMV(
Expand Down
4 changes: 1 addition & 3 deletions clifford/_conformal_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ def __init__(self, *args, layout=None, **kwargs):
super().__init__(*args, **kwargs)
self._base_layout = layout

basis_vectors = self.basis_vectors
added_keys = sorted(basis_vectors.keys())[-2:]
ep, en = [basis_vectors[k] for k in added_keys]
ep, en = self.basis_vectors_lst[-2:]

# setup null basis, and minkowski subspace bivector
eo = .5 ^ (en - ep)
Expand Down
88 changes: 56 additions & 32 deletions clifford/_layout.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import re
from functools import reduce
from typing import List, Tuple
from typing import List, Tuple, Optional, Dict, Container

import numpy as np
import sparse
Expand All @@ -18,6 +18,7 @@
from . import _numba_utils
from .io import read_ga_file
from . import _settings
from ._multivector import MultiVector


class _cached_property:
Expand Down Expand Up @@ -389,10 +390,6 @@ def vee(aval, bval):
return lc_func(omt_func(rc_func(aval), rc_func(bval)))
return vee

@property
def basis_names(self):
return np.array(list(sorted(self.basis_vectors.keys())), dtype=bytes)

def __repr__(self):
return "{}({!r}, {!r}, firstIdx={!r}, names={!r})".format(
type(self).__name__,
Expand Down Expand Up @@ -422,7 +419,7 @@ def __eq__(self, other):
else:
return NotImplemented

def parse_multivector(self, mv_string: str) -> 'cf.MultiVector':
def parse_multivector(self, mv_string: str) -> MultiVector:
""" Parses a multivector string into a MultiVector object """
# Get the names of the canonical blades
blade_name_index_map = {name: index for index, name in enumerate(self.names)}
Expand All @@ -431,7 +428,7 @@ def parse_multivector(self, mv_string: str) -> 'cf.MultiVector':
cleaned_string = re.sub('[()]', '', mv_string)

# Create a multivector
mv_out = cf.MultiVector(self)
mv_out = MultiVector(self)

# Apply the regex
for m in _blade_pattern.finditer(cleaned_string):
Expand Down Expand Up @@ -626,13 +623,13 @@ def get_right_gmt_matrix(self, x):
"""
return val_get_right_gmt_matrix(self.gmt, x.value)

def MultiVector(self, *args, **kwargs):
def MultiVector(self, *args, **kwargs) -> MultiVector:
'''
Create a multivector in this layout
convenience func to Multivector(layout)
convenience func to MultiVector(layout)
'''
return cf.MultiVector(self, *args, **kwargs)
return MultiVector(self, *args, **kwargs)

def load_ga_file(self, filename):
"""
Expand All @@ -658,10 +655,11 @@ def rotor_mask(self):

@property
def metric(self):
basis_vectors = self.basis_vectors_lst
if self._metric is None:
self._metric = np.zeros((len(self.basis_vectors), len(self.basis_vectors)))
for i, v in enumerate(self.basis_vectors_lst):
for j, v2 in enumerate(self.basis_vectors_lst):
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)[0]
return self._metric.copy()
else:
Expand Down Expand Up @@ -713,16 +711,39 @@ def randomRotor(self):
R = reduce(cf.gp, self.randomV(n, normed=True))
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)

@property
def basis_vectors(self):
return cf.basis_vectors(self)
'''dictionary of basis vectors'''
return dict(zip(self.basis_names, self.basis_vectors_lst))

@property
def basis_names(self):
"""
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 [
name
for name, grade in zip(self.names, self.gradeList)
if grade == 1
]

@property
def basis_vectors_lst(self):
d = self.basis_vectors
return [d[k] for k in sorted(d.keys())]
return self.blades_of_grade(1)

def blades_of_grade(self, grade: int) -> List['MultiVector']:
def blades_of_grade(self, grade: int) -> List[MultiVector]:
'''
return all blades of a given grade,
Expand All @@ -735,37 +756,40 @@ def blades_of_grade(self, grade: int) -> List['MultiVector']:
--------
blades : list of MultiVectors
'''
return [k for k in self.blades_list if k.grades() == {grade}]
return [
self._basis_blade(i)
for i, i_grade in enumerate(self.gradeList)
if i_grade == grade
]

@property
def blades_list(self) -> List['MultiVector']:
def blades_list(self) -> List[MultiVector]:
'''
List of blades in this layout matching the order of `self.bladeTupList`
'''
blades = self.blades
return [blades[n] for n in self.names]
return [self._basis_blade(i) for i in range(self.gaDims)]

@property
def blades(self):
return self.bases()

def bases(self, *args, **kwargs):
'''
Returns a dictionary mapping basis element names to their MultiVector
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.bases())
>>> locals().update(layout.blades())
See Also
---------
bases
'''
return cf.bases(layout=self, *args, **kwargs)
.. 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.gradeList))
if grades is None or grade in grades
}

def _compute_reordering_sign_and_canonical_form(self, blade):
"""
Expand Down
6 changes: 5 additions & 1 deletion clifford/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ def write_ga_file(file_name, mv_array, metric, basis_names, compression=True,
dset_metric = f.create_dataset("metric", data=metric)

# Now the basis names
dset_basis_names = f.create_dataset("basis_names", data=basis_names)
try:
dt = h5py.string_dtype() # new in 2.10
except AttributeError:
dt = h5py.special_dtype(vlen=str)
dset_basis_names = f.create_dataset("basis_names", data=np.asarray(basis_names, dtype=dt))


def read_ga_file(file_name):
Expand Down
4 changes: 2 additions & 2 deletions clifford/test/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class TestHDF5BasicIO:
def test_write_and_read(self, tmp_path):
file_name = str(tmp_path / "test.ga")

basis_names = np.array(list(sorted(layout.basis_vectors.keys())), dtype=bytes)
basis_names = np.array(layout.basis_names, dtype=str)

mv_array = ConformalMVArray([random_point_pair() for i in range(1000)]).value
write_ga_file(file_name, mv_array, layout.metric, basis_names, compression=True,
Expand Down Expand Up @@ -50,7 +50,7 @@ class TestJSONBasicIO:
def test_write_and_read(self, tmp_path):
file_name = str(tmp_path / "test.ga.json")

basis_names = np.array(list(sorted(layout.basis_vectors.keys())))
basis_names = np.array(layout.basis_names, dtype=str)

mv_array = ConformalMVArray([random_point_pair() for i in range(1000)]).value
write_json_file(file_name, mv_array, layout.metric, basis_names, compression=True,
Expand Down
9 changes: 3 additions & 6 deletions clifford/tools/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,9 +148,7 @@ def mat2Frame(A, layout=None, is_complex=None):
if layout is None:
layout, blades = Cl(M)

e_ = layout.basis_vectors

e_ = [e_['e%i' % k] for k in range(layout.firstIdx, layout.firstIdx + M)]
e_ = layout.basis_vectors_lst[:M]

a = [0 ^ e_[0]] * N

Expand Down Expand Up @@ -259,7 +257,7 @@ def orthoFrames2Versor(B, A=None, delta=1e-3, eps=None, det=None,
A : list of vectors, or clifford.Frame
the set of vectors before the transform. If `None` we assume A is
the basis given B.layout.basis_vectors
the basis given B.layout.basis_vectors_lst
delta : float
Tolerance for reflection/rotation determination. If the normalized
Expand Down Expand Up @@ -300,8 +298,7 @@ def orthoFrames2Versor(B, A=None, delta=1e-3, eps=None, det=None,
# Checking and Setup
if A is None:
# assume we have orthonormal initial frame
bv = B[0].layout.basis_vectors
A = [bv[k] for k in sorted(bv.keys())]
A = B[0].layout.basis_vectors_lst

# make copy of original frames, so we can rotate A
A = A[:]
Expand Down
4 changes: 4 additions & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ Compatibility notes
* ``del mv[i]`` is no longer legal, the equivalent ``mv[i] = 0`` should be used instead.
* ``Layout.dict_to_multivector`` has been removed. It was accidentally broken
in 1.0.5, so there is little point deprecating it.
* :meth:`Layout.basis_names` now returns a ``list`` of ``str``, rather than a
numpy array of ``bytes``. The result now matches the construction order, rather
than being sorted alphabetically. The order of :meth:`Layout.metric` has
been adjusted for consistency.


Changes in 1.2.x
Expand Down

0 comments on commit e4412b0

Please sign in to comment.