Skip to content

Commit

Permalink
Merge pull request #301 from eric-wieser/from_function
Browse files Browse the repository at this point in the history
Add LinearMatrix.{from_function, from_rotor}
  • Loading branch information
eric-wieser committed Apr 8, 2020
2 parents cc1c6ee + 1db62aa commit ba9ac37
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 0 deletions.
17 changes: 17 additions & 0 deletions clifford/test/test_transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,20 @@ def test_between_layouts(self, g2, g3):
assert f(e1) == ex
assert f(e2) == ey
assert f(e1^e2) == ex^ey^ez

def test_from_function(self, g2, g3):
# passing tests are within the doctests

# wrong implicit layout
with pytest.raises(ValueError):
def bad_f(e):
# these paths return different layouts!
if e.grades() == {0}:
return g3.scalar
else:
return e
transformations.LinearMatrix.from_function(bad_f, g2)

# wrong explicit layout
with pytest.raises(ValueError):
transformations.LinearMatrix.from_function(lambda x: x, g2, g3)
84 changes: 84 additions & 0 deletions clifford/transformations.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
And some matrix-backed implementations:
.. autoclass:: LinearMatrix
:members:
.. autoclass:: OutermorphismMatrix
Expand Down Expand Up @@ -190,6 +191,89 @@ def adjoint(self) -> "LinearMatrix":
""" The adjoint transformation """
return LinearMatrix(self._matrix.T, self.layout_dst, self.layout_src)

@classmethod
def from_function(cls, func: Callable[[MultiVector], MultiVector], layout_src: Layout, layout_dst: Layout = None) -> 'LinearMatrix':
""" Build a linear transformation from the result of a function applied to each basis blade.
Parameters
----------
func :
A function taking basis blades from `layout_src` that produces
multivectors in `layout_dst`.
layout_src : ~clifford.Layout of S dimensions
The layout to pass into the generating function
layout_dst : ~clifford.Layout of D dimensions
The layout the generating function is expected to produce. If not
passed, this is inferred.
Example
-------
>>> from clifford import transformations, Layout
>>> l = Layout([1, 1])
>>> e1, e2 = l.basis_vectors_lst
>>> rot_90 = transformations.LinearMatrix.from_function(lambda x: (1 + e1*e2)*x*(1 - e1*e2)/2, l)
>>> rot_90(e1)
(1.0^e2)
>>> rot_90(e2)
-(1.0^e1)
>>> rot_90(e1*e2)
(1.0^e12)
See also
--------
LinearMatrix.from_rotor : a shorter way to spell the above example
clifford.linear_operator_as_matrix : a lower-level function for working with a subset of basis blades
"""
blades_dst = [
func(b_src) for b_src in layout_src.blades_list
]

# check the layouts of the resulting blades match
for d in blades_dst:
if layout_dst is None:
layout_dst = d.layout
elif d.layout != layout_dst:
raise ValueError(
"result of func() is from the wrong layout, expected: {}, "
"got {}.".format(layout_dst, d.layout))

matrix = np.array([b_dst.value for b_dst in blades_dst])
return cls(matrix, layout_src, layout_dst)

@classmethod
def from_rotor(cls, rotor: MultiVector) -> 'LinearMatrix':
""" Build a linear transformation from the result of applying a rotor sandwich.
The resulting transformation operates within the algebra of the provided rotor.
Parameters
----------
rotor :
The rotor to apply
Example
-------
>>> from clifford import transformations, Layout
>>> l = Layout([1, 1])
>>> e1, e2 = l.basis_vectors_lst
>>> rot_90 = transformations.LinearMatrix.from_rotor(1 + e1*e2)
>>> rot_90(e1)
(1.0^e2)
>>> rot_90(e2)
-(1.0^e1)
>>> rot_90(e1*e2)
(1.0^e12)
"""
# precompute for speed
rotor_rev = ~rotor
rotor_mag2 = rotor.mag2()
return cls.from_function(
lambda x: (rotor*x*rotor_rev)/rotor_mag2,
rotor.layout, rotor.layout
)


class OutermorphismMatrix(LinearMatrix):
r"""
Expand Down

0 comments on commit ba9ac37

Please sign in to comment.