This study provides an objective assessment of the Java

advertisement
Table of Contents
Chapter 1. Independent Study Overview ......................................................... 2
1. Abstract ........................................................................................................ 2
2. History .......................................................................................................... 2
3. Lessons Learned .......................................................................................... 2
Chapter 2. Runtime Performance Assessment ............................................... 4
1. Approach ...................................................................................................... 4
2. The Program - Kepler ................................................................................... 4
3. Results .......................................................................................................... 4
4. Conclusions ................................................................................................... 7
Chapter 3. Programmability Assessment ........................................................ 8
1. Floating Point Support .................................................................................. 8
2. Arrays ........................................................................................................... 9
3. Standard Library ......................................................................................... 10
Chapter 4. Current Deployments ..................................................................... 12
Chapter 5. The Answer .................................................................................... 14
Annotated Bibliography ................................................................................... 15
Appendix A. Kepler Internals .......................................................................... 18
1. The Main Algorithm ..................................................................................... 18
2. Program Validation ...................................................................................... 20
Appendix B. Kepler Source Listing ................................................................. 22
Chapter 1. Independent Study Overview
1. Abstract
This study provides an objective assessment of the Java programming language
for use in scientific computing. Several aspects of the language are assessed including:
 runtime performance
 programmability
 floating point support
 standard library richness and
 current deployment in industry.
2. History
Choice of programming language is one of those issues often fervently debated
among software engineers. Unfortunately, the people in these debates sometimes lose
their objectivity and take on the role of either "advocate" or "opponent" of a particular
technology. Decision makers who resist change run the risk of missed opportunities and
those too eager to try something new risk wasted time. Objective assessments of
programming languages are needed, and that is precisely the goal of this study.
In the field of scientific computing, there is an undeniable dichotomy between the
attitudes of veterans with decades of experience and those of a younger generation on the
cutting edge. The veterans still hold Fortran near and dear to their hearts. After all,
Fortran has the distinct honor of being the programming language that sent a man to the
moon. Unfortunately, the veterans are still suspicious of object oriented programming
and C++ (many have not yet heard of Java). While the veterans are more problem
focused, the younger generation is very technology focused and often uses a new
technology because it is “cool” regardless of whether or not it is the best for the job. Of
course, there are many who do not exemplify these stereotypes. Nevertheless, the
prevalence of these two extreme attitudes is surprising and has motivated me to undertake
this study in an objective and balanced fashion.
3. Lessons Learned
I have learned numerous lessons throughout the course of this independent study.
The most important thing that I learned was the answer to the question that I set out
answer: that Java is a suitable programming language for scientific computing. I learned
that this is true, but I also learned that Java is not likely to take the scientific community
by storm either. In fact, I now believe that there are areas in scientific computing where
Java is probably not the best solution. The main lesson learned is that Java has its place
in scientific computing.
In carrying out the performance experiments, I used an implementation of the
same program in two different languages, Java and C++. I learned just how similar these
languages are in their core syntax and semantics. Sure there are differences, but when I
completed the translation of the program in C++ and finally got it to compile, it worked
correctly the first time. I never expected that. On the other hand, it was quite an effort to
get it to compile, not because of syntax differences, but because the I/O library calls are
2
so different. This was something I had not really appreciated before. I never thought that
translating a significant program between two languages that I knew very well would be
so full of surprises.
In analyzing the programmability, I learned about the mechanics of floating point
arithmetic and about the mysterious FP-strict mode of Java. Proper use of floating point
numbers is quickly becoming a lost art and I consider this to be a very valuable lesson. I
plan to continue to study and think about floating point numbers.
One lesson that I learned that seems quite obvious in retrospect is the difficulty in
discovering all the application domains that a programming language is used for. For
some reason, I initially thought that there would be a lot of information on all the
different domains that Java was being used for, but once I began looking for this
information, I realized that there was not that much of it. It makes sense because most of
the time when a software engineer solves a problem in a particular domain he or she does
not proceed to write a report to be published for all to see. So I learned that determining
how much Java is being used for scientific computing is a very hard task that I did not
have the time or resources to really do justice too.
Finally, I think this independent study has increased my maturity as a student of
computer science. I think the opportunity to take an idea of one’s own, work that idea
into a reality, and see how it thrives and where it fails, is a rare opportunity indeed.
3
Chapter 2. Runtime Performance Assessment
1. Approach
In scientific computing, one of the most important features of a programming
language is its runtime performance. Scientific computing has a reputation for being
extremely computationally intensive. On the other hand, Java has a reputation for being a
slow language. This reputation exists in large part because early JVMs were nothing
more than mere bytecode interpreters. It is well known that in recent years advances in
JIT compiling and Hotspot optimization have dramatically improved the runtime
performance of Java. It is therefore necessary to assess the runtime performance of Java
programs using the current state of the art in JVM technology.
The strategy of this study is to develop a real scientific program in Java, translate
that program into C++, and then “race” the two. This provides a nearly perfect
experimental environment since all variables (input files, algorithms, design structure,
programmer, computer, operating system, etc) remain constant except the programming
language. Real programs are the ideal programs for benchmarking because synthetic
programs (such as those that execute an arbitrary operation in a loop) do not accurately
predict the performance of real programs [4]. It is also highly desirable to have the
comparisons done on a wide range of hardware, compilers, and runtime environments
using a wide range of scientific programs. However, due to logistical limitations such a
suite of benchmarks is beyond the scope of this study. Nevertheless, this study gives
much needed insight into the relative performance possibilities of Java.
2. The Program - Kepler
The program developed for this runtime performance study is called Kepler and it
uses Kepler's equations to propagate planetary ephemeris. Given an initial planetary state,
the program calculates a position vector for the planet between two user-specified times
and at a user-specified frequency.
To give some idea of the size and nature of the program, it has 12 classes and is
approximately 700 lines of code. The program involves numerious floating point
expressions, numerous sin and cos function calls, the taking of several square roots, a
matrix multiplication, and a root finding algorithm. Kepler's I/O operations include
reading a run control file at startup and writing out each position vector as it is calculated.
Implementations of Kepler were created in both Java and C++ for the purposes of this
study. The internals of are Kepler discussed in more detail in Appendix A.
3. Results
3.1 Initial Experimentation
Several experiments were performed using the Java version of Kepler. These
experiments were done with two JVMs (1.4.2_03 and 1.5.0 beta from Sun Microsystems)
and one native code compiler (gcj 3.3.2). Some trials were run with the –server option.
In addition, a separate version of Kepler was compiled with each class declared as
strictfp.
4
The strictfp keyword forces all floating point calculations to be done FP-strict.
FP-strict mode enforces bit-for-bit identical results among all platforms by requiring that
Benchmark
Compiler/Runtime
Environment
Performance Options
Wall Time
(sec)
Kepler (Java)
J2SE 1.4.2_03
-
345.382
Kepler (Java)
J2SE 1.4.2_03
-server
323.212
Kepler (Java)
J2SE 1.5.0 (beta)
-
356.191
Kepler (Java)
J2SE 1.5.0 (beta)
-server
312.414
Kepler (Java)
gcj 3.3.2
-O3
467.757
Kepler-strictfp (Java)
J2SE 1.4.2_03
-
364.819
Kepler-strictfp (Java)
J2SE 1.4.2_03
-server
302.496
Kepler-strictfp (Java)
J2SE 1.5.0 (beta)
-
378.12
Kepler-strictfp (Java)
J2SE 1.5.0 (beta)
-server
303.865
Kepler-strictfp (Java)
gcj 3.3.2
-O3
508.556
Kepler (C++)
g++ 3.3.2
-O3
315.702
Table 2.1 Kepler (I/O Bound) Benchmarks
all intermediate floating point results be stored in IEEE 754 format. When not in FPstrict mode, JVM implementers are free to store intermediate floating point results in
other native formats which may be faster or have greater precision. FP-strict mode is
discussed in greater detail in section 2.
Table 1 shows the wall time for the various runs and figure 1 highlights the best
runs in chart form. From this data there are several interesting observations to be made.
The three best Java runs were slightly faster than the C++ run. Surprisingly the native
code compiled version performed the worst. With the two JVMs, performance improved
in FP-strict mode when the “–server” option was used but worsened when the -server
option was not used. J2SE 1.5.0 beta did not yet seem to be much of an improvement
over J2SE 1.4.2_03 and in some cases it was slightly worse.
3.2 Forced CPU-Bound Experimentation
After carrying out these experiments it became apparent that Kepler was spending
more time doing I/O than crunching numbers. Some scientific programs operate in this
way, although this study would not be adequate without a look at a CPU-bound program.
So, Kepler was forced to be CPU-bound by altering its main algorithm to search for a
particular value in one of the components of the position vectors that it calculates and
only output it upon a match. This modification eliminated nearly all I/O operations, but
also preserved all calculations.
The results presented in table 2 and figure 2 tell quite a different story than the I/O
bound results. It is clear that Java performance lags far behind C++ taking about a factor
of 3 longer in wall time. Here native code compilation has a slight edge, outperforming
all J2SE runs. This fact is interesting because the I/O-bound results showed native code
5
compilation lagging behind the JVMs quite a bit. This suggests the existence of
inefficiencies in the I/O library used by the gcj compiler.
Figure 2.1 Best Kepler (I/O-Bound) Benchmarks
500
450
Wall Time (sec)
400
350
300
250
200
150
100
50
0
Java (Native Compilation)
Java (Hotspot JVM)
C++
Table 2.2 Kepler (CPU-Bound) Benchmarks
Benchmark
Compiler/Runtime
Environment
Performance
Options
Wall Time
(sec)
Kepler-CPU (Java)
J2SE 1.4.2_03
-
108.513
Kepler-CPU (Java)
J2SE 1.4.2_03
-server
106.532
Kepler-CPU (Java)
J2SE 1.5.0 (beta)
-
104.527
Kepler-CPU (Java)
J2SE 1.5.0 (beta)
-server
103.094
Kepler-CPU (Java)
gcj 3.3.2
-O3
85.332
Kepler-CPU-strictfp (Java)
J2SE 1.4.2_03
-
117.739
Kepler-CPU-strictfp (Java)
J2SE 1.4.2_03
-server
89.257
Kepler-CPU-strictfp (Java)
J2SE 1.5.0 (beta)
-
181.274
Kepler-CPU-strictfp (Java)
J2SE 1.5.0 (beta)
-server
87.822
Kepler-CPU-strictfp (Java)
gcj 3.3.2
-O3
85.957
Kepler-CPU (C++)
g++ 3.3.2
-O3
33.026
6
100
90
Wall Time (sec)
80
70
60
50
40
30
20
10
0
Java (Native Compilation)
Java (Hotspot JVM)
C++
Figure 2.2 Best (CPU-Bound) Benchmarks
It is important to remember that these results are only valid for the platforms and
environments that were tested and that results could vary considerably on other
platforms. The idea here is to begin to see what performance differences are possible and
this has been accomplished.
4. Conclusions
These experiments have revealed that Java can be a formidable contender to C++
for I/O-bound scientific programs. In CPU-bound programs, programmers can expect
Java to be about a factor of 3 slower for the current JVMs from Sun Microsystems FPstrict mode in conjunction with the -server option provides the best runtime performance.
7
Chapter 3. Programmability Assessment
1. Floating Point Support
1.1 The IEEE 754 Standard
Any language that is to be used for serious scientific computing must have robust
support for floating point numbers. In this section we discuss Java’s support for floating
point numbers and whether it is apt for the rigors of scientific computing.
Java uses the IEEE 754 standard for floating point arithmetic. The IEEE 754
standard describes four different floating point formats:
 single
 single-extended
 double and
 double-extended.
The single and double formats correspond to Java's float and double respectively.
Single-extended and double-extended are not supported in Java.
The IEEE 754 standard calls for floating point exceptions, such as divide by zero,
to result in a handler call. The standard requires the ability to mask those signals and
install custom handlers. This is where Java deviates from the standard. In Java no
floating point operation will ever result in an exception being raised. If a program needs
to know about such exceptional situations it must explicitly test for it. For example,
float x, y;
...
float z = x / y; // Could be divide by zero.
if (z == Float.INFINITY) {
// Handle divide by zero here...
}
Java requires all floating point variables (float or double) to be stored in the
respective IEEE 754 standard format. However, Java allows for JVM implementers to
use other native formats for intermediate results. These other formats may have more
precision and, therefore, may be less likely to result in an overflow or underflow for any
given calculation, or these other native formats may be faster. The bottom line for the
scientific programmer is that implementation differences could lead to different results on
different machines. To achieve true portability, the program must perform its
calculations in FP-strict mode. This is done on a class or method basis by including the
strictfp keyword in the respective class or method declaration. This ability to easily
guarantee identical results on different platforms is one that many scientific programmers
will appreciate.
The performance results discussed in section 1 show improved performance for
FP-strict calculations on the Intel IA32 platform. This suggests that the JVM
implementation uses a floating point format with greater precision in non-FP-strict mode
(since it was slower). So, Java programmers who do not need bit-for-bit portability
8
should experiment with FP-strict mode to see if offers better runtime performance on
their platform.
1.2 Assignment and Exponentiation
One feature of the Java language that relates to floating point numbers is that the
language requires assignments of a double precision expression to a single precision
variable to include an explicit cast. For example,
double x = 5.2;
float y = (float)(x * 2.0); // OK with cast
float z = x * 2.0;
// Error
This code results in a compilation error because the third line does not include a cast.
Although some programmers may consider this feature a nuisance, it generally results in
fewer coding defects because it is impossible to lose accuracy by accidentally mixing
single and double precision. Neither C/C++ nor Fortran have this feature.
One thing that scientific programmers versed in Fortran or Ada will miss in Java
is the exponentiation operator. In Fortran, you can efficiently raise a floating point
number to a small integer using the exponentiation operator. For example, you can cube
a variable x by the expression x**3. No such luxury exists in Java (or C/C++ for that
matter).
1.3 Arbitrary Precision Arithmetic
For those cases, where double precision is not enough the standard Java library
provides the BigDecimal class in the java.math package. The BigDecimal class implements
arbitrary precision numbers and supports all basic arithmetic operations. This package
also provides a BigInteger class for those times when arbitrarily large integers must be
stored. The following example shows the basic usage of the BigDecimal class:
BigDecimal x = new BigDecimal(4.5);
BigDecimal y = new BigDecimal(10.1);
BigDecimal z = y.add(x.multiply(x));
System.out.println(z.toString());
All told, Java’s support for floating point calculations is on par with C/C++ and
Fortran with the added bonus of arbitrary precision numbers in the standard library.
Though, it doesn’t implement the IEEE 754 standard to 100% compliance, few (if any)
languages do. The deviations do not pose any insurmountable obstacle to the scientific
programmer. Java’s floating point support should be adequate for the vast majority
scientific programs.
2. Arrays
Many scientific programs are matrix intensive, and matrices are usually
programmed using two dimensional arrays. Java has been faulted for its lack of a “true”
9
two dimensional array. Arrays in Java are objects, and so a two dimensional array is an
array of arrays. From a programmability standpoint there is essentially no difference.
The issue is really about efficiency. We have already seen that CPU-bound Java
programs under-perform C++ by a large difference.
Many scientific programs have been plagued by subtle bugs caused by out-ofbounds array accesses. Often the presence of the bug is first noticed when a program
produces a different numerical answer when it is compiled at a different optimization
level. In Java, all array element accesses are checked at runtime and any bad element
access results in an ArrayIndexOutOfBoundsException being thrown. This is one of that
features that comes for the price of higher runtime.
3. Standard Library
The standard library for Java is enormous and covers a wide range of functional
areas. This section highlights the features most interesting to scientific programmers.
3.1 Mathematical Functions
Firstly, Java contains a class in the java.lang package called Math. This is where
the scientific programmer will find the usual array of mathematical functions including
trigonometric functions, logarithms, and rounding functions. It also conveniently
contains functions to convert from degrees to radians and vice versa. There is another
class in java.lang package called StrictMath that has the exact set of methods as Math.
StrictMath is what the scientific programmer needs for bit-for-bit portability.
Scientific programs often use random numbers to simulate natural phenomenon.
In Java pseudorandom numbers can be generated with the random() method of the Math
class or, for a richer API, the Random class in package java.util can be used. This class
provides the usual linear congruential pseudorandom number generator with functions to
draw from a uniform distribution. However, Random is unique in that there is a method to
draw a random number from a Gaussian distribution. Such a function is not standard in
Fortran or C/C++.
3.2 Formatted I/O
Scientific programmers need to be able to output floating point numbers in a
variety of formats. It is worth taking a look at how the Java library provides this type of
flexible output system. Firstly, it is well know among Java programmers that you can
output a floating point number with the following code:
double x = 5.23422;
System.out.println(“x = “ + x);
In this case, the + operator is defined for String objects and can be used to concatenate a
String representation of any primitive type (in this case a double) to another String.
The Java language standard defines the above code is to be:
double x = 5.23422;
System.out.println(“x = “ + (new Double(x)).toString());
10
The API documentation of toString() for both the Double and Float classes (in java.lang)
specifies the format returned in detail. The basic gist of it is that the number will be
formatted in scientific notation if it is outside a particular range. Otherwise, it will be
formatted in standard non-scientific decimal form. In both cases, the result will contain
as many significant digits as can be provided.
Concatenating floating point numbers to a String is one way for scientific
programmers to output numbers, but more flexibility is needed. Scientific programmers
may need to output numbers with a fixed number of fractional digits or in scientific
notation or both. This type of advanced number formatting is provided in the Java library
through the DecimalFormat class (package java.text). To demonstrate its basic usage, the
following code:
DecimalFormat f1 = new DecimalFormat("0.##");
DecimalFormat f2 = new DecimalFormat("0.0E0");
double x = 10.216849;
double y = 0.000056987923;
System.out.println("format 1 ==> " + f1.format(x));
System.out.println("format 2 ==> " + f2.format(y));
produces output:
format 1 ==> 10.22
format 2 ==> 5.7E-5
The DecimalFormat class is a very general, providing for internationalization and
features necessary for financial software. For this reason, scientific programmers will
probably find the learning curve for this class steeper than for the equivalent facilities in
other popular programming languages such as C and C++.
11
Chapter 4. Current Deployments
Determining just how commonly Java is used for scientific computing is a
difficult task. The rigorous method is to conduct a large survey of firms and universities
far and wide. Such a survey is well beyond the scope of this study. However, there are
good sources of anecdotal evidence on the web and in other publications. A brief
summary of a subset of this information is provided in this chapter.
Maestro. NASA's Jet Propulsion Laboratory is using a Java program called
Maestro to command and control the Spirit and Opportunity rovers on Mars. In addition
to command and control, the program also provides advanced image analysis capabilities
including 3D terrain visualization, and frequency domain transformations. A scaled
down version of the program (without command and control functionality) is available
for download to the public. A wide audience of scientists and hobbyists use it to explore
Martian data from the comforts of their PCs. A screenshot of Maestro is shown in figure
5.1. Similar programs have been constructed for the Mars Pathfinder and Hubble Space
Telescope projects in the past.
Figure 5.1 A picture of the Martian landscape as seen in Maestro
12
JWAVE. A firm called Visual Numerics sells a product called JWAVE that
provides a large library of scientific visualization capabilities similar to those provided its
flagship PV-WAVE product. It provides, for example, capability to produce histograms
and contour plots from a Java program. Much of the functionality is available in Bean
form and is deployable in applets, servlets, as well as regular applications.
Matlab. An extremely popular product in scientific and engineering circles,
Matlab provides a wide breadth of advanced numerical capabilities through its
programming language. Matlab also includes a full JVM and the ability to call Java from
the Matlab programming language. This makes it possible to exploit both Java’s and
Matlab’s capabilities through mixed language programming.
Mathematica. Another legendary scientific computing program, the Mathematica
kernel can be exposed to any Java program through a free toolkit called J/Link
downloadable from Wolfram Research, the maker of Mathematica.
These examples merely show that there is some interest in industry in using Java
for numerical computing whether it be in a pure Java application or an application that
uses Java in conjunction with an advanced numerical kernel.
13
Chapter 5. The Answer
I initially set out to answer the question: is Java a suitable programming language
for scientific computing? With the all the information that I have produced and
uncovered, I would say definitively that Java has a place in scientific computing. No one
should expect the scientific community to make Java its main programming language of
choice anytime soon. We have seen evidence of the following points:

Java does have the infrastructure in programmability to support scientific
programming.
We have seen adequate support for floating point arithmetic and the capability of
generating bit-for-bit identical results on different platforms as well as a standard library
supporting capabilities similar to those in traditional programming languages.

Java imposes virtually no runtime penalty over traditional programming
languages for I/O bound programs or programs that are not CPU-intensive.
With the current maturity of runtime environments, we can expect increased wall times
upwards of a factor of 3 over traditional programming languages for CPU-bound
programs. For this reason,

Java is probably not an appropriate language for the high-performance computing
area.
And finally,

The advantanges of Java for scientific computing are the same as the advantages
of Java for any other type of computing: simplicity and portability with rich
capability.
These benefits have made the Maestro program successful in its global reach to scientists.
The most significant benefit of Java may lie in its web capabilities. Java enables the
deployment of scientific programs over the web through applets and servlets, and this is
why the vendors of popular products such as Matlab and Mathematica have decided to
include some form of Java compatibility in their products.
These four points summarize the findings of my independent study. It has been
my desire to study this topic while keeping personal bias aside and to be objective. I
believe that I have achieved these goals.
14
Annotated Bibliography
[1] Bate, Roger R., Donald D. Mueller and Jerry E. White. Fundamentals of
Astrodynamics. New York: Dover, 1971.
This is a good introduction to orbital mechanics and its methods. It also provides
advice for implementation of those methods on a computer. This book provided the
mathematical basis for the implementation of Kepler, the performance prototype used in
this project.
[2] Goldberg, David. “What Every Computer Scientist Should Know about
Floating-Point Arithmetic.” ACM Computing Surveys. New York: ACM
Press. Vol 23, Issue 1, pp. 5-48, 1991.
This paper provides a comprehensive discussion on floating point numbers and
demystifies esoteric topics such as round off error. The paper also discusses the IEEE
754 floating point standard and the philosophy underlying its design.
[3] Gosling, James, Bill Joy, Guy Steele, Gilad Bracha. The Java Language
Specification. Sun Microsystems: 2000.
http://java.sun.com/docs/books/jls/index.html
This is the current specification of the Java language as available on the web.
When you need to understand some minute detail of the Java language, this is the place to
turn to.
[4] Hennessy, John L. and David A. Patterson. Computer Architecture: A
Quantitative Approach. 3d. Ed. New York: Morgan Kaufman, 2003.
This excellent text on computer architecture also contains an excellent section on
performance benchmarking for computer systems. The authors of this book argue that
real programs are the ideal measures of performance for an underlying computing
system. This idea has heavily influenced the performance analysis section of this project.
[5] Mak, Roger. Java Number Cruncher. Upper Saddle River, N.J.: Prentice
Hall, 2003.
This is a good introduction to numerical computing using the Java programming
language. It is very easy to read and provides numerous visualizations of the algorithms
that it covers. This is definitely one of the kinder and gentler books on numerical
computing available.
15
[6] Mathworks Inc., The. “Matlab: The Language of Technical Computing.”
http://www.mathworks.com/products/matlab/ , 2004.
This website is the homepage for the Matlab program. All general information
about Matlab can be found here including its Java interface.
[7] Nasa Jet Propulsion Laboratory. “Maestro Headquarters.”
http://mars.telascience.org/home. 2004.
This is the home page for JPL’s Maestro software. It includes general
information about the program and how to use it and a link to download it as well.
[8] Press, William H., Saul A. Teukolsky, William T. Vetterling and Brian P.
Flannery. Numerical Recipes in C. 2d. Ed. Cambridge: Cambridge
University Press, 1992.
This book discusses implementations of many numerical algorithms, both
elementary and advanced, in the C programming language. It will be a difficult read for a
programmer without a lot of numerical programming experience. The tone is unusually
offbeat, humorous, and sometimes even arrogant.
[9] Shirazi, Jack. Java Performance Tuning. 2d. Ed. Sebastopol, C.A.: O'Reilly,
2003.
This book discusses all aspects of Java performance and tells you how to make
Java programs run as fast as possible. It also discusses general code tuning and
optimization issues, such has how to proceed deliberately in meeting performance goals.
It does not, however, provide any information on Java performance relative to other
programming languages.
[10] Sun Microsystems, Inc. Java 2 Platform, Standard Edition (J2SE),
Reference Documentation. http://java.sun.com/docs/index.html , 2004.
This is the authoritative reference for the J2SE standard library. Every package
and class is documented in detail. It also contains links to other useful Java
documentation. It is a required resource for any serious Java programmer.
[11] Visual Numerics, Inc. “JWAVE”. http://www.vni.com/products/wpd/jwave/ ,
2002.
This the product homepage for JWAVE, and the one of the sources that I used for
chapter 4.
16
[12] Wolfram Research, Inc. “Connection Technologies: J/Link.”
http://www.wolfram.com/solutions/mathlink/jlink/ , 2004.
This is the main web page for the J/Link module for Mathematica. It is yet
another source of information for chapter 4.
17
Appendix A. Kepler Internals
This appendix discusses the main algorithm and design of Kepler as well as its
validation. The source code can be found in appendix B. To fully understand the
information in this appendix requires a basic understanding of orbital mechanics.
Nevertheless, the reader can gain insight into the nature of the program without such a
background.
A run of the Kepler program calculates position vectors for a planet whose orbit is
specified in a run control file at a frequency also specified in that file. In other words, a
Kepler run may calculate the orbit of a specified planet every 10 seconds for a period of 6
months. In actuality, any number of planet orbits can be specified in the run control file.
1. The Main Algorithm
The mathematical models used to implement Kepler were based off information in
[1]. We start by describing the orbit of a planet as a 6-tuple a, e, i, Ω, ω, v 0  where
a is the semi-major axis
e is the eccentricity
i is the inclination
Ω is the longitude of ascending node
ω is the argument of periapsis
v0 is the true anomaly at epoch.
This data is encapsulated in the OrbitalElementSet class in Kepler. Given an orbit, the
algorithm that Kepler uses is described in the following 6 steps.
Step 1. Calculate Additional Values Directly from the Planet’s Orbit Definition
 e  cos v0 
E0  cos 1 

1  e cos v0 
M 0  E0  e sin E0

n
t p0 
 
a3
M0
n
2
n
E0 and M0 represent the initial eccentric anomaly and mean anomaly respectively.
These are additional parameters that describe the initial position of the planet. N is the
18
mean motion and is another property of the orbit. μ is the gravitational parameter which
is a value specific to the body around which the planet is orbiting (i.e. the Sun). tp0 is the
time since the last periapsis passage for the initial position of the planet. Finally, τ is the
orbital period.
The data calculated in this step need only be calculated once per planet and are,
therefore, encapsulated in the ExtendedOrbitState class.
Step 2) Calculate Time Since Periapsis Passage tp for Time t
t 
k 
 
t p  t  k  t p 0
First we calculate the number of revolutions that the planet has made since its
initial state. Then we calculate the time past periapsis. This value is similar to the value
we calculated in step 2 except that it is for the current time for which we are calculating
the position (time t) not the initial one.
Step 3) Calculate Mean Anomaly
M  nt p
In this step we simply calculate the mean anomaly. The mean anomaly is one of
the angular measures that indirectly represents the planet’s position. This seemingly
simple step is made tricky by the fact that the above formula can result in an angle greater
than 360 degrees. Such a condition must be detected and 360 degrees must be subtracted
out.
Step 4) Calculate Eccentric Anomaly
M  E  e sin E
In this step we solve the equation above for E, the eccentric anomaly. The above
equation is transcendental in E and, so, it must be solved numerically. Kepler simply uses
the bisection method of root finding to solve this problem.
Step 5) Calculate Perifocal Position Vector
 a(cos E  e) 
 
p  a 1  e 2 sin E 


0
19
Now the perifocal position vector is calculated. This gives a position vector but it
is not yet in the final coordinate frame that we want it in.
Step 6) Rotate Perifocal Vector to Heliocentric-Ecliptic Vector
T11  cos  cos   sin  sin  cos i
T12   cos  sin   sin  cos  cos i
T13  sin  sin i
T21  sin  cos   cos  sin  cos i
T22   sin  sin   cos  cos  cos i
T23   cos  sin i
T31  sin  sin i
T32  cos  sin i
T33  cos i


p h  Tp
In this step we build up a matrix that represents the rotation from the perifocal
coordinate system for this planet to the helio-centric ecliptic coordinate system. After we
multiply the matrix with the perifocal position vector the final answer has been
calculated.
Note that step 1 is performed once per planet and steps 2 through 6 are performed
once for each time point that position vectors are calculated for.
2. Program Validation
Scientific programs such as Kepler have output consisting of merely numbers in a
text file. This is very unlike interactive applications whose output is largely user
observed behavior. It is therefore necessary to take special action to ensure that the
program is working as expected. Since Kepler is merely a performance prototype and it
carries no specification of accuracy, it can be reasonably validated by plotting the
position vectors in 3-dimensional space to see if they generally adhere to the expected
picture.
Figure A.1 contains such a plot for the 4 innermost planets in our solar system.
We see that the scale is on about the right order of magnitude, the orbit planes are all
close to the ecliptic plane, and the orbits are centered on the point (0, 0) (i.e. the location
of the sun in the helio-centric ecliptic coordinate system).
Figure A.2 contains the plot for the 5 outermost planets. Here we also see the
planets properly centered on the point (0, 0) and the right scale approximately. In
addition we can see the orbit of Pluto to be more eccentric than the others and also much
more inclined than the others who are very close to the ecliptic plane. These are all
expected effects. In addition, it is also expected that because of Pluto’s eccentricity that a
part of its orbit is closer than the orbit of Neptune. This effect is also seen in the plot.
20
These plots provide enough evidence that Kepler is a real working scientific
program adequate for performance analysis.
Figure A.1 Plot of Kepler Data for the Innermost Planets (Axes in Km)
Figure A.2 Plot of Kepler Data for the Outermost Planets (Axes in Km)
21
Appendix B. Kepler Source Listing
This appendix contains the source code for the Java version of the performance
prototype, Kepler. This source code (as well as the C++ version) can be found in
electronic form on the web at http://www.csc.villanova.edu/~jlenthe/is/downloads.html .
/*
* EccentricAnomalyFunction.java
*/
package kepler;
/**
* This <code>Function</code> class calculates the function
* f(E) = E - e*sin(E) - M. When a root is found the
* eccentric anomaly has been found.
*/
public class EccentricAnomalyFunction implements Function {
private double m_M;
private double m_e;
/**
* @param M mean anomaly
* @param e eccentricity
*/
public EccentricAnomalyFunction(double M, double e) {
m_M = M;
m_e = e;
}
public double at(double E) {
return E - m_e*Math.sin(E) - m_M;
}
}
/* End of File */
/*
* ExtendedOrbitState.java
*/
package kepler;
/**
* This class encapsulated calculations that only need to be done once
* per planet.
*/
public class ExtendedOrbitState {
/** Eccentric Anomaly */
public double m_e0;
/** Mean Anomaly */
public double m_m0;
22
/** Time since last periapsis passage (at epoch) */
public double m_tp0;
/** Mean Motion */
public double m_n;
/** Orbital period */
public double m_tau;
/** Perifocal-to-Heliocentric-Ecliptic transformation matrix. */
public Matrix3x3 m_a = new Matrix3x3();
/**
* Constructor
* @param orb the orbit from which to derive this
* <code>ExtendedOrbitState</code>
* @param mu the gravitational parameter for the body which this planet
* is orbiting around.
*/
public ExtendedOrbitState(OrbitalElementSet orb, double mu) {
double e = orb.m_eccentricity;
double v0 = orb.m_trueAnomAtEpoch * Math.PI/180.0;
double a = orb.m_semiMajAxis;
m_e0 = Math.acos((e+Math.cos(v0))/(1+e*Math.cos(v0)));
m_m0 = m_e0 - e * Math.sin(m_e0);
m_n = Math.sqrt(mu/(a*a*a));
m_tp0 = m_m0 / m_n;
m_tau = 2*Math.PI / m_n;
double
double
double
double
double
double
sinL
cosL
sinW
cosW
sinI
cosI
=
=
=
=
=
=
Math.sin(orb.m_longAscNode
Math.cos(orb.m_longAscNode
Math.sin(orb.m_argOfPeriapsis
Math.cos(orb.m_argOfPeriapsis
Math.sin(orb.m_inclination
Math.cos(orb.m_inclination
m_a.setEntry(0,
m_a.setEntry(0,
m_a.setEntry(0,
m_a.setEntry(1,
m_a.setEntry(1,
m_a.setEntry(1,
m_a.setEntry(2,
m_a.setEntry(2,
m_a.setEntry(2,
0,
1,
2,
0,
1,
2,
0,
1,
2,
*
*
*
*
*
*
Math.PI/180.0);
Math.PI/180.0);
Math.PI/180.0);
Math.PI/180.0);
Math.PI/180.0);
Math.PI/180.0);
cosL*cosW - sinL*sinW*cosI);
-cosL*sinW - sinL*cosW*cosI);
sinL*sinI);
sinL*cosW + cosL*sinW*cosI);
-sinL*sinW + cosL*cosW*cosI);
-cosL*sinI);
sinW*sinI);
cosW*sinI);
cosI);
}
}
/* End of File */
/*
* Function.java
*/
23
package kepler;
/**
* This interface is an abstraction of a mathematical function. It requires
* an <code>at()</code> to return the value of the function at a particular
* abscissa.
*/
public interface Function {
/**
* This method returns the value of the fuction at the specified
* abscissa.
*
* @param x The abscissa to evalulate the function at.
* @return The value of the function at the abscissa.
*/
double at(double x);
}
/* End of File */
/*
* Kepler.java
*/
package kepler;
import java.io.*;
import java.util.*;
/**
* This class is the main application class of the Kepler orbit propagator.
*/
public class Kepler {
/** The run control data used in the run. */
private RunControl m_rc;
/** Create a Kepler application object with the given run control
* information */
public Kepler(RunControl rc) {
m_rc = rc;
}
/** Invoke the main driver of the application. */
public void run() throws IOException {
int nPlanets = m_rc.getNumPlanets();
for (int p = 0; p < nPlanets; p++) {
doPlanet(m_rc.getOrbit(p), m_rc.getRunParams(p));
}
}
/**
* This method does the actual work of propagating the orbit of a
* single planet.
*/
private void doPlanet(OrbitalElementSet orb, RunParams runParams)
throws IOException {
String filename = orb.m_name + ".dat";
24
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter(filename)));
printFileHeader(out, filename);
ExtendedOrbitState exOrb =
new ExtendedOrbitState(orb, m_rc.getGravParam());
double t = runParams.m_ephStart;
OrbiterState state;
while (t <= runParams.m_ephEnd) {
state = calcOrbiterState(orb, exOrb, t);
out.println("" + state.m_time + " " +
state.m_position.m_x + " " +
state.m_position.m_y + " " +
state.m_position.m_z + " " +
state.m_velocity.m_x + " " +
state.m_velocity.m_y + " " +
state.m_velocity.m_z);
t += runParams.m_timeStep;
}
out.println("# End of File");
out.close();
}
/**
* This is the heart of the orbit model. Uses Kepler's equation to find
* the position and velocity at time <code>t</code>.
*/
private OrbiterState calcOrbiterState(OrbitalElementSet orb,
ExtendedOrbitState exOrb,
double t) {
double k = Math.floor(t/exOrb.m_tau);
double tp = t - k*exOrb.m_tau + exOrb.m_tp0;
double M = exOrb.m_n * tp;
if (M > 2.0*Math.PI) {
M = M - 2.0*Math.PI;
}
Function f = new EccentricAnomalyFunction(M, orb.m_eccentricity);
double E = RootFinder.findRoot(f, 0, 2.0*Math.PI, 0.000001);
double Mdeg = M * 180.0/Math.PI;
double Edeg = E * 180.0/Math.PI;
//System.out.println("t=" + t + "
tp=" + tp + "
M=" + Mdeg + "
double cosE = Math.cos(E);
double sinE = Math.sin(E);
double e2 = orb.m_eccentricity * orb.m_eccentricity;
Vec3D
p.m_x
p.m_y
p.m_z
p
=
=
=
= new Vec3D();
orb.m_semiMajAxis * (cosE - orb.m_eccentricity);
orb.m_semiMajAxis * Math.sqrt(1.0-e2) * sinE;
0.0;
OrbiterState result = new OrbiterState();
result.m_time = t;
result.m_position = exOrb.m_a.multiply(p);
return result;
}
private void printFileHeader(PrintWriter out, String filename)
throws IOException {
String timestamp = (new Date()).toString();
out.println("# " + filename + " generated on " + timestamp);
25
E=" + Edeg);
out.println("#");
out.println("# Time Past");
out.println("# Epoch (sec) Px(km)
out.println("# ----------- -------out.println();
Py(km)
Pz(km)
-------- --------
Vx(km)
Vy(km)
Vz(km)");
-------- -------- --------");
}
/**
* This method is the main entry point for the application.
*/
public static void main(String[] args) {
PrintStream out = System.out;
PrintStream err = System.err;
if (args.length != 1) {
err.println("Please supply a single run control file name.");
System.exit(1);
}
try {
RunControl rc = new RunControl(args[0]);
//rc.display(out);
Kepler k = new Kepler(rc);
try {
k.run();
} catch (IOException e) {
err.println("I/O Error");
err.println();
err.println(e.toString());
}
} catch (FileNotFoundException e) {
err.println("Couldn't find " + args[0]);
System.exit(1);
} catch (IOException e) {
err.println("There has been an error reading " + args[0]);
System.exit(1);
} catch (RunControlSyntaxException e) {
err.println("Syntax error in file " + args[0] + ": ");
err.println("
" + e.toString());
System.exit(1);
}
}
}
/* End of File */
/*
* Matrix3x3.java
*/
package kepler;
/**
* This class represents a generic 3x3 Matrix (good for coordinate
* system transformations).
*/
public class Matrix3x3 {
/** Creates a new instance of Matrix3x3 */
26
public Matrix3x3()
m_data[0][0] =
m_data[1][1] =
m_data[2][2] =
}
{
1.0;
1.0;
1.0;
/** Creates a new instance of Matrix3x3 */
public Matrix3x3(Vec3D row1, Vec3D row2, Vec3D row3) {
m_data[0][0] = row1.m_x;
}
public Vec3D multiply(Vec3D v) {
Vec3D result = new Vec3D();
result.m_x = m_data[0][0]*v.m_x +
m_data[0][1]*v.m_y +
m_data[0][2]*v.m_z;
result.m_y = m_data[1][0]*v.m_x +
m_data[1][1]*v.m_y +
m_data[1][2]*v.m_z;
result.m_z = m_data[2][0]*v.m_x +
m_data[2][1]*v.m_y +
m_data[2][2]*v.m_z;
return result;
}
public Matrix3x3 multiply(Matrix3x3 m) {
Matrix3x3 result = new Matrix3x3();
// First col of result
result.m_data[0][0] = m_data[0][0]*m.m_data[0][0] +
m_data[0][1]*m.m_data[1][0] +
m_data[0][2]*m.m_data[2][0];
result.m_data[1][0] = m_data[1][0]*m.m_data[0][0] +
m_data[1][1]*m.m_data[1][0] +
m_data[1][2]*m.m_data[2][0];
result.m_data[2][0] = m_data[2][0]*m.m_data[0][0] +
m_data[2][1]*m.m_data[1][0] +
m_data[2][2]*m.m_data[2][0];
// Second col of result
result.m_data[0][1] = m_data[0][0]*m.m_data[0][1] +
m_data[0][1]*m.m_data[1][1] +
m_data[0][2]*m.m_data[2][1];
result.m_data[1][1] = m_data[1][0]*m.m_data[0][1] +
m_data[1][1]*m.m_data[1][1] +
m_data[1][2]*m.m_data[2][1];
result.m_data[2][1] = m_data[2][0]*m.m_data[0][1] +
m_data[2][1]*m.m_data[1][1] +
m_data[2][2]*m.m_data[2][1];
// Third col of result
result.m_data[0][2] = m_data[0][0]*m.m_data[0][2] +
m_data[0][1]*m.m_data[1][2] +
m_data[0][2]*m.m_data[2][2];
result.m_data[1][2] = m_data[1][0]*m.m_data[0][2] +
27
m_data[1][1]*m.m_data[1][2] +
m_data[1][2]*m.m_data[2][2];
result.m_data[2][2] = m_data[2][0]*m.m_data[0][2] +
m_data[2][1]*m.m_data[1][2] +
m_data[2][2]*m.m_data[2][2];
return result;
}
public double getEntry(int row, int col) {
assert row >= 0 && row <= 2 && col >= 0 && col <= 2;
return m_data[row][col];
}
public void setEntry(int row, int col, double value) {
assert row >= 0 && row <= 2 && col >= 0 && col <= 2;
m_data[row][col] = value;
}
public Matrix3x3 getTranspose() {
Matrix3x3 m = new Matrix3x3();
m.m_data[0][0] = m_data[0][0];
m.m_data[1][0] = m_data[0][1];
m.m_data[2][0] = m_data[0][2];
m.m_data[0][1] = m_data[1][0];
m.m_data[1][1] = m_data[1][1];
m.m_data[2][1] = m_data[1][2];
m.m_data[0][2] = m_data[2][0];
m.m_data[1][2] = m_data[2][1];
m.m_data[2][2] = m_data[2][2];
return m;
}
public static Matrix3x3 xRotatation(double theta) {
return new Matrix3x3();
}
public static Matrix3x3 yRotatation(double theta) {
return new Matrix3x3();
}
public static Matrix3x3 zRotatation(double theta) {
return new Matrix3x3();
}
public String toString() {
StringBuffer sb = new StringBuffer("[");
sb.append(m_data[0][0]);
sb.append(" ");
sb.append(m_data[0][1]);
sb.append(" ");
sb.append(m_data[0][2]);
sb.append("; ");
sb.append(m_data[1][0]);
sb.append(" ");
sb.append(m_data[1][1]);
sb.append(" ");
28
sb.append(m_data[1][2]);
sb.append("; ");
sb.append(m_data[2][0]);
sb.append(" ");
sb.append(m_data[2][1]);
sb.append(" ");
sb.append(m_data[2][2]);
sb.append("]");
return sb.toString();
}
private double[][] m_data = new double[3][3];
public static void main(String[] args) {
Matrix3x3 m1 = new Matrix3x3();
m1.setEntry(0, 0, 1.1);
m1.setEntry(0, 1, 1.2);
m1.setEntry(0, 2, 1.3);
m1.setEntry(1, 0, 1.4);
m1.setEntry(1, 1, 1.5);
m1.setEntry(1, 2, 1.6);
m1.setEntry(2, 0, 1.7);
m1.setEntry(2, 1, 1.8);
m1.setEntry(2, 2, 1.9);
Vec3D v = new Vec3D(1.1, 1.2, 1.3);
System.out.println("m1 = " + m1.toString());
System.out.println("v = " + v);
Vec3D r = m1.multiply(v);
System.out.println("r = " + r);
}
}
/* End of File */
/*
* OrbitalElementSet.java
*/
package kepler;
/**
* This class encapsulates the definition of a planetary orbit in terms of
* classical orbital elements (semimajor axis, eccentricity, inclination,
* argument of periapsis, longitude of ascending node, and true anomaly at
* epoch.
*
* The units of each orbital element are specified below, but the philosophy
* of <code>kepler</code> is to use metric units. Angles are in degrees for
* human interaction otherwise radians.
*/
public class OrbitalElementSet {
/** Creates a new instance of OrbitalElementSet */
public OrbitalElementSet() {
m_name = "<none>";
m_semiMajAxis = 1.0;
m_eccentricity = 0.0;
m_inclination = 0.0;
29
m_argOfPeriapsis = 0.0;
m_longAscNode = 0.0;
m_trueAnomAtEpoch = 0.0;
}
/** The name of the orbiter. */
public String m_name;
/** Semimajor Axis (km) */
public double m_semiMajAxis;
/** Eccentricity (unitless) */
public double m_eccentricity;
/** Inclination (deg) */
public double m_inclination;
/** Longitude of Ascending Node (deg) */
public double m_longAscNode;
/** Argument of Periapsis (deg) */
public double m_argOfPeriapsis;
/** True Anomaly at Epoch (deg) */
public double m_trueAnomAtEpoch;
}
/* End of File */
/*
* OrbiterState.java
*/
package kepler;
/**
* This class represents the position and velocity of a orbiting body.
*/
public class OrbiterState {
/** Creates a new instance of OrbiterState */
public OrbiterState() {
m_position = new Vec3D(1.0, 0.0, 0.0);
m_velocity = new Vec3D(0.0, 1.0, 0.0);
m_time = 0.0;
}
public OrbitalElementSet toElementSet() {
return new OrbitalElementSet();
}
public Vec3D m_position;
public Vec3D m_velocity;
public double m_time;
}
/* End of File */
30
/*
* RootFinder.java
*/
package kepler;
/**
* This class has a method that can be used to find the root of a given
* function.
*/
public class RootFinder {
/**
* This method uses the bisection method to find the root of a function
* to the accuracy specified by the <code>acc</code> parameter.
* The root of the function must be bracketed between <code>x1</code>
* and <code>x2</code>
*/
public static double findRoot(Function f, double x1, double x2,
double acc) {
// Assert that the root is bracketed.
assert f.at(x1) * f.at(x2) < 0;
// Order x1 and x2
if (x1 > x2) {
double temp = x1;
x1 = x2;
x2 = temp;
}
// Do the bisection algorithm.
double delta;
double midX;
double midY;
do {
delta = x2 - x1;
midX = x1 + delta/2.0;
midY = f.at(midX);
if (midY > 0) {
x2 = midX;
} else {
x1 = midX;
}
} while (Math.abs(x1 - x2) > acc);
return midX;
}
/**
* This method runs a test of the algorithm.
*/
public static void main(String[] args) {
class SquareEquation implements Function {
private double y;
public SquareEquation(double initY) {
y = initY;
}
public double at(double x) {
return x*x - y;
}
}
31
double v = 25.0;
RootFinder rf = new RootFinder();
SquareEquation sq = new SquareEquation(v);
double root = rf.findRoot(sq, 0.0, 25.0, 0.0000001);
System.out.println("sqrt of " + v + " = " + root);
}
}
/* End of File */
/*
* RunControl.java
*/
package kepler;
import java.util.*;
import java.io.*;
/**
* This class encapsulates all the run control data for <code>kepler</code>
* and the code that reads it in.
*/
public class RunControl {
/** The gravitational parameter for the body that the planets orbit around
(i.e. the Sun). */
private double m_gravParam;
/** An ArrayList of OrbitalElements objects for every planet read in. */
private ArrayList m_orbElements = new ArrayList();
/** An ArrayList of RunParams that correspond to the the
<code>m_orbElements</code> entries. The <code>RunParams</code>
describe how to run each planet. */
private ArrayList m_runParams = new ArrayList();
/** A temporary variable that stores the current line during parsing. */
private int m_curLine = 0;
/** A temporary variable that stores the current col during parsing. */
private int m_curCol = 0;
/**
* Create a new run control read in from a file.
* @param filename The path of the file to read in.
*/
public RunControl(String filename)
throws FileNotFoundException, IOException, RunControlSyntaxException {
BufferedReader in = new BufferedReader(new FileReader(filename));
parseInput(in);
}
/**
* @return the number of planets loaded in from the run control file.
*/
public int getNumPlanets() {
32
return m_orbElements.size();
}
/**
* @return the <code>OrbitalElementSet</code> of the i'th planet. If no
* such planet is present then null is returned.
*/
public OrbitalElementSet getOrbit(int i) {
if (i >= m_orbElements.size()) {
return null;
} else {
return (OrbitalElementSet) m_orbElements.get(i);
}
}
/**
* @return the <code>RunParams</code> for the i'th planet or null if no
* such planet exists.
*/
public RunParams getRunParams(int i) {
if (i >= m_runParams.size()) {
return null;
} else {
return (RunParams) m_runParams.get(i);
}
}
/**
* @return Gravitational parameter
*/
public double getGravParam() {
return m_gravParam;
}
/**
* This method is the main algorithm that parses the input file.
* @param in The Reader to parse the data from.
*/
private void parseInput(BufferedReader in)
throws IOException, RunControlSyntaxException {
int c = 0;
int state = 1;
eatWhitespace(in);
while (c != -1) {
in.mark(1024);
c = readChar(in);
if(c == '#') {
eatLine(in);
} else if (Character.isWhitespace((char) c)) {
eatWhitespace(in);
} else if (c != -1) {
in.reset();
if (state == 1) {
parseGravParam(in);
state = 2;
} else {
parseNextOrbit(in);
}
}
}
}
33
/**
* Read the gravitational parameter line from <code>in</code>.
*/
private void parseGravParam(BufferedReader in)
throws IOException, RunControlSyntaxException {
String paramName = getNextField(in);
if (paramName == null || !paramName.equals("gravitation_parameter")) {
throw new RunControlSyntaxException(
"Expecting \"gravitation_parameter\" at" + getLocString());
}
eatWhitespace(in);
String paramVal = getNextField(in);
try {
m_gravParam = Double.parseDouble(paramVal);
} catch (NumberFormatException e) {
throw new RunControlSyntaxException("Expecting a number at " +
getLocString());
}
}
/**
* Read the next orbit from <code>in</code> and place it in
* <code>m_</code>.
*/
private void parseNextOrbit(BufferedReader in)
throws IOException, RunControlSyntaxException {
OrbitalElementSet orb = new OrbitalElementSet();
RunParams runParams = new RunParams();
String str;
try {
orb.m_name = getNextField(in);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Semimajor Axis");
orb.m_semiMajAxis = Double.parseDouble(str);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Eccentricity");
orb.m_eccentricity = Double.parseDouble(str);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Inclination");
orb.m_inclination = Double.parseDouble(str);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Argument of Periapsis");
orb.m_argOfPeriapsis = Double.parseDouble(str);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Longitude of Ascending Node");
orb.m_longAscNode = Double.parseDouble(str);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "True Anomaly at Epoch");
orb.m_trueAnomAtEpoch = Double.parseDouble(str);
34
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Ephemeris Start");
runParams.m_ephStart = Double.parseDouble(str);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Ephemeris End");
runParams.m_ephEnd = Double.parseDouble(str);
eatWhitespace(in);
str = getNextField(in);
checkField(str, "Timestep");
runParams.m_timeStep = Double.parseDouble(str);
m_orbElements.add(orb);
m_runParams.add(runParams);
} catch (NumberFormatException e) {
throw new RunControlSyntaxException("Wrong kind of data at " +
getLocString());
}
}
/**
* Convenience method called by parseNextOrbit. Checks if field
* was present. If not, builds and exception and throws it.
* @param str the string read in.
* @param name the field name to be used in the exeception.
*/
private void checkField(String str, String name)
throws RunControlSyntaxException {
if (str == null) {
throw new RunControlSyntaxException("Expecting " + name + " at " +
getLocString());
}
}
/**
* Gets the next whitespace delimited field field on the current line (the
* BufferedReader must be positioned
*/
private String getNextField(BufferedReader in)
throws IOException, RunControlSyntaxException {
StringBuffer field = new StringBuffer();
int c = readChar(in);
while (c != -1 && !Character.isWhitespace((char) c)) {
field.append((char) c);
c = readChar(in);
}
if (field.length() == 0) {
return null;
} else {
return field.toString();
}
}
/**
* Eat characters from the current input position until the end of the
* current line.
* @param in The BufferedReader from which to eat characters.
*/
private void eatLine(BufferedReader in)
35
throws IOException {
int c = 0;
while (c != -1 && c != '\n') {
c = readChar(in);
}
}
/**
* Eat a string of whitespace up until the end of the line.
* @param The BufferedReader from which to eat characters.
*/
private void eatWhitespace(BufferedReader in)
throws IOException {
int c = ' ';
while (c != -1 && c != '\n' && Character.isWhitespace((char) c)) {
in.mark(1024);
c = readChar(in);
}
if (c != '\n') {
in.reset();
}
}
/**
* Read a character from <code>in</code> updating <code>m_curCol</code>
* <code>m_curLine</code>.
*/
private int readChar(BufferedReader in) throws IOException {
int c = in.read();
if (c == '\n') {
m_curCol = 0;
m_curLine++;
} else if (c != -1) {
m_curCol++;
}
return c;
}
/**
* @return The current input head position in the form of line,col (e.g.
* 10,45.
*/
private String getLocString() {
return m_curLine + "," + m_curCol;
}
/**
* Display the data contained in the class for debugging purposes.
*/
public void display(PrintStream out) {
assert m_orbElements.size() == m_runParams.size();
int n = m_orbElements.size();
for (int i = 0; i < n; i++) {
OrbitalElementSet orb = (OrbitalElementSet) m_orbElements.get(i);
RunParams runParams = (RunParams) m_runParams.get(i);
out.println("name=" + orb.m_name);
out.println("semi=" + orb.m_semiMajAxis);
out.println("ecc=" + orb.m_eccentricity);
out.println("inc=" + orb.m_inclination);
out.println("argop=" + orb.m_argOfPeriapsis);
out.println("longAN=" + orb.m_longAscNode);
out.println("trueA=" + orb.m_trueAnomAtEpoch);
36
out.println("ephSt=" + runParams.m_ephStart);
out.println("ephEn=" + runParams.m_ephEnd);
out.println("ts=" + runParams.m_timeStep);
out.println();
out.println();
}
}
/**
* RunControl reader test method.
*/
public static void main(String[] args) {
try {
RunControl rc = new RunControl("run_control");
rc.display(System.out);
} catch (FileNotFoundException e) {
System.err.println("Couldn't open run_control");
System.exit(1);
} catch (RunControlSyntaxException e) {
System.err.println(e.toString());
System.exit(1);
} catch (IOException e) {
e.printStackTrace(System.err);
System.exit(1);
}
}
}
/* End of File */
/*
* RunControlSyntaxException.java
*/
package kepler;
/**
* This exception class is trhown when there is a syntax error in the run
* control file.
*/
public class RunControlSyntaxException extends Exception {
/**
* Constructor
*/
RunControlSyntaxException(String reason) {
super(reason);
}
}
/* End of File */
/*
* RunParams.java
*/
package kepler;
37
/**
* This class encapsulates the
* orbit.
*/
public class RunParams {
/** The time in sec to start
public double m_ephStart;
/** The time in sec at which
public double m_ephEnd;
/** The amount of time (sec)
public double m_timeStep;
basic information about how to run a planet's
generating ephemeris for */
ephemeris generation should stop. */
between ephemeris points in the output.*/
public RunParams() {
m_ephStart = 0.0;
m_ephEnd = 0.0;
m_timeStep = 1.0;
}
}
/* End of File */
/*
* Vec3D.java
*/
package kepler;
/**
* This class represents a standard 3-element vector. It has the usual variety
* of operations: vector addition/subtraction, scalar multiplication/division,
* dot product, cross product, and unitize. The three elements of the vector
* <code>m_x</code>, <code>m_y</code>, <code>m_z</code> are public and can
* be changed anytime by the user of the Vec3D object.
*/
public class Vec3D implements Cloneable {
/** Creates a new instance of Vec3D with all members initialized to 0.0. */
public Vec3D() {
m_x = 0.0;
m_y = 0.0;
m_z = 0.0;
}
/** Creates a new instance of Vec3D with members initialized to
parameters. */
public Vec3D(double x, double y, double z) {
m_x = x;
m_y = y;
m_z = z;
}
/**
* Dot Product
* @return dot product of this Vec3D and <code>r</code>.
*/
public double dot(Vec3D r) {
return m_x*r.m_x + m_y*r.m_y + m_z*r.m_z;
}
38
/**
* Cross Product
* @return cross product of this Vec3D and <code>r</code>.
*/
public Vec3D cross(Vec3D r) {
Vec3D result = new Vec3D();
result.m_x = m_y*r.m_y - m_z*r.m_y;
result.m_y = m_z*r.m_x - m_x*r.m_z;
result.m_z = m_x*r.m_y - m_y*r.m_x;
return result;
}
/**
* Negation
* @return A new Vec3D that is the negative of this Vec3D
*/
public Vec3D negative() {
Vec3D result = new Vec3D(-m_x, -m_y, -m_z);
return result;
}
/**
* Vector addition.
* @return A new Vec3D that is this Vec3D added to <code>a</code>
*/
public Vec3D add(Vec3D a) {
Vec3D result = new Vec3D(m_x+a.m_x, m_y+a.m_y, m_z+a.m_z);
return result;
}
/**
* Vector subtraction.
* @return A new Vec3D that is <code>s</code> subtracted from this Vec3D.
*/
public Vec3D subtract(Vec3D s) {
return add(s.negative());
}
/**
* Scalar multiplication.
* @return A new Vec3D that is the scalar multiple of this Vec3D and
* <code>f</code>
*/
public Vec3D multiply(double f) {
Vec3D result = new Vec3D(f*m_x, f*m_y, f*m_z);
return result;
}
/**
* Scalar division.
* @return A new Vec3D that is the scalar dividend of this Vec3D and
* <code>d</code>
*/
public Vec3D divide(double d) {
Vec3D result = new Vec3D(m_x/d, m_y/d, m_z/d);
return result;
}
/**
* Magnitude.
* @return manitude of the vector.
39
*/
public double mag() {
return Math.sqrt(m_x*m_x + m_y*m_y + m_z*m_z);
}
/**
* Unitizes the vector.
*/
public void unitize() {
double m = this.mag();
m_x /= m;
m_y /= m;
m_z /= m;
}
/**
* Calculate unit vector.
* @return a new vector that is the unit vector of this vector.
*/
public Vec3D unitVector() {
Vec3D n = (Vec3D) this.clone();
n.unitize();
return n;
}
/**
* Converts this Vec3D to a nice printable <code>String</code>.
*/
public String toString() {
return "[" + m_x + " " + m_y + " " + m_z + "]";
}
/**
* Clones this Vec3D.
*/
public Object clone() {
Object o = null;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
}
return o;
}
/** The first element of the vector. */
public double m_x;
/** The second element of the vector. */
public double m_y;
/** The third element of the vector. */
public double m_z;
/**
* Vec3D test driver.
*/
public static void main(String[] args) {
Vec3D u = new Vec3D(-3.1433442478798322E7,
4.869290210297687E7,
6873692.051502123);
Vec3D v = new Vec3D(-6.877397621142556E7,
-5277100.001259293,
40
5841996.722290518);
double beta = Math.acos((u.unitVector()).dot(v.unitVector())) *
180.0/Math.PI;
System.out.println("acos(u . v) = " + beta);
Vec3D c;
c = u.cross(v).unitVector();
Vec3D z = new Vec3D(0.0, 0.0, 1.0);
double ang = Math.acos(c.dot(z)) * 180.0/Math.PI;
System.out.println("mag = " + c.mag());
System.out.println("ang = " + ang);
}
}
/* End of File */
41
Download