Note: A web application version of this program is was available at www.lehett.net/PotentialFieldVisualizer. With the revamp of my personal site, it will have to be ported over eventually. For now, you can see a demo of the working project here: https://www.youtube.com/watch?v=iMxBzQnYfJ8
Potential field simulations are one method for solving robotic pathfinding problems. In this simulation, the cell in which the robot resides in the potential field represents the velocity with which the robot would move on its next time step.
Potential fields can be easily implemented via NumPy arrays. The starting potential field with no schemas applied to it is simply a zero-state, where all of the cells contain the velocity vector [0, 0]. However, by adding new schemas, such as repulsion, attraction, or target direction, you can begin to create a field that represents where a robot should go. In each of these schemas, a potential field that solves that problem alone is constructed. Then, this field is added to the current field that the robot is using. By layering potential fields in this way, you can create a potential field that solves the pathfinding problem given.
In this Python project, I wanted to explore how potential fields change as you add different schemas. The display is handled via PyGame, and is capable of displaying a potential field as a grid of arrows pointing in the direction of the velocity, with their size representing the magnitude of the vector.
An example potential field diagram produced with the program.
First, you must initialize the pygame display which handles the PyGame window code. You may also want to specify a field size in this section and use that as the display dimensions.
from display import Display
FIELD_SIZE = (1600, 1000)
display = Display(FIELD_SIZE, windowTitle='Potential Field Testing')
You must also initialize the potential field itself.
import potentialField as pf
potentialField = pf.PotentialField(FIELD_SIZE)
Then you can add any schemas you would like to the potential field, with specified properties. Schemas control how the potential field changes. Schemas can include things like repulsion from a specified point, attraction to a point, flow towards a specified direction, and more. Schemas can be added to the potential field using the addSchema() function provided by the PotentialField class.
addSchema() takes 1 type argument and then as many kwargs as the schema type calls for. The constants representing each schema type and the kwargs each schema takes will be specified below.
"""
IN_DIRECTION causes the potential field to flow in the direction specified by the kwargs. Magnitude represents the strength this
schema has on the field, and angle is the angle given in radians in which the field should flow.
"""
potentialField.addSchema(
pf.IN_DIRECTION,
magnitude=__float__, angle=__float__
)
"""
TO_TARGET causes the potential field to flow towards a coordinate specified by the kwargs. As opposed to ATTRACT, this schema will
have a greater magnitude the further away it is from the target and it will slow down as it gets closer to the target. TargetPos
represents the specified coordinate the field should flow towards, minVel represents the minimum strength this schema has on the
field, and maxVel represents the maximum strength this schema has on the field.
"""
potentialField.addSchema(
pf.TO_TARGET,
targetPos=__(float, float)__, minVel=__float__, maxVel=__float__
)
"""
REPULSE causes the potential field to flow away from a coordinate specified by the kwargs. RepulsePos represents the specified
coordinate the field should flow away from, radius represents the influence size the schema has on the potential field, minVel
represents the minimum strength this schema has on the field, and maxVel represents the maximum strength this schema has on the
field.
"""
potentialField.addSchema(
pf.REPULSE,
repulsePos=__(float, float)__, radius=__float__, minVel=__float__, maxVel=__float__
)
"""
ATTRACT causes the potential field to flow towards a coordinate specified by the kwargs. As opposed to TO_TARGET, this schema will
have a smaller magnitude the further away it is from the target and it will speed up as it gets closer to the target. AttractPos
represents the specified coordinate the field should flow towards, radius represents the influence size the schema has on the
potential field, minVel represents the minimum strength this schema has on the field, and maxVel represents the maximum strength
this schema has on the field.
"""
potentialField.addSchema(
pf.ATTRACT,
attractPos=__(float, float)__, radius=__float__, minVel=__float__, maxVel=__float__
)
"""
CENTER_VERTICAL pushes the potential field towards the center of the screen, vertically, as if constricted through a hallway.
BorderRadius represents the size of the 'hallway', and maxVel represents the maximum strength this schema has on the field.
"""
potentialField.addSchema(
pf.CENTER_VERTICAL,
borderRadius=__float__, maxVel=__float__
)
"""
CENTER_HORIZONTAL pushes the potential field towards the center of the screen, horizontally, as if constricted through a hallway.
BorderRadius represents the size of the 'hallway', and maxVel represents the maximum strength this schema has on the field.
"""
potentialField.addSchema(
pf.CENTER_HORIZONTAL,
borderRadius=__float__, maxVel=__float__
)
"""
RANDOM_NOISE introduces random noise into the field with the hopes of preventing a robot from getting stuck in a zero-sum state.
MaxVel represents the maximum strength this schema has on the field.
"""
potentialField.addSchema(
pf.RANDOM_NOISE,
maxVel=__float__
)
Once you are done adding schemas, you can choose to clamp the field down to a certain maximum magnitude with the clampField function:
potentialField.clampField(15.0) # Where 15.0 is the maximum magnitude of the field
From there, the standard way to display the potential field is given below:
# Import the PyGame library
import pygame
# Main loop for refreshing the display
while True:
# Handle events for quitting the simulation
event = pygame.event.poll()
if event.type == pygame.QUIT:
break
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
break
if event.key == pygame.K_q:
break
# Clear the screen to black before rendering next frame
display.clearScreen()
# Draw the potential field to the screen
pf.drawPotentialField(
display.getScreen(),
FIELD_SIZE,
stride=(20, 20)
)
# Update the display to show the current frame
display.updateScreen()
# Shut down the pygame display
display.shutDown()