1

I am writing software that deals with ephemerides—tables describing the positions and orientations of celestial bodies or spacecraft—and I am struggling with how to represent them in the my code.

Some background: the inputs to this software are a set of ephemeris files chosen by the user, in various file formats. I need to keep track of which files are being used in a given computation. I have a generic File superclass with subclasses for each of the file formats.

I also have ephemerides that are derived from one or more files; e.g. one file provides the Earth-to-spacecraft vector and another provides the Sun-to-Earth vector, and by combining the two I get the spacecraft-to-Sun vector.

I have two different representation schemes I am thinking of:

  1. Have an ephemeris object represent a single pair of objects. For example, Earth-to-spacecraft position, or spacecraft-to-instrument orientation. In this scheme, a file object stores one or more ephemeris objects, and an ephemeris object has one or more parents (files or other ephemerides). The interface would look something like this:

    class File {
      /* metadata not shown */
      EphemerisIterator beginEphemerides();
      EphemerisIterator endEphemerides();
    }
    
    class Ephemeris {
      Timestamp startTime(); /* time range for which the ephemeris is valid */
      Timestamp stopTime();
      FileIterator beginParentFiles(); /* iterates recursively over parent ephemerides */
      FileIterator endParentFiles();
    }
    
    class PositionEphemeris : Ephemeris {
      Object source(); /* Object is an enum with values like Object::Earth */
      Object target();
      Frame frame(); /* coordinate reference frame, e.g. Frame::J2000 */
      Vector3 position(Timestamp); /* vector from source() to target() */
    }
    
    class AttitudeEphemeris : Ephemeris {
      Frame source();
      Frame target();
      Quaternion attitude(Timestamp); /* rotation quaternion from source() to target() */
    }
    

    One disadvantage of this scheme is that the consumer of the File object has to search through a list of ephemerides to find the one they're looking for. A file may contain a lot of ephemerides—e.g. the solar system ephemeris I'm using contains the positions of the eight planets, plus Pluto, the Sun, and the Moon—but often contains only one.

    Another disadvantage is that in the multiple-ephemeris case, the user has to explicitly construct a composite ephemeris, e.g. to get Earth-to-Sun from the solar system ephemeris, you need to combine SSB (solar system barycenter)-to-Sun, SSB-to-EMB (Earth-Moon barycenter), and EMB-to-Earth.

  2. Have an ephemeris object store data for multiple objects. In this case File objects and Ephemeris objects could have a one-to-one correspondence, so a concrete class for a particular file format could subclass both.

    class File {
      /* metadata not shown */
    }
    
    class Ephemeris {
      FileIterator beginParentFiles(); /* iterates recursively over parent ephemerides */
      FileIterator endParentFiles();
    }
    
    class PositionEphemeris : Ephemeris {
      /* vector from source to target in frame */
      Vector3 position(Timestamp, Object source, Object target, Frame frame);
    }
    
    class AttitudeEphemeris : Ephemeris {
       /* rotation quaternion from source to target */
      Quaternion attitude(Timestamp, Frame source, Frame target);
    }
    

    In this scheme the work of searching for and combining the correct ephemerides is fully taken care of by the Ephemeris object; however, the search now has to be done every time position or attitude are called, which may occur in a tight inner loop (e.g. for root-finding). Plus we now need to introduce exception handling in the case that the specified source, target, and/or frame are not valid for the given ephemeris.

    Another disadvantage is that composite ephemerides take a little more work to construct. Take a ChainedPositionEphemeris: in scheme 1 it might look something like this:

    class ChainedPositionEphemeris : PositionEphemeris {
      PositionEphemeris _ephem1, _ephem2;
    
      ChainedPositionEphemeris(PositionEphemeris e1, PositionEphemeris e2) :
        _ephem1(e1), _ephem2(e2) {
        assert(_ephem1.target() == _ephem2.source());
        assert(_ephem1.frame() == _ephem2.frame());
      }
    
      Timestamp startTime() { return max(_ephem1.startTime(), _ephem2.startTime()); }
      Timestamp stopTime() { return min(_ephem1.stopTime(), _ephem2.stopTime()); }
    
      /* beginParentFiles and endParentFiles omitted */
    
      Object source() { return _ephem1.source(); }
      Object target() { return _ephem2.target(); }
      Frame frame() { return _ephem1.frame(); }
    
      Vector3 position(Timestamp t) { return _ephem1.position(t) + _ephem2.position(t); }
    }
    

    In contrast, the implementation for the second scheme would have to look something like this:

    class ChainedPositionEphemeris : PositionEphemeris {
      PositionEphemeris _base, _extension;
      Object _sharedObject;
      Set<Object> _extendedObjects;
    
      /* constructor omitted */
    
      Vector3 position(Timestamp t, Object source, Object target, Frame frame) {
        bool sourceIsExtended = source == _sharedObject || _extendedObjects.contains(source);
        bool targetIsExtended = target == _sharedObject || _extendedObjects.contains(target);
        if(sourceIsExtended && targetIsExtended)
          return _extension.position(t, source, object, frame);
        else if(sourceIsExtended)
          return _extension.position(t, source, _sharedObject, frame) +
                 _base.position(t, _sharedObject, target, frame);
        else if(targetIsExtended)
          return _extension.position(t, _sharedObject, target, frame) +
                 _base.position(t, source, _sharedObject, frame);
        else
          return _base.position(t, source, object, frame);
      }
    }
    

    A further small disadvantage is that a single ephemeris may contain unrelated data from multiple sources, making startTime and stopTime meaningless.

I'd like your opinions on which scheme would lead to easier-to-maintain code, or if there is a better representation I haven't thought of.

  • 1
    I suspect you are getting bogged down in the details of implementation, based on the data that is available. It might help if you started with "what are the queries the end-users want to make?" – BobDalgleish Sep 27 '18 at 22:31
  • Is it possible to construct some mathematical model from the input files that would enable you to relatively inexpensively compute these on demand? Or do you have to use the data read from the files as essentially a lookup table, and maybe combine or interpolate when necessary? In any case, the way you gather and represent the input data internally doesn't have to match the structure of the files, and your ephemeris objects can be a completely separate thing, and have nothing to do with the files (they could instead rely on the data as represented in memory). – Filip Milovanović Sep 28 '18 at 6:03
  • @BobDalgleish The set of "queries" used by the application is actually fixed. When I said "consumer" or "user" I was just talking about the code that will be using these objects. – 2012rcampion Sep 28 '18 at 19:12
  • @FilipMilovanović You are correct, there is not necessarily a one-to-one mapping in between ephemerides and files. As I explained, scheme 1 has a one-to-many mapping from files to ephemerides, and both schemes allow for "composite" ephemerides that don't come from a particular file. The File objects are there to 1) organize the code that parses the different file formats, and 2) keep track of which data are used to produce a particular result, for traceability and repeatability purposes. – 2012rcampion Sep 28 '18 at 19:17
  • You misunderstood me; I'm trying to suggest that maybe abstracting away from files by utilizing an (in-memory or offline) representation that's better suited for your usage scenario and querying could lead you to a more flexible design. It seems that the problem is that your code is constrained by the fact that it has to read these files (it's a "slave" to the file formats, in a sense). Maybe transforming the data on the fly, or preprocessing it could lead to simpler, better designed code. As for reproducibility, you can associate metadata with each ephemeris that keeps info about the files. – Filip Milovanović Sep 28 '18 at 21:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.