<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Matrix Representations of Geometric Functions
More info can be found in Chapter 5 of "New foundations for classical mechanics", by David Hestenes.
This notebook shows how some matrix groups can be represented in geometric algebra. Not as spinors in CGA, or PGA, just as functions in plain old Geometric Algebra.
This is done by
Step1:  Start with initializing a euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard.
Step2:  Anti-symmetric
This is so easy,
$$x \rightarrow x\cdot B$$
Step3:  Whats the B?  you can read its values straight off the matrix.
Step4:  Diagonal ( Directional Scaling)
A bit awkward this one,  but its made by projection onto each basis vector, then scaling the component by some amount. 
$$ x \rightarrow \sum{\lambda_i (x\cdot e_i) e_i} $$
Step5:  Orthgonal, Rotation
$$ x\rightarrow Rx\tilde{R}$$
where 
$$R=e^{B/2}$$
Step6:  The inverse of this is , 
$$ x\rightarrow \tilde{R}xR $$
Step7:  Orthogonal, Reflection
$$ x \rightarrow -axa^{-1} $$
Step8:  Notice the determinant for reflection is -1, and for rotation is +1.
Step9:  Symmetric
This can be built up from the functions we just defined ie  Rotation*Dilation/Rotation
$$ x \rightarrow r(d(r^{-1}(x)) $$
which if you write it out, looks kind of dumb
$$ x \rightarrow R[\sum{\lambda_i (\tilde{R}x R\cdot e_i) e_i]R} $$
So, the antisymmetric matrix is interpreted as a set dilations about a some orthogonal frame rotated from the basis (what basis,eh? exactly what basis!). 
More generally we could include reflection in the $R$ too.
Step10:  Eigen stuffs
By definition the eigen-stuff is the invariants of the transformation, sometimes this is a vector, and other times it is a plane. 
Rotation
The eigen blades of a rotation are really the axis and plane of rotation.
Step11:  If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized
Step12:  Symmetric
For the symmetric matrix, the invariant thing the orthonormal frame along which the dilations take place
Step13:  This is easily found by using the rotation part of the symmetric operator,
Step14:  Primitive Visualization in 2D
<ASSISTANT_TASK:>
Python Code:
import numpy as np 
def func2Mat(f,I):
    '''
    Convert a function acting on a vector into a matrix, given 
    the space defined by psuedoscalar I
    '''
    A = I.basis()
    B = [f(a) for a in A]
    M = [float(b | a) for a in A for b in B]
    return np.array(M).reshape(len(B), len(B)) 
Explanation: Matrix Representations of Geometric Functions
More info can be found in Chapter 5 of "New foundations for classical mechanics", by David Hestenes.
This notebook shows how some matrix groups can be represented in geometric algebra. Not as spinors in CGA, or PGA, just as functions in plain old Geometric Algebra.
This is done by: 
Creating a  geometric function
Apply it to an orthonormal frame
Convert the resultant frame into a matrix 
The matrix is defined as the inner product of each basis element of original and transformed frame. 
$$M_{ij} = a_i\cdot b_j = a_i\cdot f(a)_j $$ 
(or vice-versa with the i,j, you get the point). Since we are going to do this repeatedly,  define a func2Mat()
End of explanation
from clifford import Cl
from math import * 
l,b = Cl(3)        # returns (layout,blades). you can change dimesion here
I = l.pseudoScalar 
Explanation: Start with initializing a euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard.
End of explanation
B = l.randomIntMV()(2) # we use randIntMV because its easier to read
f = lambda x:x|B
func2Mat(f,I=I)
Explanation: Anti-symmetric
This is so easy,
$$x \rightarrow x\cdot B$$
End of explanation
B
Explanation: Whats the B?  you can read its values straight off the matrix.
End of explanation
ls = range(1,len(I.basis())+1) # some dilation values (eigenvalues) 
A = I.basis()
d = lambda x: sum([(x|a)/a*l for a,l in zip(A,ls)])
func2Mat(d,I=I)
Explanation: Diagonal ( Directional Scaling)
A bit awkward this one,  but its made by projection onto each basis vector, then scaling the component by some amount. 
$$ x \rightarrow \sum{\lambda_i (x\cdot e_i) e_i} $$
End of explanation
B = l.randomMV()(2)
R = e**(B/2)
r = lambda x: R*x*~R
func2Mat(r,I=I)
Explanation: Orthgonal, Rotation
$$ x\rightarrow Rx\tilde{R}$$
where 
$$R=e^{B/2}$$
End of explanation
rinv = lambda x: ~R*x*R # the inverse rotation 
func2Mat(rinv,I=I)
Explanation: The inverse of this is , 
$$ x\rightarrow \tilde{R}xR $$
End of explanation
a = l.randomIntMV()(1)
n = lambda x: -a*x/a
func2Mat(n,I=I)
a
Explanation: Orthogonal, Reflection
$$ x \rightarrow -axa^{-1} $$
End of explanation
from numpy.linalg import det 
det(func2Mat(n,I=I)), det(func2Mat(r,I=I))
Explanation: Notice the determinant for reflection is -1, and for rotation is +1.
End of explanation
g = lambda x: r(d(rinv(x)))
func2Mat(g,I=I)
Explanation: Symmetric
This can be built up from the functions we just defined ie  Rotation*Dilation/Rotation
$$ x \rightarrow r(d(r^{-1}(x)) $$
which if you write it out, looks kind of dumb
$$ x \rightarrow R[\sum{\lambda_i (\tilde{R}x R\cdot e_i) e_i]R} $$
So, the antisymmetric matrix is interpreted as a set dilations about a some orthogonal frame rotated from the basis (what basis,eh? exactly what basis!). 
More generally we could include reflection in the $R$ too.
End of explanation
from numpy.linalg import eig
vals, vecs = eig(func2Mat(r,I=I))
np.round(vecs,3)
Explanation: Eigen stuffs
By definition the eigen-stuff is the invariants of the transformation, sometimes this is a vector, and other times it is a plane. 
Rotation
The eigen blades of a rotation are really the axis and plane of rotation.
End of explanation
B/(abs(B))
B
vals
cos(abs(B)), sin(abs(B))
Explanation: If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized
End of explanation
vals, vecs = eig(func2Mat(g,I=I))
np.round(vecs,5).T
Explanation: Symmetric
For the symmetric matrix, the invariant thing the orthonormal frame along which the dilations take place
End of explanation
[R*a*~R  for a in I.basis()]
Explanation: This is easily found by using the rotation part of the symmetric operator,
End of explanation
from pylab import linspace, plot,axis,legend
def plot_ps(ps,**kw):
    x = [p[e1] for p in ps]
    y = [p[e2] for p in ps]
    plot(x,y, marker='o',ls='',**kw)
    
l,b = Cl(2)
locals().update(b)
I = l.pseudoScalar
## define function of interest 
B = l.randomMV()(2)
R = e**(B/2)
f = lambda x:  R*x*~R
## loop though cartesian grid and apply f, 
ps,qs=[],[]
for x in linspace(-1,1,11):
    for y in linspace(-1,1,11):
        p = x*e1 + y*e2
        q = f(p)
        ps.append(p)
        qs.append(q)
plot_ps(ps,label='before')
plot_ps(qs,label='after')
axis('equal')
legend()
func2Mat(f,I =I )
Explanation: Primitive Visualization in 2D
End of explanation
<END_TASK>