I am trying to plot a gravitational vector field with Matplotlib's quiver function, I have a function which calculates the gravitational field caused by any amount of masses but when plotting it, I get strange results around the mass' position. The function returns, for the purpose of the vector plotting, field components in x and y direction, their x and y coordinates and a 2D field array which is not necessary for the plotting but for other code later on not relevant to this. The x and y components are lists where each values in the 4 lists for each index correspond to the same element, so at index 0 in all 4 lists we can obtain the x and y component of the field and the position of it.
The current code is as follows:
import matplotlib.pyplot as plt
import numpy as np
import math
G = 6.674 * 10**(-11)
#Function to calculate gravitational field at each point on the screen caused by all other masses whose mass and positions are defined in lists along with the size of the screen and the components of the field
def gravfield(mass, x, y, screen_width, screen_height, give_components = False):
#Define screen position points
x_screen = list(range(screen_width))
y_screen = list(range(screen_height))
#Define empty array containing all field magnitudes at each pixel position of the screen along with empty field component lists and positions
field = np.zeros((screen_width, screen_height))
field_x = []
field_y = []
field_x_position = []
field_y_position = []
#Calculate the gravitational field from each mass by the vector method
for i in range(len(x_screen)):
for n in range(len(y_screen)):
#Define empty variable which will end up being the value of the field at a specific position after all mass contributions have been added
x_field = 0
y_field = 0
for j in range(len(mass)):
#Calculate position vector from the mass causing the field to the point affected
x_distance = x[j] - x_screen[i]
y_distance = y[j] - y_screen[n]
distance = np.sqrt((x_distance**2) + (y_distance**2))
if not math.isclose(distance, 0):
#Calculate the unit vector of the shortest path between the mass and point
x_unit_vector = x_distance / distance
y_unit_vector = y_distance / distance
#Calculate magnitude of the field
magnitude = (G * mass[j] * 1) / (distance ** 2)
#Calculate the component of the field in each direction and add them to the variable which contains the resultant field value at that position
x_field = (magnitude * x_unit_vector) + x_field
y_field = (magnitude * y_unit_vector) + y_field
else:
pass
#Add the resultant field component value and its position to the defined lists and calculate its magnitude which is assigned to an array where each position corresponds to a pixel on the screen
#Only fill the field component lists if specified
if give_components == True:
gx = x_field
gy = y_field
field_x.append(gx)
field_y.append(gy)
g = np.sqrt((gx**2) + (gy**2))
field[n, i] = g
field_x_position.append(i)
field_y_position.append(n)
else:
gx = x_field
gy = y_field
g = np.sqrt((gx**2) + (gy**2))
field[n, i] = g
#Return the specified elements
if give_components == True:
return field, field_x, field_y, field_x_position, field_y_position
else:
return field
mass = [400000000000000000000000, 800000000000000000000000]
x_cord = [10, 15]
y_cord = [10, 15]
screen_width = 20
screen_height = 20
give_components = True
field, field_x, field_y, field_x_position, field_y_position = gravfield(mass, x_cord, y_cord, screen_width, screen_height, give_components)
fig, ax = plt.subplots()
# Plot the vectors using quiver
ax.quiver(field_x_position, field_y_position, field_x, field_y)
# Set the x and y axis limits
ax.set_xlim([min(field_x_position), max(field_x_position)])
ax.set_ylim([min(field_y_position), max(field_y_position)])
# Show the plot
plt.show()
This function works when representing the intensity of the field in a color-coded form, but when plotting it with the quiver function I obtain what you can see in the image. The field is correct apart from near the mass itself where interesting vectors arise. I do not think it is a divide by zero error since those I just ignore when calculating.
I believe your solution is correct, but when using the default parameters (namely, angles="uv"
rather than angles="xy"
) or not using ax.set_aspect(1)
(equivalent: ax.set_aspect("equal")
), the vectors can look wrong. After making one of those changes, the results look fine, as shown below. I also made some other edits to your code to simplify things (note: there is no need to do if value == True
because you can just do if value
).
import matplotlib.pyplot as plt
import numpy as np
import math
plt.close("all")
G = 6.674e-11
# Function to calculate gravitational field at each point on the screen caused
# by all other masses whose mass and positions are defined in lists along with
# the size of the screen and the components of the field
def gravfield(mass, x, y, screen_width, screen_height, give_components=False):
# Define screen position points
x_screen = list(range(screen_width))
y_screen = list(range(screen_height))
# Define empty array containing all field magnitudes at each pixel position
# of the screen along with empty field component lists and positions
field = np.zeros((screen_width, screen_height))
field_x = []
field_y = []
field_x_position = []
field_y_position = []
# Calculate the gravitational field from each mass by the vector method
for i in range(len(x_screen)):
for n in range(len(y_screen)):
# Define empty variable which will end up being the value of the
# field at a specific position after all mass contributions have
# been added
x_field = 0
y_field = 0
for j in range(len(mass)):
# Calculate position vector from the mass causing the field to
# the point affected
x_distance = x[j] - x_screen[i]
y_distance = y[j] - y_screen[n]
distance = np.sqrt((x_distance**2) + (y_distance**2))
if not math.isclose(distance, 0):
# Calculate the unit vector of the shortest path between
# the mass and point
x_unit_vector = x_distance / distance
y_unit_vector = y_distance / distance
# Calculate magnitude of the field
magnitude = (G*mass[j]*1)/distance**2
# Calculate the component of the field in each
# direction and add them to the variable which contains
# the resultant field value at that position
x_field += magnitude*x_unit_vector
y_field += magnitude*y_unit_vector
# Add the resultant field component value and its position to the
# defined lists and calculate its magnitude which is assigned to an
# array where each position corresponds to a pixel on the screen
field[n, i] = np.sqrt((x_field**2) + (y_field**2))
# Only fill the field component lists if specified
if give_components:
field_x_position.append(i)
field_y_position.append(n)
field_x.append(x_field)
field_y.append(y_field)
# Return the specified elements
if give_components:
return field, field_x, field_y, field_x_position, field_y_position
return field
mass = [4000]
x_cord = [10]
y_cord = [10]
screen_width = 20
screen_height = 20
give_components = True
field, field_x, field_y, field_x_position, field_y_position = gravfield(mass, x_cord, y_cord, screen_width, screen_height, give_components)
field_x = np.array(field_x)
field_y = np.array(field_y)
fig, ax = plt.subplots()
# Plot the vectors using quiver
ax.quiver(field_x_position, field_y_position, field_x, field_y)
ax.set_aspect(1)
# Set the x and y axis limits
ax.set_xlim([min(field_x_position), max(field_x_position)])
ax.set_ylim([min(field_y_position), max(field_y_position)])
# Show the plot
fig.show()