JEphem Informatic Trail build classes source code TestVSOP87.java
//*********************************************************************************
// class TestVSOP87 // default package
// Software released under the General Public License (version 2 or later), available at
// http://www.gnu.org/copyleft/gpl.html
//*********************************************************************************

import jephem.astro.planets.vsop87.VSOP87;
import jephem.astro.Body;
import jephem.astro.SolarSystemConstants;
import jephem.util.JEphemException;
import jephem.astro.AstroException;
import java.io.*;
import java.util.Date;
import java.text.NumberFormat;

/******************************************************************
Contains methods used to test package <CODE>jephem.astro.planets.vsop87</CODE>.
<BR>JEphem program doesn't use it ; it was used to build JEphem.
<BR>This class is dependant from <CODE>jephem</CODE> package, which must be in the classpath.
<BR>In this class, when arrays characterizing planetary data were needed (ex : for "planetNames"), the indexes of <CODE>jephem.astro.SolarSystemConstants.java</CODE> were used.
@author Thierry Graff
@history jan 20 2001 creation.
@history dec 18 2001 review, cleaned the comments.

@todo modify formulae in calcGeocentricError() (take eccentricity into account)
*****************************************************************/
public abstract class TestVSOP87 implements SolarSystemConstants{

  //=================================================================================
  //                                 CONSTANTS
  //=================================================================================

  /** Filter applied to BDL series (full precision or truncated). */
  private static int testedFilter;
  /** Type of output. String of 2 characters ; each character indicates if the corresponding type
  of output is desired. See parameter 'OutputType' of testJEphemPrecision. */
  private static String strOutputType;

  /** Number of JDs concerned by the test. */
  private static int _nbJD;
  /** Number of planets concerned by the test. */
  private static int _nbPlan;
  private static final int NB_COORD = 6;

  /** Values of the tests computed by BDL. */
  private static JEphemTest[][] bdlT;
  /** Values of the tests computed by JEphem. */
  private static JEphemTest[][] jephemT;

  /** Differences between the values of JEphem and BDL [planet][jd][coord]. */
  private static double[][][] delta;
  /** Norms of vectors representing the difference of position [planet][jd]. */
  private static double[][] deltaNormPos;
  /** Norms of vectors representing the difference of velocity [planet][jd]. */
  private static double[][] deltaNormVel;
  /** Maximal norms of vectors representing the difference of position [planet]. */
  private static double[] deltaMaxNormPos;
  /** Maximal norms of vectors representing the difference of velocity [planet]. */
  private static double[] deltaMaxNormVel;

  /** To know computation time. */
  private static Date startCalcTime, endCalcTime;

  //=================================================================================
  //                                 CONSTANTS
  //=================================================================================

  // planet names ; indexes correspond to jephem.astro.ISolarSystem.java constants.
  private static String[] planetNames	= {"", "", "Mercury", "Venus", "Earth", "Mars", "Jupiter",
                                  "Saturn", "Uranus", "Neptune"};
  // semi-major axis ; index corresopnd to jephem.astro.ISolarSystem.java constants.
  private final static double[] a0s = {0.0, 0.0, 0.39, 0.72, 1.00, 1.52, 5.20, 9.55, 19.22, 30.11}; // au
  // eccentricities ; index corresopnd to jephem.astro.ISolarSystem.java constants.
  private static double[] eccs = {0.0, 0.0, 0.21, 0.01, 0.02, 0.09, 0.05, 0.06, 0.05, 0.01};

  private static final String[] strUnitPos = {"au", "au", "au", "au/d", "au/d", "au/d"};
  private static final String[] coordNames = {"x", "y", "z", "x'", "y'", "z'"};

  // Repeats fields of JEphem
  private static final int FILTER_FULL = 0;
  private static final int FILTER_JEPHEM = 1;
  private static final int FILTER_BDL = 2;

  // Absolute path to the files containing terms for summation (for full prec. only)
  private final static String DATA_PATH = "d:\\b_dvpt\\jephem\\data\\astro\\planets\\vsop87\\VSOP87A\\";

  private static final String LS = System.getProperty("line.separator");
  private static final NumberFormat NF = NumberFormat.getNumberInstance();
  private static final NumberFormat NF2 = NumberFormat.getNumberInstance();
  static{
    NF.setMaximumFractionDigits(10);
    NF.setMinimumFractionDigits(10);
    NF2.setMaximumFractionDigits(3);
    NF2.setMinimumFractionDigits(3);
  };


  //=================================================================================
  //                                 PUBLIC METHODS
  //=================================================================================

  //****************** main() ************************
  /** Entry point to call the different methods of the class.
  <BR>If the first element of <CODE>args</CODE> is "testPerf", <CODE>testPerf()</CODE> is called.
  <BR>Otherwise, <CODE>testJEphemPrecision()</CODE> is called.
  */
  public static void main(String[] args){
//    for (int i = 0; i < args.length; i++) System.out.println(args[i]);
    if(args[0].equalsIgnoreCase("testPerf")){
      testPerf(args[1], args[2], args[3]);
    }
    else
      testJEphemPrecision(args[0], args[1], args[2], args[3], args[4], args[5]);
  }// end main

  //**************** testJEphemPrecision() ****************************************
  /** Method used to test results from JEphem compared with BDL text files.
  <LI>To work, the check file must be in the current directory.</LI>
  <LI>This method assumes that the corresponding version is used by JEphem.</LI>
  <LI>To make it run, jephem package must be in the classpath (a simple way is to use the
  'classpath' option from the 'java' tool).</LI>
  <LI>To work properly, all the tested planets must be computable by JEphem (for example,
  'EARTH-MOON' was removed from the file to test VSOP87A).</LI>

  <BR><BR>It parses the .chk file, calls JEphem to compute the corresponding results (for all
  JDs and planets), put all these data in arrays.
  <B>Output</B> : a serie of tables, HTML formatted, permitting to compare the results of JEphem
  with tests provided by the BDL. Result can be seen on JEphemSite.

  <BR><BR>Example of call : <CODE>TestVSOP87 vsop87A.chk 10 8 FULL DETAIL,DIFF,SUMMARY test_jephem_full.htm</CODE>

  @param checkFile : Name of a file containing extracts from ftp://ftp.bdl.fr/pub/planets/... VSOP87.chk,
  containing only the tests for the concerned version.
  <BR>'checkFile' name is assumed to start with "VSOP87X", where X represents the Version letter.
  @param strNbJD Number of julian days concerned with the tests.
  @param strNbPlan Number of planets concerned with the tests.
  @param strFilter Part of JEphem to test (full precision, truncated). Can take the values "JEPHEM"
  or "BDL" or "FULL".
  @param outputType Output to generate : can be :
  <LI>"DETAIL" : comparison of each value of the tests with the values computed by JEphem, ordered
   by julian day ;</LI>
  <LI>"DIFF" : differences between BDL results and JEphem, ordered by planet.</LI>
  <LI>"SUMMARY" A summary table containing only the maximal geocentric errors.</LI>
  To get more than one of these outputs, write the different values separated with comas, without
  space (ex : "DETAIL,DIFF").
  @param outputFile Name of the file to be generated (HTML format).

  @history jan 20 2001 : creation.
  @history aug 15 2001 : adaptation to new version of VSOP87.java.

  @better a matching could be done with planetNames
  ********************************************************************************/
  public static void testJEphemPrecision(String checkFile, String strJD, String strNbPlan,
                                         String strFilter, String outputType, String outputFile){

    // *** 1 - Parameter parsing
    // _nbJD
    _nbJD = Integer.parseInt(strJD);
System.out.println("_nbJD = " + _nbJD);
    // _nbPlan
    _nbPlan = Integer.parseInt(strNbPlan);
System.out.println("_nbPlan = " + _nbPlan);
    // tested filter
    if (strFilter.indexOf("FULL") != -1){
      testedFilter = FILTER_FULL;
    }
    else if (strFilter.indexOf("BDL") != -1){
      testedFilter = FILTER_BDL;
    }
    else if (strFilter.indexOf("JEPHEM") != -1){
      testedFilter = FILTER_JEPHEM;
    }
    else {
      System.out.println("'strFilter' parameter not correct");
      return;
    }
System.out.println("strFilter = " + strFilter);
    //output type
    strOutputType = "";
    if (outputType.indexOf("DETAIL") != -1) strOutputType += "1";
    else strOutputType += "0";
    if (outputType.indexOf("DIFF") != -1) strOutputType += "1";
    else strOutputType += "0";
    if (outputType.indexOf("SUMMARY") != -1) strOutputType += "1";
    else strOutputType += "0";
    if (outputType.equals("000")){
      System.out.println("No correct output found - check 'ouputType' parameter");
      return;
    }
System.out.println("strOutputType = " + strOutputType);


    // *** 2 - get the data
    System.out.println("Getting BDL results...");
    getBDLResults(checkFile);
    System.out.println("Getting JEphem results...");
    getJEphemResults();
    System.out.println("Computing the differences...");
    getDifferences();

    // *** 3 - compute the output
    System.out.println("Generating the output...");
    try{
      FileOutputStream fos = new FileOutputStream(new File(outputFile));

      String strRes = buildOutputBeginning();
      fos.write(strRes.getBytes());

      if (strOutputType.charAt(2) == '1')
        buildOutputSummary(fos);
      if (strOutputType.charAt(1) == '1')
        buildOutputDiffByPlanet(fos);
      if (strOutputType.charAt(0) == '1')
        buildOutputDetailByJD(fos);

      //End
      strRes = "</BODY></HTML>";

      fos.write(strRes.getBytes());
      fos.close();
    }
    catch(Exception e){
      e.printStackTrace();
      System.exit(0);
    }

  }// end testJEphemPrecision

  //**************************** testPerf() **************
  /** Calls <CODE>jephem.astro.planets.vsop87.VSOP87</CODE> several times and displays the time taken by the 
  computations.
  */
  //2451545.0 = 1/1/2000
  public static void testPerf(String strNbDates, String strBaseJD,
                              String strPrecision){
    try{
      // parameter parsing
      int nbDates = Integer.parseInt(strNbDates);
      double baseJD = Double.parseDouble(strBaseJD);
      double precision = Double.parseDouble(strPrecision);

      System.out.println(nbDates + " computations");
      System.out.println("Precision : " + precision);

      VSOP87.setDataPath(DATA_PATH);

      int i;
      Body b = new Body(MERCURY);
      double[] dates = new double[nbDates];
      // build an array of dates - centered on baseJD
      for (i = 0; i < nbDates; i++){
        dates[i] = baseJD - (double)(nbDates/2 + i);
      }
      // get the time
      long begTime = System.currentTimeMillis();
      for (i = 0; i < nbDates; i++){
        VSOP87.calcCoord(dates[i], b, precision, true);
      }
      long endTime = System.currentTimeMillis();
      long deltaT = endTime - begTime;
      double timeForOne = (double)deltaT/(double)nbDates;

      System.out.println("deltaT = " + deltaT);
      System.out.println("timeForOne = " + timeForOne);

    }
    catch(Exception e){
      e.printStackTrace();
      System.exit(0);
    }

  }// end testPerf

  //=================================================================================
  //                                 PRIVATE METHODS
  //=================================================================================

  //******************************** getBDLResults() *****************************
  private static void getBDLResults(String checkFile){
    try{

      bdlT = new JEphemTest[_nbPlan][_nbJD];

      int iJD, iPlanet, iCoord; // main indexes

      String line = new String();
//  		FileInputStream fis = new FileInputStream(new File("test_jephem.htm"));
      LineNumberReader lnr = new LineNumberReader(new FileReader(new File(checkFile)));

      String strTmp; // temporary variable
      int iSpace; // temporary variable
      int planetNumber; // number of the planet, corresponding to VSOP87.java

      // for a couple (planet, JD, a group of 4 lines is read
      for (iPlanet = 0; iPlanet < _nbPlan; iPlanet ++){
        for (iJD = 0; iJD < _nbJD; iJD ++){

          bdlT[iPlanet][iJD] = new JEphemTest();

        // *** 1.1 Read comment line
          line = lnr.readLine().trim();
          // get rid of "VSOP87X"
          iSpace = line.indexOf(" ");
          line = line.substring(iSpace).trim();
          // get planet name
          iSpace = line.indexOf(" ");
          strTmp = line.substring(0, iSpace).trim();
          // @better2
          planetNumber = 0;
          if (strTmp.equals("MERCURY")) planetNumber = 2;
          else if (strTmp.equals("VENUS")) planetNumber = 3;
          else if (strTmp.equals("EARTH")) planetNumber = 4;
          else if (strTmp.equals("MARS")) planetNumber = 5;
          else if (strTmp.equals("JUPITER")) planetNumber = 6;
          else if (strTmp.equals("SATURN")) planetNumber = 7;
          else if (strTmp.equals("URANUS")) planetNumber = 8;
          else if (strTmp.equals("NEPTUNE")) planetNumber = 9;
          //else error;
          bdlT[iPlanet][iJD].body.setIndex(planetNumber);
          // get JD
          line = line.substring(iSpace).trim();
          iSpace = line.indexOf(" ");
          strTmp = line.substring(2, iSpace).trim(); // 2 because of "JD" in front of the number
          bdlT[iPlanet][iJD].jd = Double.parseDouble(strTmp);
          // get strDate
          line = line.substring(iSpace).trim();
          bdlT[iPlanet][iJD].strDate = line;

        // *** 1.2 line containing positions and velocities
          for (int i=0; i<2; i++){
            line = lnr.readLine().trim();

            // get rid of "x"
            iSpace = line.indexOf(" ");
            line = line.substring(iSpace).trim();
            // get x
            iSpace = line.indexOf(" ");
            strTmp = line.substring(0, iSpace).trim();
            bdlT[iPlanet][iJD].body.setCoord(0 + 3*i, Double.parseDouble(strTmp));
            line = line.substring(iSpace).trim();
            // get rid of "au"
            iSpace = line.indexOf(" ");
            line = line.substring(iSpace).trim();
            // get rid of "y"
            iSpace = line.indexOf(" ");
            line = line.substring(iSpace).trim();
            // get y
            iSpace = line.indexOf(" ");
            strTmp = line.substring(0, iSpace).trim();
            bdlT[iPlanet][iJD].body.setCoord(1 + 3*i, Double.parseDouble(strTmp));
            line = line.substring(iSpace).trim();
            // get rid of "au"
            iSpace = line.indexOf(" ");
            line = line.substring(iSpace).trim();
            // get rid of "z"
            iSpace = line.indexOf(" ");
            line = line.substring(iSpace).trim();
            // get z
            iSpace = line.indexOf(" ");
            strTmp = line.substring(0, iSpace).trim();
            bdlT[iPlanet][iJD].body.setCoord(2 + 3*i, Double.parseDouble(strTmp));
          } // end for i = 1 to 2

        // *** 1.2 empty line
          line = lnr.readLine();
        } // end for iJD
      } // end for iPlanet

      lnr.close();
//      fis.close();
    }
    catch(Exception e){
      System.out.println("Exception" + e.toString());
      e.printStackTrace();
      System.exit(0);
    }
  }// end getBDLResults()

  //******************************** getJEphemResults() **************************
  private static void getJEphemResults(){

    //if (testedFilter == FILTER_FULL) VSOP87.setDataPath(DATA_PATH);
    VSOP87.setDataPath(DATA_PATH);

    int iJD, iPlanet, iCoord; // main indexes

    jephemT = new JEphemTest[_nbPlan][_nbJD];
    startCalcTime = new Date();
    for (iPlanet = 0; iPlanet < _nbPlan; iPlanet ++){
      for (iJD = 0; iJD < _nbJD; iJD ++){

        jephemT[iPlanet][iJD] = new JEphemTest(bdlT[iPlanet][iJD].body.getIndex());

        jephemT[iPlanet][iJD].jd = bdlT[iPlanet][iJD].jd;
        jephemT[iPlanet][iJD].strDate = bdlT[iPlanet][iJD].strDate;
        //System.out.println("Computing : "
        //                   + planetNames[jephemT[iPlanet][iJD].body.getIndex()]
        //                   + ", " + jephemT[iPlanet][iJD].strDate);

        // ***** Call to JEphem *****
        double precision = 0;
        if (testedFilter == FILTER_FULL) precision = 0;
        if (testedFilter == FILTER_BDL || testedFilter == FILTER_JEPHEM) precision = 4;
        try{
          VSOP87.calcCoord(jephemT[iPlanet][iJD].jd, jephemT[iPlanet][iJD].body, precision, true);
        }
        catch(Exception e){
          System.out.println("error in VSOP87.calcCoord()  iPlanet = " + iPlanet
                              + "   jd = " + jephemT[iPlanet][iJD].jd);
          System.out.println("    " + e.toString());
        }
      } // end for iJD
    } // end for iPlanet
    endCalcTime = new Date();

  }// end getJEphemResults()

  //******************************** getDifferences() **************************
  /** Gets the differences between full and truncated versions. */
  private static void getDifferences(){

    int iJD, iPlanet, iCoord; // main indexes
    int retCode;

    double tmp;
    delta = new double[_nbPlan][_nbJD][NB_COORD];
    deltaNormPos = new double[_nbPlan][_nbJD];
    deltaNormVel = new double[_nbPlan][_nbJD];
    deltaMaxNormPos = new double[_nbPlan];
    deltaMaxNormVel = new double[_nbPlan];

    for (iPlanet = 0; iPlanet < _nbPlan; iPlanet ++){
      deltaMaxNormVel[iPlanet] = deltaMaxNormPos[iPlanet] = 0.0;
      for (iJD = 0; iJD < _nbJD; iJD ++){
        tmp = 0;
        // for positions
        for (iCoord = 0; iCoord < NB_COORD/2; iCoord ++){
          delta[iPlanet][iJD][iCoord] = Math.abs(jephemT[iPlanet][iJD].body.getCoord(iCoord) -
                                                 bdlT[iPlanet][iJD].body.getCoord(iCoord));
          tmp += delta[iPlanet][iJD][iCoord] * delta[iPlanet][iJD][iCoord];
        }
        deltaNormPos[iPlanet][iJD] = Math.sqrt(tmp);
        if (deltaNormPos[iPlanet][iJD] > deltaMaxNormPos[iPlanet])
          deltaMaxNormPos[iPlanet] = deltaNormPos[iPlanet][iJD];
        // for velocities
        tmp = 0;
        for (iCoord = NB_COORD/2; iCoord < NB_COORD; iCoord ++){
          delta[iPlanet][iJD][iCoord] = Math.abs(jephemT[iPlanet][iJD].body.getCoord(iCoord) -
                                                 bdlT[iPlanet][iJD].body.getCoord(iCoord));
          tmp += delta[iPlanet][iJD][iCoord] * delta[iPlanet][iJD][iCoord];
        }
        deltaNormVel[iPlanet][iJD] = Math.sqrt(tmp);
        if (deltaNormVel[iPlanet][iJD] > deltaMaxNormVel[iPlanet])
          deltaMaxNormVel[iPlanet] = deltaNormVel[iPlanet][iJD];
      } // end for iJD
    } // end for iPlanet

  }// end getDifferences()

  //******************************** buildOutputBeginning() **************************
  private static String buildOutputBeginning(){

    String strRes = new String();

    strRes += "<HTML>" + LS;
    strRes += "<HEAD>" + LS;
    strRes += "<STYLE>" + LS;
    strRes += "TABLE {text-align:center}" + LS;
    strRes += "TABLE.Small {font-size:smaller}" + LS;
    strRes += "</STYLE>" + LS;
    strRes += "</HEAD>" + LS;
    strRes += "<BODY>" + LS;

    strRes += "<CENTER><H1>JEphem precision tests</H1>" +LS;
    strRes += "Generated by <CODE>TestVSOP87.java</CODE></CENTER>" + LS;

    strRes += "<BR>Version of JEphem used : <B>";
    if(testedFilter == FILTER_FULL)
      strRes +=  "full precision";
    if(testedFilter == FILTER_BDL || testedFilter == FILTER_JEPHEM)
      strRes +=  "truncated series";
    strRes += ".</B>" + LS;
    strRes += "<BR>Time to perform " + Integer.toString(_nbPlan * _nbJD) + " computations : "
           + Double.toString((endCalcTime.getTime() - startCalcTime.getTime()) / 1000.0)
           + " seconds.<BR>" + LS + LS;
    strRes += "<BR>In this page : " + LS;
    if (strOutputType.charAt(2) == '1')
      strRes += "Summary&nbsp;&nbsp;&nbsp;&nbsp;" + LS;      strRes += "<A HREF=\"#Summary\">Summary</A>    " + LS;
    if (strOutputType.charAt(1) == '1')
      strRes += "Difference tables&nbsp;&nbsp;&nbsp;&nbsp;" + LS;      strRes += "<A HREF=\"#DiffByPlan\">Difference tables</A>    " + LS;
    if (strOutputType.charAt(0) == '1')
      strRes += "Detailed tests&nbsp;&nbsp;&nbsp;" + LS;      strRes += "<A HREF=\"#DetailByJD\">Detailed tests</A>   " + LS;

    return strRes;
  }// end buildOutputBeginning()

  //******************************** buildOutputDetailByJD() *********************
  /** Generates tables showing all the results, grouped by julian days */
  private static void buildOutputDetailByJD(FileOutputStream fos){
    try{
      int iJD, iPlanet, iCoord; // main indexes
      String strRes = new String();

      strRes += LS + LS + "<!-- ************************************************** -->" + LS;
      strRes += "<H2><A NAME=\"DetailByJD\"></A>Detailed tables by JD</H2>" + LS;
      // for each julian day
      for (iJD = 0; iJD < _nbJD; iJD ++){
        // title
        strRes += "<TABLE CLASS=\"Small\" BORDER = \"1\">" + LS;
        strRes += "<TR><TD COLSPAN = \"2\"></TD>" + LS;
        strRes += "<TD COLSPAN = \"6\"><B>" + LS;
        strRes += "JD = " + Double.toString(bdlT[0][iJD].jd); // 0 because JDs are the same
        strRes += "    " + bdlT[0][iJD].strDate; //for different planets
        strRes += "</B></TD></TR>" + LS;
        // legends
        strRes += "<TR><TD COLSPAN = \"2\">Variables</TD>" + LS;
        for (int i=0; i < NB_COORD; i++){
          strRes += "<TD>" + coordNames[i] + " (" + strUnitPos[i] + ")"
                 + "</TD>" + LS;
        }
        strRes += "</TR>" + LS;

        //for each planet
        for (iPlanet = 0; iPlanet < _nbPlan; iPlanet ++){
//System.out.println(" - iJD = " + iJD + "iPlanet = " + iPlanet);
          // write planet name
          strRes += "<TR BGCOLOR =\"#FAD6AD\">";
          strRes += "<TD ROWSPAN = \"3\">";
          for (int i = 0; i < 3; i++)
            strRes += planetNames[bdlT[iPlanet][0].body.getIndex()].substring(i, i+1) + "<BR>";
          strRes += "</TD>" + LS;

          // *** BDL line
          strRes += "<TD>BDL</TD>" + LS;
          for (iCoord = 0; iCoord < NB_COORD; iCoord ++){
            strRes += "<TD>"
                   + NF.format(bdlT[iPlanet][iJD].body.getCoord(iCoord))
                   + "</TD>" + LS;
          } // end for iCoord
          strRes += "</TR>" + LS;

          // *** JEphem line
          strRes += "<TR>" + LS;
          strRes += "<TD>JEphem full</TD>" + LS;
          for (iCoord = 0; iCoord < NB_COORD; iCoord ++){
            strRes += "<TD>"
                   +  NF.format(jephemT[iPlanet][iJD].body.getCoord(iCoord))
                   + "</TD>" + LS;
          } // end for iCoord
          strRes += "</TR>" + LS;

          // *** Difference JEphem  - BDL line
          strRes += "<TR BGCOLOR =\"yellow\">" + LS;
          strRes += "<TD>|J. full - BDL|</TD>" + LS;
          for (iCoord = 0; iCoord < NB_COORD; iCoord ++){
            strRes += "<TD>"
                   + NF.format(delta[iPlanet][iJD][iCoord])
                   + "</TD>" + LS;
          } // end for iCoord
          strRes += "</TR>" + LS;

          fos.write(strRes.getBytes());
          strRes ="";
        } // end for each planet
        strRes += "<TABLE/><BR><BR>" + LS;
        strRes += "<BR>" + LS;

      } // end for each JD

      fos.write(strRes.getBytes());
    }
    catch(Exception e){
      System.out.println("Exception" + e.toString());
      e.printStackTrace();
      System.exit(0);
    }
  }// end buildOutputDetailByJD()

  //******************************** buildOutputDiffByPlanet() *********************
  private static void buildOutputDiffByPlanet(FileOutputStream fos){
    try{
      int iJD, iPlanet, iCoord; // main indexes
      String strRes = new String("");

      double[][] deltaMax = new double[_nbPlan][NB_COORD];

      strRes += LS + LS + "<!-- ************************************************** -->" + LS;
      strRes += "<H2><A NAME=\"DiffByPlan\"></A>Differences tables by planet</H2>" + LS;

      // for each planet
      for (iPlanet = 0; iPlanet < _nbPlan; iPlanet ++){
        // title
        strRes += "<TABLE CLASS=\"2\" BORDER = \"1\">" + LS;
        strRes += "<TR><TD></TD>" + LS;
        strRes += "<TD COLSPAN = \"8\"><B>" + LS;
        strRes += planetNames[bdlT[iPlanet][0].body.getIndex()];
        strRes += "</B></TD></TR>" + LS;

        // coordinate legends
        strRes += "<TR><TD>Dates</TD>" + LS;
        for (int i = 0; i < NB_COORD; i++){
          strRes += "<TD>Delta " + coordNames[i] + " (" + strUnitPos[i]
                 + ")</TD>" + LS;
          if (i == 2)
          strRes += "<TD>Norm (" + strUnitPos[0]
                 + ")</TD>" + LS;
        }
        strRes += "<TD>Norm (" + strUnitPos[NB_COORD - 1]
               + ")</TD>" + LS;

        strRes += "</TR>" + LS;

        //for each julian day, write a line of data
        for (iJD = 0; iJD < _nbJD; iJD ++){
//System.out.println(" - iJD = " + iJD + "iPlanet = " + iPlanet);
          strRes += "<TR>" + LS;
          // write date
          int idx = bdlT[iPlanet][iJD].strDate.indexOf(" ");
          strRes += "<TD>" + bdlT[iPlanet][iJD].strDate.substring(0, idx).trim()
          + "</TD>" + LS;

          // write data for delta pos and vel
          for (iCoord = 0; iCoord < NB_COORD; iCoord ++){
            strRes += "<TD>" + NF.format(delta[iPlanet][iJD][iCoord])
                   + "</TD>" + LS;
            if (delta[iPlanet][iJD][iCoord] > deltaMax[iPlanet][iCoord])
              deltaMax[iPlanet][iCoord] = delta[iPlanet][iJD][iCoord];
            if (iCoord==2)
              // write data for delta norm pos
              strRes += "<TD><B>" + NF.format(deltaNormPos[iPlanet][iJD])
                     + "</B></TD>" + LS;

          } // end for iCoord
          // write data for delta norm vel
          strRes += "<TD><B>" + NF.format(deltaNormVel[iPlanet][iJD])
                 + "</B></TD>" + LS;

          strRes += "</TR>" + LS;

          fos.write(strRes.getBytes());
          strRes ="";
        } // end for each JD

        // *** Delta max
        strRes += "<TR BGCOLOR = \"yellow\"><TD>Delta max</TD>" + LS;
        for (iCoord = 0; iCoord < NB_COORD; iCoord ++){
          strRes += "<TD>" + NF.format(deltaMax[iPlanet][iCoord])
                 + "</TD>" + LS;
          if (iCoord==2)
            // write data for delta max norm pos
            strRes += "<TD><B>" + NF.format(deltaMaxNormPos[iPlanet])
                   + "</B></TD>" + LS;
        } // end for iCoord
        // write data for delta max norm vel
        strRes += "<TD><B>" + NF.format(deltaMaxNormVel[iPlanet])
               + "</B></TD>" + LS;

        strRes += "</TR>" + LS;
        strRes += "<TABLE/>" + LS;

        // *** Delta max for the norms
        int indPla = bdlT[iPlanet][0].body.getIndex();
        strRes += "Delta max of vector norms (built only with delta max) for <B>"
               + planetNames[indPla] + "</B> :";
        strRes += "<BR><B>Position</B> : " + NF.format(deltaMaxNormPos[iPlanet]) + " au = "
               + NF2.format(deltaMaxNormPos[iPlanet] * KM_PER_AU) + " km.";

        strRes += "<BR>Seen from Earth, this represents an error of <B>"
               + NF2.format(calcGeocentricError(deltaMaxNormPos[iPlanet], indPla))
               + "</B> arc sec.";
        strRes += "<BR><B>Velocity</B> : " + NF.format(deltaMaxNormVel[iPlanet]) + " au/d = "
               + NF2.format(deltaMaxNormVel[iPlanet] * KM_PER_AU * 1000 / 84600) + " m/s.";

        strRes += "<BR><BR>" + LS;

      } // end for each planet

      fos.write(strRes.getBytes());
    }
    catch(Exception e){
      System.out.println("Exception" + e.toString());
      e.printStackTrace();
      System.exit(0);
    }

  }// end buildOutputDiffByPlanet()

  //******************************** buildOutputSummary() ************************
  private static void buildOutputSummary(FileOutputStream fos){
    int iPlanet;
    String strRes = new String("");
    try{
      strRes += LS + LS + "<!-- ************************************************** -->" + LS;
      strRes += "<H2><A NAME=\"Summary\"></A>Summary</H2>" + LS;
      strRes += "<TABLE BORDER = \"1\" BGCOLOR=\"#FFFFCC\">" + LS;
      strRes += "<TR><TD></TD>" + LS;
      strRes += "<TD COLSPAN = \"2\"><B>Positions</B></TD>" + LS;
      strRes += "<TD><B>Velocities</B></TD></TR>" + LS;
      strRes += "<TR><TD></TD><TD>Norm of<BR>delta max</TD>" + LS;
      strRes += "<TD>Geocentric<BR>angular error</TD>" + LS;
      strRes += "<TD>Norm of<BR>delta max</TD></TR>" + LS;

      int indPla;
      for(iPlanet = 0; iPlanet < _nbPlan; iPlanet ++){
        indPla = bdlT[iPlanet][0].body.getIndex();
        strRes += "<TR><TD>" + planetNames[bdlT[iPlanet][0].body.getIndex()] + "</TD>" + LS;
        // norm of delta max pos
        strRes+= "<TD>" + NF.format(deltaMaxNormPos[iPlanet]) + " au<BR>" + LS;
        strRes+= NF2.format(deltaMaxNormPos[iPlanet] * KM_PER_AU) + " km</TD>" + LS;
        // geocentric error
        strRes += "<TD><B>"
               + NF2.format(calcGeocentricError(deltaMaxNormPos[iPlanet], indPla))
               + " \"</B></TD>" + LS;
        // norm of delta max vel
        strRes+= "<TD>" + NF.format(deltaMaxNormVel[iPlanet]) + " au/s<BR>" + LS;
        strRes+= NF2.format(deltaMaxNormVel[iPlanet] * KM_PER_AU * 1000/84600) + " m/s - " + LS;
        strRes+= NF2.format(deltaMaxNormVel[iPlanet] * KM_PER_AU * 1000/84600 * 3.6)
              + " km/h</TD>" + LS;
        strRes+= "</TR>" + LS;
      }

      strRes += "</TABLE><BR><BR>";
      fos.write(strRes.getBytes());
    }
    catch(Exception e){
      System.out.println("Exception" + e.toString());
      e.printStackTrace();
      System.exit(0);
    }

  }// end buildOutputSummary()


  //**************************** calcGeocentricError() **************
  /******************************************************************
  Given the norm of a vector representing the error on a position, calculates the angular error
  as seen from Earth. Computation done for a case when the planet is in its nearest position from
  Earth (worst case).
  @param norm Norm of the error vector, in a<B>u</B>.
  @param indPla Index of the planet, using constants of <CODE>ISolarSystem.java</CODE>.
  @return Error as seen from Earth, in <B>arcseconds</B>.
  *****************************************************************/
  private static double calcGeocentricError(double norm, int indPla){
    double d; // dist(Earth planet) in au.
    if (indPla == MERCURY || indPla == VENUS)
      d = 1.0 - a0s[indPla];
    else if(indPla == EARTH)
      d = a0s[indPla];
    else
      d = a0s[indPla] - 1.0;

    return Math.toDegrees(Math.atan(norm / d)) * 3600.0;
  }// end calcGeocentricError()

  //=================================================================================
  //                                AUXILIARY CLASSES
  //=================================================================================
  /******************************************************************
  Class internal to <A HREF="TestVSOP87.html">TestVSOP87</A> containing results of a computation for
  a planet and a JD.
  <BR>
  @author Thierry Graff
  @history jan 21 2001 creation.
  @todo remove strDate when a method to format date from JD is available.
  @todo other constructors
  @todo link in the comment for Body
  *****************************************************************/
  static class JEphemTest{
    /** Julian day concerned by this test. */
    double jd;
    /** Date representation od the julian day concerned by this test. */
    String strDate;
    /** index of the planet concerned by this test (referring to jephem.astro.SolarSystem
    constants). */
    //int planetIndex;
    /** results of the test */
    //double[] coords;
  
    // Repeats a fields of tig.GeneralConstants
    private static final int NO_SPECIF = -1;
  
    /** jephem.astro.Body concerned by this test. */
    Body body;
  
    /** Constructor just initializing <CODE>this.body</CODE>. */
    public JEphemTest(){
      this.body = new Body(NO_SPECIF);
    }
  
    /** Constructor just initializing coords <CODE>this.body</CODE>.*/
    public JEphemTest(int planetIndex){
      this.body = new Body(planetIndex);
    }
  
  }//end class JEphemTest

}//end class TestVSOP87