This notebook illustrates some vector calculus capabilities of SageMath within the 3-dimensional Euclidean space. The corresponding tools have been developed within the SageManifolds project.
Click here to download the notebook file (ipynb format). To run it, you must start SageMath with the Jupyter interface, via the command sage -n jupyter
NB: a version of SageMath at least equal to 8.3 is required to run this notebook:
version()
'SageMath version 9.0, Release Date: 2020-01-01'
First we set up the notebook to display math formulas using LaTeX formatting:
%display latex
Before evaluating some vector-field operators, one needs to define the arena in which vector fields live, namely the 3-dimensional Euclidean space $\mathbb{E}^3$. In SageMath, we declare it, along with the standard Cartesian coordinates $(x,y,z)$, as follows:
E.<x,y,z> = EuclideanSpace()
print(E)
E
Euclidean space E^3
Thanks to the notation <x,y,z>
in the above declaration, the coordinates
$(x,y,z)$ are immediately available as three symbolic variables x
,
y
and z
(there is no need to type x, y, z = var('x y z')
):
x is E.cartesian_coordinates()[1]
y is E.cartesian_coordinates()[2]
z is E.cartesian_coordinates()[3]
type(z)
Besides, $\mathbb{E}^3$ is endowed with the orthonormal vector frame $(e_x, e_y, e_z)$ associated with Cartesian coordinates:
E.frames()
At this stage, this is the default vector frame on $\mathbb{E}^3$ (being the only vector frame introduced so far):
E.default_frame()
We define a vector field on $\mathbb{E}^3$ from its components in the vector frame $(e_x,e_y,e_z)$:
v = E.vector_field(-y, x, sin(x*y*z), name='v')
v.display()
We can access to the components of $v$ via the square bracket operator:
v[1]
v[:]
A vector field can evaluated at any point of $\mathbb{E}^3$:
p = E((3,-2,1), name='p')
print(p)
Point p on the Euclidean space E^3
p.coordinates()
vp = v.at(p)
print(vp)
Vector v at Point p on the Euclidean space E^3
vp.display()
Vector fields can be plotted (see the list of options for customizing the plot)
v.plot(max_range=1.5, scale=0.5)
We may define a vector field with generic components $(u_x, u_y, u_z)$:
u = E.vector_field(function('u_x')(x,y,z),
function('u_y')(x,y,z),
function('u_z')(x,y,z),
name='u')
u.display()
u[:]
Its value at the point $p$ is then
up = u.at(p)
up.display()
u.dot(v) == u[1]*v[1] + u[2]*v[2] + u[3]*v[3]
$s= u\cdot v$ is a scalar field, i.e. a map $\mathbb{E}^3 \to \mathbb{R}$:
s = u.dot(v)
s
print(s)
Scalar field u.v on the Euclidean space E^3
s.display()
It maps points of $\mathbb{E}^3$ to real numbers:
s(p)
Its coordinate expression is
s.expr()
The norm $\|u\|$ of the vector field $u$ is defined in terms of the dot product by $\|u\| = \sqrt{u\cdot u}$:
norm(u) == sqrt(u.dot(u))
It is a scalar field on $\mathbb{E}^3$:
s = norm(u)
print(s)
Scalar field |u| on the Euclidean space E^3
s.display()
s.expr()
For $v$, we have
norm(v).expr()
The cross product $u\times v$ is obtained by the method cross_product
, which admits cross
as a shortcut alias:
s = u.cross(v)
print(s)
Vector field u x v on the Euclidean space E^3
s.display()
We can check the standard formulas expressing the cross product in terms of the components:
all([s[1] == u[2]*v[3] - u[3]*v[2],
s[2] == u[3]*v[1] - u[1]*v[3],
s[3] == u[1]*v[2] - u[2]*v[1]])
Let us introduce a third vector field, $w$ say. 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:
w = E.vector_field(name='w')
w[1] = x*z
w[2] = y*z
w.display()
The scalar triple product of the vector fields $u$, $v$ and $w$ is obtained as follows:
triple_product = E.scalar_triple_product()
s = triple_product(u, v, w)
print(s)
Scalar field epsilon(u,v,w) on the Euclidean space E^3
s.expr()
Let us check that the scalar triple product of $u$, $v$ and $w$ is $u\cdot(v\times w)$:
s == u.dot(v.cross(w))
While the standard operators $\mathrm{grad}$, $\mathrm{div}$, $\mathrm{curl}$, etc. involved in vector calculus are accessible via the dot notation (e.g. v.div()
), let us import functions grad
, div
, curl
, etc. that allow for using standard mathematical notations
(e.g. div(v)
):
from sage.manifolds.operators import *
We first introduce a scalar field, via its expression in terms of Cartesian coordinates; in this example, we consider some unspecified function of $(x,y,z)$:
F = E.scalar_field(function('f')(x,y,z), name='F')
F.display()
The value of $F$ at a point:
F(p)
The gradient of $F$:
print(grad(F))
Vector field grad(F) on the Euclidean space E^3
grad(F).display()
norm(grad(F)).display()
The divergence of the vector field $u$:
s = div(u)
s.display()
For $v$ and $w$, we have
div(v).expr()
div(w).expr()
An identity valid for any scalar field $F$ and any vector field $u$:
div(F*u) == F*div(u) + u.dot(grad(F))
The curl of the vector field $u$:
s = curl(u)
print(s)
Vector field curl(u) on the Euclidean space E^3
s.display()
To use the notation rot
instead of curl
, simply do
rot = curl
An alternative is
from sage.manifolds.operators import curl as rot
We have then
rot(u).display()
rot(u) == curl(u)
For $v$ and $w$, we have
curl(v).display()
curl(w).display()
The curl of a gradient is always zero:
curl(grad(F)).display()
The divergence of a curl is always zero:
div(curl(u)).display()
An identity valid for any scalar field $F$ and any vector field $u$ is $$ \mathrm{curl}(Fu) = \mathrm{grad}\, F\times u + F\, \mathrm{curl}\, u, $$ as we can check:
curl(F*u) == grad(F).cross(u) + F*curl(u)
The Laplacian $\Delta F$ of a scalar field $F$ is another scalar field:
s = laplacian(F)
s.display()
The following identity holds: $$\Delta F = \mathrm{div}\left(\mathrm{grad}\, F\right)$$ as we can check:
laplacian(F) == div(grad(F))
The Laplacian $\Delta u$ of a vector field $u$ is another vector field:
Du = laplacian(u)
print(Du)
Vector field Delta(u) on the Euclidean space E^3
whose components are
Du.display()
In the Cartesian frame, the components of $\Delta u$ are nothing but the (scalar) Laplacians of the components of $u$, as we can check:
e = E.cartesian_frame()
Du == sum(laplacian(u[[i]])*e[i] for i in E.irange())
In the above formula, u[[i]]
return the $i$-th component of $u$ as a scalar field, while u[i]
would have returned the coordinate expression of this scalar field; besides, e
is the Cartesian frame:
e[:]
For the vector fields $v$ and $w$, we have
laplacian(v).display()
laplacian(w).display()
We have
curl(curl(u)).display()
grad(div(u)).display()
A famous identity is
$$ \mathrm{curl}\left(\mathrm{curl}\, u\right) = \mathrm{grad}\left(\mathrm{div}\, u\right) - \Delta u.$$Let us check it:
curl(curl(u)) == grad(div(u)) - laplacian(u)
frame = E.cartesian_frame()
frame
But this can be changed, thanks to the method set_name
:
frame.set_name('a', indices=('x', 'y', 'z'))
frame
v.display()
frame.set_name(('hx', 'hy', 'hz'),
latex_symbol=(r'\mathbf{\hat{x}}', r'\mathbf{\hat{y}}',
r'\mathbf{\hat{z}}'))
frame
v.display()
The coordinates symbols are defined within the angle brackets <...>
at the construction of the Euclidean space. Above we did
E.<x,y,z> = EuclideanSpace()
which resulted in the coordinate symbols $(x,y,z)$ and in the corresponding Python variables x
, y
and z
(SageMath symbolic expressions). To use other symbols, for instance $(X,Y,Z)$, it suffices to create E
as
E.<X,Y,Z> = EuclideanSpace()
We have then:
E.atlas()
E.cartesian_frame()
v = E.vector_field(-Y, X, sin(X*Y*Z), name='v')
v.display()
By default the LaTeX symbols of the coordinate coincide with the letters given within the angle brackets. But this can be adjusted through the optional argument symbols
of the function EuclideanSpace
, which 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 (:
):
E.<xi,et,ze> = EuclideanSpace(symbols=r"xi:\xi et:\eta ze:\zeta")
E.atlas()
v = E.vector_field(-et, xi, sin(xi*et*ze), name='v')
v.display()