Basic Implementation details

Coordinate Systems and Vectors

Currently, sympy.vector is able to deal with the Cartesian (also called rectangular), spherical and other curvilinear coordinate systems.

A 3D Cartesian coordinate system can be initialized in sympy.vector as

>>> from sympy.vector import CoordSys3D
>>> N = CoordSys3D('N')

The string parameter to the constructor denotes the name assigned to the system, and will primarily be used for printing purposes.

Once a coordinate system (in essence, a CoordSys3D instance) has been defined, we can access the orthonormal unit vectors (i.e. the \(\mathbf{\hat{i}}\), \(\mathbf{\hat{j}}\) and \(\mathbf{\hat{k}}\) vectors) and coordinate variables/base scalars (i.e. the \(\mathbf{x}\), \(\mathbf{y}\) and \(\mathbf{z}\) variables) corresponding to it. We will talk about coordinate variables in the later sections.

The basis vectors for the \(X\), \(Y\) and \(Z\) axes can be accessed using the i, j and k properties respectively.

>>> N.i
N.i
>>> type(N.i)
<class 'sympy.vector.vector.BaseVector'>

As seen above, the basis vectors are all instances of a class called BaseVector.

When a BaseVector is multiplied by a scalar (essentially any SymPy Expr), we get a VectorMul - the product of a base vector and a scalar.

>>> 3*N.i
3*N.i
>>> type(3*N.i)
<class 'sympy.vector.vector.VectorMul'>

Addition of VectorMul and BaseVectors gives rise to formation of VectorAdd - except for special cases, ofcourse.

>>> v = 2*N.i + N.j
>>> type(v)
<class 'sympy.vector.vector.VectorAdd'>
>>> v - N.j
2*N.i
>>> type(v - N.j)
<class 'sympy.vector.vector.VectorMul'>

What about a zero vector? It can be accessed using the zero attribute assigned to class Vector. Since the notion of a zero vector remains the same regardless of the coordinate system in consideration, we use Vector.zero wherever such a quantity is required.

>>> from sympy.vector import Vector
>>> Vector.zero
0
>>> type(Vector.zero)
<class 'sympy.vector.vector.VectorZero'>
>>> N.i + Vector.zero
N.i
>>> Vector.zero == 2*Vector.zero
True

All the classes shown above - BaseVector, VectorMul, VectorAdd and VectorZero are subclasses of Vector.

You should never have to instantiate objects of any of the subclasses of Vector. Using the BaseVector instances assigned to a CoordSys3D instance and (if needed) Vector.zero as building blocks, any sort of vectorial expression can be constructed with the basic mathematical operators +, -, *. and /.

>>> v = N.i - 2*N.j
>>> v/3
1/3*N.i + (-2/3)*N.j
>>> v + N.k
N.i + (-2)*N.j + N.k
>>> Vector.zero/2
0
>>> (v/3)*4
4/3*N.i + (-8/3)*N.j

In addition to the elementary mathematical operations, the vector operations of dot and cross can also be performed on Vector.

>>> v1 = 2*N.i + 3*N.j - N.k
>>> v2 = N.i - 4*N.j + N.k
>>> v1.dot(v2)
-11
>>> v1.cross(v2)
(-1)*N.i + (-3)*N.j + (-11)*N.k
>>> v2.cross(v1)
N.i + 3*N.j + 11*N.k

The & and ^ operators have been overloaded for the dot and cross methods respectively.

>>> v1 & v2
-11
>>> v1 ^ v2
(-1)*N.i + (-3)*N.j + (-11)*N.k

However, this is not the recommended way of performing these operations. Using the original methods makes the code clearer and easier to follow.

In addition to these operations, it is also possible to compute the outer products of Vector instances in sympy.vector. More on that in a little bit.

SymPy operations on Vectors

The SymPy operations of simplify, trigsimp, diff, and factor work on Vector objects, with the standard SymPy API.

In essence, the methods work on the measure numbers(The coefficients of the basis vectors) present in the provided vectorial expression.

>>> from sympy.abc import a, b, c
>>> from sympy import sin, cos, trigsimp, diff
>>> v = (a*b + a*c + b**2 + b*c)*N.i + N.j
>>> v.factor()
((a + b)*(b + c))*N.i + N.j
>>> v = (sin(a)**2 + cos(a)**2)*N.i - (2*cos(b)**2 - 1)*N.k
>>> trigsimp(v)
N.i + (-cos(2*b))*N.k
>>> v.simplify()
N.i + (-cos(2*b))*N.k
>>> diff(v, b)
(4*sin(b)*cos(b))*N.k
>>> from sympy import Derivative
>>> Derivative(v, b).doit()
(4*sin(b)*cos(b))*N.k

Integral also works with Vector instances, similar to Derivative.

>>> from sympy import Integral
>>> v1 = a*N.i + sin(a)*N.j - N.k
>>> Integral(v1, a)
(Integral(a, a))*N.i + (Integral(sin(a), a))*N.j + (Integral(-1, a))*N.k
>>> Integral(v1, a).doit()
a**2/2*N.i + (-cos(a))*N.j + (-a)*N.k

Points

As mentioned before, every coordinate system corresponds to a unique origin point. Points, in general, have been implemented in sympy.vector in the form of the Point class.

To access the origin of system, use the origin property of the CoordSys3D class.

>>> from sympy.vector import CoordSys3D
>>> N = CoordSys3D('N')
>>> N.origin
N.origin
>>> type(N.origin)
<class 'sympy.vector.point.Point'>

You can instantiate new points in space using the locate_new method of Point. The arguments include the name(string) of the new Point, and its position vector with respect to the ‘parent’ Point.

>>> from sympy.abc import a, b, c
>>> P = N.origin.locate_new('P', a*N.i + b*N.j + c*N.k)
>>> Q = P.locate_new('Q', -b*N.j)

Like Vector, a user never has to expressly instantiate an object of Point. This is because any location in space (albeit relative) can be pointed at by using the origin of a CoordSys3D as the reference, and then using locate_new on it and subsequent Point instances.

The position vector of a Point with respect to another Point can be computed using the position_wrt method.

>>> P.position_wrt(Q)
b*N.j
>>> Q.position_wrt(N.origin)
a*N.i + c*N.k

Additionally, it is possible to obtain the \(X\), \(Y\) and \(Z\) coordinates of a Point with respect to a CoordSys3D in the form of a tuple. This is done using the express_coordinates method.

>>> Q.express_coordinates(N)
(a, 0, c)

Dyadics

A dyadic, or dyadic tensor, is a second-order tensor formed by the juxtaposition of pairs of vectors. Therefore, the outer products of vectors give rise to the formation of dyadics. Dyadic tensors have been implemented in sympy.vector in the Dyadic class.

Once again, you never have to instantiate objects of Dyadic. The outer products of vectors can be computed using the outer method of Vector. The | operator has been overloaded for outer.

>>> from sympy.vector import CoordSys3D
>>> N = CoordSys3D('N')
>>> N.i.outer(N.j)
(N.i|N.j)
>>> N.i|N.j
(N.i|N.j)

Similar to Vector, Dyadic also has subsequent subclasses like BaseDyadic, DyadicMul, DyadicAdd. As with Vector, a zero dyadic can be accessed from Dyadic.zero.

All basic mathematical operations work with Dyadic too.

>>> dyad = N.i.outer(N.k)
>>> dyad*3
3*(N.i|N.k)
>>> dyad - dyad
0
>>> dyad + 2*(N.j|N.i)
(N.i|N.k) + 2*(N.j|N.i)

dot and cross also work among Dyadic instances as well as between a Dyadic and Vector (and also vice versa) - as per the respective mathematical definitions. As with Vector, & and ^ have been overloaded for dot and cross.

>>> d = N.i.outer(N.j)
>>> d.dot(N.j|N.j)
(N.i|N.j)
>>> d.dot(N.i)
0
>>> d.dot(N.j)
N.i
>>> N.i.dot(d)
N.j
>>> N.k ^ d
(N.j|N.j)