N-Body Simulation Checklist
Goals
- Learn about a scientific computing application.
- Learn about reading input using the
java.util.Scanner
library and formatting strings using theString.format()
function. - Learn about plotting graphics using Sedgewick’s
StdDraw
library. - Learn about using the command line to redirect standard input to read from a file.
- Learn to use arrays.
Operating System Issue
- My IDE (IntelliJ, Eclipse, etc) does not let me redirect standard input. What should I do instead?
Sorry, no IDE supports IO redirection or piping; use the Terminal/Command Prompt.
FAQ
- What preparation do I need before beginning this assignment?
Learn Sedgewick’s StdDraw and StdAudio libraries and familiarize yourself with the command line.
For help with standard input, review Students.kt. Compile and execute to make sure your system is configured properly. You can use students.txt as input.
For help with standard drawing, review BouncingBall.kt. There are many methods in
StdDraw
, but for this assignment, you will need only those used inBouncingBall.kt
. Compile and execute to make sure your system is configured properly. You will need stdlib.jar, laser.wav, pop.wav, and earth.gif.
- Any advice on completing this programming assignment?
This program is more involved than the previous ones, so you should budget more time accordingly. The best advice we can give you is to carefully test, debug, and re-test your code as you write it. Do not attempt to write the whole program at once—if you do, then you will have no idea where to find the errors when the program doesn't work. Proactive testing will save you enormous amounts of time in the long run.
- Do I have to follow the assignment specifications for reading input from command-line arguments and standard input, and writing output to standard drawing and standard output?
Yes, or you will lose a substantial number of points.
- Where can I find the APIs for
Scanner
?
The
java.util.Scanner
class documentation is here.
- Where can I find the APIs for
StdDraw
, andStdAudio
?
They are avaiable through these links: StdDraw and StdAudio.
- How do I draw an image to standard drawing?
The command
StdDraw.picture(x, y, filename)
reads the image (either JPEG, GIF, or PNG format) from the specified filename and draws it to standard drawing, centered at (x, y).
Simulation
- Can I combine Steps A, B, and C for each body?
No, as indicated in the assignment, you must separate Step A (calculate all of the forces) from Step B(iii) (update the positions); otherwise, you will be computing the forces at time t using the velocities and positions of some of bodies at time t and others at time t + Δt.
- What is Δx?
It's the change in x-coordinate between two bodies (not between a body at time t and at time t + Δt).
- How many steps should my program simulate if Τ is not a multiple of Δt?
Simulate the universe as long as t < Τ. For example, if Τ is 50,000 and Δt is 25,000, you should simulate the system for two steps (t = 0 and 25,000). If Τ is 60,000 and Δt is 25,000, you should simulate the system for three steps (t = 0, 25,000 and 50,000).
- Does my program need to plot anything if T equals 0?
No.
Errors and Bugs
- You named the variable
T
but that begins with an uppercase letter. Which name should I use instead?
By convention, a variable in Kotlin should begin with a lowercase letter and use camel case, such as
isLeapYear
. A constant variable (a variable whose value does not change during the execution of a program, or from one execution of the program to the next) should begin with an uppercase letter and use underscores to separate any word boundaries, such asGRAVITATIONAL_CONSTANT
. So, a Kotlin variable name might not always perfectly align with the corresponding domain-specific mathematical notation. In this case,tao
orstoppingTime
would be fine choices.
Why do I get an
InputMismatchException
or aNoSuchElementException
when I read data usingjava.util.Scanner
?InputMismatchException
means that your program is attempting to read data of one type but the next piece of data on standard input is of an incompatible type. For example, if you callscanner.nextInt()
and the next token on standard input is3.14
.NoSuchElementException
means that your program is trying to read data from standard input when there is no more data available from standard input. For example, if you usescanner.nextInt()
to read two integers but standard input contains only a single integer. (This can also happen if you wipe out an input file, such asplanets.txt
, by errantly typing>
instead of a<
during a redirection command.)
My computer doesn't display the animation smoothly. Is there anything I can do?
Be sure to follow the template in BouncingBall.kt. In particular, call
StdDraw.enableDoubleBuffering()
once at the beginning of your program; and callStdDraw.show()
andStdDraw.pause(20)
at the end of each time step (frame), not after each call toStdDraw.picture()
.
- My animation looks fine, but the numbers are off a little bit when I follow the tests below, what could be causing this?
Check that you followed the assignment instructions exactly. In particular, you should have separate loops for Steps A, B, and C and you should update the velocities before the positions in Step B.
- I draw the bodies, but they do not appear on the screen. Why?
Did you use
StdDraw.setXscale()
andStdDraw.setYscale()
to change the x- and y-coordinate systems to use the physics coordinates instead of the unit box? Since you want it centered on the origin with a "square radius" of radius, the minimum of each axis should be –radius and the maximum should be +radius.
- My bodies repel each other. Why don't they attract each other?
Make sure that you get the sign right when you apply Newton's law of gravitation:
(x[j] - x[i])
vs.(x[i] - x[j])
. Note that Δx and Δy can be positive or negative so do not usekotlin.math.abs()
. Do not consider changing the universal gravitational constant G to patch your code!
Sound
- When I add the music, I get the following error message on OS X. How can I fix it?
This application, or a library it uses, is using the deprecated Carbon Component Manager for hosting Audio Units. Support for this will be removed in a future release.
You can ignore it. It is a warning intended for the authors of the Mac OS X Java 6 sound library. You should update your JDK to Java 8.
- I can play MP3s using my favorite media player, but no sound plays in Java. What could be wrong?
If you are running Windows, be sure that the audio stream that Java uses is not muted via Start -> Programs -> Accessories -> Multimedia -> Volume Control -> Wave Out.
- When I add the music, I get the following error message on Windows. How can I fix it?
java.util.prefs.WindowsPreferences (init). Could not open/create prefs root node Software\JavaSoft\Prefs at root 0x80000002. Windows RegCreateKeyEx(...) returned error code 5.
Try running your command prompt as administrator (right-click on the shortcut icon). Then use it as before and try again.
Testing and Debugging
Inserting print statements is a good way to trace what your program is doing. Our input files were created with the following println()
statements.
println(n) println("%.2e".format(radius)) for (i in 0 until n) { println("%11.4e %11.4e %11.4e %11.4e %11.4e %12s" .format(px[i], py[i], vx[i], vy[i], mass[i], image[i])) }
As indicated in the collaboration policy, you may not copy or adapt code (except from the course materials). Moreover, you need to cite any code that you copy or adapt (except for code provided with the assignment). So, you are free to copy or adapt this code fragment, with or without citation.
Here are our results for a few sample inputs. Note that I had unzipped the file ‘nbody.zip’ into my current directory that contains the Kotlin source code for this project. During unzipping, the ‘nbody’ folder should have been created and other files should have been unzipped into the ‘nbody’ directory automatically.
$ java NbodyKt 0.0 25000.0 < nbody/planets.txt // zero steps 5 2.50e+11 1.4960e+11 0.0000e+00 0.0000e+00 2.9800e+04 5.9740e+24 earth.gif 2.2790e+11 0.0000e+00 0.0000e+00 2.4100e+04 6.4190e+23 mars.gif 5.7900e+10 0.0000e+00 0.0000e+00 4.7900e+04 3.3020e+23 mercury.gif 0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 1.9890e+30 sun.gif 1.0820e+11 0.0000e+00 0.0000e+00 3.5000e+04 4.8690e+24 venus.gif $ java NbodyKt 25000.0 25000.0 < nbody/planets.txt // one step 5 2.50e+11 1.4960e+11 7.4500e+08 -1.4820e+02 2.9800e+04 5.9740e+24 earth.gif 2.2790e+11 6.0250e+08 -6.3860e+01 2.4100e+04 6.4190e+23 mars.gif 5.7875e+10 1.1975e+09 -9.8933e+02 4.7900e+04 3.3020e+23 mercury.gif 3.3087e+01 0.0000e+00 1.3235e-03 0.0000e+00 1.9890e+30 sun.gif 1.0819e+11 8.7500e+08 -2.8329e+02 3.5000e+04 4.8690e+24 venus.gif $ java NbodyKt 50000.0 25000.0 < nbody/planets.txt // two steps 5 2.50e+11 1.4959e+11 1.4900e+09 -2.9640e+02 2.9799e+04 5.9740e+24 earth.gif 2.2790e+11 1.2050e+09 -1.2772e+02 2.4100e+04 6.4190e+23 mars.gif 5.7826e+10 2.3945e+09 -1.9789e+03 4.7880e+04 3.3020e+23 mercury.gif 9.9262e+01 2.8198e-01 2.6470e-03 1.1279e-05 1.9890e+30 sun.gif 1.0818e+11 1.7499e+09 -5.6660e+02 3.4998e+04 4.8690e+24 venus.gif $ java NbodyKt 60000.0 25000.0 < nbody/planets.txt // three steps 5 2.50e+11 1.4958e+11 2.2349e+09 -4.4460e+02 2.9798e+04 5.9740e+24 earth.gif 2.2789e+11 1.8075e+09 -1.9158e+02 2.4099e+04 6.4190e+23 mars.gif 5.7752e+10 3.5905e+09 -2.9682e+03 4.7839e+04 3.3020e+23 mercury.gif 1.9852e+02 1.1280e+00 3.9705e-03 3.3841e-05 1.9890e+30 sun.gif 1.0816e+11 2.6248e+09 -8.4989e+02 3.4993e+04 4.8690e+24 venus.gif $ java NbodyKt 31557600.0 25000.0 < nbody/planets.txt // one year 5 2.50e+11 1.4959e+11 -1.6531e+09 3.2949e+02 2.9798e+04 5.9740e+24 earth.gif -2.2153e+11 -4.9263e+10 5.1805e+03 -2.3640e+04 6.4190e+23 mars.gif 3.4771e+10 4.5752e+10 -3.8269e+04 2.9415e+04 3.3020e+23 mercury.gif 5.9426e+05 6.2357e+06 -5.8569e-02 1.6285e-01 1.9890e+30 sun.gif -7.3731e+10 -7.9391e+10 2.5433e+04 -2.3973e+04 4.8690e+24 venus.gif // this test should take only a few seconds. 4.294E9 is bigger than the largest Int $ java NbodyKt 4.294E9 2.147E9 < nbody/3body.txt 3 1.25e+11 2.1470e+12 -7.8082e-03 5.0000e+02 -3.6368e-12 5.9740e+24 earth.gif 1.2882e+14 -1.5100e+17 3.0000e+04 -3.5165e+07 1.9890e+30 sun.gif -1.2882e+14 1.5100e+17 -3.0000e+04 3.5165e+07 1.9890e+30 sun.gif
Possible Progress Steps
These are purely suggestions for how you might make progress. You do not have to follow these steps. If you get stumped or frustrated on some portion of the assignment, you should not hesitate to consult your lab instructor, or a TA, or a tutor.
The key to composing a large program is decomposing it into smaller steps that can be implemented and tested incrementally. Here is the decomposition we recommend for this assignment.
Parse command-line arguments
Read universe from standard input
Initialize standard drawing
Play music on standard audio
Simulate the universe
- Calculate net forces
- Update velocities and positions
- Draw universe to standard drawing
Print universe to standard output
These are the order in which the components will appear in your code. However, to facilitate testing and debugging, we recommend implementing them in the following order.
Step 1: parse command-line arguments
- Parse the two command-line arguments Τ and Δt and store their values. Name the first command-line argument
tau
orstoppingTime
since you should not begin a Java variable name with an uppercase letter. Print the variables to make sure you parsed them correctly (and in the specified order).
$ java NbodyKt 157788000.0 25000.0 < nbody/planets.txt T = 1.57788E8 dt = 25000.0
Step 2: read universe from standard input
- Review the input format specified on the assignment page. The sample input files planets.txt and 3body.txt conform to this format. Your program should be able to read either of these files, via redirection from standard input.
- Read the data from standard input, storing the number of bodies in an integer variable
n
, the radius of the universe in a floating-point variableradius
, and the remaining information in six parallel arrays: Letpx[i]
,py[i]
,vx[i]
,vy[i]
, andmass[i]
be floating-point numbers which store the current position (x- and y-coordinates), velocity (x- and y-components), and mass of bodyi
; letimage[i]
be a string which represents the filename of the image used to display bodyi
.
Step 6: print universe to standard output
- Print the universe in the specified format.
Run your program with
planets.txt
. You should see exactly the output below.$ java NbodyKt 0.0 25000.0 < nbody/planets.txt 5 2.50e+11 1.4960e+11 0.0000e+00 0.0000e+00 2.9800e+04 5.9740e+24 earth.gif 2.2790e+11 0.0000e+00 0.0000e+00 2.4100e+04 6.4190e+23 mars.gif 5.7900e+10 0.0000e+00 0.0000e+00 4.7900e+04 3.3020e+23 mercury.gif 0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 1.9890e+30 sun.gif 1.0820e+11 0.0000e+00 0.0000e+00 3.5000e+04 4.8690e+24 venus.gif
Test your program on another input file.
$ java NbodyKt 0.0 1.0 < nbody/3body-zero-gravity.txt 3 5.12e+02 0.0000e+00 0.0000e+00 1.0000e+00 1.0000e+00 1.0000e-30 earth.gif 1.2800e+02 0.0000e+00 2.0000e+00 1.0000e+00 1.0000e-40 venus.gif 0.0000e+00 1.2800e+02 1.0000e+00 2.0000e+00 1.0000e-50 mars.gif
Step 3: initialize standard drawing
- Set the scale of the standard drawing window so that (0, 0) is at the center, (−radius, −radius) is the lower-left corner, and (+radius, +radius) is the upper-right corner.
- Call
StdDraw.enableDoubleBuffering()
to enable double buffering and support animation.
Step 4: play music on standard audio
- Call
StdAudio.play("nbody/2001.wav")
to play the background music.
Step 5: simulate the universe
- Create the "time" loop. Its body will perform a single time step of the simulation, starting at t = 0.0, and continuing in Δt increments as long as t < Τ. This code goes between the code where you read the input and the code where you print the output.
- Test your program by printing the time t at each time step.
- Once you are confident that your time loop is correct, comment out the code that prints the time t at each time step.
- Steps 5C, 5B, and 5A (below) will go inside this time loop.
Step 5B: update the velocities and positions
- Write a loop to calculate the new velocity and position for each body. Since you haven't yet incorporated gravity, assume the acceleration acting on each body is zero.
Test on an artificial universe, where it is easy to check the expected results by hand.
$ java NbodyKt 1 1 < nbody/3body-zero-gravity.txt 3 5.12e+02 1.0000e+00 1.0000e+00 1.0000e+00 1.0000e+00 1.0000e-30 earth.gif 1.3000e+02 1.0000e+00 2.0000e+00 1.0000e+00 1.0000e-40 venus.gif 1.0000e+00 1.3000e+02 1.0000e+00 2.0000e+00 1.0000e-50 mars.gif $ java NbodyKt 192 1 < nbody/3body-zero-gravity.txt 3 5.12e+02 1.9200e+02 1.9200e+02 1.0000e+00 1.0000e+00 1.0000e-30 earth.gif 5.1200e+02 1.9200e+02 2.0000e+00 1.0000e+00 1.0000e-40 venus.gif 1.9200e+02 5.1200e+02 1.0000e+00 2.0000e+00 1.0000e-50 mars.gif
Step 5C: draw universe to standard drawing
- Use
StdDraw.picture(x, y, filename)
to draw the background imagestarfield.jpg
, centered at (0, 0). - Then, write a loop to display the n bodies. (This code goes after the code from Step 5B.)
Run the program with
planets.txt
. You should now see the four planets moving off the screen in a straight line, with constant velocity.$ java NbodyKt 157788000.0 25000.0 < nbody/planets.txt
- If the planets are flickering, be sure you are using double buffering, following the template in BouncingBall.kt. In particular, call
StdDraw.enableDoubleBuffering()
once at the beginning of the program; and callStdDraw.show()
andStdDraw.pause(20)
at the end of each time step (frame), not after each call toStdDraw.picture()
. Run your program on another input file, such as
kaleidoscope.txt
.$ java NbodyKt 157788000.0 25000.0 < nbody/kaleidoscope.txt
Step 5A: calculate the forces
- To calculate the net force acting on each body, use two additional arrays
fx[i]
andfy[i]
to store the net force acting on bodyi
. (This code goes before the code from Step 5B.) - Initialize all the net forces to zero.
- Write two nested
for
loops to calculate the net force exerted by bodyj
on bodyi
. Add these values tofx[i]
andfy[i]
, but skip the case wheni
equalsj
. - Once you have these values computed, go back to Step 5B and use them to compute the acceleration (instead of assuming it is zero).
- Test your program on several data files.
Enrichment
- What is the music in
nbody/2001.wav
?
It's the fanfare to Also sprach Zarathustra by Richard Strauss. It was popularized as the key musical motif in Stanley Kubrick's 1968 film 2001: A Space Odyssey.
- I'm a physicist. Why should I use the leapfrog method instead of the formula I derived in high school? In other words, why does the position update formula use the velocity at the updated time step rather than the previous one? Why not use the 1/2 a t^2 formula?
The leapfrog method is more stable for integrating Hamiltonian systems than conventional numerical methods like Euler's method or Runge–Kutta. The leapfrog method is symplectic, which means it preserves properties specific to Hamiltonian systems (conservation of linear and angular momentum, time-reversibility, and conservation of energy of the discrete Hamiltonian). In contrast, ordinary numerical methods become dissipative and exhibit qualitatively different long-term behavior. For example, the earth would slowly spiral into (or away from) the sun. For these reasons, symplectic methods are extremely popular for n-body calculations in practice. You asked!
Here's a more complete explanation of how you should interpret the variables. The classic Euler method updates the position uses the velocity at time t instead of using the updated velocity at time t + Δt. A better idea is to use the velocity at the midpoint t + Δt / 2. The leapfrog method does this in a clever way. It maintains the position and velocity one-half time step out of phase: At the beginning of an iteration, (px, py) represents the position at time t and (vx, vy) represents the velocity at time t − Δt / 2. Interpreting the position and velocity in this way, the updated position (px + Δt vx, py + Δt vy). uses the velocity at time t + Δt / 2. Almost magically, the only special care needed to deal with the half time-steps is to initialize the system's velocity at time t = −Δt / 2 (instead of t = 0.0), and you can assume that we have already done this for you. Note also that the acceleration is computed at time t so that when we update the velocity, we are using the acceleration at the midpoint of the interval under consideration.
- Are there analytic solutions known for n-body systems?
Yes, Sundman and Wang developed global analytic solutions using convergent power series. However, the series converge so slowly that they are not useful in practice. See The Solution of the N-body Problem for a brief history of the problem.
- Who uses n-body simulation?
N-body simulations play a crucial role in our understanding of the universe. Astrophysicists use it to study stellar dynamics at the galactic center, stellar dynamics in a globular cluster, colliding galaxies, and the formation of the structure of the Universe. The strongest evidence we have for the belief that there is a black hole in the center of the Milky Way comes from very accurate n-body simulations. Many of the problems that astrophysicists want to solve have millions or billions of particles. More sophisticated computational techniques are needed.
The same methods are also widely used in molecular dynamics, except that the heavenly bodies are replaced by atoms, gravity is replaced by some other force, and the leapfrog method is called Verlet's method. With van der Waals forces, the interaction energy may decay as 1/R 6 instead of an inverse square law. Occasionally, 3-way interactions must be taken into account, e.g., to account for the covalent bonds in diamond crystals.
Kepler's Orrery uses an n-body simulator to compose and play music.
- What techniques are used to do n-body simulation in practice?
Here's a wealth of information on n-body simulation.
- Any ideas for creating my own universe?
Here are some interesting two-body systems. Here is beautiful 21-body system in a figure-8 —reproducing this system (in a data file in our format) will earn you a small amount of extra credit.