Channels ▼
RSS

C/C++

Porting the SPICE Library

Source Code Accompanies This Article. Download It Now.


Dec99: Programmer's Toolchest

Ed is a member of the JPL NAIF team and founder of the JPL Perl User Group. He can be contacted at ewright@spice.jpl.nasa.gov.


The Navigation Ancillary Information Facility (NAIF), under the direction of NASA's Office of Space Science, created the SPICE data system and library in the early 1990s to assist scientists with the planning and interpretation of scientific observations from space-borne instruments. The system provides ancillary information needed to recover the full value of science instrument data and facilitate the correlation of individual instrument data sets with data from other instruments on the same or other spacecraft.

NAIF serves as the Ancillary Data Node of NASA's Planetary Data System, responsible for the distribution of the SPICE data sets, called "kernel files," produced by NASA's planetary flight projects. SPICE data kernels exist for:

  • Spacecraft trajectory, given as a function of time (SPK kernels).
  • Planet, satellite, comet, asteroid, associated physical, and cartographic constants (PCK kernels).

  • Instrument information, including internal timing and other geometric information (I kernels).

  • C matrix, time-tagged orientation data of mounted structures and instruments (C kernels).

  • Events for the spacecraft and ground data system, both planned and unplanned (E kernels).

Hence, the SPICE acronym. NAIF also assembles and distributes PCK kernels based on software provided by JPL's Solar System Dynamics Group.

The SPICE library (SPICELIB; available via anonymous ftp at ftp://naif.jpl.nasa.gov/ in/pub/naif/toolkit) consists of 952 portable Fortran routines with 79,369 lines of executable code and 153,649 comment lines. The library contains reader subroutines to retrieve data (position, velocity, and instrument observation geometry parameters) from each of the SPICE kernels, plus a wide assortment of geometry, math, time conversion, and utility routines. SPICELIB compiles natively on several platforms, including HP, SunOS, Solaris, Macintosh, Macintosh Power PC, SGI, Windows 95/NT, DEC VAX, DEC/Alpha, and NeXT. In this article, I'll describe what's involved in porting a large Fortran library such as SPICELIB to C. I'll then examine some of the issues we've encountered in moving the C implementation to C++.

Introducing CSPICE

Many NAIF users have asked for a C version of the SPICELIB library. Why? Because not every site has access to a Fortran compiler or programmer, although most do possess a C compiler. C improves ease-of-use, and C libraries avoid cross-language I/O problems and nonportable interface issues. C integrates easily with Java, C++, and software environments such as Interactive Data Language (IDL).

Consequently, in 1997 we began development of CSPICE, a library that extends the SPICE system to the C language realm. The functionality of CSPICE (available at http://naif.jpl.nasa.gov/CSPICE.html and via anonymous ftp at naif.jpl.nasa.gov in /pub/naif/cspice) approximates that of SPICELIB, with minor differences due to the disparate properties of ANSI C versus Fortran. The CSPICE toolkit consists of an ANSI C version of SPICELIB, support library, SPICE utility programs, documentation, and example programs. The toolkit's main component -- the CSPICE library -- includes the source for all C routines generated by f2c (a freely available Fortran-to-C converter; see http://www.netlib.org/f2c/) from SPICELIB routines, both f2c support libraries (libF77, libI77), a set of hand-coded wrapper routines that encapsulate certain translated routines, and all required header files. Again, functional library ports exist for HP, Solaris, Macintosh PPC (Metrowerks compiler), SGI (o32 and n32), Windows 95/NT, Linux, and DEC Alpha Digital UNIX.

Other C libraries exist, which provide geometric/vector/matrix math functions (though not all are as numerically stable), but only CSPICE provides the ability to read and write SPICE kernel files. CSPICE also includes extensive time conversion routines and a sophisticated, user-configurable error trace/signal system that emulates exception handling.

SPICELIB to CSPICE

CSPICE requires the automatic conversion of Fortran code to C via the f2c utility. The utility creates C code that emulates the behavior of input Fortran code. The conversion of 79,000-plus lines of Fortran code to C is impractical without f2c, so lacking f2c, development of CSPICE would require a routine-by-routine rewrite of SPICELIB and take a horrendous amount of time.

NAIF uses a naming convention to distinguish between the various forms of a routine. Given a Fortran routine sub in file sub.f(or), f2c creates a C routine sub_ in file sub.c. The wrapper's name is sub_c in file sub_c.c and sub_c may call sub_. The code base of the CSPICE library is the set of all f2c translated routines.

The f2c application has several command-line options. The CSPICE library builds with the options: -u -C -a -A -P -!bs. Table 1 describes these options. The Macintosh version of CSPICE does not use the -a argument due to a 32K size limit for local variables imposed by the Metrowerks CodeWarrior compiler.

The f2c distribution consists of the source for the application, as well as the source for the libI77 and libF77 libraries, which simulate Fortran functionality in C.

Wrappers

Wrappers provide a C-friendlier interface to the more commonly used routines or to routines hand coded in C and not dependent on an f2c translation (such as math functions). Compare the program interface between a Fortran routine, its f2c counterpart, and the corresponding wrapper. Example 1 presents the argument list for the Fortran version of SPKEZ. Example 2 is the f2c created interface. Finally, Example 3 shows the argument list of the wrapper spkez_c (this wrapper calls spkez_).

spkez_c passes C strings, single values (for input), and pointers (strings and outputs); spkez_ passes only Fortran strings, pointers (input and output), and the string length values. Internally, f2c uses Fortran-style strings -- blank padded without null terminators.

Wrappers also replace system-dependent calls. SPICELIB contains several routines with such calls; the most common return Fortran equivalents of argv, argc, and system time. f2c understandably fails to translate these routines to usable C, so those are manually recoded.

A number of nonportable SPICELIB routines provide some functionality available in the standard C library. The appropriate C calls replace the CSPICE versions of the routines via either a macro or wrapper.

The CSPICE design assumes calls to the library from pure C programs, not an f2c version of some program. You should access CSPICE through a wrapper, assuming that a wrapper for the needed routine exists. Otherwise, direct calls to the f2c code base are needed. A wrapper may call other wrappers, but the f2c code base calls only other f2c code (sub1_ calls sub2_, not sub2_c).

Problems and Solutions

The process of creating a C version of a large Fortran library lends itself to numerous problems, from code format and style issues to the use of internal data types. The requirement of full ANSI compatibility for the wrappers ensures few or no portability problems. Wrapper development uses the GNU C compiler (gcc) with the arguments -ansi (support ANSI standard), -Wall (warn of all errors), and -pedantic (reject nonANSI extensions).

The NAIF team defined a coding standard for C routines that includes complete, informative, and human-understandable internal documentation. NAIF SPICELIB routines contain extensive headers that list revisions, authors, and platform-specific modifications, as well as a detailed description of the routine's function.

With regards to the generation of the wrappers, the original Fortran routines are the seeds for the wrappers. DOC2C.PL, a simple Perl script (available electronically; see "Resource Center," page 5), casts the Fortran comments to C style, then creates a skeleton for the new subroutine containing the comments. (Admittedly, the script isn't the best example of Perl coding, but I wrote it at the beginning of my involvement with CSPICE to avoid brain dulling rewrite work. I knew far less about Perl than I do now. Still, it gets the job done.)

CSPICE and f2c use typedefs to emulate Fortran data types. CSPICE deliberately uses typedefs that differ from the f2c typedefs. The basic f2c typedefs are:

typedef long int integer;

typedef short int shortint;

typedef float real;

typedef double doublereal;

typedef long int logical;

typedef long int ftnlen;

f2c treats all characters and strings as char *. The most commonly used CSPICE typedefs:

typedef char SpiceChar;

typedef double SpiceDouble;

typedef float SpiceFloat;

typedef long SpiceInt;

typedef const char ConstSpiceChar;

typedef const double ConstSpiceDouble;

typedef const float ConstSpiceFloat;

typedef const long ConstSpiceInt;

enum _Spiceboolean { SPICEFALSE = 0, SPICETRUE = 1 };

typedef enum _Spiceboolean Spice Boolean;

typedef const enum _Spiceboolean Const SpiceBoolean;

The const types are not required by a need of function, but their use ensures that input values are not unintentionally modified within a routine. All functions with input-only vectors and matrices have those arguments declared constant.

A problem exists with the function declarations in stdlib.h on the Sun platform. The f2c header file, f2c.h, defines several macros that conflict with those found in stdlib.h. As some CSPICE functions require both f2c.h and stdlib.h, our solution is to copy the needed typedefs to CSPICE instead of including f2c.h in a CSPICE header file.

The translated routines' argument list and f2c internal string formats caused most of the problems during the first stage of CSPICE development. As mentioned, f2c-converted code uses Fortran strings, consequently, a string passed from a standard C routine to a translated routine must be converted to a Fortran string; a string passed from a translated routine to a standard C routine must be converted to a C string. Translated routines do not detect possible error modes such as zero-length strings or null-character pointers, so several subroutines and a set of macros handle these string operations. As in other Fortran-C interfaces, the routines require string-length arguments.

Performance

The C code created by f2c may well emulate the behavior of the source Fortran code, but it tends to run slow without compiler optimization. Consequently, CSPICE uses optimization on all target platforms when possible. A small difficulty expressed itself, however, during the first attempts to compile CSPICE under CodeWarrior Pro 3. If the compiler optimization settings are:

Instruction Scheduling for 604,

Optimize for SPEED,

Global Optimization Level 4,

Peephole Optimization

several routines fail to compile due to memory constraints on the CodeWarrior IDE. Trial and error proved the culprit to be Global Optimization Level 4, so the offensive routines now compile at Global Optimization Level 1. Another routine resists optimization on the Microsoft platform; this routine compiles unoptimized.

C++ Issues

CSPICE is the basis for a prototype object-oriented version of the SPICE library in C++ (SPICE++, still under design). We encountered several problems with calls to CSPICE from C++ code.

All input arrays and matrices to wrappers are of type ConstSpiceDouble (const double). Some pesky compilers flag a "non-const passed to a const" warning when passing nonconst arguments. Nat Bachman wrote a set of interface macros that perform type casting for the appropriate routines. The macros prevent the warning without forcing users to explicitly declare vectors or arrays as const.

Another issue is name mangling. A C++ compiler does not create the same symbol name for a routine as a C compiler -- a consequence of C++ function overloading. To link a C library to a C++ program requires that the C routine be defined as an external routine. Again, Nat Bachman added a compile-time flag to identify the CSPICE prototypes as external functions when linking the CSPICE library against a C++ routine.

Applications of the CSPICE Library

Listing One is a program called "states" with a complete header, which retrieves SPICE kernel data to calculate a body's state with respect to some observer in a user-defined reference frame. The program demonstrates how to load SPK, PCK, and leapseconds kernel files, convert a time/date string to the epoch time (a time measured against a known reference date), then retrieve a state in a particular reference frame at the epoch time.

Other CSPICE uses include:

  • IDL, CSPICE, and a collection of interface routines allows IDL to access SPICE kernel data.
  • Solar System Calculator (SSC), a simple scripting interface to CSPICE. Originally designed by Mike Spencer, updated to use CSPICE by Ed Wright.

  • SOAP, a sophisticated orbit-analysis tool by The Aerospace Corp. for the Macintosh PPC, Windows NT, and Solaris. For more information, see http://www.aero .org/software/soap/index.html. The use of CSPICE gives SOAP users the ability to visualize trajectory data and view geometry from data in SPICE kernels. Figure 1, for instance, is a SOAP-generated view of the Cassini probe at its first encounter (fly-by) with Venus in April 1998.

Future Work

CSPICE work continues for the foreseeable future. Our current goals include an expansion of the number of wrappers, improvements in platform compatibility, C-specific documentation, and bug tracking and correction. Long range goals include the release of SPICE++, decoupling the f2c I/O libraries from CSPICE, use of dynamic memory allocation in low-level CSPICE routines, and complete documentation with tutorial code examples. CSPICE is not designed for multithreaded applications, nor does NAIF plan to add this capability. SPICELIB and CSPICE are stable across all dates and calendars.

Acknowledgment

Development of CSPICE was accomplished by the Jet Propulsion Laboratory, California Institute of Technology, under contract with NASA. My thanks to Nat Bachman, who provided input and reviews of this article.

DDJ

Listing One

/*
-Procedure states( Compute state of one body relative to another )
-Abstract
   This "cookbook" program demonstrates the use of NAIF S- and P-
   Kernel (SPK) files and subroutines to calculate the state
   (position and velocity) of one solar system body relative to
   another solar system body. The purpose of this program is twofold:
      1) To show how NAIF ephemeris data may be made available to a program.
      2) To show how the apparent, true, or geometric state
         (inertially referenced cartesian position and velocity)
         of one solar system body relative to another solar
         system body may be calculated.
   The CSPICE subroutine spklef_c (S/P Kernel, Load Ephemeris
   File) handles the first task by maintaining a database of
   ephemeris files. The calling program indicates which files
   to load by passing their names to spklef_c.

   spkezr_c (S/P Kernel, Real easy reader) handles the second task
   by accessing the data loaded with spklef_c (spkezr_c does not
   require the name of an SPK file as input).

-Copyright
   Copyright (1998), California Institute of Technology.
   U.S. Government sponsorship acknowledged.

-Input
   The program prompts the user for the following input:
      - The name of a NAIF leapseconds kernel file.
      - The name of a NAIF binary SPK ephemeris file.
      - The name for the observing body.
      - The name for the target body.
      - A time string of interest.
      - An reference frame, i.e., "J2000".
      - The type of aberration correction desired.

-Output
      - The state of the target body relative to the observing body.
      - The one-way light-time from the target body to the observing body.

-Particulars
   The user supplies a NAIF leapseconds kernel file, a NAIF binary
   SPK ephemeris file, valid names for both the target and
   observing bodies, and the time to calculate the body's state.

   Note that the `target body' and the `observing body' are both
   NAIF ephemeris objects described via their common names, and
   may be any of the following, provided ephemeris data are
   available for them in the SPK file:
      - a spacecraft
      - a planet or satellite mass center
      - a planet barycenter
      - the sun
      - the solar system barycenter
      - a comet
      - an asteroid
   By definition, the ephemerides in SPK files are continuous. The
   user can obtain states for any epoch within the interval of
   coverage. Epochs are always specified in ephemeris seconds past
   Julian year 2000 when accessing SPK files.

   The ephemeris data in a single SPK file may be referenced to a
   number of different (inertial or non-inertial) frames. The user
   can specify that states be returned in any of the recognized
   frames listed in the NAIF IDs Required Reading, including J2000
   and B1950.

   spkezr_c returns apparent, true, or geometric states depending
   on the value of the aberration flag when it is called.

   Flag    Type of correction           State computed by spkezr_c
   ---------------------------------------------------------------
   "LT+S"   light-time and stellar aberration             Apparent
   "LT"     light-time only                                   True
   "NONE"   no correction                                Geometric

   For the sake of brevity, this program performs no error checks
   on its inputs. Mistakes will cause the program to crash.

-References
   For additional information, see NAIF IDS Required Reading, and
   the headers of the CSPICE subroutines spklef_c, spkez_c and str2et_c.

-Restrictions
   None.

-Literature_References
   None.

-Author_and_Institution
   E.D. Wright     (JPL)

-Version
   -CSPICE Version 1.0.0, 01-MAR-1998   (EDW)

-&
*/

   /* Load needed headers. */
   #include <stdio.h>
   #include "SpiceUsr.h"

   /* Local declarations. */
   #define                 UTCLEN   48
   #define                 LENOUT   32
   #define                 FILELEN  72

   SpiceDouble             vec    [3];
   SpiceDouble             vec1   [3];
   SpiceDouble             vec2   [3];
   SpiceDouble             vout   [3];
   SpiceDouble             state  [6];
   SpiceDouble             lt;
   SpiceDouble             et;

   SpiceChar            *  leap;
   SpiceChar            *  spk;
   SpiceChar            *  corr;
   SpiceChar            *  ref;
   SpiceChar            *  utc;
   SpiceChar            *  format;
   SpiceChar            *  targ;
   SpiceChar            *  obs;
   SpiceChar               utcstr[ UTCLEN ];


   SpiceInt                prec;
   SpiceInt                handle;
void main()
   {
   /* Set the time output format and the precision of that output. */
   format = "C";
   prec   = 0;
   /* Start out by prompting for names of kernel files. Load each kernel as
   name is supplied. prompt_c allocates needed memory for returned strings.
   */

   /* Get and load the leapsecond kernel. */
   leap   = prompt_c ( "Enter name of leapseconds kernel    : ");
   ldpool_c ( leap );

   /* Get and load the spk kernel. */
   spk    = prompt_c ( "Enter name of SPK file              : ");
   spklef_c ( spk, &handle );

   /* Get the rest of the needed parameters. */
   targ   = prompt_c ( "Target (what am I looking at)       : ");
   ref    = prompt_c ( "Reference frame (J2000, B1950, etc.): ");
   corr   = prompt_c ( "Aberration correction                : ");
   obs    = prompt_c ( "Observer (where am I)               : ");
   utc    = prompt_c ( "Event time                          : ");

   /* Convert the time string to ephemeris time J2000. */
   str2et_c ( utc, &et );

   /* Compute the state of targ from obs at et. */
   spkezr_c (  targ, et, ref, corr, obs, state, &lt );

   /* Convert the ephemeris time to a calendar format. */
   et2utc_c ( et , format, prec, UTCLEN, utcstr );

   /*
   Everything's computed.  Output the results.  Units are
   kilometers and kilometers per second.
   */
   printf ("\n The state of %s wrt %s at UTC time %s\n", targ, obs, utcstr );
   printf ( " X :  %f KM \n", state[0] );
   printf ( " VX:  %f KMS\n", state[3] );
   printf ( " Y :  %f KM \n", state[1] );
   printf ( " VY:  %f KMS\n", state[4] );
   printf ( " Z :  %f KM \n", state[2] );
   printf ( " VZ:  %f KMS\n", state[5] );
   }


Back to Article


Copyright © 1999, Dr. Dobb's Journal

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video