#!/usr/bin/env python # coding: utf-8 # # Vector calculus with SageMath # # ## 3. Using cylindrical coordinates # # This notebook illustrates operations on vector fields on Euclidean spaces, as introduced in Trac ticket [#24623](https://trac.sagemath.org/ticket/24623). # In[1]: version() # In[2]: get_ipython().run_line_magic('display', 'latex') # In[3]: from sage.manifolds.operators import * # to get the operators grad, div, curl, etc. # ## The 3-dimensional Euclidean space # # We start by declaring the 3-dimensional Euclidean space $\mathbb{E}^3$, with $(\rho,\phi,z)$ as spherical coordinates: # In[4]: E. = EuclideanSpace(coordinates='cylindrical') print(E) E # $\mathbb{E}^3$ is endowed with the chart of cylindrical coordinates: # In[5]: E.atlas() # as well as with the associated orthonormal vector frame $(e_\rho, e_\phi, e_z)$: # In[6]: E.frames() # In the above output, $\left(\frac{\partial}{\partial\rho}, \frac{\partial}{\partial\phi}, \frac{\partial}{\partial z}\right)$ is the coordinate frame associated with $(\rho,\phi,z)$; it is not an orthonormal frame and will not be used below. # ## Vector fields # We define a vector field on $\mathbb{E}^3$ from its components in the orthonormal vector frame $(e_\rho,e_\phi,e_z)$: # In[7]: v = E.vector_field(rh*(1+sin(2*ph)), 2*rh*cos(ph)^2, z, name='v') v.display() # We can access to the components of $v$ via the square bracket operator: # In[8]: v[1] # In[9]: v[:] # A vector field can evaluated at any point of $\mathbb{E}^3$: # In[10]: p = E((1, pi, 0), name='p') print(p) # In[11]: p.coordinates() # In[12]: vp = v.at(p) print(vp) # In[13]: vp.display() # We may define a vector field with generic components: # In[14]: u = E.vector_field(function('u_rho')(rh,ph,z), function('u_phi')(rh,ph,z), function('u_z')(rh,ph,z), name='u') u.display() # In[15]: u[:] # In[16]: up = u.at(p) up.display() # ## Algebraic operations on vector fields # # ### Dot product # # The dot (or scalar) product of the vector fields $u$ and $v$ is obtained by the method `dot_product`, which admits `dot` as a shortcut alias: # In[17]: s = u.dot(v) s # In[18]: print(s) # $s= u\cdot v$ is a *scalar field*, i.e. a map $\mathbb{E}^3 \rightarrow \mathbb{R}$: # In[19]: s.display() # It maps point of $\mathbb{E}^3$ to real numbers: # In[20]: s(p) # Its coordinate expression is # In[21]: s.expr() # ### Norm # # The norm of a vector field is # In[22]: s = norm(u) s # In[23]: s.display() # In[24]: s.expr() # The norm is related to the dot product by $\|u\|^2 = u\cdot u$, as we can check: # In[25]: norm(u)^2 == u.dot(u) # For $v$, we have: # In[26]: norm(v).expr() # ### Cross product # # The cross product of $u$ by $v$ is obtained by the method `cross_product`, which admits `cross` as a shortcut alias: # In[27]: s = u.cross(v) print(s) # In[28]: s.display() # ### Scalar triple product # # Let us introduce a third vector field. As a example, we do not pass the components as arguments of `vector_field`, as we did for $u$ and $v$; instead, we set them in a second stage, via the square bracket operator, any unset component being assumed to be zero: # In[29]: w = E.vector_field(name='w') w[1] = rh w[3] = z w.display() # The scalar triple product of the vector fields $u$, $v$ and $w$ is obtained as follows: # In[30]: triple_product = E.scalar_triple_product() s = triple_product(u, v, w) print(s) # In[31]: s.expr() # Let us check that the scalar triple product of $u$, $v$ and $w$ is $u\cdot(v\times w)$: # In[32]: s == u.dot(v.cross(w)) # ## Differential operators # ### Gradient of a scalar field # # We first introduce a scalar field, via its expression in terms of Cartesian coordinates; in this example, we consider a unspecified function of $(\rho,\phi,z)$: # In[33]: F = E.scalar_field(function('f')(rh,ph,z), name='F') F.display() # The value of $F$ at a point: # In[34]: F(p) # The gradient of $F$: # In[35]: print(grad(F)) # In[36]: grad(F).display() # In[37]: norm(grad(F)).display() # ### Divergence # # The divergence of a vector field: # In[38]: s = div(u) s.display() # In[39]: s.expr().expand() # For $v$ and $w$, we have # In[40]: div(v).expr() # In[41]: div(w).expr() # An identity valid for any scalar field $F$ and any vector field $u$: # In[42]: div(F*u) == F*div(u) + u.dot(grad(F)) # ### Curl # # The curl of a vector field: # In[43]: s = curl(u) print(s) # In[44]: s.display() # To use the notation `rot` instead of `curl`, simply do # In[45]: rot = curl # An alternative is # In[46]: from sage.manifolds.operators import curl as rot # We have then # In[47]: rot(u).display() # In[48]: rot(u) == curl(u) # For $v$ and $w$, we have: # In[49]: curl(v).display() # In[50]: curl(w).display() # The curl of a gradient is always zero: # In[51]: curl(grad(F)).display() # The divergence of a curl is always zero: # In[52]: div(curl(u)).display() # An identity valid for any scalar field $F$ and any vector field $u$: # In[53]: curl(F*u) == grad(F).cross(u) + F*curl(u) # ### Laplacian # The Laplacian of a scalar field: # In[54]: s = laplacian(F) s.display() # In[55]: s.expr().expand() # For a scalar field, the Laplacian is nothing but the divergence of the gradient: # In[56]: laplacian(F) == div(grad(F)) # The Laplacian of a vector field: # In[57]: Du = laplacian(u) Du.display() # Since this expression is quite lengthy, we may ask for a display component by component: # In[58]: Du.display_comp() # We may expand each component: # In[59]: for i in E.irange(): Du[i].expand() Du.display_comp() # In[60]: Du[1] # In[61]: Du[2] # In[62]: Du[3] # As a test, we may check that these formulas coincide with those of # Wikipedia's article [*Del in cylindrical and spherical coordinates*](https://en.wikipedia.org/wiki/Del_in_cylindrical_and_spherical_coordinates#Del_formula). # For $v$ and $w$, we have # In[63]: laplacian(v).display() # In[64]: laplacian(w).display() # We have: # In[65]: curl(curl(u)).display() # In[66]: grad(div(u)).display() # and we may check a famous identity: # In[67]: curl(curl(u)) == grad(div(u)) - laplacian(u) # ## Customizations # # ### Customizing the symbols of the orthonormal frame vectors # # By default, the vectors of the orthonormal frame associated with cylindrical coordinates are denoted $(e_\rho,e_\phi,z)$: # In[68]: frame = E.cylindrical_frame() frame # But this can be changed, thanks to the method `set_name`: # In[69]: frame.set_name('a', indices=('rh', 'ph', 'z'), latex_indices=(r'\rho', r'\phi', 'z')) frame # In[70]: v.display() # In[71]: frame.set_name(('hrh', 'hph', 'hz'), latex_symbol=(r'\hat{\rho}', r'\hat{\phi}', r'\hat{z}')) frame # In[72]: v.display() # ### Customizing the coordinate symbols # # The coordinates symbols are defined within the angle brackets `<...>` at the construction of the Euclidean space. Above we did # In[73]: E. = EuclideanSpace(coordinates='cylindrical') # which resulted in the coordinate symbols $(\rho,\phi,z)$ and in the corresponding Python variables `rh`, `ph` and `z` (SageMath symbolic expressions). Using other symbols, for instance $(R,\Phi,Z)$, is possible through the optional argument `symbols` of the function `EuclideanSpace`. It has to be a string, usually prefixed by r (for raw string, in order to allow for the backslash character of LaTeX expressions). This string contains the coordinate fields separated by a blank space; each field contains the coordinate’s text symbol and possibly the coordinate’s LaTeX symbol (when the latter is different from the text symbol), both symbols being separated by a colon (`:`): # In[74]: E. = EuclideanSpace(coordinates='cylindrical', symbols=r'R Ph:\Phi Z') # We have then # In[75]: E.atlas() # In[76]: E.frames() # In[77]: E.cylindrical_frame() # In[78]: v = E.vector_field(R*(1+sin(2*Ph)), 2*R*cos(Ph)^2, Z, name='v') v.display()