Project 3: Mandelbrot Set Visualization

Milestone Due: October 7th, 9:00pm

Final Submission Due: October 21st, 9:00pm

TA: Daniel Tang

Warning: Your project must compile in order to get a grade. You will get a zero if your program does not compile.

Outline

New Project Submission Policy

For this project, you now have two options for submission:

Warning: If you take the second option, be wary of a few things. First, if you opt to skip the milestone, you must turn in the project by the milestone due date. If you turn in the complete project after the milestone due date without completing the milestone, you will lose points. Second, if your final project does not demonstrate that the material from the milestone is completed properly, you may lose points as well.

Relevant Book Chapters

Knowledge from chapters 8 and 9 is necessary for this project.

Knowledge of complex numbers is also assumed for this project. If you are not familiar with complex numbers, read this primer. Wikipedia is also a good resource.

Motivation and Introduction

The Mandelbrot set is a set of complex points that is derived from a fairly simple formula. The boundary of the Mandelbrot set is a fractal, which makes it an attractive visualization (see examples here). The Mandelbrot set has many interesting mathematical properties, but they are beyond the scope of this discussion. The goal of this project is to compute which points in a region are part of the Mandelbrot set, and use the given visualizers to show correctness.

Although you will not write the actual computation to determine whether or not a point is in the Mandelbrot set or not, you should be familiar with how it's computed to aid in understanding the project.

In the above image, you can see many distinct colors. Determining the color of a point depends on the level of the point. The level is computed as follows:

We define z_n=(z_{n-1})^2 + c, with c being the complex point for which we are trying to find the level and z_0=0. Therefore, we have:

We calculate up to z_{128} (but this can vary, depending on the desired precision). By definition, a complex point c is in the Mandelbrot set if when calculating these successive z values, the sequence remains bounded. That is, the values do not increase towards infinity. It has also been shown that the Mandelbrot set is contained in a circle of radius 2 on the complex plane. This means that the magnitude of c must be smaller than 2 to be in the Mandelbrot set. In addition, if any of the z values calculated for a complex point c exceeds 2, then the rest of the values will increase towards infinity (meaning c is not in the Mandelbrot set).

If one of these z calculations results in a complex point with a magnitude greater than 2, then its level is the subscript of the smallest z where this is true. Otherwise, the level is zero. In the picture above, black represents a level of zero (which means it's in the Mandelbrot set), whereas the other colors correspond to other levels.

For example, if we want to determine the level of 0+0i (or just 0), we can start calculating all of the z's:

We can quickly see that all of the z's will be zero, so none of their magnitudes will exceed 2. Thus, the level of 0 is 0.

On the other hand, if we want to determine the level of 3+4i, we can start calculating all of the z's again:

Already, we can see that z_1 has a magnitude greater than 2 (specifically, 5). Therefore it has a level of 1.

Note: As a general assumption for this project, assume that values will always be correct. That is, you shouldn't need to worry about error checking very much, if at all. The focus of this project is on concurrency, not error handling and exceptions.

Milestone (Due October 7th, 9:00pm): Computing the Mandelbrot Set

Your objective for the milestone is to create a subclass of Thread to compute the Mandelbrot set for a given coordinate region and use your results to render it in a provided GUI.

Step 1: Complete the Complex class

The first step is to complete the given code for Complex, which represents complex numbers. As an example, addition is implemented for you. You must correct the subtraction and multiplication methods, which currently return incorrect answers in most cases. In addition, a method to compute the magnitude is left empty. You should also implement this method.

Note: Before implementing these methods, make sure you understand how complex numbers are represented in this class. In particular, the instance variable a represents the real component, and the instance variable b represents the imaginary component. Also, arithmetic operations always create a new Complex object, which makes them easier to reason about, since they behave similarly to Java's primitive types. Again, a review of complex numbers is provided, in case you are not familiar with them.

It is suggested that you use JUnit to make sure your class is working correctly. A starting point is given in ComplexTest.java; it is up to you to make the tests more complete.

Step 2: Understand MandelbrotImage

A complete class MandelbrotImage is provided for you. You should not modify this class, as the original version will be used in grading, but it is important to understand what it is used for. This class represents a region of the complex plane that will be rendered by the provided GUI. An image is defined by its top left and bottom right complex points. The size parameters in the constructor, width and height, determine the number of points between and including the end points. When you create your thread class, all of the computed data will be stored in a MandelbrotImage object.

There are three interesting methods in this class:

Note: It is important to distinguish between the complex points and rows and columns. For example, if the top left point is 1+0.5i, then its corresponding row and column is 0 and 0, because it is the first point in the region. That is to say, as a increases, the column increases, but as b decreases, the row increases. The row and column are the indexes of the point, not the actual coordinates/coefficients.

As an example, if we create a new MandelbrotImage object with new MandelbrotImage(new Complex(0, 0), new Complex(2, -3), 3, 4), then the following points will be in the image at the row and column:

Row Column Complex Point
000+0i
01(2/3)+0i
02(4/3)+0i
100-0.75i
11(2/3)-0.75i
12(4/3)-0.75i
200-1.5i
21(2/3)-1.5i
22(4/3)-1.5i
300-2.25i
31(2/3)-2.25i
32(4/3)-2.25i

Note: Due to an error in the provided code, the bottom right point is actually excluded from the calculation. That's why 2-3i doesn't actually appear in the above table.

Complete the MandelbrotThread class

A starting point for creating a thread that computes the Mandelbrot set is given to you. The constructor takes a lower and upper bound on the row, as well as an image, where the computation is to be stored. You should create a run() method that computes the level for every column in the given rows. That is, for each row and column:

Testing

A partially complete example testing program is provided for you here, which requires this complete GUI to run. You should complete the class by starting all of the threads and waiting for them to finish running. That is, when the last line is called, all of the threads must be done running (if they aren't, you'll see black stripes on the GUI). You should test different scenarios (for example, different region boundaries and number of threads). The default parameters should generate a picture like this:

Note: You must run the threads in parallel. If you call run() then you will lose points.

The image below results from the set of coordinates that have been commented out, which renders an image with the top left point as -1+i and the bottom right point as 1-i.

To run the class after you've completed it, simply run MilestoneMain as you normally would run a main class. You should make the height of the image divisible by the number of threads you have, or account for a possible rowMax that is larger than the actual image size.

Summary of Files For Milestone

Milestone Submission

Your milestone must be turned in electronically by Wednesday, October 7th at 9:00 pm. You should turn in all of your Java source files. Project turnin is similar to lab turnin. Late submissions and fixing incorrect submissions (e.g. submitting the wrong file) will not be accepted. Remember that each submission overwrites all previous submissions. Prior submissions cannot be retrieved.

Use the following command from the directory of your project to turn in your project 3 milestone:

turnin -v -c cs180secXXXX -p project3m *.java 

where XXXX is the division and section number for your lab such as 0101, 0201, 0301, etc. Note that these numbers correspond to the bold numbers associated with your lab on the Lab Divisions page.

Note: Please remember to submit only the files for turnin, rather than the directory containing the files. That is, use *.java as in the previous instruction, rather than turning in a project3 directory.

Final Submission (Due October 21st, 9:00pm): Synchronizing with the GUI

Your next task is to add synchronization to allow the computation threads from the milestone to run continuously, in order to handle multiple image computations. When a MandelbrotThread starts running, it should wait for notification from the new GUI (pictured below). Once it receives notification, it should perform the necessary computations, given its parameters, which are set by the GUI before the notification. As the computation happens, the GUI waits for notification for when all of the threads finish their computation. The last thread should notify the GUI so that it can render the result. After a thread finishes its computation, it waits for the next render command from the GUI.

Step 1: Complete the MandelbrotHandler class

This new class being introduced will be used to manage communication between the new GUI and the MandelbrotThread objects that you implemented for the milestone. It has four methods you need to implement:

The setImage() method is used by the GUI to change the image before starting a render. You shouldn't need to modify it.

Step 2: Modify the MandelbrotThread class

As mentioned, instead of having a thread compute its portion of an image and exit, we now want the threads to run forever. Thus, the thread should now loop infinitely and do the following inside the loop:

  1. Wait for a render command to come from the GUI (use one of the MandelbrotHandler methods)
  2. Perform the computation as from the milestone
  3. If the thread is the last thread to finish, send the notification to indicate that the render is finished (again, use one of the MandelbrotHandler methods). You should determine whether or not the thread is the last one running by accurately maintaining the renderingThreads variable in the handler class. Notice that setImage() sets renderingThreads to the number of threads, so it's only necessary for each thread to decrease the count by one when it finishes its computation.

In addition, the constructor must take an additional parameter at the end, which is the MandelbrotHandler object. It is necessary to wait for signals and send them. For an example of how the new constructor is called, see the MandelbrotHandler code.

Note: You must make sure that possible concurrent writes are properly protected. Even though it's unlikely that 2 threads will try to modify renderingThreads at the same time, it's much more likely that concurrent modification will happen if there are 500 computation threads.

Testing

A new main class and the new GUI class should be used for testing. These classes are both complete, so you shouldn't modify them. The GUI will start with a black picture, as shown above, and show a render when you press the render button. You can change the parameters in the GUI, but you cannot press the render button while a render is in process. If you do, an error will appear. This relies on correct maintenance of renderingThreads in the MandelbrotHandler object, so if an error does not appear in the middle of a render, then your synchronization is not working.

Summary of Files Needed for Part 2

Project Submission

Your project must be turned in electronically by Wednesday, October 21st at 9:00 pm. You should turn in all of your Java source files. Project turnin is similar to lab turnin. Late submissions and fixing incorrect submissions (e.g. submitting the wrong file) will not be accepted. Remember that each submission overwrites all previous submissions. Prior submissions cannot be retrieved.

Use the following command from the directory of your project to turn in your project 3:

turnin -v -c cs180secXXXX -p project3 *.java 

where XXXX is the division and section number for your lab such as 0101, 0201, 0301, etc. Note that these numbers correspond to the bold numbers associated with your lab on the Lab Divisions page.

Note: Please remember to submit only the files for turnin, rather than the directory containing the files. That is, use *.java as in the previous instruction, rather than turning in a project3 directory.

If you are having trouble submitting your files, talk to your TA for help.

Copyright 2009, Purdue University. All rights reserved.