From ea35eca3d2f793cd328c329b050eb7592a48dcbb Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 25 Jun 2020 07:38:01 -0400 Subject: [PATCH 01/20] docfix: g3 turorial missing pi,e import --- docs/tutorials/euler-angles.ipynb | 3 +- docs/tutorials/g2-quick-start.ipynb | 40 ++++++------------------ docs/tutorials/g3-algebra-of-space.ipynb | 15 +++++++-- 3 files changed, 24 insertions(+), 34 deletions(-) diff --git a/docs/tutorials/euler-angles.ipynb b/docs/tutorials/euler-angles.ipynb index 865f0bfe..a7b42ef8 100644 --- a/docs/tutorials/euler-angles.ipynb +++ b/docs/tutorials/euler-angles.ipynb @@ -50,8 +50,7 @@ "2. rotate about the rotated $e_1$-axis, call it $e_1^{'}$\n", "3. rotate about the twice rotated axis of $e_3$-axis, call it $e_3^{''}$\n", "\n", - "So the elemental rotations are about $e_3, e_{1}^{'}, e_3^{''}$-axes, respectively.", - "\n", + "So the elemental rotations are about $e_3, e_{1}^{'}, e_3^{''}$-axes, respectively.\n", "![](../_static/Euler2a.gif)\n", "\n", "(taken from wikipedia)" diff --git a/docs/tutorials/g2-quick-start.ipynb b/docs/tutorials/g2-quick-start.ipynb index 1b5f6e39..950068ee 100644 --- a/docs/tutorials/g2-quick-start.ipynb +++ b/docs/tutorials/g2-quick-start.ipynb @@ -40,9 +40,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from numpy import e,pi\n", @@ -61,9 +59,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "blades " @@ -79,9 +75,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "e1 = blades['e1']\n", @@ -99,9 +93,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "e1*e2 # geometric product" @@ -110,9 +102,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "e1|e2 # inner product " @@ -121,9 +111,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "e1^e2 # outer product" @@ -139,9 +127,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "a = e1+e2 # the vector\n", @@ -151,9 +137,7 @@ }, { "cell_type": "markdown", - "metadata": { - "collapsed": false - }, + "metadata": {}, "source": [ "## Rotation" ] @@ -161,9 +145,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "from numpy import pi\n", @@ -175,9 +157,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": false - }, + "metadata": {}, "outputs": [], "source": [ "R*e1*~R # rotate e1 by pi/2 in the e12-plane" diff --git a/docs/tutorials/g3-algebra-of-space.ipynb b/docs/tutorials/g3-algebra-of-space.ipynb index 3df8a467..1cfaf3b5 100644 --- a/docs/tutorials/g3-algebra-of-space.ipynb +++ b/docs/tutorials/g3-algebra-of-space.ipynb @@ -39,11 +39,12 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import clifford as cf\n", + "from numpy import pi,e\n", "\n", "layout, blades = cf.Cl(3) # creates a 3-dimensional clifford algebra" ] @@ -863,7 +864,17 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.0" + "version": "3.7.3" + }, + "toc": { + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "toc_cell": false, + "toc_position": {}, + "toc_section_display": "block", + "toc_window_display": false } }, "nbformat": 4, From 57ef74b81d5fb04c31f10e79da0d333b5951011a Mon Sep 17 00:00:00 2001 From: arsenovic Date: Sat, 19 Dec 2020 13:03:43 -0500 Subject: [PATCH 02/20] allow mat2Frame to take list of vectors --- clifford/tools/__init__.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index 47240eb0..6b0d9e60 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -53,7 +53,7 @@ from math import sqrt from numpy import eye, array, sign, zeros, sin, arccos -from .. import Cl, gp, Frame +from .. import Cl, gp, Frame,Layout from .. import eps as global_eps from warnings import warn @@ -126,6 +126,10 @@ def mat2Frame(A, layout=None, is_complex=None): ------------ A : ndarray MxN matrix representing vectors + layout: None, Layout, list + if none we generate an algebra of Gn, if layout we take the + vector basis from that, and if its a list we will assume its + a vector basis. ''' # TODO: could simplify this by just implementing the real case and then @@ -147,8 +151,12 @@ def mat2Frame(A, layout=None, is_complex=None): if layout is None: layout, blades = Cl(M) - - e_ = layout.basis_vectors_lst[:M] + e_ = layout.basis_vectors_lst[:M] + elif isinstance(layout, Layout): + e_ = layout.basis_vectors_lst[:M] + elif len(layout)==N: + e_ = layout + a = [0 ^ e_[0]] * N From f876b9868915f4b1b2b74ce1606e9b870124c451 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Sun, 20 Dec 2020 09:59:57 -0500 Subject: [PATCH 03/20] BREAK: changed call sig on tools.frame2Mat and mat2Frame. added test. updated Cl doc --- clifford/__init__.py | 17 +++++++++++++ clifford/test/test_tools.py | 20 ++++++++++++--- clifford/tools/__init__.py | 49 ++++++++++++++++++++++++------------- 3 files changed, 65 insertions(+), 21 deletions(-) diff --git a/clifford/__init__.py b/clifford/__init__.py index 15ca452c..0fafdc0f 100644 --- a/clifford/__init__.py +++ b/clifford/__init__.py @@ -354,6 +354,23 @@ def Cl(p=0, q=0, r=0, sig=None, names=None, firstIdx=1, mvClass=MultiVector): The notation :math:`Cl_{p,q,r}` means that the algebra is :math:`p+q+r`-dimensional, with the first :math:`p` vectors with positive signature, the next :math:`q` vectors negative, and the final :math:`r` vectors with null signature. + Parameters + ============= + p: number + number of positive signature basis vectors + q: number + number of negative signature basis vectors + r: number + number of zero signature basis vectors + sig : array, None + sorted blade tuples. if given, p,q,r are ignored + names: list + list of names given to the basis blades + firstIdx: number + start counting basis blades at this number + + + Returns ======= layout : Layout diff --git a/clifford/test/test_tools.py b/clifford/test/test_tools.py index 0abfbd82..6519d882 100644 --- a/clifford/test/test_tools.py +++ b/clifford/test/test_tools.py @@ -9,15 +9,17 @@ from clifford.tools import orthoFrames2Versor as of2v from clifford._numba_utils import DISABLE_JIT +from clifford import tools + too_slow_without_jit = pytest.mark.skipif( DISABLE_JIT, reason="test is too slow without JIT" ) -@unittest.skip("reason unknown") +#@unittest.skip("reason unknown") class ToolsTests(unittest.TestCase): - + @unittest.skip("reason unknown") def checkit(self, p, q): # p, q =4,0 N = p + q @@ -39,7 +41,8 @@ def checkit(self, p, q): # Determined Versor implements desired transformation self.assertTrue([R_found*a*~R_found for a in A] == B) - + + @unittest.skip("reason unknown") def testOrthoFrames2VersorEuclidean(self): for p, q in [(2, 0), (3, 0), (4, 0)]: self.checkit(p=p, q=q) @@ -54,7 +57,16 @@ def testOrthoFrames2VersorBalanced(self): for p, q in [(2, 2)]: self.checkit(p=p, q=q) - + def testframe2Mat(self): + for N in [2,3,4]: + l,b = Cl(N) + X = np.random.rand((N**2)).reshape(N,N) + I = l.pseudoScalar + B,I = tools.mat2Frame(X,I=I) + X_,I= tools.frame2Mat(B=B,I=I) + testing.assert_almost_equal(X, X_) + + class G3ToolsTests(unittest.TestCase): def test_quaternion_conversions(self): diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index 6b0d9e60..eb254cab 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -31,7 +31,7 @@ ----------------------------------------------------------- Given two frames that are related by a orthogonal transform, we seek a rotor -which enacts the transform. Details of the mathematics and psuedo-code used the +which enacts the transform. Details of the mathematics and pseudo-code used the create the algorithms below can be found at Allan Cortzen's website. http://ctz.dk/geometric-algebra/frames-to-versor-algorithm/ @@ -111,7 +111,7 @@ def omoh(A, B): return lam -def mat2Frame(A, layout=None, is_complex=None): +def mat2Frame(A, I=None, is_complex=None): ''' Translates a (possibly complex) matrix into a real vector frame @@ -126,7 +126,7 @@ def mat2Frame(A, layout=None, is_complex=None): ------------ A : ndarray MxN matrix representing vectors - layout: None, Layout, list + I: None, pseudoscalar of the frame if none we generate an algebra of Gn, if layout we take the vector basis from that, and if its a list we will assume its a vector basis. @@ -149,13 +149,10 @@ def mat2Frame(A, layout=None, is_complex=None): N = N * 2 M = M * 2 - if layout is None: + if I is None: layout, blades = Cl(M) - e_ = layout.basis_vectors_lst[:M] - elif isinstance(layout, Layout): - e_ = layout.basis_vectors_lst[:M] - elif len(layout)==N: - e_ = layout + I = layout.pseudoScalar + e_ = I.basis() a = [0 ^ e_[0]] * N @@ -177,20 +174,38 @@ def mat2Frame(A, layout=None, is_complex=None): a[n_ + 1] = (a[n_ + 1]) \ + ((-A[m, n].imag) ^ e_[m_]) \ + ((A[m, n].real) ^ e_[m_ + 1]) - return a, layout + return a, I -def frame2Mat(B, A=None, is_complex=None): +def frame2Mat(B, A=None, I=None, is_complex=None): + ''' + convert a list of vectors to a matrix + + B : list + a list of vectors that have been transformed + A : None, list of vectors + a list of vectors in their initial state. if none we assume + orthonormal basis given by B.pseudoScalar, or I + I : Multivector, None + pseudoscalar of the space. if None, we use B.pseudoScalar + is_complex: Bool + do you want a complex matrix? + + ''' if is_complex is not None: raise NotImplementedError() + + + if I is None: + I = B[0].pseudoScalar if A is None: # assume we have orthonormal initial frame - A = B[0].layout.basis_vectors_lst + A = I.basis() # you need float() due to bug in clifford - M = [float(b | a) for b in B for a in A] + M = [float(b | a) for a in A for b in B ] M = array(M).reshape(len(B), len(B)) - + return M,I def orthoFrames2Versor_dist(A, B, eps=None): ''' @@ -404,7 +419,7 @@ def orthoFrames2Versor(B, A=None, delta=1e-3, eps=None, det=None, return R, r_list -def orthoMat2Versor(A, eps=None, layout=None, is_complex=None): +def orthoMat2Versor(A, eps=None, I=None, is_complex=None): ''' Translates an orthogonal (or unitary) matrix to a Versor @@ -417,12 +432,12 @@ def orthoMat2Versor(A, eps=None, layout=None, is_complex=None): ------------ ''' - B, layout = mat2Frame(A, layout=layout, is_complex=is_complex) + B, layout = mat2Frame(A, I=I, is_complex=is_complex) N = len(B) # if (A.dot(A.conj().T) -eye(N/2)).max()>eps: # warn('A doesnt appear to be a rotation. ') - A, layout = mat2Frame(eye(N), layout=layout, is_complex=False) + A, dum = mat2Frame(eye(N), I=I, is_complex=False) return orthoFrames2Versor(A=A, B=B, eps=eps) From 97bbf648bdbfff29cd28318f2328f4a2dfb2ab95 Mon Sep 17 00:00:00 2001 From: alex arsenovic Date: Tue, 29 Dec 2020 17:27:40 -0500 Subject: [PATCH 04/20] Update docs/tutorials/g3-algebra-of-space.ipynb Co-authored-by: Eric Wieser --- docs/tutorials/g3-algebra-of-space.ipynb | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/tutorials/g3-algebra-of-space.ipynb b/docs/tutorials/g3-algebra-of-space.ipynb index 6d19c546..cdf33eda 100644 --- a/docs/tutorials/g3-algebra-of-space.ipynb +++ b/docs/tutorials/g3-algebra-of-space.ipynb @@ -44,7 +44,6 @@ "outputs": [], "source": [ "import clifford as cf\n", - "from numpy import pi,e\n", "\n", "layout, blades = cf.Cl(3) # creates a 3-dimensional clifford algebra" ] From 434dbd2ca15d32714f290f9da0004514af548356 Mon Sep 17 00:00:00 2001 From: alex arsenovic Date: Wed, 30 Dec 2020 07:37:47 -0500 Subject: [PATCH 05/20] Update clifford/__init__.py Co-authored-by: Eric Wieser --- clifford/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clifford/__init__.py b/clifford/__init__.py index 0fafdc0f..9707f593 100644 --- a/clifford/__init__.py +++ b/clifford/__init__.py @@ -356,11 +356,11 @@ def Cl(p=0, q=0, r=0, sig=None, names=None, firstIdx=1, mvClass=MultiVector): Parameters ============= - p: number + p : int number of positive signature basis vectors - q: number + q : int number of negative signature basis vectors - r: number + r : int number of zero signature basis vectors sig : array, None sorted blade tuples. if given, p,q,r are ignored From e90b5943892ae84a87f6d44a3567e10bf055a10a Mon Sep 17 00:00:00 2001 From: alex arsenovic Date: Wed, 30 Dec 2020 07:37:53 -0500 Subject: [PATCH 06/20] Update clifford/__init__.py Co-authored-by: Eric Wieser --- clifford/__init__.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/clifford/__init__.py b/clifford/__init__.py index 9707f593..0b6e1372 100644 --- a/clifford/__init__.py +++ b/clifford/__init__.py @@ -362,12 +362,8 @@ def Cl(p=0, q=0, r=0, sig=None, names=None, firstIdx=1, mvClass=MultiVector): number of negative signature basis vectors r : int number of zero signature basis vectors - sig : array, None - sorted blade tuples. if given, p,q,r are ignored - names: list - list of names given to the basis blades - firstIdx: number - start counting basis blades at this number + sig, names, firstIdx + See the docs for :class:`clifford.Layout`. If ``sig`` is passed, then `p`, `q`, and `r` are ignored. From 78b545dd592ad8099ccec2a7c8239ecaf0e4a917 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 31 Dec 2020 09:29:35 -0500 Subject: [PATCH 07/20] pep8d --- clifford/__init__.py | 6 ++--- clifford/test/test_tools.py | 38 +++++++++++++++++--------------- clifford/tools/__init__.py | 44 ++++++++++++++++++------------------- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/clifford/__init__.py b/clifford/__init__.py index 0b6e1372..7b70365d 100644 --- a/clifford/__init__.py +++ b/clifford/__init__.py @@ -354,18 +354,16 @@ def Cl(p=0, q=0, r=0, sig=None, names=None, firstIdx=1, mvClass=MultiVector): The notation :math:`Cl_{p,q,r}` means that the algebra is :math:`p+q+r`-dimensional, with the first :math:`p` vectors with positive signature, the next :math:`q` vectors negative, and the final :math:`r` vectors with null signature. - Parameters + Parameters ============= p : int - number of positive signature basis vectors + number of positive signature basis vectors q : int number of negative signature basis vectors r : int number of zero signature basis vectors sig, names, firstIdx See the docs for :class:`clifford.Layout`. If ``sig`` is passed, then `p`, `q`, and `r` are ignored. - - Returns ======= diff --git a/clifford/test/test_tools.py b/clifford/test/test_tools.py index 6519d882..13b7eb45 100644 --- a/clifford/test/test_tools.py +++ b/clifford/test/test_tools.py @@ -17,9 +17,9 @@ ) -#@unittest.skip("reason unknown") +# @unittest.skip("reason unknown") class ToolsTests(unittest.TestCase): - @unittest.skip("reason unknown") + @unittest.skip("reason unknown") def checkit(self, p, q): # p, q =4,0 N = p + q @@ -29,9 +29,9 @@ def checkit(self, p, q): # create frame A = layout.randomV(n=N) # create Rotor - R = 5.*layout.randomRotor() + R = 5. * layout.randomRotor() # create rotated frame - B = [R*a*~R for a in A] + B = [R * a * ~R for a in A] # find versor from both frames R_found, rs = of2v(A, B) @@ -40,9 +40,9 @@ def checkit(self, p, q): self.assertTrue(R == R_found or R == -R_found) # Determined Versor implements desired transformation - self.assertTrue([R_found*a*~R_found for a in A] == B) - - @unittest.skip("reason unknown") + self.assertTrue([R_found * a * ~R_found for a in A] == B) + + @unittest.skip("reason unknown") def testOrthoFrames2VersorEuclidean(self): for p, q in [(2, 0), (3, 0), (4, 0)]: self.checkit(p=p, q=q) @@ -58,15 +58,15 @@ def testOrthoFrames2VersorBalanced(self): self.checkit(p=p, q=q) def testframe2Mat(self): - for N in [2,3,4]: - l,b = Cl(N) - X = np.random.rand((N**2)).reshape(N,N) + for N in [2, 3, 4]: + l, b = Cl(N) + X = np.random.rand((N**2)).reshape(N, N) I = l.pseudoScalar - B,I = tools.mat2Frame(X,I=I) - X_,I= tools.frame2Mat(B=B,I=I) + B, I = tools.mat2Frame(X, I=I) + X_, I = tools.frame2Mat(B=B, I=I) testing.assert_almost_equal(X, X_) - - + + class G3ToolsTests(unittest.TestCase): def test_quaternion_conversions(self): @@ -116,9 +116,10 @@ def test_generate_rotation_rotor_and_angle(self): euc_vector_n = random_unit_vector() theta = angle_between_vectors(euc_vector_m, euc_vector_n) - rot_rotor = generate_rotation_rotor(theta, euc_vector_m, euc_vector_n) + rot_rotor = generate_rotation_rotor( + theta, euc_vector_m, euc_vector_n) v1 = euc_vector_m - v2 = rot_rotor*euc_vector_m*~rot_rotor + v2 = rot_rotor * euc_vector_m * ~rot_rotor theta_return = angle_between_vectors(v1, v2) testing.assert_almost_equal(theta_return, theta) @@ -136,7 +137,7 @@ def test_find_rotor_aligning_vectors(self): u_list = [random_euc_mv() for i in range(50)] for i in range(100): r = random_rotation_rotor() - v_list = [r*u*~r for u in u_list] + v_list = [r * u * ~r for u in u_list] r_2 = rotor_align_vecs(u_list, v_list) print(r_2) print(r) @@ -206,7 +207,8 @@ def test_GADelaunay_facets(self): from clifford.tools.g3c import random_conformal_point, project_points_to_plane from clifford.tools.point_processing import GADelaunay point_list = [random_conformal_point() for i in range(100)] - point_list_flat = project_points_to_plane(point_list, (up(0)^up(e1)^up(e2)^einf).normal()) + point_list_flat = project_points_to_plane( + point_list, (up(0) ^ up(e1) ^ up(e2) ^ einf).normal()) hull = GADelaunay(point_list_flat, hull_dims=2) facets = hull.conformal_facets() diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index c539d64b..f95815fd 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -57,13 +57,14 @@ from numpy import eye, array, sign, zeros, sin, arccos import numpy as np -from .. import Cl, gp, Frame, MultiVector, Layout +from .. import Cl, gp, Frame, MultiVector from .. import eps as global_eps from warnings import warn -def omoh(A: Union[Frame, List[MultiVector]], B: Union[Frame, List[MultiVector]]) -> np.ndarray: +def omoh(A: Union[Frame, List[MultiVector]], + B: Union[Frame, List[MultiVector]]) -> np.ndarray: r''' Determines homogenization scaling for two :class:`~clifford.Frame`\ s related by a Rotor @@ -113,11 +114,9 @@ def omoh(A: Union[Frame, List[MultiVector]], B: Union[Frame, List[MultiVector]]) return lam - def mat2Frame(A: np.ndarray, I: Optional[MultiVector] = None, is_complex: bool = None) -> Tuple[List[MultiVector], MultiVector]: - ''' Translates a (possibly complex) matrix into a real vector frame @@ -135,9 +134,9 @@ def mat2Frame(A: np.ndarray, MxN matrix representing vectors I: None, pseudoscalar of the frame - if none we generate an algebra of Gn, if layout we take the - vector basis from that, and if its a list we will assume its - a vector basis. + if none we generate an algebra of Gn, if layout we take the + vector basis from that, and if its a list we will assume its + a vector basis. Returns @@ -170,7 +169,6 @@ def mat2Frame(A: np.ndarray, layout, blades = Cl(M) I = layout.pseudoScalar e_ = I.basis() - a = [0 ^ e_[0]] * N @@ -197,22 +195,21 @@ def mat2Frame(A: np.ndarray, def frame2Mat(B, A=None, I=None, is_complex=None): ''' convert a list of vectors to a matrix - - B : list + + B : list a list of vectors that have been transformed A : None, list of vectors - a list of vectors in their initial state. if none we assume - orthonormal basis given by B.pseudoScalar, or I + a list of vectors in their initial state. if none we assume + orthonormal basis given by B.pseudoScalar, or I I : Multivector, None pseudoscalar of the space. if None, we use B.pseudoScalar is_complex: Bool - do you want a complex matrix? - + do you want a complex matrix? + ''' if is_complex is not None: raise NotImplementedError() - - + if I is None: I = B[0].pseudoScalar if A is None: @@ -220,9 +217,10 @@ def frame2Mat(B, A=None, I=None, is_complex=None): A = I.basis() # you need float() due to bug in clifford - M = [float(b | a) for a in A for b in B ] + M = [float(b | a) for a in A for b in B] M = array(M).reshape(len(B), len(B)) - return M,I + return M, I + def orthoFrames2Versor_dist(A, B, eps=None): ''' @@ -237,7 +235,8 @@ def orthoFrames2Versor_dist(A, B, eps=None): ''' # TODO: should we test to see if A and B are related by rotation? - # TODO: implement reflect/rotate based on distance (as in:cite:`ctz-frames`) + # TODO: implement reflect/rotate based on distance (as + # in:cite:`ctz-frames`) # keep copy of original frames A = A[:] @@ -452,7 +451,8 @@ def orthoMat2Versor(A, eps=None, I=None, is_complex=None): return orthoFrames2Versor(A=A, B=B, eps=eps) -def rotor_decomp(V: MultiVector, x: MultiVector) -> Tuple[MultiVector, MultiVector]: +def rotor_decomp( + V: MultiVector, x: MultiVector) -> Tuple[MultiVector, MultiVector]: ''' Rotor decomposition of rotor V @@ -486,7 +486,7 @@ def rotor_decomp(V: MultiVector, x: MultiVector) -> Tuple[MultiVector, MultiVect def sinc(x): - return sin(x)/x + return sin(x) / x def log_rotor(V): @@ -500,4 +500,4 @@ def log_rotor(V): return log(float(V(0))) # numpy's trig correctly chooses hyperbolic or not with Complex args theta = arccos(complex(V(0))) - return V(2)/sinc(theta).real + return V(2) / sinc(theta).real From f9ff2dc580bbe125880490474cf4f96217abac05 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 31 Dec 2020 09:31:09 -0500 Subject: [PATCH 08/20] added some missing methods to docs --- clifford/tools/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index f95815fd..86acb028 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -46,7 +46,9 @@ orthoFrames2Versor orthoMat2Versor mat2Frame + frame2Mat omoh + rotor_decomp """ From 6bd529c4b9f3aee8c6c0fb13fa40bc5958bfc101 Mon Sep 17 00:00:00 2001 From: Eric Wieser Date: Thu, 31 Dec 2020 14:57:28 +0000 Subject: [PATCH 09/20] Revert unwanted autopep8 changes --- clifford/test/test_tools.py | 19 ++++++++----------- clifford/tools/__init__.py | 14 ++++---------- docs/tutorials/euler-angles.ipynb | 3 ++- docs/tutorials/g3-algebra-of-space.ipynb | 12 +----------- 4 files changed, 15 insertions(+), 33 deletions(-) diff --git a/clifford/test/test_tools.py b/clifford/test/test_tools.py index 13b7eb45..bdeaa33d 100644 --- a/clifford/test/test_tools.py +++ b/clifford/test/test_tools.py @@ -17,9 +17,8 @@ ) -# @unittest.skip("reason unknown") class ToolsTests(unittest.TestCase): - @unittest.skip("reason unknown") + def checkit(self, p, q): # p, q =4,0 N = p + q @@ -29,9 +28,9 @@ def checkit(self, p, q): # create frame A = layout.randomV(n=N) # create Rotor - R = 5. * layout.randomRotor() + R = 5.*layout.randomRotor() # create rotated frame - B = [R * a * ~R for a in A] + B = [R*a*~R for a in A] # find versor from both frames R_found, rs = of2v(A, B) @@ -40,7 +39,7 @@ def checkit(self, p, q): self.assertTrue(R == R_found or R == -R_found) # Determined Versor implements desired transformation - self.assertTrue([R_found * a * ~R_found for a in A] == B) + self.assertTrue([R_found*a*~R_found for a in A] == B) @unittest.skip("reason unknown") def testOrthoFrames2VersorEuclidean(self): @@ -116,10 +115,9 @@ def test_generate_rotation_rotor_and_angle(self): euc_vector_n = random_unit_vector() theta = angle_between_vectors(euc_vector_m, euc_vector_n) - rot_rotor = generate_rotation_rotor( - theta, euc_vector_m, euc_vector_n) + rot_rotor = generate_rotation_rotor(theta, euc_vector_m, euc_vector_n) v1 = euc_vector_m - v2 = rot_rotor * euc_vector_m * ~rot_rotor + v2 = rot_rotor*euc_vector_m*~rot_rotor theta_return = angle_between_vectors(v1, v2) testing.assert_almost_equal(theta_return, theta) @@ -137,7 +135,7 @@ def test_find_rotor_aligning_vectors(self): u_list = [random_euc_mv() for i in range(50)] for i in range(100): r = random_rotation_rotor() - v_list = [r * u * ~r for u in u_list] + v_list = [r*u*~r for u in u_list] r_2 = rotor_align_vecs(u_list, v_list) print(r_2) print(r) @@ -207,8 +205,7 @@ def test_GADelaunay_facets(self): from clifford.tools.g3c import random_conformal_point, project_points_to_plane from clifford.tools.point_processing import GADelaunay point_list = [random_conformal_point() for i in range(100)] - point_list_flat = project_points_to_plane( - point_list, (up(0) ^ up(e1) ^ up(e2) ^ einf).normal()) + point_list_flat = project_points_to_plane(point_list, (up(0)^up(e1)^up(e2)^einf).normal()) hull = GADelaunay(point_list_flat, hull_dims=2) facets = hull.conformal_facets() diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index 86acb028..f86a7c51 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -35,7 +35,6 @@ create the algorithms below can be found at Allan Cortzen's website, :cite:`ctz-frames`. - There are also some helper functions which can be used to translate matrices into GA frames, so an orthogonal (or complex unitary) matrix can be directly translated into a Versor. @@ -57,7 +56,6 @@ from typing import Union, Optional, List, Tuple from math import sqrt from numpy import eye, array, sign, zeros, sin, arccos - import numpy as np from .. import Cl, gp, Frame, MultiVector from .. import eps as global_eps @@ -140,14 +138,12 @@ def mat2Frame(A: np.ndarray, vector basis from that, and if its a list we will assume its a vector basis. - Returns ------- a : list of clifford.MultiVector The resulting vectors I : clifford.MultiVector The blade holding the vectors in ``a``. - ''' # TODO: could simplify this by just implementing the real case and then @@ -237,8 +233,7 @@ def orthoFrames2Versor_dist(A, B, eps=None): ''' # TODO: should we test to see if A and B are related by rotation? - # TODO: implement reflect/rotate based on distance (as - # in:cite:`ctz-frames`) + # TODO: implement reflect/rotate based on distance (as in:cite:`ctz-frames`) # keep copy of original frames A = A[:] @@ -453,8 +448,7 @@ def orthoMat2Versor(A, eps=None, I=None, is_complex=None): return orthoFrames2Versor(A=A, B=B, eps=eps) -def rotor_decomp( - V: MultiVector, x: MultiVector) -> Tuple[MultiVector, MultiVector]: +def rotor_decomp(V: MultiVector, x: MultiVector) -> Tuple[MultiVector, MultiVector]: ''' Rotor decomposition of rotor V @@ -488,7 +482,7 @@ def rotor_decomp( def sinc(x): - return sin(x) / x + return sin(x)/x def log_rotor(V): @@ -502,4 +496,4 @@ def log_rotor(V): return log(float(V(0))) # numpy's trig correctly chooses hyperbolic or not with Complex args theta = arccos(complex(V(0))) - return V(2) / sinc(theta).real + return V(2)/sinc(theta).real diff --git a/docs/tutorials/euler-angles.ipynb b/docs/tutorials/euler-angles.ipynb index cd5caa82..4ebe0e25 100644 --- a/docs/tutorials/euler-angles.ipynb +++ b/docs/tutorials/euler-angles.ipynb @@ -50,7 +50,8 @@ "2. rotate about the rotated $e_1$-axis, call it $e_1^{'}$\n", "3. rotate about the twice rotated axis of $e_3$-axis, call it $e_3^{''}$\n", "\n", - "So the elemental rotations are about $e_3, e_{1}^{'}, e_3^{''}$-axes, respectively.\n", + "So the elemental rotations are about $e_3, e_{1}^{'}, e_3^{''}$-axes, respectively.", + "\n", "![](../_static/Euler2a.gif)\n", "\n", "(taken from wikipedia)" diff --git a/docs/tutorials/g3-algebra-of-space.ipynb b/docs/tutorials/g3-algebra-of-space.ipynb index cdf33eda..8008a6f5 100644 --- a/docs/tutorials/g3-algebra-of-space.ipynb +++ b/docs/tutorials/g3-algebra-of-space.ipynb @@ -861,17 +861,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" - }, - "toc": { - "nav_menu": {}, - "number_sections": true, - "sideBar": true, - "skip_h1_title": false, - "toc_cell": false, - "toc_position": {}, - "toc_section_display": "block", - "toc_window_display": false + "version": "3.8.0" } }, "nbformat": 4, From d283fa5ff7ce99c83411abd07f0e5a9765d59960 Mon Sep 17 00:00:00 2001 From: alex arsenovic Date: Mon, 4 Jan 2021 08:11:35 -0500 Subject: [PATCH 10/20] Update clifford/tools/__init__.py Co-authored-by: Eric Wieser --- clifford/tools/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index f86a7c51..8000da90 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -133,7 +133,7 @@ def mat2Frame(A: np.ndarray, A : ndarray MxN matrix representing vectors - I: None, pseudoscalar of the frame + I : MultiVector if none we generate an algebra of Gn, if layout we take the vector basis from that, and if its a list we will assume its a vector basis. From 5d81208e0faf387624197d5220d6ec2a7fee11f5 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Mon, 4 Jan 2021 08:14:27 -0500 Subject: [PATCH 11/20] docfixs --- clifford/tools/__init__.py | 11 +++++++++++ docs/tutorials/euler-angles.ipynb | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index 8000da90..2db32170 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -192,8 +192,11 @@ def mat2Frame(A: np.ndarray, def frame2Mat(B, A=None, I=None, is_complex=None): ''' + convert a list of vectors to a matrix + Parameters + ------------ B : list a list of vectors that have been transformed A : None, list of vectors @@ -437,6 +440,14 @@ def orthoMat2Versor(A, eps=None, I=None, is_complex=None): Parameters ------------ + A : matrix + matrix to be transformed + eps : number + tolerance + I : MultiVector + GA of A + is_complex : boolean + is A complex? ''' B, layout = mat2Frame(A, I=I, is_complex=is_complex) diff --git a/docs/tutorials/euler-angles.ipynb b/docs/tutorials/euler-angles.ipynb index 4ebe0e25..cd5caa82 100644 --- a/docs/tutorials/euler-angles.ipynb +++ b/docs/tutorials/euler-angles.ipynb @@ -50,8 +50,7 @@ "2. rotate about the rotated $e_1$-axis, call it $e_1^{'}$\n", "3. rotate about the twice rotated axis of $e_3$-axis, call it $e_3^{''}$\n", "\n", - "So the elemental rotations are about $e_3, e_{1}^{'}, e_3^{''}$-axes, respectively.", - "\n", + "So the elemental rotations are about $e_3, e_{1}^{'}, e_3^{''}$-axes, respectively.\n", "![](../_static/Euler2a.gif)\n", "\n", "(taken from wikipedia)" From fc44e78cde21f79536c380a39e9d034ff1c1ad94 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Wed, 3 Feb 2021 17:44:05 -0500 Subject: [PATCH 12/20] added func2Mat --- clifford/tools/__init__.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index 2db32170..88795368 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -46,6 +46,7 @@ orthoMat2Versor mat2Frame frame2Mat + func2Mat omoh rotor_decomp @@ -222,6 +223,24 @@ def frame2Mat(B, A=None, I=None, is_complex=None): M = array(M).reshape(len(B), len(B)) return M, I +def func2Mat(f,I): + ''' + Convert a function to a matrix by acting on standard basis + + Parameters + --------------- + f : function + function that maps vectors to vectors + I : MultiVector + psuedoscalar of basis + + See Also + --------- + frame2Mat + ''' + A = I.basis() + B = [f(a) for a in A] + return frame2Mat(B=B, A=A,I=I) def orthoFrames2Versor_dist(A, B, eps=None): ''' From eeaeb1ba4c50014bc7bb2ddcc4884ead0610b2fe Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 4 Feb 2021 17:47:36 -0500 Subject: [PATCH 13/20] added tutorial of matrices --- ...xRepresentationsOfGeometricFunctions.ipynb | 413 ++++++++++++++++++ 1 file changed, 413 insertions(+) create mode 100644 docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb diff --git a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb new file mode 100644 index 00000000..07f3bacc --- /dev/null +++ b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb @@ -0,0 +1,413 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "favorite-visiting", + "metadata": {}, + "source": [ + "## Matrix Representations of Geometric Functions\n", + "More info can be found in Chapter 5 of \"New foundations for classical mechanics\", by David Hestenes.\n", + "\n", + "This notebook shows how some matrix groups can be represented in geometric algebra. Not as spinors in CGA, or PGA, just as functions in GA. This is done by: \n", + "1. Creating a geometric function\n", + "2. Apply it to an orthonormal frame\n", + "3. Convert the resultant frame into a matrix \n", + "\n", + "The matrix is defined as the inner product of each basis element of original and transformed frame. \n", + "\n", + "$$M_{ij} = a_i\\cdot b_j = a_i\\cdot f(a)_j $$ \n", + "\n", + "(or vice-versa with the i,j, you get the point). Since we are going to do this repeatedly, define a `func2Mat()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "realistic-colony", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np \n", + "def func2Mat(f,I):\n", + " '''\n", + " Convert a function acting on a vector into a matrix, given \n", + " the space defined by psuedoscalar I\n", + " '''\n", + " A = I.basis()\n", + " B = [f(a) for a in A]\n", + " M = [float(b | a) for a in A for b in B]\n", + " return np.array(M).reshape(len(B), len(B)) " + ] + }, + { + "cell_type": "markdown", + "id": "entertaining-render", + "metadata": {}, + "source": [ + "Start with initializing a plane old euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "novel-telling", + "metadata": {}, + "outputs": [], + "source": [ + "from pylab import * \n", + "from clifford import Cl\n", + "\n", + "l,b = Cl(3) # returns (layout,blades). you can change dimesion here\n", + "I = l.pseudoScalar " + ] + }, + { + "cell_type": "markdown", + "id": "attempted-motorcycle", + "metadata": {}, + "source": [ + "## Anti-symmetric Matrix,\n", + "This is so easy,\n", + "\n", + " $$x \\rightarrow x\\cdot B$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "exposed-utility", + "metadata": {}, + "outputs": [], + "source": [ + "B = l.randomMV()(2)\n", + "f = lambda x:x|B\n", + "func2Mat(f,I=I)" + ] + }, + { + "cell_type": "markdown", + "id": "alpine-wilderness", + "metadata": {}, + "source": [ + "Whats the B? you can read its values straight off the matrix. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "lined-nursery", + "metadata": {}, + "outputs": [], + "source": [ + "B" + ] + }, + { + "cell_type": "markdown", + "id": "square-trout", + "metadata": {}, + "source": [ + "## Diagonal Matrix ( Directional Scaling)\n", + "\n", + "A bit awkward this one, but its made by projection onto each basis vector, then scaling the component by some amount. \n", + "\n", + "$$ x \\rightarrow \\sum{\\lambda_i (x\\cdot e_i) e_i} $$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "popular-waste", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "ls = range(1,len(I.basis())+1) # some dilation values (eigenvalues) \n", + "A = I.basis()\n", + "\n", + "\n", + "d = lambda x: sum([(x|a)/a*l for a,l in zip(A,ls)])\n", + "func2Mat(d,I=I)" + ] + }, + { + "cell_type": "markdown", + "id": "corporate-letter", + "metadata": {}, + "source": [ + "## Rotation\n", + "\n", + "$$ x\\rightarrow Rx\\tilde{R}$$\n", + "where \n", + "$$R=e^{B/2}$$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "demonstrated-night", + "metadata": {}, + "outputs": [], + "source": [ + "B = l.randomMV()(2)\n", + "R = e**(B/2)\n", + "r = lambda x: R*x*~R\n", + "func2Mat(r,I=I)" + ] + }, + { + "cell_type": "markdown", + "id": "graphic-inclusion", + "metadata": {}, + "source": [ + "The inverse of this, is easy , $ x\\rightarrow \\tilde{R}xR$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "established-lancaster", + "metadata": {}, + "outputs": [], + "source": [ + "rinv = lambda x: ~R*x*R # the inverse rotation \n", + "func2Mat(rinv,I=I)" + ] + }, + { + "cell_type": "markdown", + "id": "meaning-inquiry", + "metadata": {}, + "source": [ + "## Reflection \n", + "\n", + "$$ x \\rightarrow -axa^{-1} $$" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "welsh-spelling", + "metadata": {}, + "outputs": [], + "source": [ + "a = l.randomMV()(1)\n", + "n = lambda x: -a*x/a\n", + "func2Mat(n,I=I)" + ] + }, + { + "cell_type": "markdown", + "id": "other-direction", + "metadata": {}, + "source": [ + "Notice the determinant for reflection is -1, and for rotation is +1. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "inner-adams", + "metadata": {}, + "outputs": [], + "source": [ + "from numpy.linalg import det \n", + "det(func2Mat(n,I=I)), det(func2Mat(r,I=I))" + ] + }, + { + "cell_type": "markdown", + "id": "prepared-imperial", + "metadata": {}, + "source": [ + "## Symmetric \n", + "This can be built up from the functions we just defined ie Rotation\\*Dilation/Rotation\n", + "\n", + "$$ x \\rightarrow r(d(r^{-1}(x)) $$\n", + "which if you write it out, looks kind of dumb\n", + "$$ x \\rightarrow R[\\sum{\\lambda_i (\\tilde{R}x R\\cdot e_i) e_i]R} $$\n", + "\n", + "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!). \n", + "\n", + "\n", + "More generally we could include reflection in the $R$ too." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "greater-ideal", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "g = lambda x: r(d(rinv(x)))\n", + "func2Mat(g,I=I)" + ] + }, + { + "cell_type": "markdown", + "id": "comprehensive-alexandria", + "metadata": {}, + "source": [ + "## Eigen stuffs\n", + "By definition the eigen-stuff is the invariants of the transformation, sometimes this is a vector, and other times it is a plane. " + ] + }, + { + "cell_type": "markdown", + "id": "innovative-undergraduate", + "metadata": {}, + "source": [ + "### Rotation" + ] + }, + { + "cell_type": "markdown", + "id": "warming-morocco", + "metadata": {}, + "source": [ + "The eigen blades of a rotation are really the axis and plane of rotation. " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ideal-honor", + "metadata": {}, + "outputs": [], + "source": [ + "from numpy.linalg import eig\n", + "\n", + "vals, vecs = eig(func2Mat(r,I=I))\n", + "np.round(vecs,3)" + ] + }, + { + "cell_type": "markdown", + "id": "metric-vanilla", + "metadata": {}, + "source": [ + "If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "industrial-suicide", + "metadata": {}, + "outputs": [], + "source": [ + "B/(abs(B))" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "nuclear-nomination", + "metadata": {}, + "outputs": [], + "source": [ + "B" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cathedral-portugal", + "metadata": {}, + "outputs": [], + "source": [ + "vals" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "widespread-valve", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "cos(abs(2*B)), sin(abs(2*B))" + ] + }, + { + "cell_type": "markdown", + "id": "robust-container", + "metadata": {}, + "source": [ + "### Symmetric\n", + "For the symmetric matrix, the invariant thing the orthonormal frame along which the dilations take place" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "genetic-russell", + "metadata": {}, + "outputs": [], + "source": [ + "vals, vecs = eig(func2Mat(g,I=I))\n", + "np.round(vecs,5).T" + ] + }, + { + "cell_type": "markdown", + "id": "decimal-antarctica", + "metadata": {}, + "source": [ + "This is easily found by using the rotation part of the symmetric operator, " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "raised-strain", + "metadata": {}, + "outputs": [], + "source": [ + "[R*a*~R for a in I.basis()]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "arabic-africa", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.3" + }, + "toc": { + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "toc_cell": false, + "toc_position": {}, + "toc_section_display": "block", + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 23dc2e536f0f7d19384eea300da80c4c83cb07fd Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 4 Feb 2021 18:00:26 -0500 Subject: [PATCH 14/20] added matrix tutorial to docs --- docs/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.rst b/docs/index.rst index fc7942fa..78827186 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -52,6 +52,7 @@ Scalars, vectors, and higher-grade entities can be mixed freely and consistently tutorials/PerformanceCliffordTutorial tutorials/cga/index tutorials/linear-transformations + tutorials/MatrixRepresentationsOfGeometricFunctions tutorials/apollonius-cga-augmented .. toctree:: From 2595f28e5c65cd0ad579bbe4227756d2e51bd3e7 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 4 Feb 2021 18:10:40 -0500 Subject: [PATCH 15/20] fix matrix tutorial headings --- ...xRepresentationsOfGeometricFunctions.ipynb | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb index 07f3bacc..785970e5 100644 --- a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb +++ b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb @@ -2,10 +2,10 @@ "cells": [ { "cell_type": "markdown", - "id": "favorite-visiting", + "id": "recovered-conflict", "metadata": {}, "source": [ - "## Matrix Representations of Geometric Functions\n", + "# Matrix Representations of Geometric Functions\n", "More info can be found in Chapter 5 of \"New foundations for classical mechanics\", by David Hestenes.\n", "\n", "This notebook shows how some matrix groups can be represented in geometric algebra. Not as spinors in CGA, or PGA, just as functions in GA. This is done by: \n", @@ -23,7 +23,7 @@ { "cell_type": "code", "execution_count": null, - "id": "realistic-colony", + "id": "offshore-indianapolis", "metadata": {}, "outputs": [], "source": [ @@ -41,7 +41,7 @@ }, { "cell_type": "markdown", - "id": "entertaining-render", + "id": "activated-reasoning", "metadata": {}, "source": [ "Start with initializing a plane old euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard. " @@ -50,7 +50,7 @@ { "cell_type": "code", "execution_count": null, - "id": "novel-telling", + "id": "sapphire-driving", "metadata": {}, "outputs": [], "source": [ @@ -63,7 +63,7 @@ }, { "cell_type": "markdown", - "id": "attempted-motorcycle", + "id": "unauthorized-expansion", "metadata": {}, "source": [ "## Anti-symmetric Matrix,\n", @@ -75,7 +75,7 @@ { "cell_type": "code", "execution_count": null, - "id": "exposed-utility", + "id": "opening-rebecca", "metadata": {}, "outputs": [], "source": [ @@ -86,7 +86,7 @@ }, { "cell_type": "markdown", - "id": "alpine-wilderness", + "id": "serial-moisture", "metadata": {}, "source": [ "Whats the B? you can read its values straight off the matrix. " @@ -95,7 +95,7 @@ { "cell_type": "code", "execution_count": null, - "id": "lined-nursery", + "id": "contemporary-olive", "metadata": {}, "outputs": [], "source": [ @@ -104,7 +104,7 @@ }, { "cell_type": "markdown", - "id": "square-trout", + "id": "potential-cemetery", "metadata": {}, "source": [ "## Diagonal Matrix ( Directional Scaling)\n", @@ -117,7 +117,7 @@ { "cell_type": "code", "execution_count": null, - "id": "popular-waste", + "id": "powered-fluid", "metadata": {}, "outputs": [], "source": [ @@ -132,7 +132,7 @@ }, { "cell_type": "markdown", - "id": "corporate-letter", + "id": "based-invention", "metadata": {}, "source": [ "## Rotation\n", @@ -145,7 +145,7 @@ { "cell_type": "code", "execution_count": null, - "id": "demonstrated-night", + "id": "manufactured-anthony", "metadata": {}, "outputs": [], "source": [ @@ -157,7 +157,7 @@ }, { "cell_type": "markdown", - "id": "graphic-inclusion", + "id": "potential-armor", "metadata": {}, "source": [ "The inverse of this, is easy , $ x\\rightarrow \\tilde{R}xR$" @@ -166,7 +166,7 @@ { "cell_type": "code", "execution_count": null, - "id": "established-lancaster", + "id": "innovative-discovery", "metadata": {}, "outputs": [], "source": [ @@ -176,7 +176,7 @@ }, { "cell_type": "markdown", - "id": "meaning-inquiry", + "id": "varied-teaching", "metadata": {}, "source": [ "## Reflection \n", @@ -187,7 +187,7 @@ { "cell_type": "code", "execution_count": null, - "id": "welsh-spelling", + "id": "attempted-tactics", "metadata": {}, "outputs": [], "source": [ @@ -198,7 +198,7 @@ }, { "cell_type": "markdown", - "id": "other-direction", + "id": "hourly-peripheral", "metadata": {}, "source": [ "Notice the determinant for reflection is -1, and for rotation is +1. " @@ -207,7 +207,7 @@ { "cell_type": "code", "execution_count": null, - "id": "inner-adams", + "id": "therapeutic-session", "metadata": {}, "outputs": [], "source": [ @@ -217,7 +217,7 @@ }, { "cell_type": "markdown", - "id": "prepared-imperial", + "id": "noted-stevens", "metadata": {}, "source": [ "## Symmetric \n", @@ -236,7 +236,7 @@ { "cell_type": "code", "execution_count": null, - "id": "greater-ideal", + "id": "polished-channels", "metadata": {}, "outputs": [], "source": [ @@ -247,7 +247,7 @@ }, { "cell_type": "markdown", - "id": "comprehensive-alexandria", + "id": "sixth-apartment", "metadata": {}, "source": [ "## Eigen stuffs\n", @@ -256,7 +256,7 @@ }, { "cell_type": "markdown", - "id": "innovative-undergraduate", + "id": "legitimate-commodity", "metadata": {}, "source": [ "### Rotation" @@ -264,7 +264,7 @@ }, { "cell_type": "markdown", - "id": "warming-morocco", + "id": "sufficient-glasgow", "metadata": {}, "source": [ "The eigen blades of a rotation are really the axis and plane of rotation. " @@ -273,7 +273,7 @@ { "cell_type": "code", "execution_count": null, - "id": "ideal-honor", + "id": "coral-jewelry", "metadata": {}, "outputs": [], "source": [ @@ -285,7 +285,7 @@ }, { "cell_type": "markdown", - "id": "metric-vanilla", + "id": "hispanic-consent", "metadata": {}, "source": [ "If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized " @@ -294,7 +294,7 @@ { "cell_type": "code", "execution_count": null, - "id": "industrial-suicide", + "id": "given-macro", "metadata": {}, "outputs": [], "source": [ @@ -304,7 +304,7 @@ { "cell_type": "code", "execution_count": null, - "id": "nuclear-nomination", + "id": "loved-desktop", "metadata": {}, "outputs": [], "source": [ @@ -314,7 +314,7 @@ { "cell_type": "code", "execution_count": null, - "id": "cathedral-portugal", + "id": "stunning-lesbian", "metadata": {}, "outputs": [], "source": [ @@ -324,7 +324,7 @@ { "cell_type": "code", "execution_count": null, - "id": "widespread-valve", + "id": "coordinate-ticket", "metadata": {}, "outputs": [], "source": [ @@ -334,7 +334,7 @@ }, { "cell_type": "markdown", - "id": "robust-container", + "id": "annoying-afghanistan", "metadata": {}, "source": [ "### Symmetric\n", @@ -344,7 +344,7 @@ { "cell_type": "code", "execution_count": null, - "id": "genetic-russell", + "id": "julian-custom", "metadata": {}, "outputs": [], "source": [ @@ -354,7 +354,7 @@ }, { "cell_type": "markdown", - "id": "decimal-antarctica", + "id": "informal-cinema", "metadata": {}, "source": [ "This is easily found by using the rotation part of the symmetric operator, " @@ -363,7 +363,7 @@ { "cell_type": "code", "execution_count": null, - "id": "raised-strain", + "id": "efficient-hamilton", "metadata": {}, "outputs": [], "source": [ @@ -373,7 +373,7 @@ { "cell_type": "code", "execution_count": null, - "id": "arabic-africa", + "id": "level-trustee", "metadata": {}, "outputs": [], "source": [] From b36a7132f977652212faf610e057eb444feb9357 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 4 Feb 2021 18:12:42 -0500 Subject: [PATCH 16/20] updated matrix tutorial notebook --- ...xRepresentationsOfGeometricFunctions.ipynb | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb index 785970e5..529784b7 100644 --- a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb +++ b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "recovered-conflict", + "id": "desperate-hypothesis", "metadata": {}, "source": [ "# Matrix Representations of Geometric Functions\n", @@ -23,11 +23,12 @@ { "cell_type": "code", "execution_count": null, - "id": "offshore-indianapolis", + "id": "living-backing", "metadata": {}, "outputs": [], "source": [ "import numpy as np \n", + "\n", "def func2Mat(f,I):\n", " '''\n", " Convert a function acting on a vector into a matrix, given \n", @@ -41,7 +42,7 @@ }, { "cell_type": "markdown", - "id": "activated-reasoning", + "id": "dirty-registration", "metadata": {}, "source": [ "Start with initializing a plane old euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard. " @@ -50,12 +51,12 @@ { "cell_type": "code", "execution_count": null, - "id": "sapphire-driving", + "id": "chemical-boring", "metadata": {}, "outputs": [], "source": [ - "from pylab import * \n", "from clifford import Cl\n", + "from math import * \n", "\n", "l,b = Cl(3) # returns (layout,blades). you can change dimesion here\n", "I = l.pseudoScalar " @@ -63,7 +64,7 @@ }, { "cell_type": "markdown", - "id": "unauthorized-expansion", + "id": "dated-lover", "metadata": {}, "source": [ "## Anti-symmetric Matrix,\n", @@ -75,7 +76,7 @@ { "cell_type": "code", "execution_count": null, - "id": "opening-rebecca", + "id": "objective-rachel", "metadata": {}, "outputs": [], "source": [ @@ -86,7 +87,7 @@ }, { "cell_type": "markdown", - "id": "serial-moisture", + "id": "tutorial-maryland", "metadata": {}, "source": [ "Whats the B? you can read its values straight off the matrix. " @@ -95,7 +96,7 @@ { "cell_type": "code", "execution_count": null, - "id": "contemporary-olive", + "id": "overhead-debut", "metadata": {}, "outputs": [], "source": [ @@ -104,7 +105,7 @@ }, { "cell_type": "markdown", - "id": "potential-cemetery", + "id": "viral-madrid", "metadata": {}, "source": [ "## Diagonal Matrix ( Directional Scaling)\n", @@ -117,7 +118,7 @@ { "cell_type": "code", "execution_count": null, - "id": "powered-fluid", + "id": "intended-norwegian", "metadata": {}, "outputs": [], "source": [ @@ -132,7 +133,7 @@ }, { "cell_type": "markdown", - "id": "based-invention", + "id": "egyptian-society", "metadata": {}, "source": [ "## Rotation\n", @@ -145,7 +146,7 @@ { "cell_type": "code", "execution_count": null, - "id": "manufactured-anthony", + "id": "expired-roberts", "metadata": {}, "outputs": [], "source": [ @@ -157,7 +158,7 @@ }, { "cell_type": "markdown", - "id": "potential-armor", + "id": "checked-circulation", "metadata": {}, "source": [ "The inverse of this, is easy , $ x\\rightarrow \\tilde{R}xR$" @@ -166,7 +167,7 @@ { "cell_type": "code", "execution_count": null, - "id": "innovative-discovery", + "id": "annoying-offset", "metadata": {}, "outputs": [], "source": [ @@ -176,7 +177,7 @@ }, { "cell_type": "markdown", - "id": "varied-teaching", + "id": "friendly-shame", "metadata": {}, "source": [ "## Reflection \n", @@ -187,7 +188,7 @@ { "cell_type": "code", "execution_count": null, - "id": "attempted-tactics", + "id": "breeding-release", "metadata": {}, "outputs": [], "source": [ @@ -198,7 +199,7 @@ }, { "cell_type": "markdown", - "id": "hourly-peripheral", + "id": "peaceful-deadline", "metadata": {}, "source": [ "Notice the determinant for reflection is -1, and for rotation is +1. " @@ -207,7 +208,7 @@ { "cell_type": "code", "execution_count": null, - "id": "therapeutic-session", + "id": "banned-harmony", "metadata": {}, "outputs": [], "source": [ @@ -217,7 +218,7 @@ }, { "cell_type": "markdown", - "id": "noted-stevens", + "id": "burning-receptor", "metadata": {}, "source": [ "## Symmetric \n", @@ -236,7 +237,7 @@ { "cell_type": "code", "execution_count": null, - "id": "polished-channels", + "id": "competent-grenada", "metadata": {}, "outputs": [], "source": [ @@ -247,7 +248,7 @@ }, { "cell_type": "markdown", - "id": "sixth-apartment", + "id": "theoretical-africa", "metadata": {}, "source": [ "## Eigen stuffs\n", @@ -256,7 +257,7 @@ }, { "cell_type": "markdown", - "id": "legitimate-commodity", + "id": "victorian-philippines", "metadata": {}, "source": [ "### Rotation" @@ -264,7 +265,7 @@ }, { "cell_type": "markdown", - "id": "sufficient-glasgow", + "id": "baking-laser", "metadata": {}, "source": [ "The eigen blades of a rotation are really the axis and plane of rotation. " @@ -273,7 +274,7 @@ { "cell_type": "code", "execution_count": null, - "id": "coral-jewelry", + "id": "flexible-fellowship", "metadata": {}, "outputs": [], "source": [ @@ -285,7 +286,7 @@ }, { "cell_type": "markdown", - "id": "hispanic-consent", + "id": "bizarre-empire", "metadata": {}, "source": [ "If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized " @@ -294,7 +295,7 @@ { "cell_type": "code", "execution_count": null, - "id": "given-macro", + "id": "employed-multiple", "metadata": {}, "outputs": [], "source": [ @@ -304,7 +305,7 @@ { "cell_type": "code", "execution_count": null, - "id": "loved-desktop", + "id": "honest-samuel", "metadata": {}, "outputs": [], "source": [ @@ -314,7 +315,7 @@ { "cell_type": "code", "execution_count": null, - "id": "stunning-lesbian", + "id": "returning-lyric", "metadata": {}, "outputs": [], "source": [ @@ -324,7 +325,7 @@ { "cell_type": "code", "execution_count": null, - "id": "coordinate-ticket", + "id": "strong-property", "metadata": {}, "outputs": [], "source": [ @@ -334,7 +335,7 @@ }, { "cell_type": "markdown", - "id": "annoying-afghanistan", + "id": "complete-workshop", "metadata": {}, "source": [ "### Symmetric\n", @@ -344,7 +345,7 @@ { "cell_type": "code", "execution_count": null, - "id": "julian-custom", + "id": "fossil-habitat", "metadata": {}, "outputs": [], "source": [ @@ -354,7 +355,7 @@ }, { "cell_type": "markdown", - "id": "informal-cinema", + "id": "suitable-reflection", "metadata": {}, "source": [ "This is easily found by using the rotation part of the symmetric operator, " @@ -363,7 +364,7 @@ { "cell_type": "code", "execution_count": null, - "id": "efficient-hamilton", + "id": "close-spell", "metadata": {}, "outputs": [], "source": [ @@ -373,7 +374,7 @@ { "cell_type": "code", "execution_count": null, - "id": "level-trustee", + "id": "pretty-liberal", "metadata": {}, "outputs": [], "source": [] From 0c05eeb7e34aa04bb242a431b31a1480786e1bdb Mon Sep 17 00:00:00 2001 From: arsenovic Date: Fri, 5 Feb 2021 08:31:04 -0500 Subject: [PATCH 17/20] added randomIntMV and some docs --- clifford/__init__.py | 39 ++++++++ clifford/_layout.py | 9 ++ clifford/tools/__init__.py | 2 +- docs/conf.py | 2 +- ...xRepresentationsOfGeometricFunctions.ipynb | 93 ++++++++++--------- 5 files changed, 100 insertions(+), 45 deletions(-) diff --git a/clifford/__init__.py b/clifford/__init__.py index 7b70365d..4469c497 100644 --- a/clifford/__init__.py +++ b/clifford/__init__.py @@ -70,6 +70,8 @@ grade_obj randomMV + randomIntMV + randomV """ @@ -395,11 +397,33 @@ def randomMV( Coefficients are between min and max, and if grades is a list of integers, only those grades will be non-zero. + Parameters + ------------ + layout : Layout + the layout + min : Number + minimum of random range + max : Number + maximum + grades : int, list + number or list of grades to generate + mvClass : class + the class of MultiVector + uniform : function + a function like np.randome.uniform , but not limited to + n : int + number of samples to generate + normed : bool + should results be normalized? + Examples -------- >>> randomMV(layout, min=-2.0, max=2.0, grades=None, uniform=None, n=2) # doctest: +SKIP + See Also + ---------- + randomIntMV """ if n > 1: @@ -427,6 +451,21 @@ def randomMV( return mv +def randomIntMV(layout, min=-10, max=10, uniform=np.random.randint, + *args,**kw): + ''' + Random MultiVectors with given layout. + + Coefficients are between min and max, and if grades is a list of integers, + only those grades will be non-zero. + + This is just a wrapper of `randomMV`, which sets `uniform` and `min`/`max` + + See Also + ---------- + randomMV + ''' + return randomMV(layout=layout, min=min,max=max,uniform=uniform, *args, **kw) def conformalize(layout, added_sig=[1, -1], *, mvClass=MultiVector, **kwargs): ''' diff --git a/clifford/_layout.py b/clifford/_layout.py index b1a89668..d31432f4 100644 --- a/clifford/_layout.py +++ b/clifford/_layout.py @@ -748,6 +748,15 @@ def randomMV(self, n=1, **kwargs) -> MultiVector: ''' return cf.randomMV(layout=self, n=n, **kwargs) + def randomIntMV(self, n=1, **kwargs) -> MultiVector: + ''' + Convenience method to create a random multivector wiht integer + values for components. Nice for reading, + + see `clifford.randomIntMV` for details + ''' + return cf.randomIntMV(layout=self, n=n, **kwargs) + def randomV(self, n=1, **kwargs) -> MultiVector: ''' generate n random 1-vector s diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index 88795368..944cb1ff 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -203,7 +203,7 @@ def frame2Mat(B, A=None, I=None, is_complex=None): A : None, list of vectors a list of vectors in their initial state. if none we assume orthonormal basis given by B.pseudoScalar, or I - I : Multivector, None + I : MultiVector, None pseudoscalar of the space. if None, we use B.pseudoScalar is_complex: Bool do you want a complex matrix? diff --git a/docs/conf.py b/docs/conf.py index bbf98149..53323385 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ 'IPython.sphinxext.ipython_console_highlighting', #'numpydoc', 'sphinx.ext.viewcode', - 'sphinxcontrib.bibtex', + #'sphinxcontrib.bibtex', #'sphinx.ext.mathjax', ] diff --git a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb index 529784b7..8716753c 100644 --- a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb +++ b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb @@ -2,16 +2,18 @@ "cells": [ { "cell_type": "markdown", - "id": "desperate-hypothesis", + "id": "silver-indian", "metadata": {}, "source": [ "# Matrix Representations of Geometric Functions\n", "More info can be found in Chapter 5 of \"New foundations for classical mechanics\", by David Hestenes.\n", "\n", - "This notebook shows how some matrix groups can be represented in geometric algebra. Not as spinors in CGA, or PGA, just as functions in GA. This is done by: \n", - "1. Creating a geometric function\n", - "2. Apply it to an orthonormal frame\n", - "3. Convert the resultant frame into a matrix \n", + "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. \n", + "This is done by: \n", + "\n", + " 1. Creating a geometric function\n", + " 2. Apply it to an orthonormal frame\n", + " 3. Convert the resultant frame into a matrix \n", "\n", "The matrix is defined as the inner product of each basis element of original and transformed frame. \n", "\n", @@ -23,7 +25,7 @@ { "cell_type": "code", "execution_count": null, - "id": "living-backing", + "id": "ongoing-louisiana", "metadata": {}, "outputs": [], "source": [ @@ -42,16 +44,16 @@ }, { "cell_type": "markdown", - "id": "dirty-registration", + "id": "current-disclosure", "metadata": {}, "source": [ - "Start with initializing a plane old euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard. " + "Start with initializing a euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard. " ] }, { "cell_type": "code", "execution_count": null, - "id": "chemical-boring", + "id": "demanding-sunday", "metadata": {}, "outputs": [], "source": [ @@ -64,7 +66,7 @@ }, { "cell_type": "markdown", - "id": "dated-lover", + "id": "australian-audience", "metadata": {}, "source": [ "## Anti-symmetric Matrix,\n", @@ -76,18 +78,18 @@ { "cell_type": "code", "execution_count": null, - "id": "objective-rachel", + "id": "entertaining-middle", "metadata": {}, "outputs": [], "source": [ - "B = l.randomMV()(2)\n", + "B = l.randomIntMV()(2)\n", "f = lambda x:x|B\n", "func2Mat(f,I=I)" ] }, { "cell_type": "markdown", - "id": "tutorial-maryland", + "id": "improved-booking", "metadata": {}, "source": [ "Whats the B? you can read its values straight off the matrix. " @@ -96,7 +98,7 @@ { "cell_type": "code", "execution_count": null, - "id": "overhead-debut", + "id": "elect-salem", "metadata": {}, "outputs": [], "source": [ @@ -105,7 +107,7 @@ }, { "cell_type": "markdown", - "id": "viral-madrid", + "id": "complicated-funds", "metadata": {}, "source": [ "## Diagonal Matrix ( Directional Scaling)\n", @@ -118,7 +120,7 @@ { "cell_type": "code", "execution_count": null, - "id": "intended-norwegian", + "id": "warming-stable", "metadata": {}, "outputs": [], "source": [ @@ -133,20 +135,22 @@ }, { "cell_type": "markdown", - "id": "egyptian-society", + "id": "premium-indonesia", "metadata": {}, "source": [ "## Rotation\n", "\n", "$$ x\\rightarrow Rx\\tilde{R}$$\n", + "\n", "where \n", + "\n", "$$R=e^{B/2}$$" ] }, { "cell_type": "code", "execution_count": null, - "id": "expired-roberts", + "id": "essential-essay", "metadata": {}, "outputs": [], "source": [ @@ -158,16 +162,17 @@ }, { "cell_type": "markdown", - "id": "checked-circulation", + "id": "linear-belle", "metadata": {}, "source": [ - "The inverse of this, is easy , $ x\\rightarrow \\tilde{R}xR$" + "The inverse of this is , \n", + "$$ x\\rightarrow \\tilde{R}xR $$" ] }, { "cell_type": "code", "execution_count": null, - "id": "annoying-offset", + "id": "vulnerable-textbook", "metadata": {}, "outputs": [], "source": [ @@ -177,7 +182,7 @@ }, { "cell_type": "markdown", - "id": "friendly-shame", + "id": "adaptive-cocktail", "metadata": {}, "source": [ "## Reflection \n", @@ -188,7 +193,7 @@ { "cell_type": "code", "execution_count": null, - "id": "breeding-release", + "id": "smooth-green", "metadata": {}, "outputs": [], "source": [ @@ -199,7 +204,7 @@ }, { "cell_type": "markdown", - "id": "peaceful-deadline", + "id": "alternative-assignment", "metadata": {}, "source": [ "Notice the determinant for reflection is -1, and for rotation is +1. " @@ -208,7 +213,7 @@ { "cell_type": "code", "execution_count": null, - "id": "banned-harmony", + "id": "developmental-rebound", "metadata": {}, "outputs": [], "source": [ @@ -218,14 +223,16 @@ }, { "cell_type": "markdown", - "id": "burning-receptor", + "id": "dutch-berkeley", "metadata": {}, "source": [ "## Symmetric \n", "This can be built up from the functions we just defined ie Rotation\\*Dilation/Rotation\n", "\n", "$$ x \\rightarrow r(d(r^{-1}(x)) $$\n", + "\n", "which if you write it out, looks kind of dumb\n", + "\n", "$$ x \\rightarrow R[\\sum{\\lambda_i (\\tilde{R}x R\\cdot e_i) e_i]R} $$\n", "\n", "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!). \n", @@ -237,7 +244,7 @@ { "cell_type": "code", "execution_count": null, - "id": "competent-grenada", + "id": "economic-walnut", "metadata": {}, "outputs": [], "source": [ @@ -248,7 +255,7 @@ }, { "cell_type": "markdown", - "id": "theoretical-africa", + "id": "helpful-mirror", "metadata": {}, "source": [ "## Eigen stuffs\n", @@ -257,7 +264,7 @@ }, { "cell_type": "markdown", - "id": "victorian-philippines", + "id": "romance-orlando", "metadata": {}, "source": [ "### Rotation" @@ -265,7 +272,7 @@ }, { "cell_type": "markdown", - "id": "baking-laser", + "id": "mature-labor", "metadata": {}, "source": [ "The eigen blades of a rotation are really the axis and plane of rotation. " @@ -274,7 +281,7 @@ { "cell_type": "code", "execution_count": null, - "id": "flexible-fellowship", + "id": "exclusive-evans", "metadata": {}, "outputs": [], "source": [ @@ -286,7 +293,7 @@ }, { "cell_type": "markdown", - "id": "bizarre-empire", + "id": "ceramic-pursuit", "metadata": {}, "source": [ "If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized " @@ -295,7 +302,7 @@ { "cell_type": "code", "execution_count": null, - "id": "employed-multiple", + "id": "turkish-catch", "metadata": {}, "outputs": [], "source": [ @@ -305,7 +312,7 @@ { "cell_type": "code", "execution_count": null, - "id": "honest-samuel", + "id": "latest-bridges", "metadata": {}, "outputs": [], "source": [ @@ -315,7 +322,7 @@ { "cell_type": "code", "execution_count": null, - "id": "returning-lyric", + "id": "prerequisite-latest", "metadata": {}, "outputs": [], "source": [ @@ -325,17 +332,17 @@ { "cell_type": "code", "execution_count": null, - "id": "strong-property", + "id": "excited-entity", "metadata": {}, "outputs": [], "source": [ "\n", - "cos(abs(2*B)), sin(abs(2*B))" + "cos(abs(B)), sin(abs(B))" ] }, { "cell_type": "markdown", - "id": "complete-workshop", + "id": "meaning-witness", "metadata": {}, "source": [ "### Symmetric\n", @@ -345,7 +352,7 @@ { "cell_type": "code", "execution_count": null, - "id": "fossil-habitat", + "id": "contemporary-works", "metadata": {}, "outputs": [], "source": [ @@ -355,7 +362,7 @@ }, { "cell_type": "markdown", - "id": "suitable-reflection", + "id": "perceived-weather", "metadata": {}, "source": [ "This is easily found by using the rotation part of the symmetric operator, " @@ -364,7 +371,7 @@ { "cell_type": "code", "execution_count": null, - "id": "close-spell", + "id": "fourth-translator", "metadata": {}, "outputs": [], "source": [ @@ -374,7 +381,7 @@ { "cell_type": "code", "execution_count": null, - "id": "pretty-liberal", + "id": "authentic-bidder", "metadata": {}, "outputs": [], "source": [] @@ -396,7 +403,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.3" + "version": "3.7.9" }, "toc": { "nav_menu": {}, From 045b006252c785d0462d667a51fdfd214b86bae3 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Fri, 5 Feb 2021 08:55:27 -0500 Subject: [PATCH 18/20] more docs --- ...xRepresentationsOfGeometricFunctions.ipynb | 143 +++++++++++++----- 1 file changed, 101 insertions(+), 42 deletions(-) diff --git a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb index 8716753c..da791cb4 100644 --- a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb +++ b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "silver-indian", + "id": "naughty-entrance", "metadata": {}, "source": [ "# Matrix Representations of Geometric Functions\n", @@ -19,13 +19,13 @@ "\n", "$$M_{ij} = a_i\\cdot b_j = a_i\\cdot f(a)_j $$ \n", "\n", - "(or vice-versa with the i,j, you get the point). Since we are going to do this repeatedly, define a `func2Mat()" + "(or vice-versa with the i,j, you get the point). Since we are going to do this repeatedly, define a `func2Mat()`" ] }, { "cell_type": "code", "execution_count": null, - "id": "ongoing-louisiana", + "id": "mysterious-davis", "metadata": {}, "outputs": [], "source": [ @@ -44,7 +44,7 @@ }, { "cell_type": "markdown", - "id": "current-disclosure", + "id": "adapted-southeast", "metadata": {}, "source": [ "Start with initializing a euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard. " @@ -53,7 +53,7 @@ { "cell_type": "code", "execution_count": null, - "id": "demanding-sunday", + "id": "bacterial-migration", "metadata": {}, "outputs": [], "source": [ @@ -66,10 +66,10 @@ }, { "cell_type": "markdown", - "id": "australian-audience", + "id": "distinct-punch", "metadata": {}, "source": [ - "## Anti-symmetric Matrix,\n", + "## Anti-symmetric\n", "This is so easy,\n", "\n", " $$x \\rightarrow x\\cdot B$$" @@ -78,18 +78,18 @@ { "cell_type": "code", "execution_count": null, - "id": "entertaining-middle", + "id": "adjacent-diploma", "metadata": {}, "outputs": [], "source": [ - "B = l.randomIntMV()(2)\n", + "B = l.randomIntMV()(2) # we use randIntMV because its easier to read\n", "f = lambda x:x|B\n", "func2Mat(f,I=I)" ] }, { "cell_type": "markdown", - "id": "improved-booking", + "id": "weird-nutrition", "metadata": {}, "source": [ "Whats the B? you can read its values straight off the matrix. " @@ -98,7 +98,7 @@ { "cell_type": "code", "execution_count": null, - "id": "elect-salem", + "id": "literary-bench", "metadata": {}, "outputs": [], "source": [ @@ -107,10 +107,10 @@ }, { "cell_type": "markdown", - "id": "complicated-funds", + "id": "unsigned-consciousness", "metadata": {}, "source": [ - "## Diagonal Matrix ( Directional Scaling)\n", + "## Diagonal ( Directional Scaling)\n", "\n", "A bit awkward this one, but its made by projection onto each basis vector, then scaling the component by some amount. \n", "\n", @@ -120,7 +120,7 @@ { "cell_type": "code", "execution_count": null, - "id": "warming-stable", + "id": "numerical-doctor", "metadata": {}, "outputs": [], "source": [ @@ -135,10 +135,10 @@ }, { "cell_type": "markdown", - "id": "premium-indonesia", + "id": "second-commitment", "metadata": {}, "source": [ - "## Rotation\n", + "## Orthgonal, Rotation\n", "\n", "$$ x\\rightarrow Rx\\tilde{R}$$\n", "\n", @@ -150,7 +150,7 @@ { "cell_type": "code", "execution_count": null, - "id": "essential-essay", + "id": "active-joyce", "metadata": {}, "outputs": [], "source": [ @@ -162,7 +162,7 @@ }, { "cell_type": "markdown", - "id": "linear-belle", + "id": "sensitive-heaven", "metadata": {}, "source": [ "The inverse of this is , \n", @@ -172,7 +172,7 @@ { "cell_type": "code", "execution_count": null, - "id": "vulnerable-textbook", + "id": "postal-rouge", "metadata": {}, "outputs": [], "source": [ @@ -182,10 +182,10 @@ }, { "cell_type": "markdown", - "id": "adaptive-cocktail", + "id": "critical-tyler", "metadata": {}, "source": [ - "## Reflection \n", + "## Orthogonal, Reflection \n", "\n", "$$ x \\rightarrow -axa^{-1} $$" ] @@ -193,18 +193,28 @@ { "cell_type": "code", "execution_count": null, - "id": "smooth-green", + "id": "insured-lending", "metadata": {}, "outputs": [], "source": [ - "a = l.randomMV()(1)\n", + "a = l.randomIntMV()(1)\n", "n = lambda x: -a*x/a\n", "func2Mat(n,I=I)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "cooperative-science", + "metadata": {}, + "outputs": [], + "source": [ + "a" + ] + }, { "cell_type": "markdown", - "id": "alternative-assignment", + "id": "velvet-macintosh", "metadata": {}, "source": [ "Notice the determinant for reflection is -1, and for rotation is +1. " @@ -213,7 +223,7 @@ { "cell_type": "code", "execution_count": null, - "id": "developmental-rebound", + "id": "careful-regard", "metadata": {}, "outputs": [], "source": [ @@ -223,7 +233,7 @@ }, { "cell_type": "markdown", - "id": "dutch-berkeley", + "id": "configured-elder", "metadata": {}, "source": [ "## Symmetric \n", @@ -244,7 +254,7 @@ { "cell_type": "code", "execution_count": null, - "id": "economic-walnut", + "id": "realistic-greene", "metadata": {}, "outputs": [], "source": [ @@ -255,7 +265,7 @@ }, { "cell_type": "markdown", - "id": "helpful-mirror", + "id": "challenging-examination", "metadata": {}, "source": [ "## Eigen stuffs\n", @@ -264,7 +274,7 @@ }, { "cell_type": "markdown", - "id": "romance-orlando", + "id": "impossible-daughter", "metadata": {}, "source": [ "### Rotation" @@ -272,7 +282,7 @@ }, { "cell_type": "markdown", - "id": "mature-labor", + "id": "cultural-parks", "metadata": {}, "source": [ "The eigen blades of a rotation are really the axis and plane of rotation. " @@ -281,7 +291,7 @@ { "cell_type": "code", "execution_count": null, - "id": "exclusive-evans", + "id": "deadly-supervisor", "metadata": {}, "outputs": [], "source": [ @@ -293,7 +303,7 @@ }, { "cell_type": "markdown", - "id": "ceramic-pursuit", + "id": "amino-mouse", "metadata": {}, "source": [ "If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized " @@ -302,7 +312,7 @@ { "cell_type": "code", "execution_count": null, - "id": "turkish-catch", + "id": "other-ready", "metadata": {}, "outputs": [], "source": [ @@ -312,7 +322,7 @@ { "cell_type": "code", "execution_count": null, - "id": "latest-bridges", + "id": "advisory-dallas", "metadata": {}, "outputs": [], "source": [ @@ -322,7 +332,7 @@ { "cell_type": "code", "execution_count": null, - "id": "prerequisite-latest", + "id": "annual-nursery", "metadata": {}, "outputs": [], "source": [ @@ -332,7 +342,7 @@ { "cell_type": "code", "execution_count": null, - "id": "excited-entity", + "id": "stone-henry", "metadata": {}, "outputs": [], "source": [ @@ -342,7 +352,7 @@ }, { "cell_type": "markdown", - "id": "meaning-witness", + "id": "identified-liberty", "metadata": {}, "source": [ "### Symmetric\n", @@ -352,7 +362,7 @@ { "cell_type": "code", "execution_count": null, - "id": "contemporary-works", + "id": "miniature-label", "metadata": {}, "outputs": [], "source": [ @@ -362,7 +372,7 @@ }, { "cell_type": "markdown", - "id": "perceived-weather", + "id": "mental-anger", "metadata": {}, "source": [ "This is easily found by using the rotation part of the symmetric operator, " @@ -371,20 +381,69 @@ { "cell_type": "code", "execution_count": null, - "id": "fourth-translator", + "id": "coupled-cache", "metadata": {}, "outputs": [], "source": [ "[R*a*~R for a in I.basis()]" ] }, + { + "cell_type": "markdown", + "id": "least-square", + "metadata": {}, + "source": [ + "## Primitive Visualization in 2D" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "authentic-bidder", + "id": "silent-exploration", "metadata": {}, "outputs": [], - "source": [] + "source": [ + "from pylab import linspace, plot,axis,legend\n", + "\n", + "def plot_ps(ps,**kw):\n", + " x = [p[e1] for p in ps]\n", + " y = [p[e2] for p in ps]\n", + " plot(x,y, marker='o',ls='',**kw)\n", + "\n", + " \n", + "l,b = Cl(2)\n", + "locals().update(b)\n", + "I = l.pseudoScalar\n", + "\n", + "## define function of interest \n", + "B = l.randomMV()(2)\n", + "R = e**(B/2)\n", + "f = lambda x: R*x*~R\n", + "\n", + "## loop though cartesian grid and apply f, \n", + "ps,qs=[],[]\n", + "for x in linspace(-1,1,11):\n", + " for y in linspace(-1,1,11):\n", + " p = x*e1 + y*e2\n", + " q = f(p)\n", + " ps.append(p)\n", + " qs.append(q)\n", + "\n", + "plot_ps(ps,label='before')\n", + "plot_ps(qs,label='after')\n", + "axis('equal')\n", + "legend()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "convinced-admission", + "metadata": {}, + "outputs": [], + "source": [ + "func2Mat(f,I =I )" + ] } ], "metadata": { From dbe768cd254d93e35258baf185e95509d856f431 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Sun, 7 Feb 2021 10:45:08 -0500 Subject: [PATCH 19/20] add randomMV and randomV to Multivector --- clifford/_multivector.py | 32 ++++++++++++++++++++++++++++++++ docs/conf.py | 2 +- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/clifford/_multivector.py b/clifford/_multivector.py index 1790e059..7f1014bc 100644 --- a/clifford/_multivector.py +++ b/clifford/_multivector.py @@ -839,6 +839,38 @@ def conjugate(self) -> 'MultiVector': """ return (~self).gradeInvol() + def randomMV(self,*args, **kw): + ''' + create a random MV within space of self + + This is a wrapper for `randomMV` + + See Also + --------- + Layout.randomMV + randomMV + ''' + if not self.isBlade(): + raise ValueError('I must be a blade') + return cf.randomMV(layout=self.layout, *args, **kw)(self) + + def randomV(self,*args, **kw): + ''' + create a random MV within space of self + + This is a wrapper for `Layout.randomMV` + + See Also + --------- + Layout.randomMV + ''' + if not self.isBlade(): + raise ValueError('I must be a blade') + kw.update(grades=[1]) + return cf.randomMV(layout=self.layout, *args, **kw)(self) + + + # Subspace operations def project(self, other) -> 'MultiVector': r"""Projects the multivector onto the subspace represented by this blade. diff --git a/docs/conf.py b/docs/conf.py index 53323385..bbf98149 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -53,7 +53,7 @@ 'IPython.sphinxext.ipython_console_highlighting', #'numpydoc', 'sphinx.ext.viewcode', - #'sphinxcontrib.bibtex', + 'sphinxcontrib.bibtex', #'sphinx.ext.mathjax', ] From 248d26e198fbf6198ea14be8bcbc90cf2f7e70b6 Mon Sep 17 00:00:00 2001 From: arsenovic Date: Thu, 11 Mar 2021 09:45:58 -0500 Subject: [PATCH 20/20] add log import to tools --- clifford/tools/__init__.py | 2 +- ...xRepresentationsOfGeometricFunctions.ipynb | 76 +++++++++---------- 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/clifford/tools/__init__.py b/clifford/tools/__init__.py index 944cb1ff..cfaeb993 100644 --- a/clifford/tools/__init__.py +++ b/clifford/tools/__init__.py @@ -56,7 +56,7 @@ from typing import Union, Optional, List, Tuple from math import sqrt -from numpy import eye, array, sign, zeros, sin, arccos +from numpy import eye, array, sign, zeros, sin, arccos,log import numpy as np from .. import Cl, gp, Frame, MultiVector from .. import eps as global_eps diff --git a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb index da791cb4..cc35ac68 100644 --- a/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb +++ b/docs/tutorials/MatrixRepresentationsOfGeometricFunctions.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "markdown", - "id": "naughty-entrance", + "id": "valuable-satin", "metadata": {}, "source": [ "# Matrix Representations of Geometric Functions\n", @@ -25,7 +25,7 @@ { "cell_type": "code", "execution_count": null, - "id": "mysterious-davis", + "id": "exterior-cutting", "metadata": {}, "outputs": [], "source": [ @@ -44,7 +44,7 @@ }, { "cell_type": "markdown", - "id": "adapted-southeast", + "id": "green-medicare", "metadata": {}, "source": [ "Start with initializing a euclidean N-dimensional algebra and assign our pseudoscalar to $I$, pretty standard. " @@ -53,7 +53,7 @@ { "cell_type": "code", "execution_count": null, - "id": "bacterial-migration", + "id": "absent-executive", "metadata": {}, "outputs": [], "source": [ @@ -66,7 +66,7 @@ }, { "cell_type": "markdown", - "id": "distinct-punch", + "id": "opposite-footage", "metadata": {}, "source": [ "## Anti-symmetric\n", @@ -78,7 +78,7 @@ { "cell_type": "code", "execution_count": null, - "id": "adjacent-diploma", + "id": "seeing-miniature", "metadata": {}, "outputs": [], "source": [ @@ -89,7 +89,7 @@ }, { "cell_type": "markdown", - "id": "weird-nutrition", + "id": "bridal-coordinator", "metadata": {}, "source": [ "Whats the B? you can read its values straight off the matrix. " @@ -98,7 +98,7 @@ { "cell_type": "code", "execution_count": null, - "id": "literary-bench", + "id": "healthy-sacramento", "metadata": {}, "outputs": [], "source": [ @@ -107,7 +107,7 @@ }, { "cell_type": "markdown", - "id": "unsigned-consciousness", + "id": "animal-conviction", "metadata": {}, "source": [ "## Diagonal ( Directional Scaling)\n", @@ -120,7 +120,7 @@ { "cell_type": "code", "execution_count": null, - "id": "numerical-doctor", + "id": "internal-desperate", "metadata": {}, "outputs": [], "source": [ @@ -135,7 +135,7 @@ }, { "cell_type": "markdown", - "id": "second-commitment", + "id": "electrical-community", "metadata": {}, "source": [ "## Orthgonal, Rotation\n", @@ -150,7 +150,7 @@ { "cell_type": "code", "execution_count": null, - "id": "active-joyce", + "id": "quarterly-arnold", "metadata": {}, "outputs": [], "source": [ @@ -162,7 +162,7 @@ }, { "cell_type": "markdown", - "id": "sensitive-heaven", + "id": "authorized-tuesday", "metadata": {}, "source": [ "The inverse of this is , \n", @@ -172,7 +172,7 @@ { "cell_type": "code", "execution_count": null, - "id": "postal-rouge", + "id": "single-lodging", "metadata": {}, "outputs": [], "source": [ @@ -182,7 +182,7 @@ }, { "cell_type": "markdown", - "id": "critical-tyler", + "id": "danish-focus", "metadata": {}, "source": [ "## Orthogonal, Reflection \n", @@ -193,7 +193,7 @@ { "cell_type": "code", "execution_count": null, - "id": "insured-lending", + "id": "atmospheric-lender", "metadata": {}, "outputs": [], "source": [ @@ -205,7 +205,7 @@ { "cell_type": "code", "execution_count": null, - "id": "cooperative-science", + "id": "focal-velvet", "metadata": {}, "outputs": [], "source": [ @@ -214,7 +214,7 @@ }, { "cell_type": "markdown", - "id": "velvet-macintosh", + "id": "premier-cooking", "metadata": {}, "source": [ "Notice the determinant for reflection is -1, and for rotation is +1. " @@ -223,7 +223,7 @@ { "cell_type": "code", "execution_count": null, - "id": "careful-regard", + "id": "proper-startup", "metadata": {}, "outputs": [], "source": [ @@ -233,7 +233,7 @@ }, { "cell_type": "markdown", - "id": "configured-elder", + "id": "powered-intelligence", "metadata": {}, "source": [ "## Symmetric \n", @@ -254,7 +254,7 @@ { "cell_type": "code", "execution_count": null, - "id": "realistic-greene", + "id": "postal-crazy", "metadata": {}, "outputs": [], "source": [ @@ -265,7 +265,7 @@ }, { "cell_type": "markdown", - "id": "challenging-examination", + "id": "friendly-rebel", "metadata": {}, "source": [ "## Eigen stuffs\n", @@ -274,7 +274,7 @@ }, { "cell_type": "markdown", - "id": "impossible-daughter", + "id": "integrated-front", "metadata": {}, "source": [ "### Rotation" @@ -282,7 +282,7 @@ }, { "cell_type": "markdown", - "id": "cultural-parks", + "id": "hearing-principle", "metadata": {}, "source": [ "The eigen blades of a rotation are really the axis and plane of rotation. " @@ -291,7 +291,7 @@ { "cell_type": "code", "execution_count": null, - "id": "deadly-supervisor", + "id": "entitled-clearing", "metadata": {}, "outputs": [], "source": [ @@ -303,7 +303,7 @@ }, { "cell_type": "markdown", - "id": "amino-mouse", + "id": "independent-terminology", "metadata": {}, "source": [ "If you checkout the real column, and compare this to the bivector which generated this rotation (aka the generator), after its been normalized " @@ -312,7 +312,7 @@ { "cell_type": "code", "execution_count": null, - "id": "other-ready", + "id": "insured-karma", "metadata": {}, "outputs": [], "source": [ @@ -322,7 +322,7 @@ { "cell_type": "code", "execution_count": null, - "id": "advisory-dallas", + "id": "median-planet", "metadata": {}, "outputs": [], "source": [ @@ -332,7 +332,7 @@ { "cell_type": "code", "execution_count": null, - "id": "annual-nursery", + "id": "resident-detroit", "metadata": {}, "outputs": [], "source": [ @@ -342,7 +342,7 @@ { "cell_type": "code", "execution_count": null, - "id": "stone-henry", + "id": "frank-alexandria", "metadata": {}, "outputs": [], "source": [ @@ -352,7 +352,7 @@ }, { "cell_type": "markdown", - "id": "identified-liberty", + "id": "spare-fleece", "metadata": {}, "source": [ "### Symmetric\n", @@ -362,7 +362,7 @@ { "cell_type": "code", "execution_count": null, - "id": "miniature-label", + "id": "turkish-brooklyn", "metadata": {}, "outputs": [], "source": [ @@ -372,7 +372,7 @@ }, { "cell_type": "markdown", - "id": "mental-anger", + "id": "rolled-hebrew", "metadata": {}, "source": [ "This is easily found by using the rotation part of the symmetric operator, " @@ -381,7 +381,7 @@ { "cell_type": "code", "execution_count": null, - "id": "coupled-cache", + "id": "atomic-controversy", "metadata": {}, "outputs": [], "source": [ @@ -390,7 +390,7 @@ }, { "cell_type": "markdown", - "id": "least-square", + "id": "variable-honor", "metadata": {}, "source": [ "## Primitive Visualization in 2D" @@ -399,7 +399,7 @@ { "cell_type": "code", "execution_count": null, - "id": "silent-exploration", + "id": "fatal-french", "metadata": {}, "outputs": [], "source": [ @@ -438,7 +438,7 @@ { "cell_type": "code", "execution_count": null, - "id": "convinced-admission", + "id": "specific-myrtle", "metadata": {}, "outputs": [], "source": [ @@ -462,7 +462,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.9" + "version": "3.7.7" }, "toc": { "nav_menu": {},