Vector Fields – manim Series: Part 9

The post is part of a series on learning how to use manim.  You can find the previous tutorial post in this series here and the overview of the entire series here.

Important Note:  These posts are based on an earlier version of manim which uses Python 2.7.  The latest version of manim is using Python 3.  To follow along with these posts, use Python 2.7 and the May 9, 2018 commit of manim .

9.0 Vector Fields

Before diving into draw a vector field, we should set up a Cartesian axes using NumberPlane(). This gives you two axes and an underlying grid. The CONFIG{} for the NumberPlane() class is

class NumberPlane(VMobject):
CONFIG = {
"color": BLUE_D,
"secondary_color": BLUE_E,
"axes_color": WHITE,
"secondary_stroke_width": 1,
"x_radius": None,
"y_radius": None,
"x_unit_size": 1,
"y_unit_size": 1,
"center_point": ORIGIN,
"x_line_frequency": 1,
"y_line_frequency": 1,
"secondary_line_ratio": 1,
"written_coordinate_height": 0.2,
"propagate_style_to_family": False,
"make_smooth_after_applying_functions": True,
}

You can change any of these default values by passing a dictionary with new values as keyword arguments. For example, if you want to change the spacing of the grid lines you could change x_line_frequency and y_line_frequency by defining a dictionary with these variables and then passing the dictionary to NumberPlane(). If you want to see the x-axis and y-axis indicated you can use get_axis_labels() to draw an x and ay next to the appropriate axis. See the code below.

class DrawAnAxis(Scene):
CONFIG = { "plane_kwargs" : {
"x_line_frequency" : 2,
"y_line_frequency" :2
}
}

def construct(self):
my_plane = NumberPlane(**self.plane_kwargs)
my_plane.add(my_plane.get_axis_labels())
self.add(my_plane)
self.wait()

The double asterisk in front of the argument self.plane_kwargs lets the class know that this is a dictionary that needs to be unpacked.

I recommend changing the various properties to see what affect they have on the axes and grid. This is the best way to learn what things do.

9.1 A Simple Vector Field

Let’s start with a simple vector field; a constant field. We first need to define a set of vector points for each grid point, define the field at each grid point, then create the Vector() for the field at each point. Finally we combine all the Vector() instances into a VGroup to allow us to draw all vector lines with a single command.

class SimpleField(Scene):
CONFIG = {
"plane_kwargs" : {
"color" : RED
},
}
def construct(self):
plane = NumberPlane(**self.plane_kwargs) #Create axes and grid
plane.add(plane.get_axis_labels()) #add x and y label
self.add(plane) #Place grid on screen

points = [x*RIGHT+y*UP
for x in np.arange(-5,5,1)
for y in np.arange(-5,5,1)
] #List of vectors pointing to each grid point

vec_field = [] #Empty list to use in for loop
for point in points:
field = 0.5*RIGHT + 0.5*UP #Constant field up and to right
result = Vector(field).shift(point) #Create vector and shift it to grid point
vec_field.append(result) #Append to list

draw_field = VGroup(*vec_field) #Pass list of vectors to create a VGroup

self.play(ShowCreation(draw_field)) #Draw VGroup on screen

After creating the NumberPlane() we use a list comprehension to create a list of the location of all grid points. Remember that RIGHT=np.array(1,0,0) and UP=np.array(0,1,0) so this list comprehension covers all points from (5,5,0) down to (-5,-5,0) in unit step sizes. The last number in arange() specifies the step size. Next we create an empty list vec_field to hold all of the vectors we are going to create. The for loop goes through each grid location in points and creates a vector whose length and direction are defined by field. It is inefficient to keep defining field each time through the loop but we are setting things up for later. The shift(point) command moves the vector to the grid location defined by point. These results are then appended to a list. After going through the for loop. all of the vectors are grouped together in a single VGroup called draw_field. The only reason for doing this is that you can then add draw_field using a single add or play command. You could have included self.add(result) inside each iteration of the for loop instead of showing the creation of draw_field, but using the VGroup feels cleaner.

9.2 A Variable Vector Field

For a slightly more interesting field we will look at the electric field due to a postive point charge. The electric field is:

\displaystyle \vec{E} = \frac{1}{4 \pi \epsilon_0} \frac{q}{r^3} \vec{r}

where q_1 is the charge on the point charge, \vec{r} is the distance vector between the charge and the observation point, and r is the magnitude of that vector. The constant out front \frac{1}{4 \pi \epsilon_0} = 9 \times 10^9 Nm^2/C^2 is essentially a conversion factor. For our purposes we will set all constants equal to zero and just look at

\displaystyle \vec{E} = \frac{1}{r^3} \vec{r}.

class FieldWithAxes(Scene):
CONFIG = {
"plane_kwargs" : {
"color" : RED_B
},
"point_charge_loc" : 0.5*RIGHT-1.5*UP,
}
def construct(self):
plane = NumberPlane(**self.plane_kwargs)
plane.main_lines.fade(.9)
plane.add(plane.get_axis_labels())
self.add(plane)

field = VGroup(*[self.calc_field(x*RIGHT+y*UP)
for x in np.arange(-9,9,1)
for y in np.arange(-5,5,1)
])

self.play(ShowCreation(field))

def calc_field(self,point):
x,y = point[:2]
Rx,Ry = self.point_charge_loc[:2]
r = math.sqrt((x-Rx)**2 + (y-Ry)**2)
efield = (point - self.point_charge_loc)/r**3

The location of the point charge is set in CONFIG{}. To create the vector field we’ve condensed the previous code. We use a list comprehension and the function calc_field() as the argument of VGroup(). The calc_field() function defines the field to calculate. To make the formulas a little easier to read we unpack the x- and y-coordinates from the point vector and the self.point_charge_loc vector. The code x,y=point[:2] is equivalent to x=point[0] and y=point[1].

The fade(0.9) method sets the opacity of the lines to be one minus the fade level (so in this case the opacity is set to 0.1). This was done to make it easier to see the tiny field arrows farther from the charge location.

Things to try:
– Change each of the elements in CONFIG{} for NumberPlane() to see what affect they have on the axes and grid lines.
– Calculate different fields
– Try efield = np.array((-y,x,0))/math.sqrt(x**2+y**2)
– Try efield = np.array(( -2*(y%2)+1 , -2*(x%2)+1 , 0 ))/3
– Come up with your own equation

Note: If you end up creating a video using manim and post it online, please link to it in the comments. I’m sure everyone would love to see what sort of fun things everyone is doing.

Next time we will look at plotting vector fields in three dimensions.

Advertisements
This entry was posted in Just for Fun, Programming and tagged , , , . Bookmark the permalink.

4 Responses to Vector Fields – manim Series: Part 9

  1. Pingback: More Graphing – manim Series: Part 8 | Talking Physics

  2. Pingback: Learning How To Animate Videos Using manim Series – A Journey | Talking Physics

  3. Pingback: 3D Scenes – manim Series: Part 10 | Talking Physics

  4. Pingback: Fields of a Moving Charge – manim Series: Part 11 | Talking Physics

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.