Homework 5: NBody 2.0

A. Goals

The purpose of this homework is to get an introduction to objects and object-oriented programming by rewriting NBody. The specific goals are to

At the end of this assignment, you will have produced a visual simulation of the gravitational interactions among particles in two dimensions.

B. Background

A long time ago in an extraterrestrial body far, far away, you wrote a simulator for celestial physics (i.e. NBody for HW02). You were but a young Padawan, and wrote this with no methods and the entire thing in a single main function. Now, however, you have learned the ways of Computer Programming and have picked up skills. Skills such as the ability to write functions and now create objects! We will put these skills to use by revisiting our old friend NBody, and giving it an upgrade. We will also witness the benefits of having a modularized program, and how we can use this to easily test our program.

This assignment will have the same end goal as HW2. You will implement a program that simulates the motion of the planets.

C. Getting started

Here is a Video explaining the Physics background for this assignment. This is the same video as in HW2, so if you need a refresher you can find it here.

Download the two starting skeleton files, Body.java and Space.java. Save these files to the folder you will be working in and add a header comment. These are your starting files for the first two parts of the assignment.

Create a Java class called NBodyObj with an empty main() function. Give your file a header comment.

Download and compile PennDraw.java into the same folder as NBodyObj.java.

Download nbody_data.zip and decompress it into the same folder as NBodyObj.java. Note, this means the CONTENTS of the zipped folder (including the images and solarsystem.txt files should be in the SAME directory as your NBody.java file, NOT in a subdirectory called nbody-data). This is the same zip file as in HW2.

D. Advice

A. Body.java skeleton

In this section, you will write Body.java. We have given you a starting skeleton file that you will finish.

A body can represent a planet, star, or any other celestial body. Each body maintains its own mass (m), position (px, py) and velocity (vx, vy). Each body also stores its own image file (img). In addition, a body has methods that can be called to perform various things, such as (calculating distance to another object, drawing the body on the canvas, etc.) By implementing these methods, you can recreate the behavior of your celestial bodies in N-Body using object-oriented design.

You are given a skeleton file that contains an incomplete stub for every method, with the exception of toString(), which is completed.

Some notes on the skeleton file:

B. Fields

The fields, or attributes, of each body are provided to you in the skeleton. They are:

These attributes maintain the "state" of the body. Later on in our program, we'll talk about when we manipulate these attributes to simulate motion and gravity. However, for now, just view them as field variables similar to field variables of other objects we have covered in class, such as PezDispenser

Again, it's important you don't modify these variables. You will get a style warning saying to make these variables private. Leave these variables public, however, and ignore those style warnings. You will not lose points for these style warnings, but you will lose points if you make these variables private.

C. Constructor

The Constructor is the method that starts with:

public Body(double mass, double posX, double posY, double velX, double velY, String imageFile);

The constructor takes in (via the arguments above) the mass, starting position (posX and posY), starting velocity (velX and velY), and image filename of the planet. This constructor simply needs to assign the arguments to the correct attributes.

You must complete the constructor before moving on to any other methods. Look at the constructor from PezDispenser.java from the first day we covered objects. This will be very helpful to you here.

D. Methods

toString

String toString();

This method has been completed for you. Do not change anything.

This will be useful later for printing out your planets for the sake of testing.

distanceToX

double distanceToX(Body other);

This method gets the distance between the calling body and the Body other along the X-Axis, or Δx. Note – as in HW2 you should not be returning the absolute value of the distance. The distance could be negative.

For example, if we had two body variables, earth and sun, we could get the Δx from the earth to the sun relative to x by calling:

earth.distanceToX(sun);

See the diagram below for how to calculate this. Note that in the diagram below, the distance from the earth to the sun is positive (going to the right) and the distance from the sun to the earth is negative (going to the left).

distanceToY

double distanceToY(Body other);

Similar to the previous method, this method gets the distance between the calling body and the Body other along the Y-Axis, or Δy. See the diagram above. Again, this result can be negative. For example, the Δy from the sun to the earth is negative, since it is downwards.

distanceTo

double distanceTo(Body other);

This gets the true distance (d in the diagram above), between the bodies via a straight line. See the diagram above for how to calculate this. Remember, when squaring numbers, do not use Math.pow, use simple multiplication. It is much faster.

force

double force(Body other);

This function gets the gravitational force between the calling body and the argument body. The force is calculated by:

G * m1 * m2 / (d * d)

Where:

forceX

double forceX(Body other);

This function returns the x component of the force between the calling planet and the called planet. This is calculated by:

Fx = force * dx / d

Where:

forceY

double forceY(Body other);

This function returns the y component of the force between the calling planet and the called planet. This is calculated by:

Fy = force * dy / d

Where:

draw

void draw()

Draw the calling body by calling the appropriate PennDraw function with the appropriate attributes of the body. This method should be a single line of code.

move

void move(double timeStep)

Update the position of the calling body. For the original values of position: (px, py) the resultant values should be (px + Δt vx, py + Δt vy). Δt, in this formula, represents timeStep.

There should be NO LOOP inside this function

getAffectedBy

void getAffectedBy(Body other, double timeStep)

Update the velocity of the calling body due to the gravitational pull of the argument other. This function should not change the velocity of the body other For the original values of velocity: (vx, vy) you will update the velocities to (vx + Δt ax, vy + Δt ay). The acceleration is calculated by the force of gravity between the planets.

ax can be calculated by fx / m where fx is the x component of the force between the two planets. m is the mass of the calling body. You can calculate

ay in a similar way.

There should be NO LOOP inside this function

Note that this ONLY operates between two bodies, the calling body and the argument body. There should be no loop in the function. This is a departure from HW2. In HW2, you calculated the change in velocity on one planet caused by every other planet at the same time. Here, you will use this function to calculate the change in velocity due to every other body one at a time

It may seem strange that we are only calculating the force between two bodies, not one body to every other body. However, ensuring all the bodies interact correctly will be handled in Space.java.

C. Checkpoint

Compile Body.java to ensure it compiles with no syntax errors. Submit your Body.java through the dashboard. There will be a series of individual function tests. Ensure ALL of your tests pass BEFORE moving on to Space.java

Note that you will get style warnings for the variables from the skeleton file. You can ignore these warnings below:

Body.java:X:XX: Variable 'px' must be private and have get/set methods.
Body.java:X:XX: Variable 'py' must be private and have get/set methods.
Body.java:X:XX: Variable 'vx' must be private and have get/set methods.
Body.java:X:XX: Variable 'vy' must be private and have get/set methods.
Body.java:X:XX: Variable 'm' must be private and have get/set methods.
Body.java:X:XX: Variable 'img' must be private and have get/set methods.

All other style warnings must be corrected. The above style warning relates to visibility, which we will discuss in the following week in class. However, for this assignment, do not change the variable declarators. You will not lose points for these checkstyle errors.

A. Purpose

Space will act as a "planet manager". The Space object maintains an array of planets, and provides means to simulate all the planets en masse (that is, all at once rather than 1 at a time). Space also maintains the radius of the system we are simulating. Further, Space.java is responsible for drawing the starfield and then planets on top of it.

Begin by opening the skeleton file for Space.java

B. Constructor

The constructor takes in a universe file. It uses this file to set the radius attribute, as well as to initilialize and populate the bodies array.

You will be provided with a text file of the following format. (Note the blue text is not in the file, we have it here just to annotate the file.)

5        numParticles 
2.50e+11 radius 
5.97400e+24  1.49600e+11  0.00000e+00  0.00000e+00  2.98000e+04    earth.gif
6.41900e+23  2.27900e+11  0.00000e+00  0.00000e+00  2.41000e+04     mars.gif
3.30200e+23  5.79000e+10  0.00000e+00  0.00000e+00  4.79000e+04  mercury.gif
1.98900e+30  0.00000e+00  0.00000e+00  0.00000e+00  0.00000e+00      sun.gif
4.86900e+24  1.08200e+11  0.00000e+00  0.00000e+00  3.50000e+04    venus.gif
m          px         py        vy         vy             img

In this file,

Remember, unlike in HW2, you will not be storing these values into parallel arrays. Rather, you should use each row of the file to instantiate one Body object and store that object into the bodies array.

Start by opening the file the same as you did in HW2 by creating an In variable to read a file. If you forgot how to do this, you can looking at the Reading From a File portion of hw2.

From there, you will process the file similar to HW2. However, instead of storing the data in arrays, like you did in HW2, you will store the data in objects. First, read numParticles. Once you know how many particles there are, you can create your Body array variable. Your Body array should have size numParticles, so initialize that array now.

After that, you can set the Space radius variable. Immediately after setting this variable, you can go ahead and call:

PennDraw.setXscale(-radius, radius);
PennDraw.setYscale(-radius, radius);

Finally, iterate through the next numParticles lines for the input file. For each line, you should create an instance of the Body class and store that instance in the bodies array.

C. Methods

simulate

toString

String toString()

Once again, this method is given too you, do not change it. The output of this method should match the print statement in HW2.

void simulate(double timeStep)

In this method, you will simulate the physics of the bodies for a duration of timeStep. You can think of this as similar to the contents of one iteration of your time loop in HW2. In this method, you must first update the velocities of all the bodies. After ALL of the bodies velocities are update, you must update the positions of the bodies. If you are finding your results are slightly off, you may have the update velocities and update positions either out of order or staggered.

Note that this method has no time loop, that will be handled in the final class you write for this assignment. So you should have no loop that is over a duration of time. You will, however, have multiple loops over the bodies arrays.

Updating Velocities

To update the velocities, you will use the getAffectedBy() method on each body multiple times. Say for example we were simulating three body variables: earth, sun, and moon. You would need to call:

earth.getAffectedBy(sun, timeStep);	earth.getAffectedBy(moon, timeStep);
sun.getAffectedBy(earth, timeStep);	sun.getAffectedBy(moon, timeStep);
moon.getAffectedBy(sun, timeStep);	moon.getAffectedBy(earth, timeStep);

Note that in this class, you don't have body variables. Rather, you have a array storing Body objects. You must have each planet getAffectedBy() every other planet. However, you do not want a planet to affect itself (i.e., the earth's gravity has no effect on the earth itself).

Updating Positions

After updating velocities, you should update all planet positions. This can be done by simply calling the move() method on each body instance.

draw

void draw()

First, this method should draw the starfield.jpg at the center of the screen. Similar to HW2, this can be accomplished by PennDraw.picture(0.0, 0.0, "starfield.jpg"). After drawing the starfield, this method will draw each body. This should be done by calling every individual body's draw method.

D. Checkpoint

At this point, you can submit both Body.java and Space.java to the course website. You should ensure all tests are passing.

To test drawing, add a main() function to Space.java add add the following two lines of code to it.

Space s = new Space("solarSystem.txt");
s.draw();

This should produce the following drawing:

NBody still

If your drawing does not look like this, try to debug your draw() functions. Consider:

A. Purpose

This is the "main" class that executes NBody. This is the file you will run to utilize the Space and Body classes to similuate a gravity. this program will take in command line arguments, create the Space object, and then execute the time loop to simulate the movement of the planets. This will also be by far your smallest class in this program.

You were not given a skeleton for this program. However, you will only need one function, and that is your standard main function. All your code should be in main.

Command Line Arguments

These command line arguments are the same as HW2.

Utilize these arguments the same way as in HW2. For a command line below should simulate our inner solar system for the duration of one year, run your program with:

java NBodyObj 31557600.0 25000.0 solarSystem.txt

B. Creating the Space Object

You simply need to create an instance of the Space class using the input filename. This should be done after you process the Command Line Arguments, but before the Time Loop.

C. Time Loop

Similar to HW2, you should have a loop to iterate over the duration of the simulation, starting at elapsedTime = 0 and looping until elapsedTime is no longer less than simulationTime. You should increment elapsedTime by timeStep every time.

Do not forget to use PennDraw.enableAnimation(), PennDraw.advance(), and PennDraw.disableAnimation() in the appropriate places.

Within the body of the time loop, you should simply call the simulate() and draw() methods on your Space object. Because Space class handles all of the physics simulation, it keeps this loop very simple and short.

After your time loop finishes (that is, after the closing bracket of the loop), for testing purposes, write the following line of code:

System.out.println(nameOfYourSpaceVariable);

Obviously, replace nameOfYourSpaceVariable with the variable name you got from "Creating the Space Object".

This will cause the Space object's toString() function to be called. This will print the universe's parameters, including all the details about the planets, AFTER your simulation finishes. This will be useful for comparing the output of your NBodyObj with the expected output in the submission report.

A. Readme

Complete readme_nbody2.txt in the same way that you have done for previous assignments.

B. Submission

Submit Body.java, Space.java, NBodyObj.java, and readme_nbody.txt on the course website.

Before submission remove any print statements that were used for debugging or testing your functions. The only print statement that shouldn't be removed is the final print statement in NBodyObj.java.

Be sure that each class had an appropriate header comment (as in previous assignments). Also ensure that every method has an appropriate header comment, and that your code is well-documented.

As noted in several places, you will get some style warnings here. You can ignore the following style warnings:

Body.java:X:XX: Variable 'px' must be private and have get/set methods.
Body.java:X:XX: Variable 'py' must be private and have get/set methods.
Body.java:X:XX: Variable 'vx' must be private and have get/set methods.
Body.java:X:XX: Variable 'vy' must be private and have get/set methods.
Body.java:X:XX: Variable 'm' must be private and have get/set methods.
Body.java:X:XX: Variable 'img' must be private and have get/set methods.
Space.java:X:XX: Variable 'bodies' must be private and have get/set methods.
Space.java:X:XX: Variable 'radius' must be private and have get/set methods.

These style warnings relate to visibility, which will be discussed in class in the coming week. However, as noted in the skeleton, do not edit these variables. Simply leave them as is. However, you must fix all other style warnings.