Main Page   Class Hierarchy   Data Structures   File List   Data Fields   Globals  

kepler.C

Go to the documentation of this file.
00001 
00016 
00017 //   Steve McMillan, Fall 1994, Spring 1999.
00018 //   SPZ and JM, Feb 1998.
00019 
00020 #include "dyn.h"
00021 
00022 #define  ITERAC    1.0e-12      // Iteration accuracy for Kepler's equation
00023 #define  MAXITER   100          // Maximum number of iterations
00024 
00025 #define  TRIG_TOL  1.e-12       // Tolerance in sine and cosine functions
00026                                 //   _______________________________________
00027 #define  SQRT_TRIG_TOL  1.e-6   // \/ Tolerance in sine and cosine functions
00028 
00029 #define  INFINITY  VERY_LARGE_NUMBER
00030 
00031 /*----------------------------------------------------------------------------
00032  *
00033  * Kepler conventions and use of variables:
00034  * ---------------------------------------
00035  *
00036  *                      E = energy
00037  *                      h = angular momentum
00038  *                      e = eccentricity
00039  *                      a = semi-major axis
00040  *                      P = period
00041  *                      n = mean motion
00042  *                      r = separation
00043  *
00044  * All definitions are standard, and Kepler's equation is used in the usual
00045  * fashion, with the following conventions:
00046  *
00047  *              E < 0                   E = 0                   E > 0
00048  *
00049  * h > 0        e < 1                   e = 1                   e > 1
00050  *              a < INFINITY            a = INFINITY            a = INFINITY
00051  *              P < INFINITY            P = INFINITY            P = INFINITY
00052  *                                      n --> n'
00053  *
00054  * h = 0        e = 1                   e = 1                   e = 1
00055  *              a < INFINITY            a = INFINITY            a = INFINITY
00056  *              P < INFINITY            P = INFINITY            P = INFINITY
00057  *                                      n --> n''
00058  *              true_an = ecc_an        true_an = r^(3/2)       true_an = ecc_an
00059  *                                      mean_an = true_an
00060  *
00061  *----------------------------------------------------------------------------
00062  */
00063 
00064 #ifndef TOOLBOX
00065 
00066 local real cos_to_sin(real c) // Special treatment near |cos| = 1
00067 {
00068     if (abs(c) >= 1) return 0;
00069     
00070     real eps = 1 - abs(c);
00071     if (eps > TRIG_TOL) return sqrt(1 - c*c);
00072     
00073     return sqrt(2*eps);
00074 }
00075 
00076 //----------------------------------------------------------------------------
00077 
00078 // Simple mechanism for handling trigonometric errors in the Kepler package:
00079 
00080 static int kepler_tolerance_level = 0;
00081 static bool print_trig_warning = false;
00082 
00083 // Options:
00084 
00085 //      0: quit on any trig error
00086 //      1: quit on large trig error
00087 //      2: force to periastron or apastron on trig error
00088 
00089 void set_kepler_tolerance(int i)
00090 {
00091     if (i < 0) i = 0;
00092     if (i > 2) i = 2;
00093     kepler_tolerance_level = i;
00094 }
00095 
00096 void set_kepler_print_trig_warning(bool p)
00097 {
00098     print_trig_warning = p;
00099 }
00100 
00101 int kepler_tolerance()
00102 {
00103     return kepler_tolerance_level;
00104 }
00105 
00106 local int check_trig_limit(kepler* k, real &c, char *s)
00107 {
00108     int forced = 0;
00109 
00110     if (abs(c) > 1) {
00111         if (kepler_tolerance_level == 0) {
00112             cerr.precision(HIGH_PRECISION);
00113             cerr << s << ": c = " << c << endl;
00114             k->print_all(cerr);
00115             err_exit("cosine out of range");
00116         } else if (kepler_tolerance_level == 1) {
00117             if (c > 1) {
00118                 if (c > 1 + TRIG_TOL) {
00119                     cerr.precision(HIGH_PRECISION);
00120                     cerr << s << ": c = " << c << endl;
00121                     k->print_all(cerr);
00122                     err_exit("cosine > 1");
00123                 }
00124                 c = 1;
00125             } else if (c < -1) {
00126                 if (c < -1 - TRIG_TOL) {
00127                     cerr.precision(HIGH_PRECISION);
00128                     cerr << s << ": c = " << c << endl;
00129                     k->print_all(cerr);
00130                     err_exit("cosine < -1");
00131                 }
00132                 c = -1;
00133             }
00134         } else {
00135             if (print_trig_warning) {
00136                 int p = cerr.precision(HIGH_PRECISION);
00137                 cerr << "warning: " << s << ": c = " << c << endl;
00138                 cerr.precision(p);
00139             }
00140             c = max(-1.0, min(1.0, c));
00141         }
00142 
00143         forced = 1;
00144     }
00145 
00146     return forced;
00147 }
00148 
00149 local void err_or_warn(char *s)
00150 {
00151     if (kepler_tolerance_level > 0)
00152         warning(s);
00153     else
00154         err_exit(s);
00155 }
00156 
00157 //----------------------------------------------------------------------------
00158 
00159 // Local "Kepler" functions:
00160 // ------------------------
00161 
00162 /*----------------------------------------------------------------------------
00163  *  keplerseq  --  solve Kepler's equation by iteration, for elliptic orbits
00164  *                 litt: H. Goldstein (1980), Classical Mechanics, eq. (3-76);
00165  *                       S.W. McCuskey (1963), Introduction to Celestial
00166  *                                             Mechanics, Sec. 3-7.
00167  *                 method: true_an follows from ecc_an, which is determined
00168  *                         by inverting Kepler's equation:
00169  *                         mean_an = ecc_an - ecc * sin(ecc_an)
00170  *
00171  *                 Note that ecc = 1 is OK (linear bound orbit).
00172  *
00173  *----------------------------------------------------------------------------
00174  */
00175 
00176 local int  keplerseq(real mean_an,      // mean anomaly
00177                      real ecc,          // eccentricity
00178                      real &true_an,     // true anomaly (returns in (-pi,pi])
00179                      real &ecc_an)      // eccentric anomaly
00180 {
00181     if (ecc < 0) {                      // inconsistent
00182         cerr << "keplerseq: eccentricity e = " << ecc << " < 0\n";
00183         exit (1);
00184     }
00185 
00186     if (ecc > 1) {                      // inconsistent
00187         cerr << "keplerseq: eccentricity e = " << ecc << " > 1\n";
00188         exit (1);
00189     }
00190 
00191     mean_an = sym_angle( mean_an );     // -pi < mean_an < pi
00192     int iter = 0;
00193 
00194 
00195     if (mean_an == 0)                   // Special case.
00196 
00197         ecc_an = 0;
00198 
00199     else {
00200 
00201         real  delta_ecc_an;             // iterative increment in ecc_an
00202         real  function;                 // function = 0  solves Kepler's eq.
00203         real  derivative;               // d function / d ecc_an
00204 
00205         ecc_an = mean_an;               // first guess for ecc_an
00206         delta_ecc_an = 1;               // just to start the while loop
00207 
00208         int counter = 2;
00209 
00210         while (counter > 0) {
00211 
00212             if (abs( delta_ecc_an ) < ITERAC ) counter--;
00213 
00214             if (++iter > MAXITER)
00215               err_or_warn("keplerseq: convergence too slow");
00216 
00217             function = -mean_an + ecc_an - ecc * sin(ecc_an);
00218                                // function = 0 solves Kepler's equation
00219             derivative = 1 - ecc * cos(ecc_an);
00220                                // d(function) / d(ecc_an)
00221             delta_ecc_an = -function / derivative;
00222                                // use Newton's method to find roots
00223 
00224             if ( delta_ecc_an > 1 )
00225               delta_ecc_an = 1;
00226             else if ( delta_ecc_an < -1 )
00227               delta_ecc_an = -1;        // avoid large jumps
00228 
00229             ecc_an += delta_ecc_an;
00230         }
00231 
00232 //      PRL(iter);
00233     }
00234 
00235     // Note convention that true_an = ecc_an if ecc = 1.
00236 
00237     if (ecc < 1)
00238       true_an = 2 * atan( sqrt((1 + ecc)/(1 - ecc)) * tan(ecc_an/2) );
00239     else
00240       true_an = ecc_an;
00241 
00242     return iter;
00243 }
00244 
00245 /*----------------------------------------------------------------------------
00246  *  keplershypeq  --  solves Kepler's equation by iteration: hyperbolic orbits
00247  *                    litt: S.W. McCuskey (1963), Introduction to Celestial
00248  *                                                Mechanics, Sec. 3-10.
00249  *                    method: true_an follows from ecc_an, which is determined
00250  *                            by inverting the hyperbolic analog of
00251  *                            Kepler's equation:
00252  *                            mean_an = -ecc_an + ecc * sinh(ecc_an)
00253  *
00254  *                 Note that ecc = 1 is OK (linear unbound orbit).
00255  *
00256  *----------------------------------------------------------------------------
00257  */
00258 
00259 local int  keplershypeq(real mean_an,   // mean anomaly
00260                         real ecc,       // eccentricity
00261                         real &true_an,  // true anomaly
00262                         real &ecc_an)   // eccentric anomaly (hyperbolic analog)
00263 {
00264     real  delta_ecc_an;                 // iterative increment in ecc_an
00265     real  function;                     // function = 0  solves Kepler's eq.
00266     real  derivative;                   // d function / d ecc_an
00267     int  i;
00268 
00269     if (ecc < 1)                        // inconsistent
00270         {
00271         cerr << "keplershypeq: eccentricity e = " << ecc << " < 1\n";
00272         exit (1);
00273         }
00274 
00275     if (mean_an == 0)           // Special case
00276 
00277         ecc_an = 0;
00278 
00279     else {
00280 
00281             ecc_an = asinh( mean_an / ecc );  // first guess for ecc_an
00282 
00283             i = 0;
00284             delta_ecc_an = 1;                 // just to start the while loop
00285 
00286             int counter = 2;
00287 
00288             while ( counter > 0) {
00289 
00290                 if (abs( delta_ecc_an ) < ITERAC ) counter--;
00291 
00292                 if (++i > MAXITER) {
00293                     cerr << "keplershypeq:  mean_an = " << mean_an
00294                          << "  ecc = " << ecc << "  MAXITER = " << MAXITER
00295                          << endl;
00296                     err_or_warn("keplershypeq: convergence too slow");
00297                 }
00298 
00299                 function = -mean_an - ecc_an + ecc * sinh(ecc_an);
00300                                     // function = 0 solves Kepler's equation
00301                 derivative = -1 + ecc * cosh(ecc_an);
00302                                     // d(function) / d(ecc_an)
00303                 delta_ecc_an = -function / derivative;
00304                                     // use Newton's method to find roots
00305 
00306                 if  ( abs(derivative) < 1 ) { // avoid large jumps
00307                     if ( delta_ecc_an > 1 )
00308                       delta_ecc_an = 1;
00309                     else if ( delta_ecc_an < -1 )
00310                       delta_ecc_an = -1;
00311                 }
00312                 ecc_an += delta_ecc_an;
00313             }
00314         }
00315 
00316     // Note convention that true_an = ecc_an if ecc = 1.
00317 
00318     if (ecc > 1)
00319         true_an = 2 * atan( sqrt((ecc + 1)/(ecc - 1)) * tanh(ecc_an/2) );
00320     else
00321         true_an = ecc_an;
00322 
00323     return i;
00324 }
00325 
00326 /*----------------------------------------------------------------------------
00327  *  keplerspareq  --  solves Kepler's equation for parabolic orbits
00328  *                    litt: S.W. McCuskey (1963), Introduction to Celestial
00329  *                                                Mechanics, Sec. 3-9.
00330  *                    method: true_an (f) is determined by inverting the
00331  *                            parabolic analog of Kepler's equation:
00332  *                            mean_an = tan(f/2) * (1 + tan(f/2)*tan(f/2) / 3)
00333  *----------------------------------------------------------------------------
00334  */
00335 local real keplerspareq(real mean_an,  // mean anomaly
00336                         real &true_an) // true anomaly
00337 
00338 // This should be used ONLY for non-linear zero-energy orbits.
00339 
00340 {
00341     if (mean_an == 0) {
00342 
00343         true_an = 0;
00344         return 0;
00345 
00346     } else {
00347 
00348         // This analytic solution is probably expensive!
00349 
00350         real s = atan(2/(3*mean_an));
00351         real w = atan(pow(tan(0.5*abs(s)),0.33333333333333333));
00352         if (s < 0) w = -w;
00353         real t = 2/tan(2*w);
00354         true_an = 2*atan(t);
00355         
00356         return t;
00357     }
00358 }
00359 
00360 //----------------------------------------------------------------------------
00361 
00362 // Basic Kepler class member functions:
00363 // -----------------------------------
00364 
00365 kepler::kepler()
00366 {
00367     time = total_mass = energy = angular_momentum = 0;
00368     rel_pos = rel_vel = 0;
00369     normal_unit_vector = 0;
00370     longitudinal_unit_vector = 0;
00371     transverse_unit_vector = 0;
00372     semi_major_axis = eccentricity = 0;
00373     true_anomaly = mean_anomaly = 0;
00374     time_of_periastron_passage = 0;
00375     pred_time = pred_separation = pred_true_anomaly = 0;
00376     pred_rel_pos = pred_rel_vel = 0;
00377     mean_motion = period = periastron = separation = pred_mean_anomaly = 0;
00378     circular_binary_limit = 0;
00379 }
00380 
00381 void  new_kepler(dyn * com,             // com is the center-of-mass dyn
00382                  real t)                // default = 0
00383 {
00384     kepler * k;
00385     k = new kepler;
00386 
00387     com->set_kepler(k);
00388     dyn_to_kepler(com, t);
00389 }
00390 
00391 void  new_child_kepler(dyn * com,       // com is the center-of-mass dyn
00392                        real t,                  // default = 0
00393                        real circ_limit)         // default = 0
00394 {
00395     kepler * k;
00396     k = new kepler;
00397 
00398     k->set_circular_binary_limit(circ_limit);
00399 
00400     com->get_oldest_daughter()->set_kepler(k);
00401     dyn_to_child_kepler(com, t);
00402 }
00403 
00404 void  dyn_to_kepler(dyn * com,              // com = center-of-mass
00405                     real t)                 // default = 0
00406 {
00407     kepler * k = com->get_kepler();
00408     dyn *d1, *d2;
00409 
00410     if (!(d1 = com->get_oldest_daughter()))
00411         err_exit("dyn_to_kepler: no oldest daughter present");
00412     if (!(d2 = d1->get_younger_sister()))
00413         err_exit("dyn_to_kepler: no second daughter present");
00414 
00415     k->set_time(t);
00416     k->set_total_mass( d1->get_mass() + d2->get_mass() );
00417     k->set_rel_pos( d2->get_pos() - d1->get_pos() );
00418     k->set_rel_vel( d2->get_vel() - d1->get_vel() );
00419     k->initialize_from_pos_and_vel();
00420 }
00421 
00422 void  dyn_to_child_kepler(dyn * com,    // com = center-of-mass
00423                           real t)       // default = 0
00424 {
00425     kepler * k = com->get_oldest_daughter()->get_kepler();
00426     dyn *d1, *d2;
00427 
00428     if (!(d1 = com->get_oldest_daughter()))
00429         err_exit("dyn_to_kepler: no oldest daughter present");
00430     if (!(d2 = d1->get_younger_sister()))
00431         err_exit("dyn_to_kepler: no second daughter present");
00432 
00433     k->set_time(t);
00434     k->set_total_mass( d1->get_mass() + d2->get_mass() );
00435     k->set_rel_pos( d2->get_pos() - d1->get_pos() );
00436     k->set_rel_vel( d2->get_vel() - d1->get_vel() );
00437     k->initialize_from_pos_and_vel();
00438 }
00439 
00440 void  kepler::kep_to_dyn_story(story * s)
00441 {
00442     putrq(s, "kepler_time", time);
00443     putrq(s, "kepler_total_mass", total_mass);
00444     putvq(s, "kepler_rel_pos", rel_pos);
00445     putvq(s, "kepler_rel_vel", rel_vel);
00446     putrq(s, "kepler_semi_major_axis", semi_major_axis);
00447     putrq(s, "kepler_eccentricity", eccentricity);
00448 }
00449 
00450 void  kepler::print_all(ostream & s)
00451 {
00452     int p = s.precision(HIGH_PRECISION);
00453 
00454     s << "  time             = " << time << endl;
00455     s.precision(8);
00456     s << "  total_mass       = " << total_mass    << endl;
00457     s << "  rel_pos          = " << rel_pos    << endl;
00458     s << "  rel_vel          = " << rel_vel << endl;
00459     s << "  separation       = " << separation << endl;
00460     s << "  true_anomaly     = " << (energy < 0 ? sym_angle(true_anomaly) :
00461                                         true_anomaly) << endl;
00462     s << "  mean_anomaly     = " << (energy < 0 ? sym_angle(mean_anomaly) :
00463                                         mean_anomaly) << endl;
00464     s << "  semi_major_axis  = " << semi_major_axis << endl;
00465     s << "  eccentricity     = " << eccentricity << endl;
00466     s << "  circular_binary_limit = " << circular_binary_limit << endl;
00467     s << "  periastron       = " << periastron << endl;
00468     s << "  apastron         = " << (energy >= 0 ? INFINITY : 
00469                                        semi_major_axis * (1 + eccentricity))
00470                                     << endl;
00471     s << "  energy           = " << energy << endl;
00472     s << "  angular_momentum = " << angular_momentum << endl;
00473     s << "  period           = " << period << endl;
00474     s << "  mean_motion      = " << mean_motion << endl;
00475     s << "  normal_unit_vector         = " << normal_unit_vector << endl;
00476     s << "  longitudinal_unit_vector   = "
00477                                     << longitudinal_unit_vector << endl;
00478     s << "  transverse_unit_vector     = " << transverse_unit_vector << endl;
00479     s << "  time_of_periastron_passage = "
00480                                     << time_of_periastron_passage << endl;
00481 
00482     s.precision(p);
00483 }
00484 
00485 void  kepler::print_dyn(ostream & s)
00486 {
00487     s << "  time             = " << time << endl;
00488     s << "  rel_pos          = " << rel_pos    << endl;
00489     s << "  rel_vel          = " << rel_vel << endl;
00490     s << "  separation       = " << separation << endl;
00491     s << "  true_anomaly     = " << (energy < 0 ? sym_angle(true_anomaly) :
00492                                         true_anomaly) << endl;
00493     s << "  mean_anomaly     = " << (energy < 0 ? sym_angle(mean_anomaly) :
00494                                         mean_anomaly) << endl;
00495     s << "  time_of_periastron_passage = "
00496                                     << time_of_periastron_passage << endl;
00497 }
00498 
00499 void  kepler::print_elements(ostream & s)
00500 {
00501     int p = s.precision(8);
00502 
00503     s << "  time             = " << time << endl;
00504     s << "  separation       = " << separation << endl;
00505     s << "  semi_major_axis  = " << semi_major_axis    << endl;
00506     s << "  eccentricity     = " << eccentricity << endl;
00507     s << "  circular_binary_limit = " << circular_binary_limit << endl;
00508     s << "  periastron       = " << periastron << endl;
00509     s << "  apastron         = " << (energy >= 0 ? INFINITY : 
00510                                        semi_major_axis * (1 + eccentricity))
00511                                     << endl;
00512     s << "  energy           = " << energy << endl;
00513     s << "  angular_momentum = " << angular_momentum << endl;
00514     s << "  period           = " << period << endl;
00515 
00516     s.precision(p);
00517 }
00518 
00519 //----------------------------------------------------------------------------
00520 
00521 // Manipulation of orbit:
00522 // ---------------------
00523 
00524 /*----------------------------------------------------------------------------
00525  *  kepler::pred_true_to_mean_anomaly  --  sign conventions:
00526  *                                    elliptic orbit:
00527  *                                      mean anomaly will return with a value
00528  *                                      in (-pi, pi), independent of the
00529  *                                      initial sign of the true anomaly.
00530  *                                    parabolic or hyperbolic orbit:
00531  *                                      both true and mean anomaly will return
00532  *                                      with the same sign as the initial true
00533  *                                      anomaly.
00534  *                                    linear orbit:
00535  *                                      true_anomaly is not defined, so assume
00536  *                                      that true_anomaly = eccentric_anomaly
00537  *                                      on entry.
00538  *
00539  *----------------------------------------------------------------------------
00540  */
00541 void  kepler::pred_true_to_mean_anomaly()
00542 {
00543     real  ecc_anomaly;
00544 
00545     if (energy < 0) {                       // ellipse or bound linear orbit
00546 
00547         if (eccentricity < 1) {
00548             ecc_anomaly = acos((eccentricity + cos(pred_true_anomaly)) /
00549                                (1 + eccentricity*cos(pred_true_anomaly)));
00550             if (pred_true_anomaly < 0) ecc_anomaly = -ecc_anomaly;
00551             
00552         } else
00553 
00554             ecc_anomaly = pred_true_anomaly;    // convention in linear case
00555 
00556         pred_mean_anomaly = ecc_anomaly - eccentricity * sin(ecc_anomaly);
00557 
00558     } else if (energy > 0) {            // hyperbola or unbound linear orbit
00559 
00560         if (eccentricity > 1) {
00561 
00562             // Check that the true anomaly is legal.
00563 
00564             real  maxtruean = PI - acos( 1 / eccentricity );
00565 
00566             if (pred_true_anomaly < -maxtruean) {
00567                 cerr << "pred_true_to_mean_anomaly: hyp. true anom. = "
00568                   << pred_true_anomaly << " < minimum = " << -maxtruean <<"\n";
00569                 exit (1);
00570             }
00571 
00572             if (pred_true_anomaly > maxtruean) {
00573                 cerr << "pred_true_to_mean_anomaly: hyp. true anom. = "
00574                   << pred_true_anomaly << " > maximum = " << maxtruean << "\n";
00575                 exit (1);
00576             }
00577 
00578             ecc_anomaly = acosh((eccentricity + cos(pred_true_anomaly)) /
00579                                 (1 + eccentricity * cos(pred_true_anomaly)));
00580             if (pred_true_anomaly < 0) ecc_anomaly = -ecc_anomaly;
00581 
00582         } else
00583 
00584             ecc_anomaly = pred_true_anomaly;    // convention in linear case
00585             
00586         pred_mean_anomaly = -ecc_anomaly + eccentricity * sinh(ecc_anomaly);
00587 
00588     } else {                                    // parabola or linear orbit
00589 
00590         // Note the non-standard definitions of both the true and the mean
00591         // anomaly in this case.
00592 
00593         if (angular_momentum > 0) {             // parabola
00594 
00595             real tan_half_true_an = tan(0.5*pred_true_anomaly);
00596             pred_mean_anomaly = tan_half_true_an
00597                                  *(1 + tan_half_true_an*tan_half_true_an/3);
00598 
00599         } else                                  // linear (note convention)
00600 
00601             pred_mean_anomaly = pred_true_anomaly;
00602 
00603     }
00604 }
00605 
00606 /*----------------------------------------------------------------------------
00607  *  kepler::mean_anomaly_to_periastron_passage  --  sign conventions:
00608  *                                    elliptic orbit:
00609  *                                      the previous function
00610  *                                      pred_true_to_mean_anomaly() will
00611  *                                      guarantee that the mean anomaly is
00612  *                                      already positive.
00613  *                                      The time_of_periastron_passage is the
00614  *                                      time of the previous periastron
00615  *                                      passage.
00616  *                                    hyperbolic orbit:
00617  *                                      the previous function
00618  *                                      pred_true_to_mean_anomaly() allows
00619  *                                      either sign for the mean anomaly.
00620  *                                      The time_of_periastron_passage is the
00621  *                                      time of the one and only periastron
00622  *                                      passage, which may be either in the
00623  *                                      past or in the future with respect to
00624  *                                      the present time `time'.
00625  *----------------------------------------------------------------------------
00626  */
00627 void  kepler::mean_anomaly_to_periastron_passage()
00628 {
00629 
00630     // Note that the mean anomaly is defined so that this equation is
00631     // correct in ALL cases.
00632 
00633     time_of_periastron_passage = time - mean_anomaly / mean_motion;
00634 }
00635 
00636 void  kepler::update_time_of_periastron()
00637     {
00638 
00639     if (energy >= 0) return;
00640 
00641     real  dt = (time - time_of_periastron_passage) / period;
00642 
00643     int  dt_int = (int) abs(dt);        // Conversion is machine-dependent!
00644     if (dt < 0.0) dt_int = -dt_int - 1;
00645 
00646     time_of_periastron_passage += dt_int * period;
00647     }
00648 
00649 void kepler::set_real_from_pred()
00650 {
00651     time = pred_time;
00652     rel_pos = pred_rel_pos;
00653     rel_vel = pred_rel_vel;
00654     separation = pred_separation;
00655     true_anomaly = pred_true_anomaly;
00656     mean_anomaly = pred_mean_anomaly;
00657     update_time_of_periastron();
00658 }
00659 
00660 void kepler::to_pred_rel_pos_vel(real cos_true_an, real sin_true_an)
00661 {
00662 
00663     vector r_unit = cos_true_an * longitudinal_unit_vector +
00664                     sin_true_an * transverse_unit_vector;
00665     pred_rel_pos = pred_separation * r_unit;
00666 
00667     real rel_vel_squared = 2 * (energy + total_mass / pred_separation);
00668     real v_t = angular_momentum / pred_separation;
00669     real v_r = sqrt(max(0.0, rel_vel_squared - v_t * v_t));
00670                // It is assumed here that negative values for the argument
00671                // arise from round-off errors, and can be replaced by zero.
00672     if (sin_true_an < 0) v_r = -v_r;
00673 
00674     pred_rel_vel = v_r * r_unit
00675                     + v_t * (-sin_true_an * longitudinal_unit_vector
00676                              + cos_true_an * transverse_unit_vector);
00677 }
00678 
00679 void kepler::to_pred_rel_pos_vel_linear(real true_an) // Special case.
00680 {
00681     pred_rel_pos = -pred_separation * longitudinal_unit_vector;
00682 
00683     if (pred_separation > 0) {
00684         real v_r = sqrt(max(0.0, 2 * (energy + total_mass / pred_separation)));
00685         if (true_an < 0) v_r = -v_r;
00686         pred_rel_vel = -v_r * longitudinal_unit_vector;
00687     } else {
00688         warning("to_pred_rel_pos_vel_linear: r <= 0");
00689         pred_rel_vel = VERY_LARGE_NUMBER*transverse_unit_vector;
00690     }
00691 }
00692 
00693 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
00694 //
00695 // New "fast" static data and accessors:
00696 
00697 static bool kepler_fast_flag = false;
00698 static real kepler_fast_tol = 1.0e-6;
00699 
00700 void set_kepler_fast_flag(bool flag)            // default = true
00701                                                 {kepler_fast_flag = flag;}
00702 void clear_kepler_fast_flag()                   {kepler_fast_flag = false;}
00703 bool get_kepler_fast_flag()                     {return kepler_fast_flag;}
00704 
00705 void set_kepler_fast_tol(real tol)              // default = 1.e-6
00706                                                 {kepler_fast_tol = tol;}
00707 void reset_kepler_fast_tol()                    {kepler_fast_tol = 1.e-6;}
00708 real get_kepler_fast_tol()                      {return kepler_fast_tol;}
00709 
00710 local inline void fast_keplerseq(real M, real e, real tol,
00711                                  real &s, real &c)
00712 {
00713     // Newton-Raphson solver, with "Danby" initial guess.
00714     // See test_solver.C for timing code.
00715 
00716     M = sym_angle(M);                   // -pi < M < pi
00717 
00718     // Make an initial guess for E (Danby would have a coefficient of 0.85).
00719 
00720     real dE = 0.94*e;
00721     if (M < 0) dE = -dE;
00722 
00723     real E = M + dE;
00724 
00725     while (1) {
00726 
00727         s = sin(E);
00728 //      c = cos(E);
00729 
00730         // May be marginally faster to use the trig. identity:
00731 
00732         c = sqrt(1 - s*s);
00733         if (fabs(E) > M_PI/2) c = -c;
00734 
00735         real func = E - e*s - M;
00736         if (fabs(func) <= tol) return;
00737 
00738         E -= func/(1 - e*c);    // NR iteration
00739     }
00740 }
00741 
00742 // New kepler member functions (Steve, 6/01):
00743 
00744 void kepler::fast_to_pred_rel_pos_vel(real r_cos_true_an,       // r cos f
00745                                       real r_sin_true_an)       // r sin f
00746 {
00747     // Relative position vector:
00748 
00749     pred_rel_pos = r_cos_true_an * longitudinal_unit_vector +
00750                     r_sin_true_an * transverse_unit_vector;
00751 
00752     // Now compute the relative velocity vector.
00753 
00754     real ri = 1 / pred_separation;
00755     vector r_unit = ri * pred_rel_pos;
00756 
00757     real rel_vel_squared = 2 * (energy + total_mass * ri);
00758     real v_t = angular_momentum * ri;
00759     real v_r = sqrt(max(0.0, rel_vel_squared - v_t * v_t));
00760                // It is assumed here that negative values for the argument
00761                // arise from round-off errors, and can be replaced by zero.
00762     if (r_sin_true_an < 0) v_r = -v_r;
00763 
00764     pred_rel_vel = v_r * r_unit
00765                     + v_t * ri * (-r_sin_true_an * longitudinal_unit_vector
00766                                   + r_cos_true_an * transverse_unit_vector);
00767 }
00768 
00769 void  kepler::fast_pred_mean_anomaly_to_pos_and_vel()
00770 {
00771     real cos_ecc_an, sin_ecc_an;
00772     fast_keplerseq(pred_mean_anomaly, eccentricity, kepler_fast_tol,
00773                    sin_ecc_an, cos_ecc_an);
00774 
00775     pred_separation = semi_major_axis * (1 - eccentricity * cos_ecc_an);
00776     real r_cos_true_an = semi_major_axis * (cos_ecc_an - eccentricity);
00777 
00778     // Writing b this way avoids sqrt(1 - eccentricity*eccentricity)!
00779 
00780     real semi_minor_axis = angular_momentum * period / (2*M_PI*semi_major_axis);
00781     real r_sin_true_an = semi_minor_axis * sin_ecc_an;
00782 
00783     fast_to_pred_rel_pos_vel(r_cos_true_an, r_sin_true_an);
00784 }
00785 
00786 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
00787 
00788 // Modified kepler member function (Steve, 6/01):
00789 
00790 void  kepler::pred_mean_anomaly_to_pos_and_vel()
00791 {
00792     if (!kepler_fast_flag || energy >= 0 || angular_momentum == 0) {
00793 
00794         // Standard method, as used by kira:
00795 
00796         real ecc_an;
00797 
00798         // Determine the separation and true anomaly.
00799 
00800         if (energy != 0) {
00801 
00802             if (energy < 0) {           // ellipse or linear bound orbit
00803 
00804                 keplerseq(pred_mean_anomaly, eccentricity,
00805                           pred_true_anomaly, ecc_an);
00806 
00807                 pred_separation = semi_major_axis 
00808                                         * (1 - eccentricity * cos(ecc_an));
00809 
00810             } else {                    // hyperbola or linear unbound orbit
00811 
00812                 keplershypeq(pred_mean_anomaly, eccentricity,
00813                              pred_true_anomaly, ecc_an);
00814 
00815                 pred_separation = semi_major_axis 
00816                                         * (eccentricity * cosh(ecc_an) - 1);
00817             }
00818 
00819             // Note: Use ecc_an for better results for very eccentric orbits.
00820             //
00821             // pred_separation = periastron * (1 + eccentricity)
00822             //                    / (1 + eccentricity * cos(pred_true_anomaly));
00823 
00824         } else {                                // parabola or linear orbit
00825 
00826             if (angular_momentum > 0) {
00827 
00828                 real tan_half_true_an = 
00829                     keplerspareq(pred_mean_anomaly, pred_true_anomaly);
00830 
00831                 pred_separation = periastron
00832                                     * (1 + tan_half_true_an*tan_half_true_an);
00833 
00834             } else {    // linear case (note convention).
00835 
00836                 pred_true_anomaly = pred_mean_anomaly;
00837                 pred_separation = pow(abs(pred_true_anomaly), 2/3.0);
00838 
00839             }
00840         }
00841 
00842         // Calculate position and velocity.
00843 
00844         if (angular_momentum > 0)
00845             to_pred_rel_pos_vel(cos(pred_true_anomaly), sin(pred_true_anomaly));
00846         else
00847             to_pred_rel_pos_vel_linear(pred_true_anomaly);
00848 
00849     } else {
00850 
00851         // New faster function for bound orbits does everything in
00852         // a single call.  For use by the 4tree interpolation.
00853 
00854         fast_pred_mean_anomaly_to_pos_and_vel();        // <--- new ---
00855     }
00856 }
00857 
00858 //-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
00859 
00860 #if 0
00861 
00862 // ***** OLD CODE: *****
00863 
00864 void  kepler::pred_mean_anomaly_to_pos_and_vel()
00865 {
00866     real ecc_an;
00867 
00868     // Determine the separation and true anomaly.
00869 
00870     if (energy != 0) {
00871 
00872         if (energy < 0) {               // ellipse or linear bound orbit
00873 
00874             keplerseq(pred_mean_anomaly, eccentricity,
00875                       pred_true_anomaly, ecc_an);
00876 
00877 
00878             pred_separation = semi_major_axis 
00879                                * (1 - eccentricity * cos(ecc_an));
00880 
00881         } else {                        // hyperbola or linear unbound orbit
00882 
00883             keplershypeq(pred_mean_anomaly, eccentricity,
00884                          pred_true_anomaly, ecc_an);
00885 
00886             pred_separation = semi_major_axis 
00887                                * (eccentricity * cosh(ecc_an) - 1);
00888         }
00889 
00890         // Note: Using ecc_an gives better results for very eccentric orbits.
00891         //
00892         // pred_separation = periastron * (1 + eccentricity)
00893         //                    / (1 + eccentricity * cos(pred_true_anomaly));
00894 
00895     } else {                            // parabola or linear orbit
00896 
00897         if (angular_momentum > 0) {
00898 
00899             real tan_half_true_an = 
00900               keplerspareq(pred_mean_anomaly, pred_true_anomaly);
00901 
00902             pred_separation = periastron
00903                                * (1 + tan_half_true_an*tan_half_true_an);
00904 
00905         } else {        // linear case (note convention).
00906 
00907             pred_true_anomaly = pred_mean_anomaly;
00908             pred_separation = pow(abs(pred_true_anomaly), 0.6666666666666667);
00909 
00910         }
00911     }
00912 
00913     // Calculate position and velocity.
00914 
00915     if (angular_momentum > 0)
00916         to_pred_rel_pos_vel(cos(pred_true_anomaly), sin(pred_true_anomaly));
00917     else
00918         to_pred_rel_pos_vel_linear(pred_true_anomaly);
00919 }
00920 
00921 #endif
00922 
00923 //----------------------------------------------------------------------------
00924 
00925 // Initialization:
00926 // --------------
00927 
00928 // Align_with_axes: initialize the right-handed triad (l, t, n) with the
00929 //                  longitudinal unit vector l pointing along the specified
00930 //                  axis (note that x, y, z = 1, 2, 3, Piet).
00931 
00932 void  kepler::align_with_axes(int axis)
00933 {
00934     if (axis == 1) {
00935 
00936         longitudinal_unit_vector = vector(1, 0, 0);
00937         transverse_unit_vector = vector(0, 1, 0);
00938 
00939     } else if (axis == 2) {
00940 
00941         longitudinal_unit_vector = vector(0, 1, 0);
00942         transverse_unit_vector = vector(0, 0, 1);
00943 
00944     } else {
00945 
00946         longitudinal_unit_vector = vector(0, 0, 1);
00947         transverse_unit_vector = vector(1, 0, 0);
00948     }
00949     
00950     normal_unit_vector = longitudinal_unit_vector ^ transverse_unit_vector;
00951 
00952 }
00953 
00954 // Initialize_from_pos_and_vel: start with time, mass, position and velocity
00955 //                              and determine all other orbit properties.
00956 
00957 void  kepler::initialize_from_pos_and_vel(bool minimal, bool verbose)
00958 {
00959     // Dynamics and geometry:
00960     // ---------------------
00961 
00962     energy = 0.5 * rel_vel * rel_vel - total_mass / separation;
00963 
00964     normal_unit_vector = rel_pos ^ rel_vel;
00965     angular_momentum = abs(normal_unit_vector);
00966 
00967     if (energy != 0) {
00968 
00969         semi_major_axis = -0.5 * total_mass / energy;
00970 
00971         real rdotv = rel_pos * rel_vel;
00972         real temp = 1 - separation / semi_major_axis;
00973         eccentricity = sqrt(rdotv * rdotv / (total_mass * semi_major_axis)
00974                             + temp * temp);
00975 
00976         // Deal with rounding error:
00977 
00978         if (eccentricity < 1.e-14) eccentricity = 0;
00979 
00980         if ((energy > 0 && eccentricity < 1
00981                        && eccentricity > 1 - SQRT_TRIG_TOL)
00982             || (angular_momentum > 0 && eccentricity == 1)) {
00983 
00984             // Force a linear orbit.
00985 
00986             eccentricity = 1;
00987             angular_momentum = 0;
00988 
00989         } else if (eccentricity > 0 &&
00990                    eccentricity < circular_binary_limit) {
00991 
00992             // Force a circular orbit (SPZ 2/98).
00993 
00994             // PROBLEM: this may be OK for pragmatic reasons in an
00995             // N-body simulation with real stars, but it is not OK
00996             // in a scattering experiment where we may be interested
00997             // insmall eccentricity changes.  There is no mathematical
00998             // reason to expect problems near eccentricity = 0.
00999 
01000             // Fix by introducing circular_binary_limit, which specifies
01001             // the maximum eccentricity that should be forced to 0 here.
01002 
01003             if (verbose && (print_trig_warning
01004                             || eccentricity > 0.1*circular_binary_limit))
01005                 cerr << "kepler: binary with ecc = " << eccentricity
01006                      << " forced to be circular" << endl
01007                      << "        in kepler::initialize_from_pos_and_vel"
01008                      << endl;
01009 
01010             eccentricity = 0;
01011 
01012             // Tidally circularized binaries pose some problem in
01013             // the transformation from hdyn to kepler.
01014             // This might solve the problem, but I am not sure.
01015             // There may be binaries that are almost circular, and
01016             // these can still give the problem.
01017             //                                            SPZ 2/98
01018         }
01019 
01020         // Convention: semi_major_axis is always > 0.
01021 
01022         semi_major_axis = abs(semi_major_axis);
01023         periastron = semi_major_axis * abs(1 - eccentricity);
01024         
01025         mean_motion = sqrt(total_mass / 
01026                            (semi_major_axis*semi_major_axis*semi_major_axis));
01027         period = TWO_PI / mean_motion;
01028 
01029     } else {
01030 
01031         eccentricity = 1;
01032 
01033         semi_major_axis = INFINITY;
01034         periastron = 0.5 * angular_momentum * angular_momentum / total_mass;
01035 
01036         mean_motion = sqrt(total_mass / 
01037                            (2 * periastron * periastron * periastron));
01038 
01039         // (The real mean motion is undefined, but this is useful in
01040         //  determining time from radius.)
01041 
01042         period = INFINITY;
01043     }
01044 
01045     if (angular_momentum != 0) 
01046         normal_unit_vector /= angular_momentum;
01047     else {
01048         eccentricity = 1;
01049         periastron = 0;
01050         if (energy == 0) mean_motion = sqrt(4.5*total_mass);    // Yes, really!
01051     }
01052 
01053 
01054     if (minimal) return;        // "Minimal" kepler has orbital elements,
01055                                 // but no phase or orientation information.
01056 
01057 
01058     // Phase:
01059     // -----
01060 
01061     vector r_unit = rel_pos / separation;
01062 
01063     if (angular_momentum == 0) {
01064         vector temp = vector(1,0,0);  // Construct an arbitrary normal vector.
01065         if (abs(r_unit[0]) > .5) temp = vector(0,1,0);
01066         normal_unit_vector = r_unit ^ temp;
01067         normal_unit_vector /= abs(normal_unit_vector);
01068     }
01069 
01070     vector t_unit = normal_unit_vector ^ r_unit;
01071 
01072     real cos_true_an = 1, sin_true_an = 0;
01073 
01074     // Determine the true anomaly (note conventions in special cases).
01075 
01076     if (eccentricity > 0) 
01077       if (eccentricity != 1) {
01078 
01079           // Bizarre error with g++ 2.7.2.1 on Redhat Linux 4.1:
01080           // Setting cos_true_an directly here with
01081           //
01082           //  cos_true_an = ((periastron/separation) * (1 + eccentricity) - 1)
01083           //                       / eccentricity;
01084           //
01085           // can set |cos_true_an| > 1 if eccentricity is close to zero.
01086           // However, setting a temporary variable to evaluate exactly
01087           // the same expression produces a different and correct result!
01088 
01089           real tmp = ((periastron/separation) * (1 + eccentricity) - 1)
01090                            / eccentricity;
01091           cos_true_an = tmp;
01092 
01093           // Note: The order of operations above is important for very
01094           //       eccentric orbits!
01095 
01096           // For almost circular orbits, the above expression is likely
01097           // to be very inaccurate (sep ~ peri ~ a ==> tmp = 1), so expect
01098           // check_trig_limit() to complain...
01099 
01100           if (fabs(cos_true_an) > 1.0) {
01101               // cerr << "  rp, rsep, ecc =  " << periastron << " " 
01102               //      << separation << " " << eccentricity << endl;
01103               // print_all();
01104           }
01105 
01106           check_trig_limit(this, cos_true_an, "set_phase #1");
01107 
01108           sin_true_an = cos_to_sin(cos_true_an);
01109           if (rel_pos * rel_vel < 0) sin_true_an = -sin_true_an;
01110 
01111           if (1 - cos_true_an < TRIG_TOL) { // More special treatment!
01112               true_anomaly = 0;
01113               cos_true_an = 1;
01114               sin_true_an = 0;
01115           } else
01116             true_anomaly = acos(cos_true_an);
01117 
01118           if (sin_true_an < 0) true_anomaly = -true_anomaly;
01119 
01120       } else {
01121 
01122           if (angular_momentum > 0) {
01123 
01124               // Special case: see McCuskey, p. 54.
01125 
01126               true_anomaly = 2*acos(sqrt(periastron/separation));
01127               if (rel_pos * rel_vel < 0) true_anomaly = -true_anomaly;
01128 
01129               cos_true_an = cos(true_anomaly);
01130               sin_true_an = sin(true_anomaly);
01131 
01132           } else {      // Linear orbit: "true anomaly" = eccentric anomaly.
01133 
01134               if (energy < 0) {
01135 
01136                   cos_true_an = 1 - separation / semi_major_axis;
01137                   check_trig_limit(this, cos_true_an, "set_phase #2");
01138                   true_anomaly = acos(cos_true_an);
01139 
01140               } else if (energy > 0) 
01141 
01142                   true_anomaly = acosh(1 + separation / semi_major_axis);
01143 
01144               else      // Special case...
01145 
01146                   true_anomaly = pow(separation, 1.5);
01147 
01148               if (rel_pos * rel_vel < 0) true_anomaly = -true_anomaly;
01149 
01150               cos_true_an = -1; // (To get the unit vectors right below.)
01151               sin_true_an = 0;
01152           }
01153       }
01154 
01155     longitudinal_unit_vector = cos_true_an * r_unit - sin_true_an * t_unit;
01156     transverse_unit_vector = sin_true_an * r_unit + cos_true_an * t_unit;
01157 
01158     pred_true_anomaly = true_anomaly;
01159     pred_true_to_mean_anomaly();
01160     mean_anomaly = pred_mean_anomaly;
01161 
01162     mean_anomaly_to_periastron_passage();
01163 }
01164 
01165 // Initialize_from_shape and phase: start with time, mass, semi-major axis,
01166 //                                  eccentricity and mean anomaly, and
01167 //                                  determine all other orbit properties.
01168 //
01169 // Does not set unit vectors, so those must be specified beforehand in order
01170 // for rel_pos and rel_vel to be defined.
01171 
01172 // Convention: on input, specify semi-major axis > 0 and eccentricity.  If
01173 //             eccentricity = 1, look at periastron to distinguish a parabola
01174 //             from a linear orbit.  If the orbit is linear, use the sign of
01175 //             the energy to distinguish a bound from an unbound orbit.
01176 
01177 void  kepler::initialize_from_shape_and_phase()
01178 {
01179     if (eccentricity != 1) {         // Simple elliptical or hyperbolic motion.
01180 
01181         energy = .5 * total_mass / semi_major_axis;
01182         if (eccentricity < 1) energy = -energy;
01183 
01184         periastron = semi_major_axis * abs(1 - eccentricity);
01185         mean_motion = sqrt(total_mass
01186                            /(semi_major_axis*semi_major_axis*semi_major_axis));
01187         period = (energy < 0 ? TWO_PI / mean_motion : INFINITY);
01188 
01189         angular_momentum = sqrt(abs(1 - eccentricity * eccentricity)
01190                                  * total_mass * semi_major_axis);
01191             
01192     } else if (periastron == 0) {    // Linear orbit
01193 
01194         angular_momentum = 0;
01195 
01196         if (energy == 0) {
01197 
01198             period = semi_major_axis = INFINITY;
01199             mean_motion = sqrt(4.5*total_mass); // Yes, really!
01200 
01201         } else {
01202 
01203             energy = sign(energy) * .5 * total_mass / semi_major_axis;
01204 
01205             mean_motion = sqrt(total_mass/(semi_major_axis
01206                                            *semi_major_axis*semi_major_axis));
01207             period = (energy < 0 ? TWO_PI / mean_motion : INFINITY);
01208         }
01209 
01210     } else {                         // Parabola.
01211 
01212         energy = 0;
01213         period = semi_major_axis = INFINITY;
01214 
01215         mean_motion = sqrt(total_mass
01216                            / (2*periastron*periastron*periastron));
01217 
01218         angular_momentum = sqrt(2 * total_mass * periastron);
01219     }
01220 
01221     mean_anomaly_to_periastron_passage();
01222     pred_time = time;
01223     pred_mean_anomaly = mean_anomaly;
01224 
01225     pred_mean_anomaly_to_pos_and_vel();
01226     set_real_from_pred();
01227 }
01228 
01229 //----------------------------------------------------------------------------
01230 
01231 // Transformation functions:
01232 // ------------------------
01233 
01234 real  kepler::pred_advance_to_periastron()     {
01235 
01236     if (eccentricity >= 1 && true_anomaly > 0)
01237         err_or_warn("pred_advance_to_periastron: outgoing hyperbolic orbit");
01238 
01239     pred_time = time_of_periastron_passage;
01240 
01241     if (eccentricity < 1) {
01242 
01243         // Original formula (incorrect if mean_motion is large):
01244         // pred_time = time_of_periastron_passage + TWO_PI/mean_motion;
01245 
01246         // Next version (slow if mean_motion very large):
01247         // while (pred_time - time < 0.0) pred_time += TWO_PI/mean_motion;
01248 
01249         // New implementation (SPZ&JM, 2/98):
01250 
01251         real n_orbits;
01252         if (pred_time - time < 0.0) {
01253             
01254             real n_orbits = ceil((time-pred_time)/(TWO_PI/mean_motion));
01255             pred_time += n_orbits * (TWO_PI/mean_motion);
01256         }
01257         if (pred_time < time) {
01258             cerr << "kepler::pred_advance_to_periastron(): shouldn't happen!"
01259                  << endl;
01260             PRC(pred_time); PRC(time); PRC(n_orbits); PRL(pred_time-time);
01261         }
01262     }
01263 
01264     pred_true_anomaly = 0;
01265     pred_mean_anomaly = 0;
01266 
01267     pred_separation = periastron;
01268 
01269     to_pred_rel_pos_vel(1, 0);
01270 
01271     return pred_time;
01272 }
01273 
01274 real  kepler::pred_return_to_periastron()
01275     {
01276     if (eccentricity >= 1 && true_anomaly < 0)
01277         err_or_warn("pred_return_to_periastron: incoming hyperbolic orbit");
01278 
01279     pred_time = time_of_periastron_passage;
01280     pred_true_anomaly = 0;
01281     pred_mean_anomaly = 0;
01282 
01283     pred_separation = periastron;
01284 
01285     to_pred_rel_pos_vel(1, 0);
01286 
01287     return pred_time;
01288     }
01289 
01290 real  kepler::pred_advance_to_apastron()
01291     {
01292     if (eccentricity >= 1)
01293         err_or_warn("pred_advance_to_apastron: hyperbolic orbit");
01294 
01295     if (true_anomaly < 0)
01296         pred_time = time_of_periastron_passage + 1.5 * TWO_PI / mean_motion;
01297     else
01298         pred_time = time_of_periastron_passage + PI / mean_motion;
01299 
01300     pred_true_anomaly = PI;
01301     pred_mean_anomaly = PI;
01302     pred_separation = semi_major_axis * (1 + eccentricity);
01303     to_pred_rel_pos_vel(-1, 0);
01304 
01305     return pred_time;
01306     }
01307 
01308 real  kepler::pred_return_to_apastron()
01309     {
01310     if (eccentricity >= 1)
01311         err_or_warn("pred_return_to_apastron: hyperbolic orbit");
01312 
01313     if (true_anomaly < 0)
01314         pred_time = time_of_periastron_passage + PI / mean_motion;
01315     else
01316         pred_time = time_of_periastron_passage - PI / mean_motion;
01317 
01318     pred_true_anomaly = PI;
01319     pred_mean_anomaly = PI;
01320     pred_separation = semi_major_axis * (1 + eccentricity);
01321     to_pred_rel_pos_vel(-1, 0);
01322 
01323     return pred_time;
01324     }
01325 
01326 real kepler::pred_transform_to_radius(real r, int direction)
01327 {
01328     // Transform forward or backwards in time, depending on direction.
01329 
01330     if (energy < 0)
01331         true_anomaly = sym_angle(true_anomaly); // Unnecessary?
01332     else if (separation > r && direction*true_anomaly > 0) {
01333         err_or_warn(
01334             "pred_transform_to_radius: unbound orbit inside target radius");
01335         if (direction > 0)
01336           cerr << "Mapping to incoming branch." << endl;
01337         else
01338           cerr << "Mapping to outgoing branch." << endl;
01339     }
01340 
01341     if (r < periastron) {
01342         if (kepler_tolerance_level <= 1)
01343             err_exit("pred_transform_to_radius: r < periastron");
01344         r = periastron;
01345     }
01346 
01347     pred_separation = r;
01348     real cos_true_an, sin_true_an;
01349 
01350     // Determine the absolute magnitude of the true anomaly in all cases.
01351 
01352     if (eccentricity != 1) {
01353 
01354         // >>>> Commented if () below incorrectly replaced by SPZ 2/98.
01355         // >>>> **** Check reason for change... ****
01356 
01357         // if (eccentricity > 0 && eccentricity < 1 && r > 0)
01358         if (eccentricity > 0 && r > 0)
01359 
01360             cos_true_an = ((periastron/r) * (1 + eccentricity) - 1)
01361                             / eccentricity;
01362         else
01363             cos_true_an = 2;    // Force the trig check below.
01364 
01365         if (check_trig_limit(this, cos_true_an,
01366                              "pred_transform_to_radius #1")) {
01367 
01368             // Requested r was outside the allowed range.  Map directly
01369             // to periastron or apastron.
01370 
01371             if (cos_true_an > 0)
01372                 return (direction == 1 ? pred_advance_to_periastron()
01373                                        : pred_return_to_periastron());
01374             else
01375                 return (direction == 1 ? pred_advance_to_apastron()
01376                                        : pred_return_to_apastron());
01377         }
01378 
01379         pred_true_anomaly = acos(cos_true_an);
01380         sin_true_an = cos_to_sin(cos_true_an);
01381 
01382     } else {
01383 
01384         // Treat parabolic/linear motion as a special case.
01385 
01386         if (angular_momentum > 0) {
01387 
01388             pred_true_anomaly = 2*acos(sqrt(min(1.0, periastron/r)));
01389             cos_true_an = cos(pred_true_anomaly);
01390             sin_true_an = sin(pred_true_anomaly);
01391 
01392         } else {        // Linear motion.
01393 
01394             if (energy < 0) {
01395 
01396                 cos_true_an = 1 - r / semi_major_axis;
01397                 check_trig_limit(this, cos_true_an,
01398                                  "pred_transform_to_radius #2");
01399                 pred_true_anomaly = acos(cos_true_an);
01400 
01401             } else if (energy > 0) 
01402 
01403                 pred_true_anomaly = acosh(1 + r / semi_major_axis);
01404 
01405             else
01406 
01407                 pred_true_anomaly = pow(r, 1.5);
01408         }
01409     }
01410 
01411     // Determine the sign of the true anomaly.
01412 
01413     if ( direction * (r - separation) < 0 ) {
01414         pred_true_anomaly = -pred_true_anomaly;
01415         if (angular_momentum > 0) sin_true_an = -sin_true_an;
01416     }
01417 
01418     // Calculate position and velocity...
01419 
01420     if (angular_momentum > 0)
01421         to_pred_rel_pos_vel(cos_true_an, sin_true_an);
01422     else        
01423         to_pred_rel_pos_vel_linear(pred_true_anomaly);
01424 
01425     // ...and the mean anomaly.
01426 
01427     pred_true_to_mean_anomaly();
01428 
01429     // Ensure the smallest change in mean anomaly in the right direction.
01430 
01431     if (energy < 0)
01432       while ( direction * (pred_mean_anomaly - mean_anomaly) > TWO_PI )
01433         pred_mean_anomaly -= direction*TWO_PI;
01434 
01435     // Update the predicted time.  Looks like mean_anomaly will remain
01436     // in the range (-PI, PI).  Make sure the time increases or decreases
01437     // appropriately, depending on direction.
01438 
01439     pred_time = time + (pred_mean_anomaly - mean_anomaly) / mean_motion;
01440     while (direction*(pred_time-time) < 0) pred_time += direction*period;
01441 
01442     return pred_time;
01443 }
01444 
01445 real  kepler::pred_advance_to_radius(real r) // transform forwards in time
01446 {
01447     return pred_transform_to_radius(r, +1);
01448 }
01449 
01450 real  kepler::pred_return_to_radius(real r)  // transform backwards in time
01451 {
01452     return pred_transform_to_radius(r, -1);
01453 }
01454 
01455 real  kepler::pred_transform_to_time(real t)
01456 {
01457     pred_mean_anomaly = mean_anomaly + mean_motion * (t - time);
01458 //    pred_mean_anomaly = mean_anomaly + mean_motion * (fmod(t - time,period));
01459     pred_time = t;
01460     
01461     pred_mean_anomaly_to_pos_and_vel();
01462     return pred_separation;
01463 }
01464 
01465 real  kepler::advance_to_radius(real r)           // transform forwards in time
01466     {
01467     time = pred_advance_to_radius(r);
01468     set_real_from_pred();
01469 
01470     return time;
01471     }
01472 
01473 real  kepler::return_to_radius(real r)           // transform backwards in time
01474     {
01475     time = pred_return_to_radius(r);
01476     set_real_from_pred();
01477 
01478     return time;
01479     }
01480 
01481 real  kepler::advance_to_periastron()
01482     {
01483     time = pred_advance_to_periastron();
01484     set_real_from_pred();
01485 
01486     return time;
01487     }
01488 
01489 real  kepler::return_to_periastron()
01490     {
01491     time = pred_return_to_periastron();
01492     set_real_from_pred();
01493 
01494     return time;
01495     }
01496 
01497 real  kepler::advance_to_apastron()
01498     {
01499     time = pred_advance_to_apastron();
01500     set_real_from_pred();
01501 
01502     return time;
01503     }
01504 
01505 real  kepler::return_to_apastron()
01506     {
01507     time = pred_return_to_apastron();
01508     set_real_from_pred();
01509 
01510     return time;
01511     }
01512 
01513 real  kepler::transform_to_time(real t)
01514     {
01515     separation = pred_transform_to_time(t);
01516     set_real_from_pred();
01517 
01518     return separation;
01519     }
01520 
01521 // Handy functions:
01522 
01523 // make_standard_kepler: set standard properties for an existing
01524 //                       kepler structure.   By default, the new
01525 //                       orbit is in the x-y plane, with the major
01526 //                       axis in the x direction.
01527 
01528 void make_standard_kepler(kepler &k, real t, real mass,
01529                           real energy, real eccentricity,
01530                           real q, real mean_anomaly,
01531                           int align_axis)
01532 {
01533     // Note: energy is the true energy of the system;
01534     //       q is the true periastron only in the case of a parabolic orbit.
01535     //
01536     // In the event of inconsistent input (e.g. energy > 0, eccentricity < 1),
01537     // the internals of initialize_from_shape_and_phase() will silently force
01538     // the energy to follow the eccentricity (i.e. if eccentricity < 1, then
01539     // energy will be forced < 0).
01540 
01541     k.set_time(t);
01542     k.set_total_mass(mass);
01543     k.set_semi_major_axis((energy == 0 ? VERY_LARGE_NUMBER 
01544                                        : .5*mass/abs(energy)));
01545     k.set_eccentricity(eccentricity);
01546     k.set_periastron(q);
01547     k.set_energy(energy);
01548     k.set_mean_anomaly(mean_anomaly);
01549     if (align_axis >= 1 && align_axis <= 3)
01550         k.align_with_axes(align_axis);
01551 
01552     k.initialize_from_shape_and_phase();        // Expects a, e [, q [, E]]
01553 }
01554 
01555 // set_random_orientation: randomly set the orientation of a kepler structure.
01556 
01557 void set_random_orientation(kepler &k, int planar)
01558 {
01559     real cos_theta = (real) planar, sin_theta = 0;
01560 
01561     if (planar == 0) {
01562         cos_theta = randinter(-1, 1);
01563         sin_theta = sqrt(1 - cos_theta * cos_theta);
01564     }
01565 
01566     real phi = randinter(0, TWO_PI);
01567     real psi = randinter(0, TWO_PI);
01568 
01569     // Construct the normal vector:
01570 
01571     vector n = vector(sin_theta*cos(phi), sin_theta*sin(phi), cos_theta);
01572 
01573     // Construct unit vectors a and b perpendicular to n:
01574 
01575     vector temp = vector(1, 0, 0);
01576     if (abs(n[0]) > .5) temp = vector(0, 1, 0); // temp is not parallel to n
01577     if (n[2] < 0) temp = -temp;
01578 
01579     vector b = n ^ temp;
01580     b /= abs(b);
01581     vector a = b ^ n;
01582     if (n[2] < 0) a = -a;       // Force (a, b) to be (x, y) for n = +/-z
01583     
01584     // Construct *random* unit vectors l and t perpendicular to each
01585     // other and to n (psi = 0 ==> periastron along a):
01586 
01587     vector l = cos(psi)*a + sin(psi)*b;
01588     vector t = n ^ l;
01589 
01590     k.set_orientation(l, t, n);
01591     k.initialize_from_shape_and_phase();
01592 }
01593 
01594 #else
01595 
01596 kepler k;
01597 
01598 local void tt(real t)
01599 {
01600     cerr << "  transform_to_time " << t << endl << endl;
01601     k.transform_to_time(t);
01602     k.print_dyn();
01603 }
01604 
01605 local void ar(real r)
01606 {
01607     cerr << "  advance_to_radius " << r << endl << endl;
01608     k.advance_to_radius(r);
01609     k.print_dyn();
01610 }
01611 
01612 local void rr(real r)
01613 {
01614     cerr << "  return_to_radius " << r << endl << endl;
01615     k.return_to_radius(r);
01616     k.print_dyn();
01617 }
01618 
01619 main(int argc, char **argv)
01620 {
01621     check_help();
01622 
01623     bool orbit_params = true;
01624     real time = 0;
01625     real semi = 1;
01626     real ecc = 0;
01627     real mass = 1;
01628     real mean_anomaly = 0;
01629     vector r, v = vector(0);
01630 
01631     int i = 0;
01632     while (++i < argc)
01633         if (argv[i][0] == '-')
01634             switch (argv[i][1]) {
01635 
01636                 case 'a':       semi = atof(argv[++i]);
01637                                 orbit_params = true;
01638                                 break;
01639                 case 'e':       ecc = atof(argv[++i]);
01640                                 break;
01641                 case 'm':       mass = atof(argv[++i]);
01642                                 break;
01643                 case 'M':       mean_anomaly = atof(argv[++i]);
01644                                 break;
01645                 case 't':       time = atof(argv[++i]);
01646                                 break;
01647                 case 'x':       for (int k = 0; k < 3; k++)
01648                                     r[0] = atof(argv[++i]);
01649                                 orbit_params = false;
01650                                 break;
01651                 case 'v':       for (int k = 0; k < 3; k++)
01652                                     v[0] = atof(argv[++i]);
01653                                 break;
01654                 default:        cerr << "unknown option \"" << argv[i] << endl;
01655                                 exit(1);
01656             }
01657 
01658     set_kepler_tolerance(2);
01659     cout.precision(12);
01660     cerr.precision(12);
01661 
01662     k.set_time(time);
01663     k.set_total_mass(mass);
01664 
01665     if (orbit_params) {
01666 
01667         k.set_semi_major_axis(semi);
01668         k.set_eccentricity(ecc);
01669         k.set_mean_anomaly(mean_anomaly);
01670         k.align_with_axes(1);
01671         k.initialize_from_shape_and_phase();
01672 
01673         cerr << "Initialized from shape and phase" << endl;
01674 
01675     } else {
01676 
01677         k.set_rel_pos(r);
01678         k.set_rel_vel(v);
01679         k.initialize_from_pos_and_vel();
01680 
01681         cerr << "Initialized from pos and vel" << endl;
01682 
01683     }
01684 
01685     cerr << endl;
01686     k.print_all();
01687 
01688     // Loop over user input.
01689 
01690     for (;;) {
01691 
01692         char opt;
01693 
01694         cout << endl << "? ";
01695         opt = 'q';
01696         cin >> opt;
01697         if (opt == 'q') exit(0);
01698 
01699         if (opt == 'p') {
01700 
01701             cerr << endl;
01702             k.print_all();
01703 
01704         } else if (opt == 'h' || opt == '?') {
01705 
01706             cerr << "  options:   a R    advance to radius R" << endl
01707                  << "             d dT   increment time by dT" << endl
01708                  << "             p      print" << endl
01709                  << "             q      quit" << endl
01710                  << "             r R    return to radius R" << endl
01711                  << "             t T    evolve to time T" << endl;
01712 
01713         } else {
01714 
01715             real x;
01716             cin >> x;
01717 
01718             if (opt == 'd')
01719                 tt(k.get_time()+x);
01720             else if (opt == 't')
01721                 tt(x);
01722             else if (opt == 'a')
01723                 ar(x);
01724             else if (opt == 'r') 
01725                 rr(x);
01726         }
01727     }
01728 
01729 }
01730 
01731 #endif

Generated at Sun Feb 24 09:57:05 2002 for STARLAB by doxygen1.2.6 written by Dimitri van Heesch, © 1997-2001