In this assignment you will write code that transforms a 2D planning
problem for a robotic arm into a configuration space, and then searches for
a path in that space. See Section 25.4 of the textbook and Lecture 7
for background information.
You need to find a shortest path for the robotic arm from its starting
position to the goal so that the tip (open end of the arm) touches or is
inside the goal.
The tricky bit is that the robotic arm may not pass through any of the
circular objects including a goal and an obstacle. Also, any part of the arm
should not get out of the given window.
So configurations like the following are NOT allowed:
First of all, you need to understand the 2D space that describes a robotic arm, a goal, and obstacles.
The following example map, named 'Test1', would help you to understand map configuration specifications.
[Test1]
Window : (300, 200) # (Width, Height)
ArmBase : (150, 190) # (x-coordinate, y-coordinate)
ArmLinks : [
(100, 40, (20, 160)), # (length, initial angle, (min angle, max angle)
(80, 0, (-150, 150)),
]
Obstacles : [
(70, 50, 15), # (x-coordinate, y-coordinate, radius)
(140, 30, 17),
(115, 75, 17)
]
Goals : [
(110, 40, 10) # (x-coordinate, y-coordinate, radius)
]
The window size for the given example map is 300x200 pixel. The arm base is placed at (150, 200).
Note that (0, 0) is the left-top corner and (300, 200) is the right-bottom corner.
There are two arm links. The length of the first arm link is 100 and its initial angle (α) is 40.
The minimum and maximum angle for this arm link is 20 and 160. The next arm link can be explained in the same manner.
Look carefully at the conventions for α and β in the
diagram at the top of this page. Both angles are measured counter-clockwise.
α is relative to the ground and
β is relative to the direction of link-1.
There are three obstacles, each of them is described in tuple (x-coordinate, y-coordinate, radius).
Goals can be described in the same convention. Technically, there can be more than one goal.
However, for simplicity, you can assume that there is only one goal for this MP.
The generated 2D map for above configuration is shown in following:
You can play with the above map with the following command if your geometry functions are correctly implemented:
python3 mp2.py --human --map Test1 --config test_config.txt
Once the window pops up, you can rotate the arm using the following keys:
- z / x: increase / decrease α
- a / s: increase / decrease β
- q / w: increase / decrease γ (if you have 3 arm links)
Part 1: Geometry
The first part of this MP is to work out the geometrical details.
The first problem in geometry is to compute (start, end) position of each arm link for the given α
and β. The base position, initial angle, and length of link-1 is given in configuration, so the end position of link-1 can be computable.
The end position becomes the base position of link-2. Then, the end position of link-2 can be also computable for the given length and β.
We kindly provide two helper classes: Arm and ArmLink. One arm can have at least 1 arm link and at most 3 arms links.
You do not have to modify these two classes. To solve MP2, the following Arm methods would be useful:
arm.py
- getBase(): Return (x, y) of the arm base
- getArmAngle(): Return relative angles of all arm links. If there are two arm links, the return value would be (alpha, beta)
- getArmLimit(): Return (min angle, max angle) of all arm links
- setArmAngle(angles): Set angles(alpha, beta, gamma) for all arm links
- getEnd(): Returns (x, y) of the arm tip
- getArmPos(): Returns (start, end) of all arm links
The relationship among arm links are already defined in Arm. What you
need to define is how to compute the end position of the arm link for
the given length and angle. To this end, you may need to implement the
following function:
geometry.py
- computeCoordinate(start, length, angle): Return the end position for the given start position and length. Please round down the trigonometry function to integer.
Once you correctly define the above function, above red colored getEnd() and getArmPos() functions would perform as expected.
The next geometry problem is then to check whether
- the arm tip (the open end of the arm) touches or is inside the goal.
- the arm runs into obstacles. Note that the goal should be also considered as an obstacle unless the robotic arm tip touches it.
- all parts of the arm remain in the given window.
To do this, you need to implement the following functions:
geometry.py
- doesArmTouchGoals(armEnd, goals): Return True if the given arm tip position is anywhere in the given goals, including the boundary. Or return False.
- doesArmTouchObstacles(armPos, obstacles): Return True if the given arm touches any obstacle, including the boundary. Or return False.
- isArmWithinWindow(armPos, window): Return True if all parts of the arm are within the window. Or return False.
The second part of this MP is to transform the generated 2D map to the maze in MP1.
Rows and columns in the maze would match with alpha and beta, joint angles of the robotic arm.
The number of rows and columns of the maze can be decided by 1) degree granularity (angular resolution) and 2) minimum / maximum value of the angle.
The number of positions in the row or column is
maxangle−minanglegranularity+1.
For example, if range of alpha is (45, 135) and degree granularity is 2, then the number of rows becomes 46=135−452+1.
When the output of the division is not an integer, round it down.
Once the maze size is determined, then you need to fill the starting point, walls, goals in the maze.
Initial alpha and beta would be the starting point.
To contruct walls and goals, you need to use geometry functions defined in Part 1.
The naive approach is to do bruteforce checking for all pairs of alpha
and beta. Or you can apply some smart short-cut techniques to bypass
unnecessary checking. For example, if the first link already hits an
obstacle, you may not need to check beta for that alpha. Although you
use your own shortcut algorithm, the output maze should look same as
the result of the bruteforce algorithm.
Your main effort for Part 2 would be to implement the following function.
transform.py
- transformToMaze(arm, goals, obstacles, window, granularity): This function will return the maze instance created based on the given input arguments.
- The input arguments are:
- arm (Arm): arm instance
- goals (list): [(x, y, r)] of goals, where x and y are coordinates, and r is radius
- obstacles (list): [(x, y, r)] of obstacles, where x and y are coordinates, and r is radius
- window (tuple): (width, height) of the window
- granularity (int): unit of increasing/decreasing degree for angles
- Hint: use utils.py.
The following images are examples of output maze for Test1 above. The left image has the finer granularity and the right image has the coarser granularity. As you can see, the maze resolution is determined depending on the input granularity. As can be seen, you should be able to handle any level of granularity. Granularity is a natural number for simplicity.
mp2.py does not generate the maze map as above. If you would like to visualize the map, you should run mp2.py to export the maze to a text file, which is an input file of
mp1.py. Then, you can run your mp1.py to create the maze map. That is,
python3 mp2.py --map Test1 --config test_config.txt --save-maze [MAZEFILE_PATH]
python3 mp1.py [MAZEFILE_PATH]
You can also save your output picture using --save-image option. If you want to export your maze to the text file, use --save-maze option.
Please upload only search.py, transform.py, geometry.py one by one to gradescope for MP2.
For extra credit, upload search.py, transform.py, geometry.py, and maze.py to MP2_extra.
After submission, gradescope will run preliminary tests to
determine whether or not your submission appears valid. These
preliminary tests are worth 0 points, and are for your information
only. Passing all of these preliminary tests does not guarantee that
your implementations are correct.
Here's what the autograder outputs if you simply submit the provided skeleton
code:
If you don't see a similar series of test results in a reasonable
amount of time, the autograder has probably crashed (e.g. because
you imported a module that wasn't allowed for this MP) or has gone
into an infinite loop. Fix your code and resubmit. If the
autograder is still running, a resubmission will cause it to stop
work and restart on your new submission.