next up previous contents
Next: 3.5 Plotting and Printing Up: 3. Exploring with a Previous: 3.3 Running a Code

3.4 Extending a Code

Carol:
In addition, rather than changing the program each time we change step size, I'll make it an option to be provided by the user. How about this version:



\begin{Code}[forward\_euler2a.C]
\small\verbatiminput{chap3/forward_euler2a.C} \end{Code}

Bob:
And even with a polite request to remind the user what is needed. Very nice. Let's compile, run, and plot!

Carol:
Okay. Let's direct to output to a file forward2a.out, and let's rename the old output file forward1.out, to get a consistent numbering system, where the number in the source file corresponds to the same number in the output file; the `a' after `2' is added here since different input options will produce different output files.

Bob:
How about making it even clearer and add the value of the time step that we input to the name?

Carol:
Yes, even better. Let's start with $dt = 0.01$, the same value we used by default in forward_euler1.C. It never hurts to check whether we indeed get the same result. Here goes:

|gravity> g++ -o forward_euler2 forward_euler2.C
|gravity> mv forward.out forward1.out
|gravity> forward_euler2 > forward2_0.01.out

Bob:
So much for the polite reminder. What is happening, is the computer so slow? Or is it just hanging?

Alice:
Ah, I see. By redirecting the output to forward2_0.01.out, we are also redirecting our reminder to that file, spoiling the format of our nbody snapshot output, and losing our reminder.

Carol:
Yes, of course, you are right. Well, how about putting the reminder on the error stream cerr, rather than the output stream cout? While reminding you of something is not an error, it does provide a handy way to disentangle the snapshot output, meant to be read further by a computer, and the reminder output, aimed at human consumption.

Bob:
Good idea. But while we do that, let's make another modification as well. I just realized that our halting criterion is completely wrong. If we specify dt = 0.0001, the system would bail out after only 1000 steps, as before, thus advancing the time from zero to 0.1, barely enough to see the particles move, and far too little to complete even one orbit.

Alice:
Right you are. Let's put in a time counter, so that we can keep integrating till time 10, for good measure.

Carol:
Fine! And I'll rename the previous version forward_euler2a.C, while keeping the name forward_euler2.C for the corrected version. When you are developing code, it is always a good idea to keep older versions lying around, even if they are wrong. After all, subsequent versions may turn out to be even more wrong, in which case you might well want to go back to the less wrong one! Here we are:



\begin{Code}[forward\_euler2.C]
\small\verbatiminput{chap3/forward_euler2.C} \end{Code}

Carol:
And now let's try again:



\begin{Output}
\small\verbatiminput{chap3/forward2a.output} \end{Output}

Bob:
That's curious. Almost the same results as before, but not quite. Remember, earlier we got:



\begin{Output}
\small\verbatiminput{chap3/forward2b.output} \end{Output}

Bob:
Ah, I see: same results, after all, but one more time step. Our next to last line is identical to our previous last line.

Carol:
That would imply that the new file should be one line longer. Let's check by doing a word count (actually a line-word-character count):



\begin{Output}
\small\verbatiminput{chap3/wc.output} \end{Output}

Alice:
Indeed: one line more and six words more, for the extra position and velocity components. I guess our switch from integer book keeping with the number of time steps, to floating-point book keeping by using the time introduced some slight round-off which caused the program to do one more step.

Bob:
Sounds plausible, but let's check it out. Seeing is believing! Shall we put in an extra output statement on cerr? This time we're after errors after all, namely round-off errors. Let's add the following line at the end, after the two lines starting with cout:

	cerr << t << endl;

Carol:
Done. I'll call the resulting code forward_euler2b.C, following our tradition to keep all version around for a while. Let's run the code again, but I won't bother about the snapshot output, since we already know what that is. Redirecting the cout output to /dev/null is a time-honored Unix way to get rid of it. The `null' device acts like a black hole, getting rid of whatever falls into it, without a trace. We are then left only with the cerr output on the screen.



\begin{Output}
\small\verbatiminput{chap3/forward2b_cerr.output} \end{Output}

Bob:
Hah! So much for your clever theory, Alice. It stopped at t = 10, right on the mark. So round-off cannot have been the reason.

Alice:
Not so quick in claiming victory, Bob. Look at the code. In the for loop the halting criterion is t < 10. If there would have been no round-off, the code should have stopped at t = 9.99, not at t = 10.

Bob:
That makes sense. And yet there is no round-off. It seems like we are both right, but that is impossible. What gives?

Carol:
In case of doubt, find it out. I have a sneaky suspicion that there is another round-off that is fooling us: the limited accuracy of the output. The quickest way to find out is to modify the time output line. Instead of simply printing the time, let's print the difference of the real time t and the non-round-off value t = 10. So our last error output line becomes:

	cerr << t - 10.0 << endl;



\begin{Output}
\small\verbatiminput{chap3/forward2c_cerr.output} \end{Output}

Bob:
Okay, Alice was right after all. Close but no cigar. After 1,000 steps, the time is updated to a hair's breadth smaller than 10, and the code takes one extra step. Clever detective work, Carol!

Carol:
I'm glad my course in numerical methods was good for something!

Bob:
By the way, returning to the commands you gave earlier, just before we spotted the extra output line, what was that all about, that you had to remove files by hand?

Carol:
The operating system refused, fortunately, to override the existing output file that we created with our earlier version of forward_euler2.C. This is a good thing, since it is all to easy to override (much) earlier results that are perhaps hard to recreate. Similarly, when I gave the explicit command rm to remove the file, the system checked to make sure I knew what I was doing, and I answered y for yes.

Bob:
That seems like overkill to me. You asked for a specific file to be deleted, and it checked to see whether you had learned to read and write??

Carol:
For a single file this may be overkill, but often we give a command such as rm tmp* if we want to remove all temporary files in a directory. If you (or your cat) were to hit the space bar by mistake in the middle, you could wind up with rm tmp * ...

Bob:
...I see, and thus deleting all files in that directory. No, that would not be good, I agree.

Carol:
By the way, you can choose this safety option by typing rm -i with option i for inquiry, prompting the system to question you for each file to be deleted. Or simpler, you can add this to your shell definitions, for example to your .cshrc file if you use a C shell.

Bob:
And what about !$?

Carol:
Oh, that is shorthand for retyping the last word in the previous command. I could have used that two lines earlier too, by typing rm !$. It speeds things up, just like the command !! which repeats the whole previous command. Also the second time I ran the program I could have simply typed !f which would have expanded to forward_euler2 > forward2_0.01.out. Or I could have type !-2 to repeat the command that was issued two steps before. Unix has many ways to say a lot with two or three key strokes! Here is what I could have typed:



\begin{Output}
\small\verbatiminput{chap3/csh_sample.output} \end{Output}


next up previous contents
Next: 3.5 Plotting and Printing Up: 3. Exploring with a Previous: 3.3 Running a Code
The Art of Computational Science
2004/01/25