import java.io.*; class MinorPlanet extends SolarSystemBody { /* DECLARE INSTANCE VARIABLES */ /* Auxiliary Variables: */ /* A list (mp_list) is maintained of the minor planets for whom a data file (observations and orbit) exists. */ public int number_of_minor_planets = 0; public String[] mp_list = new String[1001]; /* The current minor planet on that list */ public String current_minor_planet = " "; /* In method "probability_of_collision", it is necessary to determine the state transition matrix, which essentially represents the sensitivity of the current state vector to small changes in the epoch state vector. This requires integrating forward from epoch along eight different variant paths; rather than start from scratch for each collision/near miss event, these instance variables will be used to keep a "running count" of the variant trajectories. */ public double[][] collision_sensitivity_r_variant = new double[9][4]; public double[][] collision_sensitivity_rprime_variant = new double[9][4]; public double collision_sensitivity_time = 0; public double[][][] collision_state_matrix = new double[2000][9][9]; public double[][] collision_nominal_state = new double[2000][9]; /* nominal_observable is an array containing the nominal computed value of each ra, dec, delay, or doppler observation, as determined by method get_residuals. Note that the observables treat a ra and a dec as separate, so the index will not correspond to the observation number. */ public double[] nominal_observable = new double[40001]; /* In response to feedback from Dr. Gerhard Hahn of DLR, I am adding a GUI-selectable non-database variable representing the threshold approach distance defining a near-miss. The default value of 0.05 AU matches the MPC definition. */ public double near_miss_threshold = 0.05; /* In searching for virtual impactors, users have encountered collisions on variant trajectories; to capitalize on these fortuitous accidents, I'm adding three flags that will control whether variant trajectories are checked for collisions. "VIflag1" will indicate that a search for VIs is underway. "VIflag2" is set by detect_collision to prevent the variant trajectories from being tested for collision; the absence of this flag is interpreted to mean that variant trajectories should be tested for collision. "VIflag3" is set for variant trajectories by probability_of_collision to prevent a recursive call to probability_of_collision from within detect_collision. "VIflag4" allows detect_collision while suppressing calls to probability_of_collision, thus avoiding the use of the six variant trajectories. "VIflag5" is used within detect_collision to prevent bisection calls to update from resetting the VI_trajectory_r and _rprime vectors. */ public boolean VIflag1 = false, VIflag2 = false, VIflag3 = false, VIflag4 = false, VIflag5 = false; /* In searching for virtual impactors, we often want the variant trajectories to be centered on a non-nominal trajectory. These arrays will be used to store the state vector passed to update by the VI search algorithm; if the VIflag1 flag is set, probability_of_collision will center its variant trajectories around this state vector, rather than the nominal epoch state vector. */ public double[] VI_trajectory_r = new double[4]; public double[] VI_trajectory_rprime = new double[4]; /* In method "least_squares_solution", the user can exclude observations from the final solution on the basis of excessive chi or residuals. The "testing" flags and thresholds are set in the GUI (although default values are defined here). */ public double chi_threshold = 2.8; public double optical_residual_threshold = 2.3*Math.PI/(180.0*3600.0); public double delay_residual_threshold = 30.0E-6; public double doppler_residual_threshold = 30.0; public boolean chi_testing = true; public boolean residual_testing = false; public int[] excluded_observations = new int[20001]; /* The GUI provides the user with the ability to evaluate collision or near-miss events with all bodies, or Earth-only. To save processing time, the earth_only boolean will tell detect_collision which planets to test. */ boolean earth_only = true; /* Variant collision analysis processing time is unreasonably long when the highest integration accuracy is used. To speed processing, I will allow the user to select between high (6e-9) and medium (1e-5) integration accuracy; this selection will also change the precision with which near-miss and collision events are localized. */ public double integration_error = 6e-9; public double event_localization = 1e-5; /* Since the dopri8 integration coefficients are reinitialized for every integration step, I'm going to try making them class variables; they will be initialized only once. */ double[][] dopri8_a = new double[14][14]; double[] dopri8_c = new double[14]; double[] dopri8_b = new double[14]; double[] dopri8_bhat = new double[14]; /* A colleague once said, "Show me a core, and I'll show you a matrix that fills it." Since the number of observations can be quite large, we must divide matrix_T in the least-squares algorithm into several subsections, each of which can be manipulated separately. */ public double[][] matrix_Ti = new double[501][18001]; public double[][] matrix_Tk = new double[501][18001]; /* Instance Variables Comprising Database for Each Minor Planet */ /* comet_identifier is a boolean that indicates whether the minor planet is a comet */ public boolean comet_identifier = false; /* The following variables and arrays contain the orbital solution (state vector and covariances at epoch) for the minor planet */ /* epoch_time is the TDB jultime to which the calculated or a'priori minor planet state vector applies */ public double epoch_time = 0; /* epoch_r and epoch_rprime are the calculated or a'priori minor planet state vector */ public double[] epoch_r = new double[4]; public double[] epoch_rprime = new double[4]; /* A1_A2_DT is an array containing parameters that model a comet's outgassing thrust. A1 and A2 are the radial and transverse components respectively, and are on the order of 10^(-9) AU/(day)^2. DT represents an offset from perihelion (in days, < 0 if before, > 0 if after) in the peak of a comet's light (and water-vaporization) curve. It is small, on the order of 0-80 days, and is not always required. */ public double[] A1_A2_DT = new double[4]; /* big_p is the epoch error covariance matrix, either calculated using least squares, or constructed using assumed state vector covariances. Diagonal entries 1-3 represent squared covariances in epoch position, entries 4-6 represent squared covariances in epoch velocity, and entries 7 and 8 represent squared covariances in A1 and A2. */ public double[][] big_p = new double[9][9]; /* absolute_magnitude_H is the mean absolute visual magnitude of the asteroid. */ public double absolute_magnitude_H = 0; /* slope_parameter_G is used to calculate the visual magnitude of the asteroid. */ public double slope_parameter_G = 0; /* comet_absolute_magnitude is the mean absolute visual magnitude of the comet */ public double comet_absolute_magnitude = 0; /* comet_slope_parameter is used to calculate the visual magnitude of the comet */ public double comet_slope_parameter = 0; /* The following variables and arrays contain the actual optical, radar delay and radar doppler observations of the minor planet */ /* Note that one optical observation consists of both a ra and a dec */ public int number_of_optical_observations = 0; public int number_of_delay_observations = 0; public int number_of_doppler_observations = 0; /* observation type is an integer array containing the type of each observation: = 1 for optical; = 2 for delay; = 3 for doppler; */ public int[] observation_type = new int[20001]; /* incremental_state_vector_epoch is an array containing the TDB times of each observation: = TDB time of optical observation, or = TDB receiver time for radar observations. Note: incremental_state_vector_epoch[0] = epoch_time; */ public double[] incremental_state_vector_epoch = new double[20001]; /* In method "least_squares_solution", a call to "get_residuals" requires calculation of the nominal state vector and observables at the TDB time of each observation. These state vectors are retained for use in "least_squares" and elsewhere, so as to avoid recalculation. */ /* incremental_state_vector_r is an array containing the state vector positions at the TDB times of each observation */ public double[][] incremental_state_vector_r = new double[20001][4]; /* incremental_state_vector_rprime is an array containing the state vector velocities at the TDB times of each observation */ public double[][] incremental_state_vector_rprime = new double[20001][4]; /* OPTICAL OBSERVATIONS */ /* ra and dec are in radians */ public double[] optical_ra = new double[20001]; public double[] ra_dev = new double[20001]; public double[] optical_dec = new double[20001]; public double[] dec_dev = new double[20001]; /* TDT times of optical observations */ public double[] optical_time = new double[20001]; /* visual_magnitude[i] is the observed visual-range brightness of the minor planet, made as part of optical observation i. A value of "-99" means that no visual-range brightness measurement was made. (Note that we will only solve for brightness parameters in the case of non-thrusting minor planets [asteroids]; thus, "-99" magnitude is unlikely to occur naturally.) An uncertainty of 0.1 will be assumed for all magnitude observations. */ public double[] visual_magnitude = new double[20001]; public double[] visual_magnitude_dev = new double[20001]; /* observation_geocentric is a boolean array; entry i = true iff optical observation i was geocentric */ public boolean[] observation_geocentric = new boolean[20001]; /* observation_latitude contains the geodetic latitude in radians from which each optical observation was made */ public double[] observation_latitude = new double[20001]; /* observation_longitude contains the east longitude in radians from which each optical observation was made */ public double[] observation_longitude = new double[20001]; /* observation_altitude contains the altitude in meters from which each optical observation was made */ public double[] observation_altitude = new double[20001]; /* observation_EOP[i][j] contains the Earth Orientation Parameters for optical observation i: - observation_EOP[i][1] contains the value of xp in radians at the time of observation i; - observation_EOP[i][2] contains the value of yp in radians at the time of observation i; - observation_EOP[i][3] contains the value of (UT1 - TAI) in seconds at the time of observation i; - observation_EOP[i][4] contains the value of delta psi in radians at the time of observation i; - observation_EOP[i][5] contains the value of delta epsilon in radians at the time of observation i; - observation_EOP[i][6] contains the value of mean obliquity in radians at the time of observation i; - observation_EOP[i][7] contains the value of Earth's angular velocity in radians per second at the time of observation i; */ public double[][] observation_EOP = new double[20001][8]; /* observation_r contains the barycentric rectangular equatorial position (in AUs) of the site from which each optical observation was made */ public double[][] observation_r = new double[20001][4]; /* observation_rprime contains the barycentric rectangular equatorial velocity (in AU/day) of the site from which each optical observation was made */ public double[][] observation_rprime = new double[20001][4]; /* DELAY OBSERVATIONS */ /* delay is in seconds */ public double[] radar_delay = new double[20001]; public double[] delay_dev = new double[20001]; /* UTC Julian dates of radar delay receiver observations */ public double[] radar_delay_receiver_time = new double[20001]; /* radar_delay_receiver_latitude contains the geodetic latitude in radians at which each radar delay receiver is located */ public double[] radar_delay_receiver_latitude = new double[20001]; /* radar_delay_receiver_longitude contains the east longitude in radians at which each radar delay receiver is located */ public double[] radar_delay_receiver_longitude = new double[20001]; /* radar_delay_receiver_altitude contains the altitude in meters at which each radar delay receiver is located */ public double[] radar_delay_receiver_altitude = new double[20001]; /* radar_delay_receiver_EOP[i][j] contains the Earth Orientation Parameters for the receiver in radar delay observation i, in format identical to observation_EOP */ public double[][] radar_delay_receiver_EOP = new double[20001][8]; /* radar_delay_receiver_tdb_minus_utc contains the value of (TDB-UTC) in seconds for the receiver from which each radar delay observation was made */ public double[] radar_delay_receiver_tdb_minus_utc = new double[20001]; /* radar_delay_receiver_r contains the barycentric rectangular equatorial position (in AUs) of the receiver site from which each radar delay observation was made */ public double[][] radar_delay_receiver_r = new double[20001][4]; /* radar_delay_receiver_rprime contains the barycentric rectangular equatorial velocity (in AU/day) of the receiver site from which each radar delay observation was made */ public double[][] radar_delay_receiver_rprime = new double[20001][4]; /* radar_delay_transmitter_latitude contains the geodetic latitude in radians at which each radar delay transmitter is located */ public double[] radar_delay_transmitter_latitude = new double[20001]; /* radar_delay_transmitter_longitude contains the east longitude in radians at which each radar delay transmitter is located */ public double[] radar_delay_transmitter_longitude = new double[20001]; /* radar_delay_transmitter_altitude contains the altitude in meters at which each radar delay transmitter is located */ public double[] radar_delay_transmitter_altitude = new double[20001]; /* radar_delay_transmitter_EOP[i][j] contains the Earth Orientation Parameters for the transmitter in radar delay observation i, in format identical to observation_EOP */ public double[][] radar_delay_transmitter_EOP = new double[20001][8]; /* radar_delay_transmitter_tdb_minus_utc contains the value of (TDB-UTC) in seconds for the transmitter from which each radar delay observation was made */ public double[] radar_delay_transmitter_tdb_minus_utc = new double[20001]; /* radar_delay_transmitter_frequency contains the frequency in Hertz for the transmitter from which each radar delay observation was made */ public double[] radar_delay_transmitter_frequency = new double[20001]; /* DOPPLER OBSERVATIONS */ /* doppler is in Hz */ public double[] radar_doppler = new double[20001]; public double[] doppler_dev = new double[20001]; /* UTC Julian dates of radar doppler receiver observations */ public double[] radar_doppler_receiver_time = new double[20001]; /* radar_doppler_receiver_latitude contains the geodetic latitude in radians at which each radar doppler receiver is located */ public double[] radar_doppler_receiver_latitude = new double[20001]; /* radar_doppler_receiver_longitude contains the east longitude in radians at which each radar doppler receiver is located */ public double[] radar_doppler_receiver_longitude = new double[20001]; /* radar_doppler_receiver_altitude contains the altitude in meters at which each radar doppler receiver is located */ public double[] radar_doppler_receiver_altitude = new double[20001]; /* radar_doppler_receiver_EOP[i][j] contains the Earth Orientation Parameters for the receiver in radar doppler observation i, in format identical to observation_EOP */ public double[][] radar_doppler_receiver_EOP = new double[20001][8]; /* radar_doppler_receiver_tdb_minus_utc contains the value of (TDB-UTC) in seconds for the receiver from which each radar doppler observation was made */ public double[] radar_doppler_receiver_tdb_minus_utc = new double[20001]; /* radar_doppler_receiver_r contains the barycentric rectangular equatorial position (in AUs) of the receiver site from which each radar doppler observation was made */ public double[][] radar_doppler_receiver_r = new double[20001][4]; /* radar_doppler_receiver_rprime contains the barycentric rectangular equatorial velocity (in AU/day) of the receiver site from which each radar doppler observation was made */ public double[][] radar_doppler_receiver_rprime = new double[20001][4]; /* radar_doppler_transmitter_latitude contains the geodetic latitude in radians at which each radar doppler transmitter is located */ public double[] radar_doppler_transmitter_latitude = new double[20001]; /* radar_doppler_transmitter_longitude contains the east longitude in radians at which each radar doppler transmitter is located */ public double[] radar_doppler_transmitter_longitude = new double[20001]; /* radar_doppler_transmitter_altitude contains the altitude in meters at which each radar doppler transmitter is located */ public double[] radar_doppler_transmitter_altitude = new double[20001]; /* radar_doppler_transmitter_EOP[i][j] contains the Earth Orientation Parameters for the transmitter in radar doppler observation i, in format identical to observation_EOP */ public double[][] radar_doppler_transmitter_EOP = new double[20001][8]; /* radar_doppler_transmitter_tdb_minus_utc contains the value of (TDB-UTC) in seconds for the transmitter from which each radar doppler observation was made */ public double[] radar_doppler_transmitter_tdb_minus_utc = new double[20001]; /* radar_doppler_transmitter_frequency contains the frequency in Hertz for the transmitter from which each radar doppler observation was made */ public double[] radar_doppler_transmitter_frequency = new double[20001]; /* minor_planet_radius is an estimate of the radius of the minor planet, in AU; it is used in radar calculations to estimate the bounce point */ public double minor_planet_radius = 0; public static void main(String args[]) { MinorPlanet testBody = new MinorPlanet(); } public void least_squares_solution(double[] residuals, double[] rms_residuals) { /* Method to calculate the least squares best-fit orbit of a minor planet, given optical, radar delay, and or radar doppler observations. The process is repeated until the magnitude of the corrections to the epoch state vector is less than some percentage of their corresponding covariances. The method presumes that an a priori state vector has been selected, and resides in epoch_r, epoch_rprime, and epoch_time. The method also assumes that the observations have been ordered from earliest to most recent. The output will be the epoch state vector, and the epoch error covariance matrix (all of which are instance variables of the MinorPlanet class). We will assume a maximum of 20000 observations. Provision is made for excluding observations whose chi or residuals exceed user-defined thresholds; although these observations will not be deleted, they will not be used in the final iterations of the algorithm. Adapted from: D.K. Yeomans, S.J. Ostro, and P.W. Chodas (1987). "Radar Astrometry of Near-Earth Asteroids". Astronomical Journal 94, 189-200. Modified to the "Square Root Information Filter" algorithm from Gerald J. Bierman (1974), "Sequential Square Root Filtering and Smoothing of Discrete Linear Systems", Automatica 10, 147-158. NOTE: In case of divergence, epoch_r[0] will be returned as 6.0. */ int i = 0, j = 0, k = 0, index = 0, retained_index = 0, counter = 0; boolean convergence = false, divergence = false, edit = false; double rms_optical = 0, rms_delay = 0, rms_doppler = 0; double old_rms_optical = 0, old_rms_delay = 0, old_rms_doppler = 0; double convergence_factor = 0.10; double delta = 0, delay = 0, doppler = 0; double observation_residual = 0, observation_chi = 0; int number_of_observations = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; double[] nominal_r = new double[4]; double[] nominal_rprime = new double[4]; double[] initial_r = new double[4]; double[] initial_rprime = new double[4]; double[][] r_variant = new double[7][4]; double[][] rprime_variant = new double[7][4]; double[] this_r = new double[4]; double[] this_rprime = new double[4]; double[] next_r = new double[4]; double[] next_rprime = new double[4]; double[] variant_delta = new double[7]; double[] dev = new double[40002]; double[][] matrix_a = new double[40002][7]; double[][] collision_variant = new double[2000][9]; double[][] phi = new double[7][7]; double[] inter = new double[7]; double[] sensitivity_ra = new double[7]; double[] sensitivity_dec = new double[7]; double[] sensitivity_delay = new double[7]; double[] sensitivity_doppler = new double[7]; double[] radec = new double[3]; double[] row = new double[7]; double[] corrections = new double[7]; double[] observer_r = new double[4]; double[] observer_rprime = new double[4]; double[] delay_doppler = new double[3]; double[] receiver_r = new double[4]; double[] receiver_rprime = new double[4]; double[] EOP = new double[8]; double[][] matrix_R = new double[7][7]; double[] matrix_z = new double[7]; /* How to initialize? */ double[][] matrix_temp = new double[40002][7]; double[] retained_residuals = new double[40002]; double[] initial_residuals = new double[40002]; double[] initial_rms_residuals = new double[4]; double[][] initial_big_p = new double[9][9]; /* Initialize all observations as non-excluded for get_residuals */ for (i=1;i<=20000;i++) excluded_observations[i] = 0; /* Using the a priori state vector, determine initial residuals, nominal observables, and the state vector at the tdb time of each observation. Save the a priori state vector in case of divergence. */ get_residuals(residuals,rms_residuals); rms_optical = rms_residuals[1]; rms_delay = rms_residuals[2]; rms_doppler = rms_residuals[3]; for (i=1;i<=3;i++) { initial_r[i] = epoch_r[i]; initial_rprime[i] = epoch_rprime[i]; initial_rms_residuals[i] = rms_residuals[i]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) initial_big_p[i][j] = big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) { initial_residuals[i] = residuals[i]; } /* The convergence condition is: The RMS residuals on the current loop must differ by no more than some percentage from the RMS residuals on the preceding loop. (Note that this condition was not my original choice. I tried requiring that the corrections to the epoch state vector must be less than some percentage of their corresponding covariances. However, real-world data is not sufficiently smooth. This new condition uses the observed fact that the RMS residuals decrease with successive loops until the next loop results in almost no change. At this point, the solution has converged.) Clearly, this condition will not be satisfied initially, so we will go through the loop at least once. NOTE: MUST ACCOUNT FOR DIVERGENCE, AS MEASURED BY INCREASING RMS RESIDUALS. If each of the RMS residuals increase from one loop to the next, the divergence flag will be set, and the original solution returned. */ /* Note: For the asteroid ephemeris application, we're starting with a good orbit provided by the MPC. We will therefore begin editing errant observations immediately, to promote convergence. */ edit = true; while ((convergence == false) && (divergence == false)) { /* Clear the counters */ index = 0; retained_index = 0; counter ++; /* Use the observations to generate rows of matrix_a */ for (i=1;i<=number_of_observations;i++) { /* Assume that the observation will be excluded; if not, the flag will be reset below. */ excluded_observations[i] = 1; /* Generate the state transition matrix corresponding to this observation time */ for (j=1;j<=3;j++) { nominal_r[j] = incremental_state_vector_r[i][j]; nominal_rprime[j] = incremental_state_vector_rprime[i][j]; } /* Initialize the variant trajectories */ if (i == 1) { for (j=1;j<=6;j++) { for (k=1;k<=3;k++) { r_variant[j][k] = epoch_r[k]; rprime_variant[j][k] = epoch_rprime[k]; } } for (j=1;j<=3;j++) { variant_delta[j] = 0.000001*epoch_r[j]; r_variant[j][j] = r_variant[j][j] + variant_delta[j]; variant_delta[j+3] = 0.000001*epoch_rprime[j]; rprime_variant[j+3][j] = rprime_variant[j+3][j] + variant_delta[j+3]; } } /* Propogate the variant trajectories, and calculate the transition matrix entries */ for (j=1;j<=6;j++) { for (k=1;k<=3;k++) { this_r[k] = r_variant[j][k]; this_rprime[k] = rprime_variant[j][k]; } update(this_r,this_rprime,incremental_state_vector_epoch[i-1],next_r,next_rprime,incremental_state_vector_epoch[i],false,collision_variant); for (k=1;k<=3;k++) { r_variant[j][k] = next_r[k]; rprime_variant[j][k] = next_rprime[k]; } for (k=1;k<=3;k++) { phi[k][j] = (r_variant[j][k] - nominal_r[k])/variant_delta[j]; phi[k+3][j] = (rprime_variant[j][k] - nominal_rprime[k])/variant_delta[j]; } } /* Generate the sensitivity matrix for this observation */ if (observation_type[i] == 1) { /* Optical observation */ for (j=1;j<=3;j++) { observer_r[j] = observation_r[i][j]; observer_rprime[j] = observation_rprime[i][j]; } for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radec(nominal_r,nominal_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); sensitivity_ra[j] = radec[1] - nominal_observable[index+1]; while (Math.abs(sensitivity_ra[j]) > Math.abs(sensitivity_ra[j] - 2.0*Math.PI)) sensitivity_ra[j] = sensitivity_ra[j] - 2.0*Math.PI; while (Math.abs(sensitivity_ra[j]) > Math.abs(sensitivity_ra[j] + 2.0*Math.PI)) sensitivity_ra[j] = sensitivity_ra[j] + 2.0*Math.PI; sensitivity_ra[j] = sensitivity_ra[j]/delta; sensitivity_dec[j] = (radec[2] - nominal_observable[index+2])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radec(nominal_r,nominal_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); sensitivity_ra[j+3] = radec[1] - nominal_observable[index+1]; while (Math.abs(sensitivity_ra[j+3]) > Math.abs(sensitivity_ra[j+3] - 2.0*Math.PI)) sensitivity_ra[j+3] = sensitivity_ra[j+3] - 2.0*Math.PI; while (Math.abs(sensitivity_ra[j+3]) > Math.abs(sensitivity_ra[j+3] + 2.0*Math.PI)) sensitivity_ra[j+3] = sensitivity_ra[j+3] + 2.0*Math.PI; sensitivity_ra[j+3] = sensitivity_ra[j+3]/delta; sensitivity_dec[j+3] = (radec[2] - nominal_observable[index+2])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; } /* Calculate the residuals and chi; if observations meet the applicable thresholds, calculate the resulting rows of matrix_a */ observation_residual = Math.acos(Math.sin(nominal_observable[index+2])*Math.sin(optical_dec[i]) + Math.cos(nominal_observable[index+2])*Math.cos(optical_dec[i])*Math.cos(nominal_observable[index+1] - optical_ra[i])); if ( (ra_dev[i] > 0.0) && (dec_dev[i] > 0.0) ) observation_chi = Math.sqrt(Math.pow(residuals[index+1]*Math.cos(optical_dec[i])/ra_dev[i],2.0) + Math.pow(residuals[index+2]/dec_dev[i],2.0)); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold) && (ra_dev[i] < 5.0*Math.PI/(3600.0*180.0)) && (dec_dev[i] < 5.0*Math.PI/(3600.0*180.0))) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < optical_residual_threshold) && (ra_dev[i] < 5.0*Math.PI/(3600.0*180.0)) && (dec_dev[i] < 5.0*Math.PI/(3600.0*180.0))) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_ra[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_dec[k]*phi[k][j]; matrix_a[retained_index+2][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = ra_dev[i]; dev[retained_index+2] = dec_dev[i]; /* Add these residuals to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; retained_residuals[retained_index+2] = residuals[index+2]; /* Increment the retained observation counter */ retained_index = retained_index + 2; } /* Increment the index */ index = index + 2; } else if (observation_type[i] == 2) { /* Delay observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_delay_receiver_r[i][j]; receiver_rprime[j] = radar_delay_receiver_rprime[i][j]; } for (j=1;j<=7;j++) EOP[j] = radar_delay_transmitter_EOP[i][j]; for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; sensitivity_delay[j] = (delay - nominal_observable[index+1])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; sensitivity_delay[j+3] = (delay - nominal_observable[index+1])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; } /* Calculate the residual and chi; if observations meet the applicable threshold, calculate the resulting row of matrix_a */ observation_residual = Math.abs(residuals[index+1]); if (delay_dev[i] != 0.0) observation_chi = Math.abs(residuals[index+1]/delay_dev[i]); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold)) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < delay_residual_threshold)) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_delay[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = delay_dev[i]; /* Add this residual to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; /* Increment the retained observation counter */ retained_index = retained_index + 1; } /* Increment the index */ index = index + 1; } else if (observation_type[i] == 3) { /* Doppler observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_doppler_receiver_r[i][j]; receiver_rprime[j] = radar_doppler_receiver_rprime[i][j]; } for (j=1;j<=7;j++) { EOP[j] = radar_doppler_transmitter_EOP[i][j]; } for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; sensitivity_doppler[j] = (doppler - nominal_observable[index+1])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; sensitivity_doppler[j+3] = (doppler - nominal_observable[index+1])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; } /* Calculate the residual and chi; if observations meet the applicable thresholds, calculate the resulting row of matrix_a */ observation_residual = Math.abs(residuals[index+1]); if (doppler_dev[i] != 0.0) observation_chi = Math.abs(residuals[index+1]/doppler_dev[i]); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold)) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < doppler_residual_threshold)) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_doppler[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = doppler_dev[i]; /* Add this residual to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; /* Increment the retained observation counter */ retained_index = retained_index + 1; } /* Increment the index */ index = index + 1; } } /* At this point, matrix A is complete. We proceed to convert to a unity covariance form */ /* Should be commented out */ for (i=1;i<=retained_index;i++) { for (j=1;j<=6;j++) matrix_temp[i][j] = matrix_a[i][j]/dev[i]; retained_residuals[i] = retained_residuals[i]/dev[i]; } for (i=1;i<=retained_index;i++) { for (j=1;j<=6;j++) matrix_a[i][j] = matrix_temp[i][j]; } /* Construct an orthogonal transform T using Givens rotations, and left multiply. matrix_a will now be upper triangular; isolate the upper 6x6 as matrix_R, and isolate the top 6x1 of T x residuals as matrix_z. */ givens_qr(retained_index, 6, matrix_a); read_matrix_Tk(0); for (i=1;i<=6;i++) { matrix_z[i] = 0; for (j=1;j<=retained_index;j++) matrix_z[i] = matrix_z[i] + matrix_Tk[i][j]*retained_residuals[j]; } for (i=1;i<=6;i++) { for (j=1;j<=6;j++) matrix_R[i][j] = matrix_a[i][j]; } /* Calculate the state vector corrections and covariances. */ invert_nxn(6,matrix_R); for (i=1;i<=6;i++) { corrections[i] = 0; for (k=1;k<=6;k++) /* Square Root Information Filtering assumes that we are in unit covariance form. Since we're often unable to invert matrix_R in that form, we must make the conversion to unit covariance form now, so the results are valid. In this particular calculation, we would multiply matrix_R[i][k] by dev[k] (since matrix_a has been inverted), and we would divide matrix_z by dev[k]; since these precisely cancel, no action is really needed here. */ corrections[i] = corrections[i] + matrix_R[i][k]*matrix_z[k]; } for (i=1;i<=6;i++) { for (j=1;j<=6;j++) { big_p[i][j] = 0; for (k=1;k<=6;k++) /* Square Root Information Filtering assumes that we are in unit covariance form. Since we're often unable to invert matrix_R in that form, we must make the conversion to unit covariance form now, so the results are valid. In this particular calculation, we would multiply matrix_R[i][k] by dev[k] (since matrix_a has been inverted), and we would multiply matrix_R[j][k] by dev[k] (for the same reason). */ /* big_p[i][j] = big_p[i][j] + matrix_R[i][k]*matrix_R[j][k]*(dev[k]*dev[k]); */ big_p[i][j] = big_p[i][j] + matrix_R[i][k]*matrix_R[j][k]; } } /* Update the epoch state vector */ for (i=1;i<=3;i++) { epoch_r[i] = epoch_r[i] + corrections[i]; epoch_rprime[i] = epoch_rprime[i] + corrections[i+3]; } /* Recalculate the residuals using the updated epoch state vector */ get_residuals(residuals,rms_residuals); old_rms_optical = rms_optical; old_rms_delay = rms_delay; old_rms_doppler = rms_doppler; rms_optical = rms_residuals[1]; rms_delay = rms_residuals[2]; rms_doppler = rms_residuals[3]; System.out.println("old_rms_optical = " + old_rms_optical); System.out.println("old_rms_delay = " + old_rms_delay); System.out.println("old_rms_doppler = " + old_rms_doppler); /* Calculate the asteroid's absolute magnitude, slope parameter, and radius. This last is especially important when radar observations are used, to account for the "bounce point". */ solve_for_absolute_magnitude(); /* Based on the RMS residuals, evaluate the convergence and divergence conditions */ convergence = true; if (Math.abs(old_rms_optical - rms_optical) > (convergence_factor*old_rms_optical)) convergence = false; if (Math.abs(old_rms_delay - rms_delay) > (convergence_factor*old_rms_delay)) convergence = false; if (Math.abs(old_rms_doppler - rms_doppler) > (convergence_factor*old_rms_doppler)) convergence = false; if ((counter > 1) && (rms_optical >= 1.2*old_rms_optical) && (rms_delay >= 1.2*old_rms_delay) && (rms_doppler >= 1.2*old_rms_doppler)) divergence = true; if ((convergence) && (!edit) && ((chi_testing) || (residual_testing))) { edit = true; convergence = false; } if ((counter >= 1) && (divergence == false)) convergence = true; /* We may not have yet converged. However, if the residuals have been reduced, shouldn't we keep this state vector and covariance matrix as the initial data in case the next iteration diverges? */ if ((counter == 1) || ((rms_optical <= old_rms_optical) && (rms_delay <= old_rms_delay) && (rms_doppler <= old_rms_doppler))) { for (i=1;i<=3;i++) { initial_r[i] = epoch_r[i]; initial_rprime[i] = epoch_rprime[i]; initial_rms_residuals[i] = rms_residuals[i]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) initial_big_p[i][j] = big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) initial_residuals[i] = residuals[i]; } System.out.println("convergence = " + convergence); System.out.println("divergence = " + divergence); /* The loop is complete. If the convergence or divergence condition is satisfied, we will exit; if not, we will go around again */ } if (divergence == true) { /* Return initial orbit and residuals, and set epoch_r[0] = 6.0. */ for (i=1;i<=3;i++) { epoch_r[i] = initial_r[i]; epoch_rprime[i] = initial_rprime[i]; rms_residuals[i] = initial_rms_residuals[i]; } epoch_r[0] = 6.0; for (i=1;i<=6;i++) { for (j=1;j<=6;j++) big_p[i][j] = initial_big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) residuals[i] = initial_residuals[i]; } else if (divergence == false) epoch_r[0] = 0.0; } public void get_residuals(double residual[], double rms_residual[]) { /* Method to calculate the (observed - computed) and RMS residuals in the optical, delay, and doppler observations of a minor planet, given the state vector (an instance variable in the MinorPlanet class). Note that, to improve efficiency, it is assumed that the observations are ordered from earliest to most recent. The algorithm will therefore integrate the epoch state vector successively from observation i to i+1, saving the state vector at observation i+1 in the instance variables "incremental_state_vector_r[i+1][j]" and "incremental_state_vector_rprime[i+1][j]". This incremental state vector can then be used in method "least_squares" to represent the nominal state vector at the TDB time of observation i+1 (incremental_state_vector_epoch[i+1]). */ double delay = 0, doppler = 0, rms_optical = 0, rms_delay = 0, rms_doppler = 0; double[] radec = new double[3]; double[] observer_r = new double[4]; double[] observer_rprime = new double[4]; double[] delay_doppler = new double[3]; double[] receiver_r = new double[4]; double[] receiver_rprime = new double[4]; double[] EOP = new double[8]; double[] state_vector_r = new double[4]; double[] state_vector_rprime = new double[4]; double[] new_state_vector_r = new double[4]; double[] new_state_vector_rprime = new double[4]; double[][] collision_variant = new double[2000][9]; int i = 0, j = 0, k = 0, index = 0, number_of_excluded_optical_observations = 0, number_of_excluded_delay_observations = 0, number_of_excluded_doppler_observations = 0; int number_of_observations = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; /* Initialize the required variables */ for (i=1;i<=3;i++) { incremental_state_vector_r[0][i] = epoch_r[i]; incremental_state_vector_rprime[0][i] = epoch_rprime[i]; state_vector_r[i] = epoch_r[i]; state_vector_rprime[i] = epoch_rprime[i]; } incremental_state_vector_epoch[0] = epoch_time; rms_optical = 0.0; rms_delay = 0.0; rms_doppler = 0.0; for (i=1;i<=number_of_observations;i++) { /* For each observation, determine the nominal state vector */ update(state_vector_r,state_vector_rprime,incremental_state_vector_epoch[i-1],new_state_vector_r,new_state_vector_rprime,incremental_state_vector_epoch[i],false,collision_variant); for (j=1;j<=3;j++) { incremental_state_vector_r[i][j] = new_state_vector_r[j]; incremental_state_vector_rprime[i][j] = new_state_vector_rprime[j]; state_vector_r[j] = new_state_vector_r[j]; state_vector_rprime[j] = new_state_vector_rprime[j]; } /* Depending on the observation type, determine the nominal observables, and the residuals */ if (observation_type[i] == 1) { /* Optical observation */ /* Note that method get_observed_radec is being sent the observer posvel vectors that were determined when the observation was entered. That method will see these non-zero vectors (possibly created using precise EOP values), and use them instead of calling method get_observer (which would use estimates of the EOPs to calculate observer posvel). */ for (j=1;j<=3;j++) { observer_r[j] = observation_r[i][j]; observer_rprime[j] = observation_rprime[i][j]; } get_observed_radec(new_state_vector_r,new_state_vector_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); nominal_observable[index+1] = radec[1]; nominal_observable[index+2] = radec[2]; residual[index+1] = optical_ra[i] - radec[1]; while (Math.abs(residual[index+1]) > Math.abs(residual[index+1] - 2.0*Math.PI)) residual[index+1] = residual[index+1] - 2.0*Math.PI; while (Math.abs(residual[index+1]) > Math.abs(residual[index+1] + 2.0*Math.PI)) residual[index+1] = residual[index+1] + 2.0*Math.PI; /* System.out.println("residual[" + (index+1) + "] = " + residual[index+1]); */ residual[index+2] = optical_dec[i] - radec[2]; /* System.out.println("residual[" + (index+2) + "] = " + residual[index+2]); */ /* Don't add these residuals to the rms total if the observation is excluded */ if (excluded_observations[i] == 0) { rms_optical = rms_optical + Math.pow(residual[index+1],2); rms_optical = rms_optical + Math.pow(residual[index+2],2); } else { number_of_excluded_optical_observations = number_of_excluded_optical_observations + 1; } /* Increment index to reflect two observables (ra and dec) */ index = index + 2; } else if (observation_type[i] == 2) { /* Delay observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_delay_receiver_r[i][j]; receiver_rprime[j] = radar_delay_receiver_rprime[i][j]; } for (j=1;j<=7;j++) EOP[j] = radar_delay_transmitter_EOP[i][j]; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,new_state_vector_r,new_state_vector_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; nominal_observable[index+1] = delay; residual[index+1] = radar_delay[i] - delay; /* System.out.println("residual[" + (index+1) + "] = " + residual[index+1]); */ /* Don't add this residual to the rms total if the observation is excluded */ if (excluded_observations[i] == 0) { rms_delay = rms_delay + Math.pow(residual[index+1],2); } else { number_of_excluded_delay_observations = number_of_excluded_delay_observations + 1; } /* Increment index to relect one observable (delay) */ index = index + 1; } else if (observation_type[i] == 3) { /* Doppler observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_doppler_receiver_r[i][j]; receiver_rprime[j] = radar_doppler_receiver_rprime[i][j]; } for (j=1;j<=7;j++) EOP[j] = radar_doppler_transmitter_EOP[i][j]; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,new_state_vector_r,new_state_vector_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; nominal_observable[index+1] = doppler; residual[index+1] = radar_doppler[i] - doppler; /* System.out.println("residual[" + (index+1) + "] = " + residual[index+1]); */ /* Don't add this residual to the rms total if the observation is excluded */ if (excluded_observations[i] == 0) { rms_doppler = rms_doppler + Math.pow(residual[index+1],2); } else { number_of_excluded_doppler_observations = number_of_excluded_doppler_observations + 1; } /* Increment index to relect one observable (doppler) */ index = index + 1; } } if ((number_of_optical_observations - number_of_excluded_optical_observations) > 0) rms_optical = Math.sqrt(rms_optical/(2.0*(number_of_optical_observations - number_of_excluded_optical_observations))); else rms_optical = 0; System.out.println("rms_optical = " + rms_optical); rms_residual[1] = rms_optical; if ((number_of_delay_observations - number_of_excluded_delay_observations) > 0) rms_delay = Math.sqrt(rms_delay/(number_of_delay_observations - number_of_excluded_delay_observations)); else rms_delay = 0; System.out.println("rms_delay = " + rms_delay); rms_residual[2] = rms_delay; if ((number_of_doppler_observations - number_of_excluded_doppler_observations) > 0) rms_doppler = Math.sqrt(rms_doppler/(number_of_doppler_observations - number_of_excluded_doppler_observations)); else rms_doppler = 0; System.out.println("rms_doppler = " + rms_doppler); rms_residual[3] = rms_doppler; } void get_observed_radar_delay_doppler(double receiver_latitude, double receiver_longitude, double receiver_altitude, double receiver_r[], double receiver_rprime[], double receiver_time, double transmitter_latitude, double transmitter_longitude, double transmitter_altitude, double transmitter_EOP[], double transmitter_tdb_minus_utc, double receiver_tdb_minus_utc, double transmitter_frequency, double asteroid_radius, double asteroid_r[], double asteroid_rprime[], double asteroid_epoch_time, double delay_doppler[]) { /* Algorithm to determine the expected radar delay time and doppler shift for an astrometric radar observation. Inputs: - receiver_latitude, transmitter_latitude are in radians; - receiver_longitude, transmitter_longitude, measured east, are in radians; - receiver_altitude, transmitter_altitude are in meters; - receiver_r[] is a barycentric equatorial position in AU - receiver_rprime[] is a barycentric equatorial velocity in AU/day; - receiver_time is a UTC Julian date; - transmitter_EOP[] are the Earth Observation Parameters at transmitter_time; - transmitter_tdb_minus_utc, receiver_tdb_minus_utc are in seconds; - transmitter_frequency is in Hz; - asteroid_radius is in AU; - asteroid_r, asteroid_rprime, asteroid_epoch_time is the barycentric equatorial state vector of the asteroid, given in AUs, AUs/day, and TDB Julian date respectively. Output: - delay_doppler[1] = the predicted UTC delay from transmitter to asteroid to receiver, in seconds; - delay_doppler[2] = the predicted Doppler frequency shift, in Hz; This algorithm follows the outline provided in "Asteroid and Comet Orbits Using Radar Data", Yeomans, Chodas, Keesey, and Ostro, in The Astronomical Journal, Vol. 103, number 1 (January 1992), pp. 303-317. */ double[] EOP = new double[8]; double[][] collision = new double[2000][9]; double[] r_at_bounce = new double[4]; double[] rprime_at_bounce = new double[4]; double[] rho_receiver = new double[4]; double[] rho_dot_receiver = new double[4]; double[] rho_transmitter = new double[4]; double[] rho_dot_transmitter = new double[4]; double[] dummy_rho = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double[] transmitter_r = new double[4]; double[] transmitter_rprime = new double[4]; double receiver_tdb_time = 0, bounce_time = 0, tau_receiver_delay = 0, tau_transmitter_delay = 0, dist_receiver = 0, old_tau_delay = 0, tau_diff = 1, transmitter_time = 0, dist_transmitter = 0, dot_one = 0, dot_two = 0, heliodist1 = 0, heliodist2 = 0, speed_transmitter = 0, speed_receiver = 0, rho_dot_t = 0, rho_dot_r = 0, bigT = 0; int i = 0; /* Note: I feel obligated to provide default values for TDB-UTC. However, since the difference TDB-TDT is being calculated so cavalierly, I SINCERELY HOPE THIS IS NEVER USED!!! Nevertheless, it is more accurate than a default of zero. */ if (receiver_tdb_minus_utc == 0.0) { receiver_tdb_minus_utc = (tdt_to_tdb(receiver_time,receiver_latitude,receiver_longitude,receiver_altitude,transmitter_EOP[3]) - receiver_time) + (get_leap_second(receiver_time) + 32.184); } if (transmitter_tdb_minus_utc == 0.0) { transmitter_tdb_minus_utc = (tdt_to_tdb(receiver_time,receiver_latitude,receiver_longitude,receiver_altitude,transmitter_EOP[3]) - receiver_time) + (get_leap_second(receiver_time) + 32.184); } /* First, get the barycentric position and velocity of the receiver at receiver_time. */ /* The receiver coordinates determined when the observation was entered may have been passed as an argument; these may have used precise values of the EOPs, so we don't want to recalculate. On the other hand, the receiver coordinates may be zero; this method may be creating an ephemeris, so precise values of the EOPs may not be available. We'll test and act accordingly. */ /* Convert the UTC receiver time to TDB */ receiver_tdb_time = receiver_time + receiver_tdb_minus_utc/86400.0; if (receiver_r[1] == 0.0) { /* Receiver coordinates are unavailable; calculate them. */ /* Set EOPs to zero so that method get_radar_position will use estimates and defaults. */ for (i=1;i<=7;i++) EOP[i] = 0.0; /* get receiver barycentric equatorial position and velocity at TDB receiver_time */ get_observer_position(receiver_latitude,receiver_longitude,receiver_altitude,receiver_tdb_time,EOP,receiver_r,receiver_rprime); } /* If receiver_r[1] is not equal to 0.0, receiver coordinates are available; use them. */ /* The downleg iteration - determine bounce_time, and the asteroid's position */ /* As a first approximation, determine the asteroid's position at receiver_time, and estimate bounce_time */ update(asteroid_r,asteroid_rprime,asteroid_epoch_time,r_at_bounce,rprime_at_bounce,receiver_tdb_time,false,collision); dist_receiver = Math.sqrt(Math.pow((r_at_bounce[1] - receiver_r[1]),2) + Math.pow((r_at_bounce[2] - receiver_r[2]),2) + Math.pow((r_at_bounce[3] - receiver_r[3]),2)); tau_receiver_delay = (dist_receiver - asteroid_radius)/(speed_of_light/86400.0); /* Iteratively improve the estimate of bounce_time; repeat until successive values of tau_delay differ by no more than 0.05 microseconds. */ while (tau_diff > 5E-08) { old_tau_delay = tau_receiver_delay; bounce_time = receiver_tdb_time - tau_receiver_delay/86400.0; update(asteroid_r,asteroid_rprime,asteroid_epoch_time,r_at_bounce,rprime_at_bounce,bounce_time,false,collision); for (i=1;i<=3;i++) { rho_receiver[i] = r_at_bounce[i] - receiver_r[i]; rho_dot_receiver[i] = rprime_at_bounce[i] - receiver_rprime[i]; } dist_receiver = Math.sqrt(Math.pow(rho_receiver[1],2) + Math.pow(rho_receiver[2],2) + Math.pow(rho_receiver[3],2)); tau_receiver_delay = (dist_receiver - asteroid_radius)/(speed_of_light/86400.0) + radar_corrections(receiver_tdb_time,r_at_bounce,receiver_r,transmitter_frequency); rho_dot_r = (rho_receiver[1]*rho_dot_receiver[1] + rho_receiver[2]*rho_dot_receiver[2] + rho_receiver[3]*rho_dot_receiver[3])/dist_receiver; tau_diff = Math.abs(tau_receiver_delay - old_tau_delay); } /* The up-leg iteration - determine transmitter_time, */ /* Using an initial approximation of transmitter_time and transmitter_r and _rprime, find the distance from the transmitter at transmitter_time to the asteroid at bounce_time, and iteratively refine transmitter_time */ transmitter_time = bounce_time - tau_receiver_delay/86400.0; /* get transmitter barycentric equatorial position and velocity at TDB transmitter_time */ get_observer_position(transmitter_latitude,transmitter_longitude,transmitter_altitude,transmitter_time,transmitter_EOP,transmitter_r,transmitter_rprime); dist_transmitter = Math.sqrt(Math.pow((r_at_bounce[1] - transmitter_r[1]),2) + Math.pow((r_at_bounce[2] - transmitter_r[2]),2) + Math.pow((r_at_bounce[3] - transmitter_r[3]),2)); tau_transmitter_delay = (dist_transmitter - asteroid_radius)/(speed_of_light/86400.0); /* Iteratively improve the estimate of transmitter_time; repeat until successive values of tau_delay differ by no more than 0.05 microseconds. */ tau_diff = 1.0; while (tau_diff > 5E-08) { old_tau_delay = tau_transmitter_delay; transmitter_time = bounce_time - tau_transmitter_delay/86400.0; get_observer_position(transmitter_latitude,transmitter_longitude,transmitter_altitude,transmitter_time,transmitter_EOP,transmitter_r,transmitter_rprime); for (i=1;i<=3;i++) { rho_transmitter[i] = r_at_bounce[i] - transmitter_r[i]; rho_dot_transmitter[i] = rprime_at_bounce[i] - transmitter_rprime[i]; } dist_transmitter = Math.sqrt(Math.pow(rho_transmitter[1],2) + Math.pow(rho_transmitter[2],2) + Math.pow(rho_transmitter[3],2)); tau_transmitter_delay = (dist_transmitter - asteroid_radius)/(speed_of_light/86400.0) + radar_corrections(transmitter_time,r_at_bounce,transmitter_r,transmitter_frequency); rho_dot_t = (rho_transmitter[1]*rho_dot_transmitter[1] + rho_transmitter[2]*rho_dot_transmitter[2] + rho_transmitter[3]*rho_dot_transmitter[3])/dist_transmitter; tau_diff = Math.abs(tau_transmitter_delay - old_tau_delay); } /* Calculate the predicted delay time, and Doppler shift */ delay_doppler[1] = (tau_receiver_delay + tau_transmitter_delay) + transmitter_tdb_minus_utc - receiver_tdb_minus_utc; dot_one = rho_transmitter[1]*transmitter_rprime[1] + rho_transmitter[2]*transmitter_rprime[2] + rho_transmitter[3]*transmitter_rprime[3]; dot_two = rho_receiver[1]*rprime_at_bounce[1] + rho_receiver[2]*rprime_at_bounce[2] + rho_receiver[3]*rprime_at_bounce[3]; get_planet_posvel(transmitter_time,11,sun_r,sun_rprime); heliodist1 = Math.sqrt(Math.pow((transmitter_r[1] - sun_r[1]),2) + Math.pow((transmitter_r[2] - sun_r[2]),2) + Math.pow((transmitter_r[3] - sun_r[3]),2)); get_planet_posvel(receiver_tdb_time,11,sun_r,sun_rprime); heliodist2 = Math.sqrt(Math.pow((receiver_r[1] - sun_r[1]),2) + Math.pow((receiver_r[2] - sun_r[2]),2) + Math.pow((receiver_r[3] - sun_r[3]),2)); speed_transmitter = Math.sqrt(Math.pow(transmitter_rprime[1],2) + Math.pow(transmitter_rprime[2],2) + Math.pow(transmitter_rprime[3],2)); speed_receiver = Math.sqrt(Math.pow(receiver_rprime[1],2) + Math.pow(receiver_rprime[2],2) + Math.pow(receiver_rprime[3],2)); delay_doppler[2] = -transmitter_frequency*(rho_dot_t + rho_dot_r)/speed_of_light - (transmitter_frequency/Math.pow(speed_of_light,2))*(rho_dot_t*dot_one/dist_transmitter - rho_dot_r*dot_two/dist_receiver - rho_dot_t*rho_dot_r + Math.pow(kappa,2)*(1.0/heliodist1 - 1.0/heliodist2) + 0.5*(Math.pow(speed_transmitter,2) - Math.pow(speed_receiver,2))); } double radar_corrections(double jultime,double asteroid_r[],double site_r[],double frequency) { /* This method computes corrections to radar delay times due to general relativity, the solar corona, and the Earth's troposphere and ionosphere. Inputs: - jultime is a TDB Julian date corresponding to the epoch of site_r[]; - asteroid_r[] and site_r[] are barycentric equatorial positions in AU; asteroid_r[] is evaluated at bounce_time, while site_r[] is evaluated at jultime (either transmitter_time or receiver_time); - frequency is in Hz; Outputs: - delay in seconds. Algorithm based on P. Kenneth Seidelmann, "Explanatory Supplement to the Astronomical Almanac", 1992, pp. 294-296. */ double e = 0, p = 0, q = 0, delay1 = 0, delay2 = 0, delay3 = 0, delay = 0, s = 0, density = 0, bigA = 1.22E+08, r = 0, a = 0.44E+06, b = 0.44E+06, beta = 0, z = 0, mag_zenith = 0, mag_rho = 0; double[] rho = new double[4]; double[] zenith = new double[4]; int i = 0, j = 0; planetary_ephemeris(jultime,rho,0); /* The time delay due to general relativity */ e = Math.sqrt(Math.pow((site_r[1] - planet_r[0][1]),2) + Math.pow((site_r[2] - planet_r[0][2]),2) + Math.pow((site_r[3] - planet_r[0][3]),2)); p = Math.sqrt(Math.pow((asteroid_r[1] - planet_r[0][1]),2) + Math.pow((asteroid_r[2] - planet_r[0][2]),2) + Math.pow((asteroid_r[3] - planet_r[0][3]),2)); q = Math.sqrt(Math.pow((asteroid_r[1] - site_r[1]),2) + Math.pow((asteroid_r[2] - site_r[2]),2) + Math.pow((asteroid_r[3] - site_r[3]),2)); delay1 = 86400.0*(2.0*Math.pow(kappa,2)/Math.pow(speed_of_light,3))*Math.log(Math.abs((e+p+q)/(e+p-q))); /* The time delay due to the solar corona */ /* We will break this integral up into 100 slices from site to asteroid, and evaluate the density at each. */ /* slice length s is in cm */ s = (q/100.0)*au*100000.0; for (i=1;i<=100;i++) { for (j=1;j<=3;j++) rho[j] = (i/100.0)*(asteroid_r[j] - site_r[j]) + (site_r[j] - planet_r[0][j]); beta = Math.asin(rho[3]/Math.sqrt(Math.pow(rho[1],2) + Math.pow(rho[2],2) + Math.pow(rho[3],2))); r = Math.sqrt(Math.pow(rho[1],2) + Math.pow(rho[2],2) + Math.pow(rho[3],2))/planet_radius_0; density = bigA/Math.pow(r,6) + (a*b/Math.sqrt(Math.pow((a*Math.sin(beta)),2) + Math.pow((b*Math.cos(beta)),2)))/Math.pow(r,2); delay2 = delay2 + (40.3/((speed_of_light*au*100000.0/86400.0)*Math.pow(frequency,2)))*density*s; } /* The time delay due to the Earth's troposphere and ionosphere */ for (i=1;i<=3;i++) { zenith[i] = site_r[i] - planet_r[3][i]; rho[i] = asteroid_r[i] - site_r[i]; } mag_zenith = Math.sqrt(Math.pow(zenith[1],2) + Math.pow(zenith[2],2) + Math.pow(zenith[3],2)); mag_rho = Math.sqrt(Math.pow(rho[1],2) + Math.pow(rho[2],2) + Math.pow(rho[3],2)); z = Math.acos((zenith[1]*rho[1] + zenith[2]*rho[2] + zenith[3]*rho[3])/(mag_zenith*mag_rho)); delay3 = 7E-09/(Math.cos(z) + 0.0014/(0.045 + 1.0/Math.tan(z))); /* The output is the sum of the three delays */ delay = delay1 + delay2 + delay3; return delay; } void get_state_transition_matrix(double rnominal[], double rprimenominal[], double time, double phi[][]) { /* Method to calculate the state transition matrix at time, given the epoch state vector (available as an instance variable of the MinorPlanet object class) and the nominal state vector at time. */ double[] epoch_rvariant = new double[4]; double[] epoch_rprimevariant = new double[4]; double[] rvariant = new double[4]; double[] rprimevariant = new double[4]; double[][] collision_variant = new double[2000][9]; double delta = 0; int i = 0, j = 0; /* Initialize the variant state vector */ for (i=1;i<=3;i++) { epoch_rvariant[i] = epoch_r[i]; epoch_rprimevariant[i] = epoch_rprime[i]; } /* Calculate the state transition matrix phi at time */ for (i=1;i<=3;i++) { /* Vary epoch_r[i], and determine the change in state vector at time */ delta = 0.000001*epoch_r[i]; epoch_rvariant[i] = epoch_r[i] + delta; update(epoch_rvariant, epoch_rprimevariant, epoch_time, rvariant, rprimevariant, time, false, collision_variant); for (j=1;j<=3;j++) { phi[j][i] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i] = (rprimevariant[j] - rprimenominal[j])/delta; } epoch_rvariant[i] = epoch_r[i]; /* Vary epoch_rprime[i], and determine the change in state vector at time */ delta = 0.000001*epoch_rprime[i]; epoch_rprimevariant[i] = epoch_rprime[i] + delta; update(epoch_rvariant, epoch_rprimevariant, epoch_time, rvariant, rprimevariant, time, false, collision_variant); for (j=1;j<=3;j++) { phi[j][i+3] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i+3] = (rprimevariant[j] - rprimenominal[j])/delta; } epoch_rprimevariant[i] = epoch_rprime[i]; /* If non-gravitational thrust parameters A1_A2_DT are being utilized, vary A1_A2_DT[i], and determine the change in the state vector at time */ if ((Math.abs(A1_A2_DT[i]) > 0.0) && (i < 3)) { delta = 0.1*A1_A2_DT[i]; A1_A2_DT[i] = A1_A2_DT[i] + delta; update(epoch_rvariant, epoch_rprimevariant, epoch_time, rvariant, rprimevariant, time, false, collision_variant); for (j=1;j<=3;j++) { phi[j][i+6] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i+6] = (rprimevariant[j] - rprimenominal[j])/delta; } phi[i+6][i+6] = 1.0; A1_A2_DT[i] = A1_A2_DT[i] - delta; } } } public void advance_epoch(double newtime) { /* Method to advance the epoch state vector and error covariance matrix (accessible as instance variables of the class MinorPlanet) from epoch_time to another specified date (newtime). Note that the epoch state vector and error covariance matrix are actually changed here; thus, they are not sent back as arguments. */ int i = 0, j = 0, k = 0; double[] newr = new double[4]; double[] newrprime = new double[4]; double[][] new_big_p = new double[9][9]; double[][] phi = new double[9][9]; double[][] inter = new double[9][9]; double[][] collision_variant = new double[2000][9]; /* Advance the epoch state vector */ update(epoch_r,epoch_rprime,epoch_time,newr,newrprime,newtime,false,collision_variant); /* Advance the epoch error covariance matrix */ get_state_transition_matrix(newr,newrprime,newtime,phi); for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { inter[i][j] = 0; for (k=1;k<=8;k++) inter[i][j] = inter[i][j] + big_p[i][k]*phi[j][k]; } } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { new_big_p[i][j] = 0; for (k=1;k<=8;k++) new_big_p[i][j] = new_big_p[i][j] + phi[i][k]*inter[k][j]; } } /* Update the state vector and error covariance matrix */ epoch_time = newtime; for (i=1;i<=3;i++) { epoch_r[i] = newr[i]; epoch_rprime[i] = newrprime[i]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) big_p[i][j] = new_big_p[i][j]; } } double probability_of_collision(int planet, double r_at_time[], double rprime_at_time[], double time, double covariance[][], int collision_counter) { /* Method to determine the probability of collision between an object and a planet, given the posvel of the body at the time of closest approach. The method also uses the epoch time and posvel of the body, and its epoch error covariance matrix, which are assumed to be instance variables. The method also permits use of the non-gravitational thrust parameters. Inputs: time = jultime of closest approach collision_counter = event counter for this call to update r_at_time = object barycentric equatorial position at time of closest approach rprime_at_time = object barycentric equatorial velocity at time of closest approach planet = number of the planet with which the object may collide (Sun=0,Mercury=1,...,Pluto=9,Moon=10) Outputs: probability = probability of collision covariance = propogated covariance matrix at time References: D.K. Yeomans, S.J. Ostro, and P.W. Chodas (1987). "Radar Astrometry of Near-Earth Asteroids". Astronomical Journal 94, 189-200. Donald K. Yeomans and Paul W. Chodas (1995). "Near Earth Objects: Orbit Determination and Impact Prediction". JPL Technical Report 95-0032. Howard Anton (1984). "Elementary Linear Algebra". pp. 204-205. Mary L. Boas (1983). "Mathematical Methods in the Physical Sciences". pp. 723-726. */ double[] rnominal = new double[4]; double[] rprimenominal = new double[4]; double[] rvariant = new double[4]; double[] rprimevariant = new double[4]; double[] epoch_rvariant = new double[4]; double[] epoch_rprimevariant = new double[4]; double[][] collision_variant = new double[2000][9]; double[][] phi = new double[9][9]; double[][] inter = new double[9][9]; double[][] transition_matrix = new double[4][4]; double[][] abc_covariance = new double[4][4]; double[] rho = new double[4]; double[] normal = new double[4]; double[] d = new double[4]; double[] r_project = new double[4]; double[] vector_a = new double[4]; double[] vector_b = new double[4]; double[] vector_c = new double[4]; double distance = 0, delta = 0, angle_cos = 0, angle_sin = 0, angle = 0, mag_normal = 0, mag_d = 0, mag_a = 0, probability = 0, probability1 = 0, probability2 = 0, probability3 = 0, start_time = 0; int i = 0, j = 0, k = 0; /* Note: epoch_time, epoch_r, and epoch_rprime are attributes of object (instance variables), hence available to all methods */ /* IMPORTANT: update must have a boolean that controls whether we test for a collision. Otherwise, we risk infinite regression */ /* Initialize variant arrays and nominal state vector */ for (i=1;i<=3;i++) { if (!VIflag1) { /* This is a standard search; variant trajectories should be centered on epoch state vector */ epoch_rvariant[i] = epoch_r[i]; epoch_rprimevariant[i] = epoch_rprime[i]; } else { /* This is a VI search; variant trajectories should be centered on Monte Carlo state vector input to update */ epoch_rvariant[i] = VI_trajectory_r[i]; epoch_rprimevariant[i] = VI_trajectory_rprime[i]; } rnominal[i] = r_at_time[i]; rprimenominal[i] = rprime_at_time[i]; collision_nominal_state[collision_counter][i] = rnominal[i]; collision_nominal_state[collision_counter][i+3] = rprimenominal[i]; } /* Calculate the position of the target planet at time, for use in determining delta and the target plane */ planetary_ephemeris(time,rho,0); distance = Math.sqrt(Math.pow((r_at_time[1] - planet_r[planet][1]), 2.0) + Math.pow((r_at_time[2] - planet_r[planet][2]), 2.0) + Math.pow((r_at_time[3] - planet_r[planet][3]), 2.0)); /* Calculate the state transition matrix phi at time */ for (i=1;i<=3;i++) { /* Skip if a VI search has resulted in a variant collision */ if (probability != -999.9) { /* If there is a previous collision/near miss event, we can start integrating where the variant trajectories left off. Otherwise, we will establish new variant trajectories */ /* delta = 0.000001*epoch_r[i]; */ /* delta = 0.000001*(distance/0.05); */ delta = 0.0000001; if (collision_sensitivity_time == 0.0) { if (!VIflag1) { /* This is a standard search; variant trajectories should be centered on epoch state vector */ epoch_rvariant[i] = epoch_r[i] + delta; start_time = epoch_time; } else { /* This is a VI search; variant trajectories should be centered on Monte Carlo state vector input to update */ epoch_rvariant[i] = VI_trajectory_r[i] + delta; start_time = epoch_time; } } else { for (j=1;j<=3;j++) { epoch_rvariant[j] = collision_sensitivity_r_variant[2*i-1][j]; epoch_rprimevariant[j] = collision_sensitivity_rprime_variant[2*i-1][j]; } start_time = collision_sensitivity_time; } if (VIflag2) { /* This is either a standard search, or a VI search in which the nominal trajectory is a VI; suppress collision detection for variant trajectories. */ update(epoch_rvariant, epoch_rprimevariant, start_time, rvariant, rprimevariant, time, false, collision_variant); } else { /* This is a VI search in which the nominal trajectory was a near-miss; allow collision detection of the variant trajectories. Set VIflag3 temporarily to prevent a recursive call back to probability_of_collision. */ VIflag3 = true; update(epoch_rvariant, epoch_rprimevariant, start_time, rvariant, rprimevariant, time, true, collision_variant); VIflag3 = false; /* If the variant trajectory results in a collision, overwrite the current Monte Carlo trajectory with this variant VI and return probability = -999.9 (which will result in no further processing in this method). After this method ends, will return to detect_collision which will set collision[1][1] to 1.0 and immediately end, returning finally to update. Update will see the collision[1][1] and probability = -999.9, overwrite the current Monte Carlo trajectory with this variant VI, and end, returning to the VI search algorithm. */ if (collision_variant[(int)collision_variant[0][0]][1] == 1.0) { /* Virtual Impactor */ probability = -999.9; VI_trajectory_r[i] = VI_trajectory_r[i] + delta; } } for (j=1;j<=3;j++) { phi[j][i] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i] = (rprimevariant[j] - rprimenominal[j])/delta; collision_sensitivity_r_variant[2*i-1][j] = rvariant[j]; collision_sensitivity_rprime_variant[2*i-1][j] = rprimevariant[j]; collision_state_matrix[collision_counter][i][j] = (rvariant[j] - rnominal[j])/delta; collision_state_matrix[collision_counter][i][j+3] = (rprimevariant[j] - rprimenominal[j])/delta; } if (collision_sensitivity_time == 0.0) { if (!VIflag1) /* This is a standard search; variant trajectories should be centered on epoch state vector */ epoch_rvariant[i] = epoch_r[i]; else /* This is a VI search; variant trajectories should be centered on Monte Carlo state vector input to update */ epoch_rvariant[i] = VI_trajectory_r[i]; } else { /* No action required; the arrays will be switched to a new set before the next calculation */ } } /* Skip if a VI search has resulted in a variant collision */ if (probability != -999.9) { /* delta = 0.000001*epoch_rprime[i]; */ /* delta = 0.0000001*(distance/0.05); */ delta = 0.000000001; if (collision_sensitivity_time == 0.0) { if (!VIflag1) { /* This is a standard search; variant trajectories should be centered on epoch state vector */ epoch_rprimevariant[i] = epoch_rprime[i] + delta; } else { /* This is a VI search; variant trajectories should be centered on Monte Carlo state vector input to update */ epoch_rprimevariant[i] = VI_trajectory_rprime[i] + delta; } } else { for (j=1;j<=3;j++) { epoch_rvariant[j] = collision_sensitivity_r_variant[2*i][j]; epoch_rprimevariant[j] = collision_sensitivity_rprime_variant[2*i][j]; } } if (VIflag2) { /* This is either a standard search, or a VI search in which the nominal trajectory is a VI; suppress collision detection for variant trajectories. */ update(epoch_rvariant, epoch_rprimevariant, start_time, rvariant, rprimevariant, time, false, collision_variant); } else { /* This is a VI search in which the nominal trajectory was a near-miss; allow collision detection of the variant trajectories. Set VIflag3 temporarily to prevent a recursive call back to probability_of_collision. */ VIflag3 = true; update(epoch_rvariant, epoch_rprimevariant, start_time, rvariant, rprimevariant, time, true, collision_variant); VIflag3 = false; /* If the variant trajectory results in a collision, overwrite the current Monte Carlo trajectory with this variant VI and return probability = -999.9 (which will result in no further processing in this method). After this method ends, will return to detect_collision which will set collision[1][1] to 1.0 and immediately end, returning finally to update. Update will see the collision[1][1] and probability = -999.9, overwrite the current Monte Carlo trajectory with this variant VI, and end, returning to the VI search algorithm. */ if (collision_variant[(int)collision_variant[0][0]][1] == 1.0) { /* Virtual Impactor */ probability = -999.9; VI_trajectory_rprime[i] = VI_trajectory_rprime[i] + delta; } } for (j=1;j<=3;j++) { phi[j][i+3] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i+3] = (rprimevariant[j] - rprimenominal[j])/delta; collision_sensitivity_r_variant[2*i][j] = rvariant[j]; collision_sensitivity_rprime_variant[2*i][j] = rprimevariant[j]; collision_state_matrix[collision_counter][i+3][j] = (rvariant[j] - rnominal[j])/delta; collision_state_matrix[collision_counter][i+3][j+3] = (rprimevariant[j] - rprimenominal[j])/delta; } if (collision_sensitivity_time == 0.0) { if (!VIflag1) /* This is a standard search; variant trajectories should be centered on epoch state vector */ epoch_rprimevariant[i] = epoch_rprime[i]; else /* This is a VI search; variant trajectories should be centered on Monte Carlo state vector input to update */ epoch_rprimevariant[i] = VI_trajectory_rprime[i]; } else { /* No action required; the arrays will be switched to a new set before the next calculation */ } } /* Skip if a VI search has resulted in a variant collision */ if (probability != -999.9) { /* If non-gravitational thrust parameters A1_A2_DT are being utilized, vary A1_A2_DT[i], and determine the change in the state vector at time */ if ((Math.abs(A1_A2_DT[i]) > 0.0) && (i < 3)) { delta = 0.1*A1_A2_DT[i]; A1_A2_DT[i] = A1_A2_DT[i] + delta; if (collision_sensitivity_time > 0.0) { for (j=1;j<=3;j++) { epoch_rvariant[j] = collision_sensitivity_r_variant[i+6][j]; epoch_rprimevariant[j] = collision_sensitivity_rprime_variant[i+6][j]; } } if (VIflag2) { /* This is either a standard search, or a VI search in which the nominal trajectory is a VI; suppress collision detection for variant trajectories. */ update(epoch_rvariant, epoch_rprimevariant, start_time, rvariant, rprimevariant, time, false, collision_variant); } else { /* This is a VI search in which the nominal trajectory was a near-miss; allow collision detection of the variant trajectories. Set VIflag3 temporarily to prevent a recursive call back to probability_of_collision. */ VIflag3 = true; update(epoch_rvariant, epoch_rprimevariant, start_time, rvariant, rprimevariant, time, true, collision_variant); VIflag3 = false; } for (j=1;j<=3;j++) { phi[j][i+6] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i+6] = (rprimevariant[j] - rprimenominal[j])/delta; collision_sensitivity_r_variant[i+6][j] = rvariant[j]; collision_sensitivity_rprime_variant[i+6][j] = rprimevariant[j]; } phi[i+6][i+6] = 1.0; A1_A2_DT[i] = A1_A2_DT[i] - delta; } } } /* Skip if a VI search has resulted in a variant collision */ if (probability != -999.9) { /* Set the collision_sensitivity_time to the end time of integration, so we can pick up there if there is another event. */ collision_sensitivity_time = time; /* Determine error covariance at time using epoch error covariance matrix big_p */ /* Note that big_p is an attribute of object (instance variable), hence available to all methods */ for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { inter[i][j] = 0; for (k=1;k<=8;k++) inter[i][j] = inter[i][j] + big_p[i][k]*phi[j][k]; } } for (i=1;i<=6;i++) { for (j=1;j<=6;j++) { covariance[i][j] = 0; for (k=1;k<=8;k++) covariance[i][j] = covariance[i][j] + phi[i][k]*inter[k][j]; } } /* Define the "impact plane", i.e., the plane perpendicular to the planet's relative velocity vector which contains the planet's position at time. */ for (i=1;i<=3;i++) normal[i] = rprime_at_time[i] - planet_rprime[planet][i]; mag_normal = Math.sqrt(Math.pow(normal[1],2) + Math.pow(normal[2],2) + Math.pow(normal[3],2)); for (i=1;i<=3;i++) { normal[i] = normal[i]/mag_normal; } /* Find the vector d from the planet to the object, and the angle between d and the normal to the impact plane */ for (i=1;i<=3;i++) d[i] = r_at_time[i] - planet_r[planet][i]; mag_d = Math.sqrt(Math.pow(d[1],2) + Math.pow(d[2],2) + Math.pow(d[3],2)); for (i=1;i<=3;i++) d[i] = d[i]/mag_d; angle_cos = d[1]*normal[1] + d[2]*normal[2] + d[3]*normal[3]; angle_sin = Math.sqrt(Math.pow((d[2]*normal[3] - d[3]*normal[2]),2) + Math.pow((d[1]*normal[3] - d[3]*normal[1]),2) + Math.pow((d[1]*normal[2] - d[2]*normal[1]),2)); angle = Math.atan2(angle_sin,angle_cos); /* Project the object position at time onto the impact plane */ for (i=1;i<=3;i++) { r_project[i] = r_at_time[i] - mag_d*angle_cos*normal[i]; } /* Create a new "a-b-c" coordinate system. Basis vector a will be in the impact plane, with its direction defined by the vector from the projected object position to the planet. Basis vector c will be the normal to the impact plane. Basis vector b will be in the impact plane, defined so as to complete the right-handed system. */ for (i=1;i<=3;i++) { vector_a[i] = planet_r[planet][i] - r_project[i]; vector_c[i] = normal[i]; } mag_a = Math.sqrt(Math.pow(vector_a[1],2) + Math.pow(vector_a[2],2) + Math.pow(vector_a[3],2)); for (i=1;i<=3;i++) vector_a[i] = vector_a[i]/mag_a; vector_b[1] = -(vector_a[2]*vector_c[3] - vector_a[3]*vector_c[2]); vector_b[2] = (vector_a[1]*vector_c[3] - vector_a[3]*vector_c[1]); vector_b[3] = -(vector_a[1]*vector_c[2] - vector_a[2]*vector_c[1]); /* Create the transition matrix, and transform the (position) error covariance matrix at time from "i-j-k" to "a-b-c". Note that we will only transform the upper 3x3 of the covariance matrix; this is all we need for our purposes. */ for (i=1;i<=3;i++) { transition_matrix[1][i] = vector_a[i]; transition_matrix[2][i] = vector_b[i]; transition_matrix[3][i] = vector_c[i]; } for (i=1;i<=3;i++) { for (j=1;j<=3;j++) { inter[i][j] = 0; for (k=1;k<=3;k++) inter[i][j] = inter[i][j] + covariance[i][k]*transition_matrix[j][k]; } } for (i=1;i<=3;i++) { for (j=1;j<=3;j++) { abc_covariance[i][j] = 0; for (k=1;k<=3;k++) abc_covariance[i][j] = abc_covariance[i][j] + transition_matrix[i][k]*inter[k][j]; } } /* Use the transformed covariances to determine the probability that the planet and the object physically overlap on the impact plane. Note that this requires integrating the normal density function over the planet's disk. However, we are making a pretty huge simplification already by projecting onto the "impact plane", in order to predict the probability of collision before or after the time of nominal closest approach. Therefore, I feel justified in approximating this integral. The integral would be over all a and b that cover the planet's disk. I will approximate this numerically three different ways. First, by evaluating the ndf at the planet's center and multiplying by the area of the planet's disk. Second, by evaluating the ndf at four points along the a and b axes, and multiplying by area/4. Third, by evaluating the ndf at four points along the a-b diagonals, and multiplying by area/4. And finally, I will average these approximations to arrive at a final result. You have to stop somewhere, and I feel that this covers the disk fairly well. */ probability1 = Math.pow(planet_radius[planet],2)*Math.exp(-Math.pow(mag_a,2)/(2*Math.abs(abc_covariance[1][1])))/(2*Math.sqrt(Math.abs(abc_covariance[1][1]))*Math.sqrt(Math.abs(abc_covariance[2][2]))); probability2 = Math.pow(planet_radius[planet],2)*(2*Math.exp(-Math.pow(mag_a,2)/(2*Math.abs(abc_covariance[1][1])))*Math.exp(-Math.pow(planet_radius[planet],2)/(4*Math.abs(abc_covariance[2][2]))) + Math.exp(-Math.pow((mag_a - planet_radius[planet]/Math.sqrt(2.0)),2)/(2*Math.abs(abc_covariance[1][1]))) + Math.exp(-Math.pow((mag_a + planet_radius[planet]/Math.sqrt(2.0)),2)/(2*Math.abs(abc_covariance[1][1]))))/(8*Math.sqrt(Math.abs(abc_covariance[1][1]))*Math.sqrt(Math.abs(abc_covariance[2][2]))); probability3 = Math.pow(planet_radius[planet],2)*(2*Math.exp(-Math.pow((mag_a - planet_radius[planet]/Math.sqrt(2.0)),2)/(2*Math.abs(abc_covariance[1][1])))*Math.exp(-Math.pow((planet_radius[planet]/Math.sqrt(2.0)),2)/(2*Math.abs(abc_covariance[2][2]))) + 2*Math.exp(-Math.pow((mag_a + planet_radius[planet]/Math.sqrt(2.0)),2)/(2*Math.abs(abc_covariance[1][1])))*Math.exp(-Math.pow((planet_radius[planet]/Math.sqrt(2.0)),2)/(2*Math.abs(abc_covariance[2][2]))))/(8*Math.sqrt(Math.abs(abc_covariance[1][1]))*Math.sqrt(Math.abs(abc_covariance[2][2]))); probability = (probability1 + probability2 + probability3)/3.0; } return probability; } void detect_collision(double object_r_at_n[],double object_rprime_at_n[],double object_r_at_n1[],double object_rprime_at_n1[],double jultime_n,double jultime_n1,double collision[][]) { /* Given the position and velocity of an object at jultime_n and jultime_n1, determine whether the object collides with a planet, or has a near miss with a planet, between time steps n and n+1 in procedure update. Inputs: object_r_at_n[], object_rprime_at_n[] = the barycentric equatorial posvel of the object at time step n; object_r_at_n1[], object_rprime_at_n1[] = the barycentric equatorial posvel of the object at time step n+1; jultime_n, jultime_n1 = the Julian dates corresponding to time steps n and n+1 The output array "collision[][]" has two indices. The first index accounts for multiple encounters, the total being stored in "collision[0][0]". For the i'th encounter, entry "collision[i][1]" indicates whether a collision ( = 1) or a near miss ( = 2) occurs. In either case, "collision[i][0]" gives the minimum distance in A.U.s. "collision[i][2]" gives the planet (1-9, Moon=10, Sun=0), "collision[i][3]" gives the Julian date, "collision[i][4]" gives the nominal distance in A.U.s., "collision[i][5]" gives the estimated probability of collision, "collision[i][6]" gives the object's x-covariance at the time of the event, "collision[i][7]" gives the object's y-covariance at the time of the event, and "collision[i][8]" gives the object's z-covariance at the time of the event. */ double[] rho = new double[4]; double[][] planet_r_at_n = new double[11][4]; double[][] planet_r_at_n1 = new double[11][4]; double[][] planet_rprime_at_n = new double[11][4]; double[][] planet_rprime_at_n1 = new double[11][4]; double[] planet_i_r_at_n = new double[4]; double[] planet_i_rprime_at_n = new double[4]; double[] planet_i_r_at_time = new double[4]; double[] planet_i_rprime_at_time = new double[4]; double[] r_at_time = new double[4]; double[] rprime_at_time = new double[4]; double[] r_at_n = new double[4]; double[] rprime_at_n = new double[4]; double[] r_at_n1 = new double[4]; double[] rprime_at_n1 = new double[4]; double[][] covariance = new double[7][7]; double[][] collision_variant = new double[2000][9]; double distance_n1 = 0, upper_time = 0, lower_time = 0, time = 0, distance = 0, distance_n = 0, deriv_1_at_n = 0, deriv_1_at_n1 = 0, deriv_1_at_time = 0; int i = 0, j = 0, collision_counter = 0, counter = 0; collision_counter = (int)(collision[0][0]); /* Get the ephemerides of the planets at each time step, without correction for light time-of-flight */ planetary_ephemeris(jultime_n,rho,0); /* Initialize barycentric planetary posvel arrays at time step n */ for (i=0;i<=10;i++) { for (j=1;j<=3;j++) { planet_r_at_n[i][j] = planet_r[i][j]; planet_rprime_at_n[i][j] = planet_rprime[i][j]; } } /* Initialize barycentric object posvel arrays at time step n */ for (j=1;j<=3;j++) { r_at_n[j] = object_r_at_n[j]; rprime_at_n[j] = object_rprime_at_n[j]; } planetary_ephemeris(jultime_n1,rho,0); /* Initialize barycentric planetary posvel arrays at time step n+1 */ for (i=0;i<=10;i++) { for (j=1;j<=3;j++) { planet_r_at_n1[i][j] = planet_r[i][j]; planet_rprime_at_n1[i][j] = planet_rprime[i][j]; } } /* Initialize barycentric object posvel arrays at time step n+1 */ for (j=1;j<=3;j++) { r_at_n1[j] = object_r_at_n1[j]; rprime_at_n1[j] = object_rprime_at_n1[j]; } /* Check each planet (and the Sun) for collision */ for (i=0;i<=10;i++) { if ((!earth_only) || ((earth_only) && (i == 3))) { /* Initialize barycentric posvel arrays for planet i */ for (j=1;j<=3;j++) { planet_i_r_at_n[j] = planet_r_at_n[i][j]; planet_i_rprime_at_n[j] = planet_rprime_at_n[i][j]; } /* Check for collision at time step n+1. */ distance_n1 = Math.sqrt(Math.pow((object_r_at_n1[1] - planet_r_at_n1[i][1]),2) + Math.pow((object_r_at_n1[2] - planet_r_at_n1[i][2]),2) + Math.pow((object_r_at_n1[3] - planet_r_at_n1[i][3]),2)); if (distance_n1 <= planet_radius[i]) { /* Collision. Increment collision counter and use bisection to find it. */ collision_counter = collision_counter + 1; collision[0][0] = collision[0][0] + 1; upper_time = jultime_n1; lower_time = jultime_n; collision[collision_counter][0] = 0; collision[collision_counter][1] = 1; collision[collision_counter][2] = i; collision[collision_counter][3] = jultime_n1; collision[collision_counter][4] = distance_n1; while ((Math.abs(upper_time - lower_time) > event_localization) && (counter < 100)) { counter = counter + 1; time = lower_time + (upper_time - lower_time)/2; /* Set VIflag5 to prevent this bisection call to update from resetting the VI_trajectory_r and _prime vectors */ VIflag5 = true; update(r_at_n,rprime_at_n,jultime_n,r_at_time,rprime_at_time,time,false,collision_variant); VIflag5 = false; planetary_ephemeris(time,rho,0); for (j=1;j<=3;j++) { planet_i_r_at_time[j] = planet_r[i][j]; planet_i_rprime_at_time[j] = planet_rprime[i][j]; } distance = Math.sqrt(Math.pow((r_at_time[1] - planet_i_r_at_time[1]),2) + Math.pow((r_at_time[2] - planet_i_r_at_time[2]),2) + Math.pow((r_at_time[3] - planet_i_r_at_time[3]),2)); if (distance <= planet_radius[i]) { collision[collision_counter][3] = time; collision[collision_counter][4] = distance; upper_time = time; } else /* Collision has not yet occurred. Increase lower limit. */ lower_time = time; } System.out.println("COLLISION with planet " + i + " at " + time + " distance = " + distance); if (!VIflag3 && !VIflag4) { /* At this point, we know that this is either a standard search, or it is a VI search and the (current) nominal trajectory is a VI; temporarily set VIflag2 to prevent probability_of_collision from attempting collision detection of the variant trajectories. */ VIflag2 = true; collision[collision_counter][5] = probability_of_collision(i,r_at_time,rprime_at_time,time,covariance,collision_counter); VIflag2 = false; collision[collision_counter][6] = Math.sqrt(Math.abs(covariance[1][1])); collision[collision_counter][7] = Math.sqrt(Math.abs(covariance[2][2])); collision[collision_counter][8] = Math.sqrt(Math.abs(covariance[3][3])); if (!VIflag1) collision[collision_counter][0] = 3.0*covariance_second_eigenvalue(covariance); } else { /* If VIflag3, this is a VI search where nominal trajectory is a near-miss and variant trajectories are now being tested for collision; avoid a recursive call to probability_of_collision. */ collision[collision_counter][5] = 0; collision[collision_counter][6] = 0; collision[collision_counter][7] = 0; collision[collision_counter][8] = 0; } if (VIflag4) { /* If VIflag4, we want collision detection without probability_of_collision and its six variant trajectories. We also want to archive the state vector at time. */ for (j=1;j<=3;j++) { collision_nominal_state[collision_counter][j] = r_at_time[j]; collision_nominal_state[collision_counter][j+3] = rprime_at_time[j]; } collision[collision_counter][5] = 0; collision[collision_counter][6] = 0; collision[collision_counter][7] = 0; collision[collision_counter][8] = 0; } } else { /* Not in collision at time step n+1; evaluate intermediate points */ /* Calculate the first derivative of distance at time steps n and n+1; check for a minimum that is less than the planetary radius for collision, or less than near_miss_threshold for a near miss. */ distance_n = Math.sqrt(Math.pow((object_r_at_n[1] - planet_r_at_n[i][1]),2) + Math.pow((object_r_at_n[2] - planet_r_at_n[i][2]),2) + Math.pow((object_r_at_n[3] - planet_r_at_n[i][3]),2)); deriv_1_at_n = ((object_r_at_n[1] - planet_r_at_n[i][1])*(object_rprime_at_n[1] - planet_rprime_at_n[i][1]) + (object_r_at_n[2] - planet_r_at_n[i][2])*(object_rprime_at_n[2] - planet_rprime_at_n[i][2]) + (object_r_at_n[3] - planet_r_at_n[i][3])*(object_rprime_at_n[3] - planet_rprime_at_n[i][3]))/distance_n; deriv_1_at_n1 = ((object_r_at_n1[1] - planet_r_at_n1[i][1])*(object_rprime_at_n1[1] - planet_rprime_at_n1[i][1]) + (object_r_at_n1[2] - planet_r_at_n1[i][2])*(object_rprime_at_n1[2] - planet_rprime_at_n1[i][2]) + (object_r_at_n1[3] - planet_r_at_n1[i][3])*(object_rprime_at_n1[3] - planet_rprime_at_n1[i][3]))/distance_n1; if ((deriv_1_at_n < 0) && (deriv_1_at_n1 > 0) && (distance_n < (1.2*near_miss_threshold))) { /* A relevant minimum has occurred. Use bisection on the first derivative of distance to find it. */ upper_time = jultime_n1; lower_time = jultime_n; while ((Math.abs(upper_time - lower_time) > event_localization) && (counter < 100)) { counter = counter + 1; time = lower_time + (upper_time - lower_time)/2; /* Set VIflag5 to prevent this bisection call to update from resetting the VI_trajectory_r and _prime vectors */ VIflag5 = true; update(r_at_n,rprime_at_n,jultime_n,r_at_time,rprime_at_time,time,false,collision_variant); VIflag5 = false; planetary_ephemeris(time,rho,0); for (j=1;j<=3;j++) { planet_i_r_at_time[j] = planet_r[i][j]; planet_i_rprime_at_time[j] = planet_rprime[i][j]; } distance = Math.sqrt(Math.pow((r_at_time[1] - planet_i_r_at_time[1]),2) + Math.pow((r_at_time[2] - planet_i_r_at_time[2]),2) + Math.pow((r_at_time[3] - planet_i_r_at_time[3]),2)); deriv_1_at_time = ((r_at_time[1] - planet_i_r_at_time[1])*(rprime_at_time[1] - planet_i_rprime_at_time[1]) + (r_at_time[2] - planet_i_r_at_time[2])*(rprime_at_time[2] - planet_i_rprime_at_time[2]) + (r_at_time[3] - planet_i_r_at_time[3])*(rprime_at_time[3] - planet_i_rprime_at_time[3]))/distance; if (deriv_1_at_time < 0) /* Minimum has not yet occurred. Increase lower limit. */ lower_time = time; else /* Minimum has already occurred. Reduce upper limit. */ upper_time = time; } /* We've now located the minimum to great accuracy. Is it a collision, or a near miss? */ if (distance <= planet_radius[i]) { /* Collision. Increment collision counter and use bisection to find it. */ collision_counter = collision_counter + 1; collision[0][0] = collision[0][0] + 1; upper_time = time; lower_time = jultime_n; collision[collision_counter][0] = 0; collision[collision_counter][1] = 1; collision[collision_counter][2] = i; collision[collision_counter][3] = time; collision[collision_counter][4] = distance; while ((Math.abs(upper_time - lower_time) > event_localization) && (counter < 200)) { counter = counter + 1; time = lower_time + (upper_time - lower_time)/2; /* Set VIflag5 to prevent this bisection call to update from resetting the VI_trajectory_r and _prime vectors */ VIflag5 = true; update(r_at_n,rprime_at_n,jultime_n,r_at_time,rprime_at_time,time,false,collision_variant); VIflag5 = false; planetary_ephemeris(time,rho,0); for (j=1;j<=3;j++) { planet_i_r_at_time[j] = planet_r[i][j]; planet_i_rprime_at_time[j] = planet_rprime[i][j]; } distance = Math.sqrt(Math.pow((r_at_time[1] - planet_i_r_at_time[1]),2) + Math.pow((r_at_time[2] - planet_i_r_at_time[2]),2) + Math.pow((r_at_time[3] - planet_i_r_at_time[3]),2)); if (distance <= planet_radius[i]) { collision[collision_counter][3] = time; collision[collision_counter][4] = distance; upper_time = time; } else /* Collision has not yet occurred. Increase lower limit. */ lower_time = time; } System.out.println("COLLISION with planet " + i + " at " + time + " distance = " + distance); if (!VIflag3 && !VIflag4) { /* At this point, we know that this is either a standard search, or it is a VI search and the (current) nominal trajectory is a VI; temporarily set VIflag2 to prevent probability_of_collision from attempting collision detection of the variant trajectories. */ VIflag2 = true; collision[collision_counter][5] = probability_of_collision(i,r_at_time,rprime_at_time,time,covariance,collision_counter); VIflag2 = false; collision[collision_counter][6] = Math.sqrt(Math.abs(covariance[1][1])); collision[collision_counter][7] = Math.sqrt(Math.abs(covariance[2][2])); collision[collision_counter][8] = Math.sqrt(Math.abs(covariance[3][3])); if (!VIflag1) collision[collision_counter][0] = 3.0*covariance_second_eigenvalue(covariance); } else { /* If VIflag3, this is a VI search where nominal trajectory is a near-miss and variant trajectories are now being tested for collision; avoid a recursive call to probability_of_collision. */ collision[collision_counter][5] = 0; collision[collision_counter][6] = 0; collision[collision_counter][7] = 0; collision[collision_counter][8] = 0; } if (VIflag4) { /* If VIflag4, we want collision detection without probability_of_collision and its six variant trajectories. We also want to archive the state vector at time. */ for (j=1;j<=3;j++) { collision_nominal_state[collision_counter][j] = r_at_time[j]; collision_nominal_state[collision_counter][j+3] = rprime_at_time[j]; } collision[collision_counter][5] = 0; collision[collision_counter][6] = 0; collision[collision_counter][7] = 0; collision[collision_counter][8] = 0; } } else if (Math.abs(distance - planet_radius[i]) < near_miss_threshold) { /* Near miss. Increment collision counter and record data. */ collision_counter = collision_counter + 1; collision[0][0] = collision[0][0] + 1; collision[collision_counter][1] = 2; collision[collision_counter][2] = i; collision[collision_counter][3] = time; collision[collision_counter][4] = distance; if (!VIflag3 && !VIflag4) { /* We know that this is a near-miss. If it is a standard search, temporarily set VIflag2 to prevent probability_of_collision from attempting collision detection of the variant trajectories. My initial feeling was that, if it is a VI search, do not set VIflag2; probability_of_collision will attempt collision detection of the variant trajectories. Upon further consideration, however, it is unlikely that the variants created by probability_of_collision would lie near the LOV, or have comparably minimized residuals. Additionally, collision detection takes time. I'll therefore set VIflag2 in all cases. */ /* if (!VIflag1) VIflag2 = true; */ VIflag2 = true; collision[collision_counter][5] = probability_of_collision(i,r_at_time,rprime_at_time,time,covariance,collision_counter); VIflag2 = false; collision[collision_counter][6] = Math.sqrt(Math.abs(covariance[1][1])); collision[collision_counter][7] = Math.sqrt(Math.abs(covariance[2][2])); collision[collision_counter][8] = Math.sqrt(Math.abs(covariance[3][3])); if (!VIflag1) collision[collision_counter][0] = 3.0*covariance_second_eigenvalue(covariance); if (collision[collision_counter][0] < 0.0) collision[collision_counter][0] = 0.0; if (collision[collision_counter][5] == -999.9) { /* A variant trajectory has resulted in an impact. Reset the number of events and the probability so update will note the event and overwrite the Monte Carlo trajectory with the variant VI. */ collision[0][0] = 1; collision[1][1] = 1; collision[1][5] = -999.9; } } else { /* If VIflag3, this is a VI search where nominal trajectory is a near-miss and variant trajectories are now being tested for collision; avoid a recursive call to probability_of_collision. */ collision[collision_counter][5] = 0; collision[collision_counter][6] = 0; collision[collision_counter][7] = 0; collision[collision_counter][8] = 0; } if (VIflag4) { /* If VIflag4, we want collision detection without probability_of_collision and its six variant trajectories. We also want to archive the state vector at time. */ for (j=1;j<=3;j++) { collision_nominal_state[collision_counter][j] = r_at_time[j]; collision_nominal_state[collision_counter][j+3] = rprime_at_time[j]; } collision[collision_counter][5] = 0; collision[collision_counter][6] = 0; collision[collision_counter][7] = 0; collision[collision_counter][8] = 0; } } } } } } } public void get_mp_state_vector(double classical_elements[], double time1, double r[], double rprime[]) { /* Method to convert the classical heliocentric ecliptic elements of a minor planet at TDB time1 into a barycentric equatorial state vector at that same epoch. Inputs: classical elements[1] - a in AU [2] - e [3] - i in radians [4] - w in radians [5] - omega in radians [6] - T expressed as a TDB julian day [7] - mean anomaly in radians [8] - q in AU time1 - TDB epoch to which the elements refer, expressed as a julday Outputs: r, rprime - barycentric equatorial position/velocity of the body at epoch Tested and verified 10 August 1999. */ double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; int i = 0; /* First, convert classical heliocentric ecliptic elements at TDB time1 to pos/vel */ convert_Keplerian_elements_to_state_vector(classical_elements,r,rprime,time1); /* Two-body routines function in modified time; convert velocity to AU/day */ for (i=1;i<=3;i++) rprime[i] = 0.01720209895*rprime[i]; /* Rotate asteroid heliocentric state vector from ecliptic to equatorial */ ecliptic_to_equatorial(r); ecliptic_to_equatorial(rprime); /* Get Sun's position at TDB time1 */ get_planet_posvel(time1,11,sun_r,sun_rprime); /* Add Sun's posvel to convert asteroid state vector from helio- to barycentric */ for (i=1;i<=3;i++) { r[i] = r[i] + sun_r[i]; rprime[i] = rprime[i] + sun_rprime[i]; } } void get_observed_radec(double state_r[], double state_rprime[], double time1, boolean geocentric, double latitude, double longitude, double altitude, double ut1_minus_tai, double observer_r[], double observer_rprime[], double time2, double radec[]) { /* Method that uses the barycentric equatorial state vector of a body at TDB time1 to calculate its observed radec from a site at TDT time2. Inputs: state_r and state_rprime in AU and AU/day time1 is a TDB julian day geocentric is a boolean indicating whether the radec should be geocentric or relative to latitude/longitude/altitude latitude and longitude are in radians, with longitude measured eastward altitude is in meters ut1_minus_tai is in seconds observer_r and observer_rprime are arrays containing the barycentric equatorial rectangular position and velocity of the observing site, in AU and AU/day time2 is a TDT julian day Output: radec is an array containing ra and dec, in radians Tested and verified 10 August 1999. */ /* This method conforms to the outline of "Explanatory Supplement to the Astronomical Almanac", 1992, section 3.31 (Apparent-Place Algorithm for Planets). Note: time1 is the TDB epoch of the asteroid's orbital elements time2 is the TDT time of observation on Earth */ double dist = 0, time3 = 0, ra = 0, dec = 0, lat = 0, longit = 0; double rho_dot = 0, r_mag = 0, r_dot = 0, oldtime3 = 10; double dist_u = 0, dist_e = 0, dist_q = 0, tau = 0; double[] EOP = new double[8]; double[] r = new double[4]; double[] rprime = new double[4]; double[] r2 = new double[4]; double[] r2prime = new double[4]; double[] r3 = new double[4]; double[] r3prime = new double[4]; double[] rho = new double[4]; double[] latlong = new double[3]; double[] vector_u = new double[4]; double[] vector_q = new double[4]; double[] vector_e = new double[4]; double[][] collision = new double[2000][9]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; int i = 0, j = 0, counter = 0; /* Section 3.311 */ /* Convert TDT time2 to its TDB equivalent; if (UT1-TAI) is zero (unknown), tdt_to_tdb algorithm will estimate it */ time2 = tdt_to_tdb(time2,latitude,longitude,altitude,ut1_minus_tai); /* Section 3.312 */ if (geocentric == true) { /* Geocentric case - get Earth barycentric equatorial position and velocity at TDB time2 */ planetary_ephemeris(time2,rho,0); for (i=1;i<=3;i++) { observer_r[i] = planet_r[3][i]; observer_rprime[i] = planet_rprime[3][i]; } } else { /* Topocentric case */ /* The observer coordinates determined when the observation was entered may have been passed as an argument; these may have used precise values of the EOPs, so we don't want to recalculate. On the other hand, the observer coordinates may be zero; this method may be creating an ephemeris, so precise values of the EOPs may not be available. We'll test and act accordingly. */ if (observer_r[1] == 0.0) { /* Observer coordinates are unavailable; calculate them. */ /* Set EOPs to zero so that method get_observer_position will use estimates and defaults. However, allow for the use of ut1_minus_tai if it is provided. */ for (i=1;i<=7;i++) EOP[i] = 0.0; EOP[3] = ut1_minus_tai; /* get observer barycentric equatorial position and velocity at TDB time2 */ get_observer_position(latitude,longitude,altitude,time2,EOP,observer_r,observer_rprime); } /* If observer_r[1] is not equal to 0.0, observer coordinates are available; use them. */ } /* Create and normalize Earth heliocentric position vector at TDB time2 */ get_planet_posvel(time2,11,sun_r,sun_rprime); for (i=1;i<=3;i++) vector_e[i] = observer_r[i] - sun_r[i]; dist_e = Math.sqrt(Math.pow(vector_e[1],2) + Math.pow(vector_e[2],2) + Math.pow(vector_e[3],2)); for (i=1;i<=3;i++) vector_e[i] = vector_e[i]/dist_e; /* Section 3.313 */ /* Get asteroid barycentric equatorial state vector at time of observation TDB time2 */ update(state_r,state_rprime,time1,r3,r3prime,time2,false,collision); /* Sections 3.314 and 3.315 */ /* Iterative correction for light time-of-flight */ for (i=1;i<=3;i++) { /* r[] and rprime[] will represent state vector at time3, the time at which light left asteroid to be observed on Earth at time2 */ r[i] = r3[i]; rprime[i] = r3prime[i]; } /* Create and normalize the line-of-sight vector from observer at time2 to asteroid at time3 */ for (i=1;i<=3;i++) vector_u[i] = r[i] - observer_r[i]; dist_u = Math.sqrt(Math.pow(vector_u[1],2) + Math.pow(vector_u[2],2) + Math.pow(vector_u[3],2)); /* First approximation: time3 = time2 - dist_u/speed_of_light */ time3 = time2 - dist_u/speed_of_light; /* Get asteroid barycentric equatorial state vector at TDB time3 */ update(r3,r3prime,time2,r,rprime,time3,false,collision); while ((Math.abs(time3 - oldtime3) > 1e-6) && (counter < 100)) { oldtime3 = time3; counter = counter + 1; /* Create and normalize the line-of-sight vector from observer at time2 to asteroid at time3 */ for (i=1;i<=3;i++) vector_u[i] = r[i] - observer_r[i]; dist_u = Math.sqrt(Math.pow(vector_u[1],2) + Math.pow(vector_u[2],2) + Math.pow(vector_u[3],2)); for (i=1;i<=3;i++) vector_u[i] = vector_u[i]/dist_u; /* Create and normalize asteroid's heliocentric position vector at TDB time3 */ get_planet_posvel(time3,11,sun_r,sun_rprime); for (i=1;i<=3;i++) vector_q[i] = r[i] - sun_r[i]; dist_q = Math.sqrt(Math.pow(vector_q[1],2) + Math.pow(vector_q[2],2) + Math.pow(vector_q[3],2)); for (i=1;i<=3;i++) vector_q[i] = vector_q[i]/dist_q; /* Recalculate time3 based on updated estimate of light time-of-flight */ tau = dist_u/speed_of_light + (2*Math.pow(kappa,2)/Math.pow(speed_of_light,3))*Math.log((dist_e + dist_u + dist_q)/(dist_e - dist_u + dist_q)); time3 = time2 - tau; /* Find asteroid position at time3 */ update(r3,r3prime,time2,r,rprime,time3,false,collision); } /* Section 3.3110 */ /* Calculate ra/dec */ radec[2] = Math.atan2(vector_u[3],Math.sqrt(Math.pow(vector_u[1],2) + Math.pow(vector_u[2],2))); radec[1] = Math.atan2(vector_u[2],vector_u[1]); } public void update(double r1[],double r1prime[],double time1,double r2[],double r2prime[],double time2,boolean check_for_collision,double collision[][]) { /* Procedure to calculate the position and velocity of a body at time2 given the position and velocity of the body at time1. The Cowell method is used, with integration performed using Runge-Kutta Prince-Dormand 8(7). Local error per unit step is controlled by variable step size. Positions and velocities are given in A.U.s and A.U.s/day, while the times are Julian dates. The procedure also checks for collisions with planets, if the Boolean check_for_collision is set. The output array "collision[][]" has two indices. The first index accounts for multiple encounters from time1 to time2, the total being stored in "collision[0][0]". For the i'th encounter, entry "collision[i][1]" indicates whether a collision ( = 1) or a near miss ( = 2) occurs. In either case, "collision[i][2]" gives the planet (1-9, Moon=10, Sun=0), "collision[i][3]" gives the Julian date, "collision[i][4]" gives the nominal distance in A.U.s., "collision[i][5]" gives the estimated probability of collision, "collision[i][6]" gives the object's x-covariance at the time of the event, "collision[i][7]" gives the object's y-covariance at the time of the event, and "collision[i][8]" gives the object's z-covariance at the time of the event. "collision[i][0]" gives the semi-width of the 3-sigma uncertainty region in A.U.s. Reference for the Cowell method is Bate, Mueller, and White, "Fundamentals of Astrodynamics", pp 386-390. Reference for RK dopri 8(7) is P.J. Prince and J.R. Dormand, "High-order embedded Runge-Kutta formulae", J. Comp. Appl. Math., vol 7, 1981, p. 67-75. The error control is adapted from the class notes of Math 551 taught at The Pennsylvania State University during Fall 1989 by Prof. Douglas Arnold. */ double[] fatn = new double[4]; double[] r = new double[4]; double[] rprime = new double[4]; double[] r_at_n = new double[4]; double[] rprime_at_n = new double[4]; double[] fatnp1 = new double[4]; double[] rho = new double[4]; double toler = 0, h = 0, n1time = 0, ntime = 0, localerror = 0, ratio = 0, test_distance = 0, min_distance = 0; int i = 0, j = 0, k = 0, istep = 0, collision_counter = 0, counter = 0, counter_limit = 0, candidate = 0; /* Initialize output collision array */ for (i=0;i<=1999;i++) { for (j=0;j<=8;j++) { collision[i][j] = 0; } } if (time1 == time2) { /* Return the state vector unchanged */ for (k=1;k<=3;k++) { r2[k] = r1[k]; r2prime[k] = r1prime[k]; } } else { /* Set local error per unit step tolerance */ /* For use in the asteroid_ephemeris procedure, we will use a fixed tolerance designed to limit integration error over 200 years */ /* toler = integration_error/Math.abs(time2 - time1); */ toler = integration_error/73050.0; /* Determine the maximum number of attempted steps to be permitted */ /* counter_limit = (int)(Math.abs(time2 - time1)/0.01); */ counter_limit = 100; /* Initialize variables for first time step */ /* istep is the step being calculated */ istep = 1; /* Set initial step size */ h = time2 - time1; /* Limit initial h to +/- 20 days */ if (Math.abs(h) > 20.0) h = 20.0*h/Math.abs(h); /* n1time is the time at the (n+1)'st step, ntime is the time at the n'th step */ n1time = time1; ntime = time1; /* Initialize position, velocity, and acceleration vectors */ for (k=1;k<=3;k++) { r[k] = r1[k]; r_at_n[k] = r1[k]; rprime[k] = r1prime[k]; rprime_at_n[k] = r1prime[k]; /* During VI search, a great many Monte Carlo trajectories are created in a normal distribution about the nominal epoch state vector; differential correction then attempts to force a VI. In calculating the differential partial derivatives, probability_of_collision must create variant trajectories centered on the Monte Carlo trajectory, rather than the nominal epoch state vector. Thus, we want to save the Monte Carlo trajectory for use by probability_of_collision. */ if ((VIflag1) && (!VIflag2) && (!VIflag3) && (!VIflag5)) { VI_trajectory_r[k] = r1[k]; VI_trajectory_rprime[k] = r1prime[k]; } } get_acceleration(ntime,r_at_n,rprime_at_n,fatn); /* Begin integration loop */ integrationloop: while ((((n1time < time2) && (time2 > time1)) || ((n1time > time2) && (time2 < time1))) && (collision[collision_counter][1] != 1.0) && (counter < counter_limit)) { /* So long as we've not reached time2 or slammed into a planet, ... */ /* Increment time to the (n+1)'st step */ n1time = ntime + h; counter = counter + 1; /* if (Math.abs(h) < 0.01) System.out.println("step size = " + h); */ /* System.out.println("step size = " + h); */ /* Integrate to get r and rprime at the (n+1)'st time step */ /* Use Runge-Kutta Dormand-Prince 8(7) */ localerror = dopri8(ntime,h,fatn,rprime_at_n,r_at_n,rprime,r); /* If a variant trajectory collides with a planet, the error may go to infinite or NaN. If so, end the integration. */ if (!(Math.abs(localerror) < 1e+10)) counter = counter_limit; /* Compute ratio of projected new step size to present step size */ if (Math.abs(localerror/h) > 0.0) /* Avoid a division by zero for small step sizes */ ratio = .8*Math.pow(Math.abs(toler/(localerror/h)),(1.0/7.0)); else /* Error and step size are very small; allow step size to double */ ratio = 2; /* Test to see if step will be accepted; if already at minimum step size, ignore */ if ((Math.abs(localerror/h) > toler) && (Math.abs(h) > 0.001)) { /* Step has failed */ /* Determine a new step size */ if ((ratio < .2) && (istep > 1)) ratio = .2; /* Note that new step size is limited to one-fifth of the previous one, except on the first step, which allows faster determination of an initial step size */ n1time = ntime; h = ratio*h; /* Enforce minimum step size to prevent ultrasmall steps near collision */ if (Math.abs(h) < 0.001) h = 0.001*(h/Math.abs(h)); continue integrationloop; } /* Step has been accepted, or we're at the minimum step size. In either case, reset the counter. */ counter = 0; /* Check for collision, if so desired */ /* Note: This check for collision must occur after a step has been accepted; otherwise, the check will be made more than once for failed steps. */ if (check_for_collision == true) { /* TESTING PURPOSES ONLY!! */ /* System.out.println("check for collision jultime = " + ntime); */ /* TESTING PURPOSES ONLY!! */ detect_collision(r_at_n,rprime_at_n,r,rprime,ntime,n1time,collision); collision_counter = (int)(collision[0][0]); if (collision[1][5] == -999.9) { /* During a VI search, a variant trajectory has resulted in an impact. Overwrite the Monte Carlo trajectory with the variant VI. Since detect_collision has set collision[1][1] = 1.0, this method will end, and we will return to the VI search algorithm. */ for (k=1;k<=3;k++) { r1[k] = VI_trajectory_r[k]; r1prime[k] = VI_trajectory_rprime[k]; } } } if ((check_for_collision == false) || ((check_for_collision == true) && (collision[collision_counter][1] != 1.0))) { /* Determine a new step size */ if (ratio > 2) ratio = 2; /* Note that new step size is limited to double the previous one */ h = ratio*h; /* Enforce minimum step size to prevent ultrasmall steps near collision */ if (Math.abs(h) < 0.001) h = 0.001*(h/Math.abs(h)); /* Calculate second derivative of r at time n+1 */ get_acceleration(n1time,r,rprime,fatnp1); /* Increment step counter */ istep = istep + 1; /* Check to prevent overrunning endpoint of integration */ if (((((n1time + h) > time2) && (time2 > time1)) || (((n1time + h) < time2) && (time2 < time1))) && (n1time != time2)) /* Reduce step size to avoid overrunning endpoint of integration */ h = time2 - n1time; /* If a variant trajectory collides with a planet, the acceleration may go to infinite or NaN. If so, end the integration. */ if (!(Math.abs(fatnp1[1]) < 1e+10) || !(Math.abs(fatnp1[2]) < 1e+10) || !(Math.abs(fatnp1[3]) < 1e+10)) counter = counter_limit; /* Update integration variables */ ntime = n1time; for (k=1;k<=3;k++) { r_at_n[k] = r[k]; rprime_at_n[k] = rprime[k]; fatn[k] = fatnp1[k]; } } } if (!(counter < counter_limit)) { System.out.println("WARNING: update exceeds max steps"); /* Proceeding on the assumption that the excessively high local error was caused by an imminent collision, make a guess as to which planet was responsible, and populate the collision array accordingly */ planetary_ephemeris(n1time,rho,0); min_distance = 1; for (k=0;k<=10;k++) { test_distance = Math.sqrt(Math.pow((r[1] - planet_r[k][1]),2) + Math.pow((r[2] - planet_r[k][2]),2) + Math.pow((r[3] - planet_r[k][3]),2)); if (test_distance < min_distance) { min_distance = test_distance; candidate = k; } } if (min_distance < 0.01) { collision[0][0] = collision[0][0] + 1; collision[(int)collision[0][0]][0] = 0; collision[(int)collision[0][0]][1] = 1; collision[(int)collision[0][0]][2] = candidate; collision[(int)collision[0][0]][3] = n1time; collision[(int)collision[0][0]][4] = min_distance; collision[(int)collision[0][0]][5] = 0; collision[(int)collision[0][0]][6] = 0; collision[(int)collision[0][0]][7] = 0; collision[(int)collision[0][0]][8] = 0; } } /* Integration complete; return new state vector, and reset collision_sensitivity state vectors, if necessary */ for (k=1;k<=3;k++) { r2[k] = r[k]; r2prime[k] = rprime[k]; if ((check_for_collision == true) && (!VIflag3)) { /* Don't reset the variant trajectories if a variant is being checked for collision during VI search */ for (j=1;j<=8;j++) { collision_sensitivity_r_variant[j][k] = 0; collision_sensitivity_rprime_variant[j][k] = 0; } collision_sensitivity_time = 0; } } } } void initialize_dopri8() { /* Initialization of the integration coefficients for dopri8 */ /* Initialize arrays */ dopri8_a[2][1] = 1.0/18.0; dopri8_a[3][1] = 1.0/48.0; dopri8_a[3][2] = 1.0/16.0; dopri8_a[4][1] = 1.0/32.0; dopri8_a[4][2] = 0; dopri8_a[4][3] = 3.0/32.0; dopri8_a[5][1] = 5.0/16.0; dopri8_a[5][2] = 0; dopri8_a[5][3] = -75.0/64.0; dopri8_a[5][4] = 75.0/64.0; dopri8_a[6][1] = 3.0/80.0; dopri8_a[6][2] = 0; dopri8_a[6][3] = 0; dopri8_a[6][4] = 3.0/16.0; dopri8_a[6][5] = 3.0/20.0; dopri8_a[7][1] = 29443.841/614563.906; dopri8_a[7][2] = 0; dopri8_a[7][3] = 0; dopri8_a[7][4] = 77736.538/692538.347; dopri8_a[7][5] = -28693.883/1125000.000; dopri8_a[7][6] = 23124.283/1800000.000; dopri8_a[8][1] = 16016.141/946692.911; dopri8_a[8][2] = 0; dopri8_a[8][3] = 0; dopri8_a[8][4] = 61564.180/158732.637; dopri8_a[8][5] = 22789.713/633445.777; dopri8_a[8][6] = 545815.736/2771057.229; dopri8_a[8][7] = -180193.667/1043307.555; dopri8_a[9][1] = 39632.708/573591.083; dopri8_a[9][2] = 0; dopri8_a[9][3] = 0; dopri8_a[9][4] = -433636.366/683701.615; dopri8_a[9][5] = -421739.975/2616292.301; dopri8_a[9][6] = 100302.831/723423.059; dopri8_a[9][7] = 790204.164/839813.087; dopri8_a[9][8] = 800635.310/3783071.287; dopri8_a[10][1] = 246121.993/1340847.787; dopri8_a[10][2] = 0; dopri8_a[10][3] = 0; dopri8_a[10][4] = -37695042.795/15268766.246; dopri8_a[10][5] = -309121.744/1061227.803; dopri8_a[10][6] = -12992.083/490766.935; dopri8_a[10][7] = 6005943.493/2108947.869; dopri8_a[10][8] = 393006.217/1396673.457; dopri8_a[10][9] = 123872.331/1001029.789; dopri8_a[11][1] = -1028468.189/846180.014; dopri8_a[11][2] = 0; dopri8_a[11][3] = 0; dopri8_a[11][4] = 8478235.783/508512.852; dopri8_a[11][5] = 1311729.495/1432422.823; dopri8_a[11][6] = -10304129.995/1701304.382; dopri8_a[11][7] = -48777925.059/3047939.560; dopri8_a[11][8] = 15336726.248/1032824.649; dopri8_a[11][9] = -45442868.181/3398467.696; dopri8_a[11][10] = 3065993.473/597172.653; dopri8_a[12][1] = 185892.177/718116.043; dopri8_a[12][2] = 0; dopri8_a[12][3] = 0; dopri8_a[12][4] = -3185094.517/667107.341; dopri8_a[12][5] = -477755.414/1098053.517; dopri8_a[12][6] = -703635.378/230739.211; dopri8_a[12][7] = 5731566.787/1027545.527; dopri8_a[12][8] = 5232866.602/850066.563; dopri8_a[12][9] = -4093664.535/808688.257; dopri8_a[12][10] = 3962137.247/1805957.418; dopri8_a[12][11] = 65686.358/487910.083; dopri8_a[13][1] = 403863.854/491063.109; dopri8_a[13][2] = 0; dopri8_a[13][3] = 0; dopri8_a[13][4] = -5068492.393/434740.067; dopri8_a[13][5] = -411421.997/543043.805; dopri8_a[13][6] = 652783.627/914296.604; dopri8_a[13][7] = 11173962.825/925320.556; dopri8_a[13][8] = -13158990.841/6184727.034; dopri8_a[13][9] = 3936647.629/1978049.680; dopri8_a[13][10] = -160528.059/685178.525; dopri8_a[13][11] = 248638.103/1413531.060; dopri8_a[13][12] = 0; dopri8_c[1] = 0; dopri8_c[2] = 1.0/18.0; dopri8_c[3] = 1.0/12.0; dopri8_c[4] = 1.0/8.0; dopri8_c[5] = 5.0/16.0; dopri8_c[6] = 3.0/8.0; dopri8_c[7] = 59.0/400.0; dopri8_c[8] = 93.0/200.0; dopri8_c[9] = 5490023.248/9719169.821; dopri8_c[10] = 13.0/20.0; dopri8_c[11] = 1201146.811/1299019.798; dopri8_c[12] = 1; dopri8_c[13] = 1; dopri8_b[1] = 13451.932/455176.623; dopri8_b[2] = 0; dopri8_b[3] = 0; dopri8_b[4] = 0; dopri8_b[5] = 0; dopri8_b[6] = -808719.846/976000.145; dopri8_b[7] = 1757004.468/5645159.321; dopri8_b[8] = 656045.339/265891.186; dopri8_b[9] = -3867574.721/1518517.206; dopri8_b[10] = 465885.868/322736.535; dopri8_b[11] = 53011.238/667516.719; dopri8_b[12] = 2.0/45.0; dopri8_b[13] = 0; dopri8_bhat[1] = 14005.451/335480.064; dopri8_bhat[2] = 0; dopri8_bhat[3] = 0; dopri8_bhat[4] = 0; dopri8_bhat[5] = 0; dopri8_bhat[6] = -59238.493/1068277.825; dopri8_bhat[7] = 181606.767/758867.731; dopri8_bhat[8] = 561292.985/797845.732; dopri8_bhat[9] = -1041891.430/1371343.529; dopri8_bhat[10] = 760417.239/1151165.299; dopri8_bhat[11] = 118820.643/751138.087; dopri8_bhat[12] = -528747.749/2220607.170; dopri8_bhat[13] = 0.25; } double dopri8(double ntime,double h,double fatn[],double rprimen[],double rn[],double rprime[],double r[]) { /* Procedure to integrate the equations of motion (Cowell's method) from ntime to ntime+h using the Runge-Kutta-type Prince-Dormand 8(7) method (see procedure update). Also finds local error in the integration step, which is returned. Reference: P.J. Prince and J.R. Dormand, "High-order embedded Runge-Kutta formulae", J. Comp. Appl. Math., vol 7, 1981, p. 67-75. Also, E. Hairer, S.P. Norsett, G. Wanner, "Solving Ordinary Differential Equations I (Non-stiff problems), Springer-Verlag, 1987, pp. 132-133, 167-171, 193-195, and 260. */ double[][] dopri8_kprime = new double[14][4]; double[][] dopri8_k = new double[14][4]; double[] low_r = new double[4]; double[] rvariant = new double[4]; double[] rprimevariant = new double[4]; double[] kprime_temp = new double[4]; double localerror = 0; int i = 0, j = 0, n = 0; /* If necessary, initialize the integration coefficients */ if (dopri8_a[2][1] != 1.0/18.0) initialize_dopri8(); /* Begin the integration from ntime to ntime+h */ /* Stage 1 */ for (n=1;n<=3;n++) { /* Initialize the Runge-Kutta variables, and the pos/vel vectors. */ dopri8_kprime[1][n] = fatn[n]; dopri8_k[1][n] = rprimen[n]; /* r is the 8'th order position solution; low_r is the 7'th order solution */ r[n] = rn[n] + h*dopri8_bhat[1]*dopri8_k[1][n]; low_r[n] = rn[n] + h*dopri8_b[1]*dopri8_k[1][n]; rprime[n] = rprimen[n] + h*dopri8_bhat[1]*dopri8_kprime[1][n]; } /* Stages 2-13 */ for (i=2;i<=13;i++) { for (n=1;n<=3;n++) { /* rvariant and rprimevariant are the intermediate pos/vel used to evaluate the acceleration function at each stage */ rvariant[n] = rn[n]; rprimevariant[n] = rprimen[n]; dopri8_k[i][n] = rprimen[n]; for (j=1;j<=(i-1);j++) { /* Sum the terms in k, rvariant, and rprimevariant needed to evaluate the acceleration function and determine kprime at this stage */ rvariant[n] = rvariant[n] + h*(dopri8_a[i][j]*dopri8_k[j][n]); rprimevariant[n] = rprimevariant[n] + h*(dopri8_a[i][j]*dopri8_kprime[j][n]); dopri8_k[i][n] = dopri8_k[i][n] + h*(dopri8_a[i][j]*dopri8_kprime[j][n]); } } get_acceleration(ntime+dopri8_c[i]*h,rvariant,rprimevariant,kprime_temp); for (n=1;n<=3;n++) { dopri8_kprime[i][n] = kprime_temp[n]; r[n] = r[n] + h*dopri8_bhat[i]*dopri8_k[i][n]; low_r[n] = low_r[n] + h*dopri8_b[i]*dopri8_k[i][n]; rprime[n] = rprime[n] + h*dopri8_bhat[i]*dopri8_kprime[i][n]; } } /* Compute the local error in position */ localerror = 0; for (n=1;n<=3;n++) localerror = localerror + Math.pow((r[n] - low_r[n]),2); localerror = Math.sqrt(localerror); return localerror; } void get_acceleration(double jultime,double r[],double rprime[],double acceleration[]) { /* Procedure to calculate the accelerations on a body for use in the differential equations of motion in Cowell's method. First, we calculate the gravitational perturbations due to the planets, using the barycentric relativistic equation for point-mass acceleration (Ref: P. Kenneth Seidelmann, "Explanatory Supplement to the Astronomical Almanac", 1992, p. 281), and then the perturbing acceleration on a comet due to reactive water vaporization (Refs: Brian G. Marsden, "Comets and Non-Gravitational Forces II", The Astronomical Journal Vol 74 No. 5, pp. 720-734; B.G. Marsden, Z. Sekanina, D.K. Yeomans, "Comets and Non-Gravitational Forces V", The Astronomical Journal, Vol 78 No. 2, pp. 211-225; D.K. Yeomans and P.W. Chodas, "An Asymmetric Outgassing Model for Cometary Nongravitational Accelerations", The Astronomical Journal, Vol 98 No. 3, pp. 1083-1093). Note that jultime is a Julian date, r[] is the barycentric position vector in A.U.s of the body in question, rprime[] is the barycentric velocity in AU/day, and acceleration() is the acceleration vector on the body given in AU/day^3. Note also that planet_r(I,j) represent the j'th component of the position of the I'th solar system body, planet_rprime(I,j) represent the j'th component of the velocity of the I'th solar system body, and planet_r2prime(I,j) represent the j'th component of the Newtonian acceleration of the I'th solar system body. Tested and verified 7-29-99. */ double[] dist = new double[321]; double[] vel = new double[321]; double[] rtemp = new double[4]; double[] rprimetemp = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double[] heliocentric_r = new double[4]; double[] heliocentric_rprime = new double[4]; double[] solar_pressure = new double[4]; double[] termj = new double[4]; double[] ecliptic_r = new double[4]; double[] ecliptic_sun = new double[4]; double[] normal = new double[4]; double velocity = 0, sum1 = 0, term1 = 0, sum2 = 0, term2 = 0, term3 = 0, term4 = 0, term5 = 0, term6 = 0, term7 = 0, bracket1 = 0, bracket2 = 0, angular_velocity = 0, r_mag = 0, rtemp_mag = 0, g = 0; double S = 0, A = 0, albedo = 0, Cr = 0, density = 0, mass = 0; double sun_J2 = 1.9E-7; double earth_J2 = 1.08262699E-3; double jupiter_J2 = 0.014736; int j = 0, k = 0; /* Get planetary and asteroid ephemeris data */ /* planetary_ephemeris(jultime,r,1); asteroid_ephemeris(jultime,1,r); */ /* Note: My initial algorithm required light-time corrections for the positions of perturbing planets and asteroids, in order to account for gravity moving at the speed of light. However, in a private communication, Jon Giorgini of JPL stated "There should be no correction for light-time for perturbing body gravitation; to first order, gravitational force comes out as directed toward the instantaneous source at the same Coordinate or Ephemeris Time. It may be counterintuitive, but there's no strange physics involved (beyond gravity itself, which is of course an open issue); it turns out that way when computing the derivatives of time retarded vector & scalar potentials. Higher-order terms exist but are insignificant at solar system speeds (much less than speed of light). Newton didn't know it at the time; his laws were empirical and took this into account implicitly, not explicitly.*/ planetary_ephemeris(jultime,r,0); /* asteroid_ephemeris(jultime,0,r); */ asteroid_ephemeris(jultime); /* First, calculate several quantities to be used later */ velocity = Math.sqrt(Math.pow(rprime[1],2) + Math.pow(rprime[2],2) + Math.pow(rprime[3],2)); for (j=0;j<=number_of_planets;j++) { dist[j] = Math.sqrt(Math.pow((r[1] - planet_r[j][1]),2) + Math.pow((r[2] - planet_r[j][2]),2) + Math.pow((r[3] - planet_r[j][3]),2)); if (dist[j] < 1.0E-12) dist[j] = 1.0E-12; vel[j] = Math.sqrt(Math.pow(planet_rprime[j][1],2) + Math.pow(planet_rprime[j][2],2) + Math.pow(planet_rprime[j][3],2)); } /* Initialize the acceleration vector */ for (j=1;j<=3;j++) { acceleration[j] = 0; } /* Calculate each term in the brackets separately, then combine them */ for (j=0;j<=number_of_planets;j++) { sum1 = 0; for (k=0;k<=number_of_planets;k++) sum1 = sum1 + mu_planet[k]/dist[k]; term1 = 4*sum1/Math.pow(speed_of_light,2); sum2 = 0; for (k=0;k<=number_of_planets;k++) { if (k != j) sum2 = sum2 + mu_planet[k]/dist[k]; } term2 = sum2/Math.pow(speed_of_light,2); term3 = Math.pow(velocity,2)/Math.pow(speed_of_light,2); term4 = 2*Math.pow(vel[j],2)/Math.pow(speed_of_light,2); term5 = 4*(rprime[1]*planet_rprime[j][1] + rprime[2]*planet_rprime[j][2] + rprime[3]*planet_rprime[j][3])/Math.pow(speed_of_light,2); term6 = 1.5*Math.pow((((r[1] - planet_r[j][1])*planet_rprime[j][1] + (r[2] - planet_r[j][2])*planet_rprime[j][2] + (r[3] - planet_r[j][3])*planet_rprime[j][3])/dist[j]),2)/Math.pow(speed_of_light,2); term7 = ((planet_r[j][1] - r[1])*planet_r2prime[j][1] + (planet_r[j][2] - r[2])*planet_r2prime[j][2] + (planet_r[j][3] - r[3])*planet_r2prime[j][3])/(2*Math.pow(speed_of_light,2)); bracket1 = 1 - term1 - term2 + term3 + term4 - term5 - term6 + term7; bracket2 = (r[1] - planet_r[j][1])*(4*rprime[1] - 3*planet_rprime[j][1]) + (r[2] - planet_r[j][2])*(4*rprime[2] - 3*planet_rprime[j][2]) +(r[3] - planet_r[j][3])*(4*rprime[3] - 3*planet_rprime[j][3]); /* Calculate contribution to acceleration of object */ for (k=1;k<=3;k++) acceleration[k] = acceleration[k] + mu_planet[j]*bracket1*(planet_r[j][k] - r[k])/Math.pow(dist[j],3) + mu_planet[j]*bracket2*(rprime[k] - planet_rprime[j][k])/(Math.pow(speed_of_light,2)*Math.pow(dist[j],3)) + 3.5*mu_planet[j]*planet_r2prime[j][k]/(Math.pow(speed_of_light,2)*dist[j]); } /* Calculate the acceleration due to the Sun's oblateness (J2); reference is Escobal pp. 50-51 */ /* Create the object and Sun position vectors */ for (k=1;k<=3;k++) { ecliptic_r[k] = r[k]; ecliptic_sun[k] = planet_r[0][k]; } /* Rotate the position vectors into the ecliptic; calculation of the J2 effects should occur in the ecliptic frame. */ equatorial_to_ecliptic(ecliptic_r); equatorial_to_ecliptic(ecliptic_sun); /* Calculate the J2 effects; reference is Escobal pp. 50-51. */ termj[1] = -mu_planet[0]*(ecliptic_r[1] - ecliptic_sun[1])*(1.5*sun_J2*Math.pow(planet_radius_0,2.0)*(1.0 - 5.0*Math.pow((ecliptic_r[3] - ecliptic_sun[3]),2.0)/Math.pow(dist[0],2.0))/Math.pow(dist[0],2.0))/Math.pow(dist[0],3.0); termj[2] = -mu_planet[0]*(ecliptic_r[2] - ecliptic_sun[2])*(1.5*sun_J2*Math.pow(planet_radius_0,2.0)*(1.0 - 5.0*Math.pow((ecliptic_r[3] - ecliptic_sun[3]),2.0)/Math.pow(dist[0],2.0))/Math.pow(dist[0],2.0))/Math.pow(dist[0],3.0); termj[3] = -mu_planet[0]*(ecliptic_r[3] - ecliptic_sun[3])*(1.5*sun_J2*Math.pow(planet_radius_0,2.0)*(3.0 - 5.0*Math.pow((ecliptic_r[3] - ecliptic_sun[3]),2.0)/Math.pow(dist[0],2.0))/Math.pow(dist[0],2.0))/Math.pow(dist[0],3.0); /* Rotate the J2 effects into the equatorial frame, and add to the acceleration vector */ ecliptic_to_equatorial(termj); for (k=1;k<=3;k++) { acceleration[k] = acceleration[k] + termj[k]; } /* Calculate the acceleration due to the Earth's oblateness (J2); reference is Escobal pp. 50-51 */ termj[1] = -mu_planet[3]*(r[1] - planet_r[3][1])*(1.5*earth_J2*Math.pow(planet_radius_3,2.0)*(1.0 - 5.0*Math.pow((r[3] - planet_r[3][3]),2.0)/Math.pow(dist[3],2.0))/Math.pow(dist[3],2.0))/Math.pow(dist[3],3.0); termj[2] = -mu_planet[3]*(r[2] - planet_r[3][2])*(1.5*earth_J2*Math.pow(planet_radius_3,2.0)*(1.0 - 5.0*Math.pow((r[3] - planet_r[3][3]),2.0)/Math.pow(dist[3],2.0))/Math.pow(dist[3],2.0))/Math.pow(dist[3],3.0); termj[3] = -mu_planet[3]*(r[3] - planet_r[3][3])*(1.5*earth_J2*Math.pow(planet_radius_3,2.0)*(3.0 - 5.0*Math.pow((r[3] - planet_r[3][3]),2.0)/Math.pow(dist[3],2.0))/Math.pow(dist[3],2.0))/Math.pow(dist[3],3.0); for (k=1;k<=3;k++) { acceleration[k] = acceleration[k] + termj[k]; } /* Calculate the acceleration due to Jupiter's oblateness (J2); reference is Escobal pp. 50-51 */ /* Create position vectors for the object and Jupiter */ for (k=1;k<=3;k++) { ecliptic_r[k] = r[k]; ecliptic_sun[k] = planet_r[5][k]; } /* Rotate the position vectors into the ecliptic; calculation of the J2 effects will be performed in the ecliptic frame as a good approximation. */ equatorial_to_ecliptic(ecliptic_r); equatorial_to_ecliptic(ecliptic_sun); /* Calculate the J2 effects; reference is Escobal pp. 50-51. */ termj[1] = -mu_planet[5]*(ecliptic_r[1] - ecliptic_sun[1])*(1.5*jupiter_J2*Math.pow(planet_radius_5,2.0)*(1.0 - 5.0*Math.pow((ecliptic_r[3] - ecliptic_sun[3]),2.0)/Math.pow(dist[5],2.0))/Math.pow(dist[5],2.0))/Math.pow(dist[5],3.0); termj[2] = -mu_planet[5]*(ecliptic_r[2] - ecliptic_sun[2])*(1.5*jupiter_J2*Math.pow(planet_radius_5,2.0)*(1.0 - 5.0*Math.pow((ecliptic_r[3] - ecliptic_sun[3]),2.0)/Math.pow(dist[5],2.0))/Math.pow(dist[5],2.0))/Math.pow(dist[5],3.0); termj[3] = -mu_planet[5]*(ecliptic_r[3] - ecliptic_sun[3])*(1.5*jupiter_J2*Math.pow(planet_radius_5,2.0)*(3.0 - 5.0*Math.pow((ecliptic_r[3] - ecliptic_sun[3]),2.0)/Math.pow(dist[5],2.0))/Math.pow(dist[5],2.0))/Math.pow(dist[5],3.0); /* Rotate the J2 effects into the equatorial frame, and add to the acceleration vector */ ecliptic_to_equatorial(termj); for (k=1;k<=3;k++) { acceleration[k] = acceleration[k] + termj[k]; } /* Begin estimation of solar raditation pressure */ /* Calculate mean solar flux at 1 AU (in Watts per sq m) */ S = 1350.0 + 650.0*Math.sin(2.0*Math.PI*(jultime - 2450358.0)/4017.75); /* Calculate the exposed surface are in sq m */ A = 2.0*Math.PI*Math.pow((minor_planet_radius*au*1000.0),2.0); /* Find the albedo */ if (comet_identifier) { /* Use best guess from Rosetta and Deep Space 1 */ albedo = 0.04; } else { if (slope_parameter_G == 0.0) /* Calculate albedo assuming slope_parameter_G of 0.15 */ albedo = 0.108; else albedo = 0.048 + (slope_parameter_G - 0.09); if (albedo < 0.029) albedo = 0.029; if (albedo > 0.3) albedo = 0.3; } Cr = 1.0 + albedo; /* Find the mass */ if (comet_identifier) { /* Assume C-class density */ density = 1.34; } else { /* C-class density = 1.34 grams per cubic centimeter */ /* S-class density = 2.7 grams per cubic centimeter */ /* M-class density = 5.3 grams per cubic centimeter */ /* Assume density decreases linearly from M class to S class */ if (albedo < 0.08) density = 1.34; else if (albedo > 0.08) density = 5.3 - 2.6*(albedo - 0.08)/0.22; } /* Convert density to MKS units */ density = density*1000.0; mass = density*(4.0/3.0)*Math.PI*Math.pow((minor_planet_radius*au*1000.0),3.0); for (k=1;k<=3;k++) { normal[k] = (r[k] - planet_r[0][k])/dist[0]; } for (k=1;k<=3;k++) { /* Calculate the Solar Radiation Pressure in meters/sec^2. Reference: "On the Observability of radiation forces acting on near-Earth asteroids", Vokrouhlicky, Chesley, and Milani, 2000; also, the NASA Goddard Trajectory Determination System user's manual, pp. 4-64 through 4-66. */ if (minor_planet_radius != 0.0) { solar_pressure[k] = (r[k] - planet_r[0][k])*(S/299792458.0)*(Cr*A/mass)/Math.pow(dist[0],3.0); } else { solar_pressure[k] = 0.0; } /* Convert the Solar Radiation Pressure to au/day^2 */ solar_pressure[k] = solar_pressure[k]*86400.0*86400.0/(au*1000.0); /* Add the Solar Radiation Pressure to the acceleration */ acceleration[k] = acceleration[k] + solar_pressure[k]; /* Account for the relativistic Poynting-Robertson effect */ if (minor_planet_radius != 0.0) { solar_pressure[k] = -(S/299792458.0)*(Cr*A/mass)*(rprime[k] + normal[k]*(rprime[1]*normal[1] + rprime[2]*normal[2] + rprime[3]*normal[3]))/(speed_of_light*Math.pow(dist[0],2.0)); } else { solar_pressure[k] = 0.0; } /* Convert the P-R Pressure to au/day^2 */ solar_pressure[k] = solar_pressure[k]*86400.0*86400.0/(au*1000.0); /* Add the P-R Pressure to the acceleration */ acceleration[k] = acceleration[k] + solar_pressure[k]; } /* Now add the perturbing acceleration on a comet due to reactive water vaporization */ if ((A1_A2_DT[1] != 0.0) || (A1_A2_DT[2] != 0.0) || (A1_A2_DT[3] != 0.0)) { /* Create heliocentric pos/vel vectors */ get_planet_posvel(jultime,11,sun_r,sun_rprime); for (j=1;j<=3;j++) { heliocentric_r[j] = r[j] - sun_r[j]; heliocentric_rprime[j] = rprime[j] - sun_rprime[j]; } /* calculate intermediate quantities */ angular_velocity = Math.sqrt(Math.pow((heliocentric_r[2]*heliocentric_rprime[3] - heliocentric_r[3]*heliocentric_rprime[2]),2) + Math.pow((heliocentric_r[1]*heliocentric_rprime[3] - heliocentric_r[3]*heliocentric_rprime[1]),2) + Math.pow((heliocentric_r[1]*heliocentric_rprime[2] - heliocentric_r[2]*heliocentric_rprime[1]),2)); r_mag = Math.sqrt(Math.pow(heliocentric_r[1],2) + Math.pow(heliocentric_r[2],2) + Math.pow(heliocentric_r[3],2)); /* Modify heliocentric velocity for use in two-body mechanics */ for (j=1;j<=3;j++) { heliocentric_rprime[j] = heliocentric_rprime[j]/kappa; } /* Find rtemp_mag at jultime - DT */ two_body_update(heliocentric_r,heliocentric_rprime,jultime,rtemp,rprimetemp,jultime-A1_A2_DT[3]); rtemp_mag = Math.sqrt(Math.pow(rtemp[1],2) + Math.pow(rtemp[2],2) + Math.pow(rtemp[3],2)); /* Restore heliocentric velocity for use in n-body mechanics */ for (j=1;j<=3;j++) { heliocentric_rprime[j] = heliocentric_rprime[j]*kappa; } /* Find the relative strength of the water vaporization curve at jultime-DT, accounting for possible assymmetry in outgassing with respect to perihelion */ g = 0.111262*Math.pow((rtemp_mag/2.808),(-2.15))*Math.pow((1 + Math.pow((rtemp_mag/2.808),5.093)),-4.6142); /* Calculate the radial and transverse components of the acceleration */ for (j=1;j<=3;j++) acceleration[j] = acceleration[j] + A1_A2_DT[1]*g*heliocentric_r[j]/r_mag + A1_A2_DT[2]*g*(r_mag*heliocentric_rprime[j] - velocity*heliocentric_r[j])/angular_velocity; } } public double conditioned_gauss(double recipa,double Lx[],double Ly[],double Lz[],double x[],double y[],double z[],double time1,double time2,double time3,double r1[],double r1prime[]) { /* Procedure to calculate the orbit of a heliocentric object, which constrains the orbit using the reciprocal of the user-specified semi-major axis (recipa, where a is in A.U.s). This method is useful for objects with an elongation of less than 120 degrees, or ones for which the Gauss method gives poor results. It would be especially effective for comets, where recipa is near zero. The error flag "kgaussfail" is returned in r1[0]. Note that kgaussfail = 1 or 2 means that procedure sector has failed. Note that kgaussfail = 3 means that the iterative determination of rho(1] has diverged. Note that kgaussfail = 4 means that the entire iterative loop has failed to converge. Reference is P. Herget, "The Computation of Orbits", pp. 66-68. */ double tau1 = 0, tau3 = 0, tau13 = 0, c1 = 0, c3 = 0, diff = 1, m = 0, bigc = 0, bigb = 0; double biga = 0, a = 0, b = 0, c = 0, littlea = 0, littleb = 0, littlec = 0, deltarho = 0; double s = 0, r1_mag = 0, r3_mag = 0, x_mag = 0, qc = 0, qd = 0, olddelta = 0, delta = 0; double lighttime1 = 0, lighttime2 = 0, lighttime3 = 0, y12 = 0, f1 = 0, g1 = 0, y13 = 0; double f2 = 0, g2 = 0, y23 = 0, f3 = 0, g3 = 0, jdepoch = 0, initial_f1 = 0, initial_g1 = 0; int kdivergence = 0, kiter = 0, k = 0, kgaussfail = 0; double[] site1 = new double[4]; double[] site2 = new double[4]; double[] site3 = new double[4]; double[] p1star = new double[4]; double[] p2star = new double[4]; double[] p3star = new double[4]; double[] rho = new double[4]; double[] oldrho = new double[4]; double[] realoldrho = new double[4]; double[] realrealoldrho = new double[4]; double[] v = new double[4]; double[] p2starxv = new double[4]; double[] r2 = new double[4]; double[] r3 = new double[4]; double[] y_f_g = new double[4]; double[] initial_r1 = new double[4]; double[] initial_r2 = new double[4]; /* Initialize all variables with notation that matches Herget */ tau1 = kappa*(time1 - time2); tau3 = kappa*(time3 - time2); tau13 = tau3 - tau1; c1 = tau3/tau13; c3 = -tau1/tau13; site1[1] = x[1]; site1[2] = y[1]; site1[3] = z[1]; site2[1] = x[2]; site2[2] = y[2]; site2[3] = z[2]; site3[1] = x[3]; site3[2] = y[3]; site3[3] = z[3]; p1star[1] = Lx[1]; p1star[2] = Ly[1]; p1star[3] = Lz[1]; p2star[1] = Lx[2]; p2star[2] = Ly[2]; p2star[3] = Lz[2]; p3star[1] = Lx[3]; p3star[2] = Ly[3]; p3star[3] = Lz[3]; /* Initialize slant ranges before beginning iterative loop to solve for them subject to recipa */ rho[1] = 0; rho[2] = 0; rho[3] = 0; /* Label "out" used to escape from loop if necessary */ out: while ((diff > 1e-08) && (kgaussfail == 0)) { for (k=1;k<=3;k++) { if (kiter > 2) { realrealoldrho[k] = realoldrho[k]; realoldrho[k] = oldrho[k]; } else if (kiter == 2) { realoldrho[k] = oldrho[k]; } oldrho[k] = rho[k]; v[k] = c1*site1[k] - site2[k] + c3*site3[k]; } p2starxv[1] = p2star[2]*v[3] - p2star[3]*v[2]; p2starxv[2] = -(p2star[1]*v[3] - p2star[3]*v[1]); p2starxv[3] = p2star[1]*v[2] - p2star[2]*v[1]; m = -(c1/c3)*(p1star[1]*p2starxv[1] + p1star[2]*p2starxv[2] + p1star[3]*p2starxv[3])/(p3star[1]*p2starxv[1] + p3star[2]*p2starxv[2] + p3star[3]*p2starxv[3]); bigc = Math.pow((m*p3star[1] - p1star[1]),2) + Math.pow((m*p3star[2] - p1star[2]),2) +Math.pow((m*p3star[3] - p1star[3]),2); bigb = -2*((m*p3star[1] - p1star[1])*(site3[1] - site1[1]) + (m*p3star[2] - p1star[2])*(site3[2] - site1[2]) + (m*p3star[3] - p1star[3])*(site3[3] - site1[3])); biga = Math.pow((site3[1] - site1[1]),2) + Math.pow((site3[2] - site1[2]),2) + Math.pow((site3[3] - site1[3]),2); a = Math.pow(site1[1],2) + Math.pow(site1[2],2) + Math.pow(site1[3],2); b = -2*(site1[1]*p1star[1] + site1[2]*p1star[2] + site1[3]*p1star[3]); c = 1; littlea = Math.pow(site3[1],2) + Math.pow(site3[2],2) + Math.pow(site3[3],2); littleb = -2*m*(site3[1]*p3star[1] + site3[2]*p3star[2] + site3[3]*p3star[3]); littlec = Math.pow(m,2); /* Evaluate delta at rho[1] = 0 before beginning iterative loop, then increment rho[1] by deltarho until a sign change occurs in delta. Use bisection to precisely locate this zero of delta, and stop when deltarho < 1D-08. */ deltarho = 1; rho[1] = 0; s = Math.sqrt(biga); r1_mag = Math.sqrt(a); r3_mag = Math.sqrt(littlea); x_mag = recipa*(r1_mag + r3_mag + s)/4; qc = 1.0 + (3.0/10.0)*x_mag + (9.0/56.0)*Math.pow(x_mag,2) + (5.0/48.0)*Math.pow(x_mag,3) + (105.0/1408.0)*Math.pow(x_mag,4) + (2835.0/49920.0)*Math.pow(x_mag,5); x_mag = recipa*(r1_mag + r3_mag - s)/4; qd = 1.0 + (3.0/10.0)*x_mag + (9.0/56.0)*Math.pow(x_mag,2) + (5.0/48.0)*Math.pow(x_mag,3) + (105.0/1408.0)*Math.pow(x_mag,4) + (2835.0/49920.0)*Math.pow(x_mag,5); olddelta = 6*tau13 - qc*Math.pow((r1_mag + r3_mag + s),1.5) + qd*Math.pow(( r1_mag + r3_mag - s),1.5); rho[1] = rho[1] + deltarho; while ((Math.abs(deltarho) > 1e-08) && (kgaussfail == 0)) { s = Math.sqrt(biga + bigb*rho[1] + bigc*Math.pow(rho[1],2)); r1_mag = Math.sqrt(a + b*rho[1] + c*Math.pow(rho[1],2)); r3_mag = Math.sqrt(littlea + littleb*rho[1] + littlec*Math.pow(rho[1],2)); x_mag = recipa*(r1_mag + r3_mag + s)/4; qc = 1.0 + (3.0/10.0)*x_mag + (9.0/56.0)*Math.pow(x_mag,2) + (5.0/48.0)*Math.pow(x_mag,3) + (105.0/1408.0)*Math.pow(x_mag,4) + (2835.0/49920.0)*Math.pow(x_mag,5); x_mag = recipa*(r1_mag + r3_mag - s)/4; qd = 1.0 + (3.0/10.0)*x_mag + (9.0/56.0)*Math.pow(x_mag,2) + (5.0/48.0)*Math.pow(x_mag,3) + (105.0/1408.0)*Math.pow(x_mag,4) + (2835.0/49920.0)*Math.pow(x_mag,5); delta = 6*tau13 - qc*Math.pow((r1_mag + r3_mag + s),1.5) + qd*Math.pow(( r1_mag + r3_mag - s),1.5); if ((olddelta)/Math.abs(olddelta) != (delta)/Math.abs(delta)) deltarho = -deltarho/2; rho[1] = rho[1] + deltarho; olddelta = delta; if (rho[1] > 1000) kgaussfail = 3; } if (kgaussfail != 0) break out; /* Use new value of rho[1] to solve for the other rho's, correct times for light time-of-flight, and calculate new sector-triangle ratios. Using these, repeat loop until rho's are consistent to 1D-08. */ rho[3] = m*rho[1]; rho[2] = -(v[1] - c1*rho[1]*p1star[1] - c3*rho[3]*p3star[1])/p2star[1]; for (k=1;k<=3;k++) { if (rho[k] < 0) rho[k] = (-1)*rho[k]; } if (kiter > 3) { for (k=1;k<=3;k++) { if (Math.abs(rho[k] - oldrho[k]) >= Math.abs(oldrho[k] - realoldrho[k])) kdivergence = 1; } if ((kiter >= 50) || (kdivergence == 1)) { for (k=1;k<=3;k++) { rho[k] = (.25)*(rho[k] + oldrho[k] + realoldrho[k] + realrealoldrho[k]); } } } lighttime1 = time1 - rho[1]/speed_of_light; lighttime2 = time2 - rho[2]/speed_of_light; lighttime3 = time3 - rho[3]/speed_of_light; tau1 = kappa*(lighttime1 - lighttime2); tau3 = kappa*(lighttime3 - lighttime2); tau13 = tau3 - tau1; for (k=1;k<=3;k++) { r1[k] = rho[1]*p1star[k] - site1[k]; if (kiter == 1) initial_r1[k] = r1[k]; r2[k] = rho[2]*p2star[k] - site2[k]; if (kiter == 1) initial_r2[k] = r2[k]; r3[k] = rho[3]*p3star[k] - site3[k]; } kgaussfail = sector(r1,r2,lighttime1,lighttime2,y_f_g); y12 = y_f_g[1]; f1 = y_f_g[2]; g1 = y_f_g[3]; if (kgaussfail != 0) break out; if (kiter == 1) { /* Save initial values of f1 and g1, but only if they could be computed */ initial_f1 = f1; initial_g1 = g1; } kgaussfail = sector(r1,r3,lighttime1,lighttime3,y_f_g); y13 = y_f_g[1]; f2 = y_f_g[2]; g2 = y_f_g[3]; if (kgaussfail != 0) break out; kgaussfail = sector(r2,r3,lighttime2,lighttime3,y_f_g); y23 = y_f_g[1]; f3 = y_f_g[2]; g3 = y_f_g[3]; if (kgaussfail != 0) break out; c1 = (y13*tau3)/(y23*tau13); c3 = -(y13*tau1)/(y12*tau13); diff = Math.abs(rho[1] - oldrho[1]); if (Math.abs(rho[2] - oldrho[2]) > diff) diff = Math.abs(rho[2] - oldrho[2]); if (Math.abs(rho[3] - oldrho[3]) > diff) diff = Math.abs(rho[3] - oldrho[3]); kiter = kiter + 1; if (kiter == 200) kgaussfail = 4; } if (kgaussfail != 0) { if (initial_g1 != 0.0) { /* Return initial approximation */ for (k=1;k<=3;k++) { r1[k] = initial_r1[k]; r1prime[k] = (initial_r2[k] - initial_f1*initial_r1[k])/initial_g1; } } else { /* Return zero solution */ for (k=1;k<=3;k++) { r1[k] = 0.0; r1prime[k] = 0.0; } } } else { /* Nominal completion - return full solution */ for (k=1;k<=3;k++) { r1prime[k] = (r2[k] - f1*r1[k])/g1; } } jdepoch = lighttime1; r1[0] = kgaussfail; return jdepoch; } public double gauss_method(double Lx[],double Ly[],double Lz[],double x[],double y[],double z[],double time1,double time2,double time3,double r1[],double r1prime[]) { /* Procedure to use the Gauss method to determine a preliminary orbit using three sets of optical observations. Algorithm uses sector-to-triangle theory for refinement of the orbit. Algorithm taken from Pedro Escobal, "Methods of Orbit Determination", pp. 245-261, and Brian G. Marsden, "Initial Orbit Determination: The Pragmatist's Point of View", The Astronomical Journal, Vol. 90, no. 8, Aug '85, pp. 1541-1547. Note: Station coordinates (x, y, and z) should be in heliocentric units, and times in Julian days. The state vector r1 (and r1prime) are given in heliocentric units (per canonical time unit). The "kgaussfail" flag is an integer. The Julian day of the epoch to which the elements refer is "jdepoch". Note: Station coordinates are inward pointing; that is, vectors x, y, and z point from the observer to the barycenter of the solar system. Note: In order to prevent divergence, the values of rho (slant range) are monitored. If negative rhos occur, their sign is changed. If convergence has not occurred by the 50th iteration, or if the rhos are diverging, the new value of rhos is taken to be the average of the indicated value with the three previous values. This technique is due to Marsden. If convergence has not occurred by the 200th iteration, calculation is halted, the "kgaussfail" flag is set to 3, and the initial approximation is returned. The kgaussfail flag (returned as r1[0]) can have the following meanings: kgaussfail = 1 or 2 means a problem has been encountered in method sector, and the method has failed. kgaussfail = 3 means over 200 iterations have occurred without convergence; the initial approximation will be returned. Tested and verified 20 June 1999. */ double[] rho = new double[4]; double[] r2 = new double[4]; double[] r3 = new double[4]; double[] oldrho = new double[4]; double[] realoldrho = new double[4]; double[] realrealoldrho = new double[4]; double[] initial_r1 = new double[4]; double[] initial_r2 = new double[4]; double[] y_f_g = new double[4]; double tau1 = 0, tau3 = 0, tau13 = 0, c1 = 0, c3 = 0, d = 0, a11 = 0, a12 = 0, a13 = 0, a21 = 0, a22 = 0, a23 = 0, a31 = 0, a32 = 0, a33 = 0; double gx = 0, gy = 0, gz = 0, diff = 1, lighttime1 = 0, lighttime2 = 0, lighttime3 = 0, y12 = 0, f1 = 0, g1 = 0, initial_f1 = 0, initial_g1 = 0; double y13 = 0, f2 = 0, g2 = 0, y23 = 0, f3 = 0, g3 = 0, jdepoch = 0; int k = 0, kiter = 1, kdivergence = 0, kgaussfail = 0; tau1 = kappa*(time1 - time2); tau3 = kappa*(time3 - time2); tau13 = tau3 - tau1; c1 = tau3/tau13; c3 = -tau1/tau13; d = Lx[1]*(Ly[2]*Lz[3] - Ly[3]*Lz[2]) - Lx[2]*(Ly[1]*Lz[3] - Ly[3]*Lz[1]) + Lx[3]*(Ly[1]*Lz[2] - Ly[2]*Lz[1]); a11 = (Ly[2]*Lz[3] - Ly[3]*Lz[2])/d; a12 = -(Lx[2]*Lz[3] - Lx[3]*Lz[2])/d; a13 = (Lx[2]*Ly[3] - Lx[3]*Ly[2])/d; a21 = -(Ly[1]*Lz[3] - Ly[3]*Lz[1])/d; a22 = (Lx[1]*Lz[3] - Lx[3]*Lz[1])/d; a23 = -(Lx[1]*Ly[3] - Lx[3]*Ly[1])/d; a31 = (Ly[1]*Lz[2] - Ly[2]*Lz[1])/d; a32 = -(Lx[1]*Lz[2] - Lx[2]*Lz[1])/d; a33 = (Lx[1]*Ly[2] - Lx[2]*Ly[1])/d; gx = c1*x[1] - x[2] + c3*x[3]; gy = c1*y[1] - y[2] + c3*y[3]; gz = c1*z[1] - z[2] + c3*z[3]; rho[1] = (a11*gx + a12*gy + a13*gz)/c1; rho[2] = -(a21*gx + a22*gy + a23*gz); rho[3] = (a31*gx + a32*gy + a33*gz)/c3; for (k=1;k<=3;k++) { if (rho[k] < 0) rho[k] = (-1)*rho[k]; } r1[1] = rho[1]*Lx[1] - x[1]; r1[2] = rho[1]*Ly[1] - y[1]; r1[3] = rho[1]*Lz[1] - z[1]; r2[1] = rho[2]*Lx[2] - x[2]; r2[2] = rho[2]*Ly[2] - y[2]; r2[3] = rho[2]*Lz[2] - z[2]; r3[1] = rho[3]*Lx[3] - x[3]; r3[2] = rho[3]*Ly[3] - y[3]; r3[3] = rho[3]*Lz[3] - z[3]; for (k=1;k<=3;k++) { initial_r1[k] = r1[k]; initial_r2[k] = r2[k]; } /* Enter into sector-to-triangle loop to get rprime */ /* Label "out" allows us to escape loop if kgaussfail flag set */ out: while ((2*diff >= 1e-08) && (kgaussfail == 0)) { for (k=1;k<=3;k++) { if (kiter > 2) { realrealoldrho[k] = realoldrho[k]; realoldrho[k] = oldrho[k]; } else if (kiter == 2) realoldrho[k] = oldrho[k]; oldrho[k] = rho[k]; } lighttime1 = time1 - rho[1]/speed_of_light; lighttime2 = time2 - rho[2]/speed_of_light; lighttime3 = time3 - rho[3]/speed_of_light; tau1 = kappa*(lighttime1 - lighttime2); tau3 = kappa*(lighttime3 - lighttime2); tau13 = tau3 - tau1; kgaussfail = sector(r1,r2,lighttime1,lighttime2,y_f_g); y12 = y_f_g[1]; f1 = y_f_g[2]; g1 = y_f_g[3]; if (kgaussfail != 0) break out; if (kiter == 1) { /* Save initial values of f1 and g1, but only if they could be computed */ initial_f1 = f1; initial_g1 = g1; } kgaussfail = sector(r1,r3,lighttime1,lighttime3,y_f_g); y13 = y_f_g[1]; f2 = y_f_g[2]; g2 = y_f_g[3]; if (kgaussfail != 0) break out; kgaussfail = sector(r2,r3,lighttime2,lighttime3,y_f_g); y23 = y_f_g[1]; f3 = y_f_g[2]; g3 = y_f_g[3]; if (kgaussfail != 0) break out; c1 = (y13*tau3)/(y23*tau13); c3 = -(y13*tau1)/(y12*tau13); gx = c1*x[1] - x[2] + c3*x[3]; gy = c1*y[1] - y[2] + c3*y[3]; gz = c1*z[1] - z[2] + c3*z[3]; rho[1] = (a11*gx + a12*gy + a13*gz)/c1; rho[2] = -(a21*gx + a22*gy + a23*gz); rho[3] = (a31*gx + a32*gy + a33*gz)/c3; for (k=1;k<=3;k++) { if (rho[k] < 0) rho[k] = (-1)*rho[k]; } diff = Math.abs(rho[1] - oldrho[1]); if (Math.abs(rho[2] - oldrho[2]) > diff) diff = Math.abs(rho[2] - oldrho[2]); if (Math.abs(rho[3] - oldrho[3]) > diff) diff = Math.abs(rho[3] - oldrho[3]); if (kiter > 3) { for (k=1;k<=3;k++) { if (Math.abs(rho[k] - oldrho[k]) >= Math.abs(oldrho[k] - realoldrho[k])) kdivergence = 1; } if ((kiter >= 50) || (kdivergence == 1)) { for (k=1;k<=3;k++) { rho[k] = (.25)*(rho[k] + oldrho[k] + realoldrho[k] + realrealoldrho[k]); } } } r1[1] = rho[1]*Lx[1] - x[1]; r1[2] = rho[1]*Ly[1] - y[1]; r1[3] = rho[1]*Lz[1] - z[1]; r2[1] = rho[2]*Lx[2] - x[2]; r2[2] = rho[2]*Ly[2] - y[2]; r2[3] = rho[2]*Lz[2] - z[2]; r3[1] = rho[3]*Lx[3] - x[3]; r3[2] = rho[3]*Ly[3] - y[3]; r3[3] = rho[3]*Lz[3] - z[3]; kiter = kiter + 1; if (kiter == 200) kgaussfail = 3; } if (kgaussfail != 0) { /* The method has failed */ if (initial_g1 != 0.0) { /* Return initial approximation, if possible */ for (k=1;k<=3;k++) { r1[k] = initial_r1[k]; r1prime[k] = (initial_r2[k] - initial_f1*initial_r1[k])/initial_g1; } } else { /* Return a zero solution */ for (k=1;k<=3;k++) { r1[k] = 0.0; r1prime[k] = 0.0; } } } else { /* Nominal completion - return full solution */ for (k=1;k<=3;k++) { r1prime[k] = (r2[k] - f1*r1[k])/g1; } } jdepoch = lighttime1; r1[0] = kgaussfail; return jdepoch; } int sector(double r1[],double r2[],double time1,double time2,double y_f_g[]) { /* Program to find the ratio of sector to triangle for the given two radius vectors and times, as well as the closed form f and g coefficients. Note: r1 and r2 should be in A.U.s, while the times should be in days. Algorithm taken from Pedro Ramon Escobal, "Methods of Orbit Determination", pp. 196-7, with universal modifications from Bate, Mueller, and White pp. 263-4. Note: The outputs y, f, and g are packed into an array to ensure return to the calling routine. Note: The kgaussfail indicator is returned by this method. Possible values are: 0 = nominal 1 = non-convergence, probably due to widely spaced observations 2 = observations too widely spaced Tested and verified 17 June 1999. */ double tau = 0, radius1 = 0, radius2 = 0, nu2nu1cos = 0, nu2nu1sin = 0, nu2nu1 = 0, l = 0, m = 0, oldy = 0, littlex = 0, coef = 0, bigx = 0; double e2e1cos = 0, e2e1sin = 0, e2e1 = 0, f2f1cosh = 0, f2f1 = 0, f2f12sinh = 0, f2f1sinh = 0, deltaanomaly = 0, p = 0; double y = 0, f = 0, g = 0; int k = 0, counter = 0, kgaussfail = 0; tau = kappa*(time2 - time1); radius1 = Math.sqrt(Math.pow(r1[1],2) + Math.pow(r1[2],2) + Math.pow(r1[3],2)); radius2 = Math.sqrt(Math.pow(r2[1],2) + Math.pow(r2[2],2) + Math.pow(r2[3],2)); /* Note: nu2nu1cos is the cosine of (nu2 – nu1) */ nu2nu1cos = (r1[1]*r2[1] + r1[2]*r2[2] + r1[3]*r2[3])/(radius1*radius2); /* Note: nu2nu1sin is the sine of (nu2 – nu1) Note: I am assuming here that the observations are not widely separated; if the change in nu is less than 180 degrees, then nu2nu1sin should be positive, and this choice is valid for both direct and retrograde orbits. If the full expression as given in Escobal were used, widely-spaced observations could be handled, but only for direct orbits. My choice seems more reasonable; attempting initial orbit determination with widely-spaced observations is unlikely. */ nu2nu1sin = Math.sqrt(1 - Math.pow(nu2nu1cos,2)); /* Note: nu2nu1 = (nu2 – nu1) */ nu2nu1 = Math.atan2(nu2nu1sin,nu2nu1cos); l = -0.5 + (radius1 + radius2)/(4*Math.sqrt(radius1*radius2)*Math.cos(nu2nu1/2)); m = (mu*Math.pow(tau,2))/Math.pow((2*Math.sqrt(radius1*radius2)*Math.cos(nu2nu1/2)),3); /* Begin iterative loop to find y */ y = 1; oldy = 0; /* Label "out" allows us to terminate these loops if necessary */ out: while ((Math.abs(y - oldy) >= 1e-09) && (kgaussfail == 0)) { oldy = y; littlex = m/Math.pow(y,2) - l; if (littlex > 1) { kgaussfail = 2; break out; } if (Math.abs(littlex) < 0.05) { /* Possible near-parabolic case. Expansion valid for ABS(littlex) < 0.05 */ coef = 1.2; bigx = 1 + coef*littlex; for (k=1;k<=10;k++) { coef = coef*(6.0 + 2.0*k)/(5.0 + 2.0*k); bigx = bigx + coef*Math.pow(littlex,(k+1)); } bigx = bigx*4.0/3.0; } else if (littlex > 0) { /* Large angle elliptic case */ /* Note: e2e1cos is the cosine of (E2 – E1)/2 */ e2e1cos = 1 - 2*littlex; /* Note: e2e1sin is the sine of (E2 – E1)/2 */ e2e1sin = Math.sqrt(4*littlex*(1 - littlex)); /* Note: e2e1 = E2 – E1 */ e2e1 = Math.atan2(e2e1sin,e2e1cos); e2e1 = 2*e2e1; bigx = (e2e1 - Math.sin(e2e1))/Math.pow(e2e1sin,3); } else { /* large angle hyperbolic case */ /* f2f1cosh is the cosh of (F2 – F1)/2 */ f2f1cosh = 1 - 2*littlex; /* f2f1 = (F2 – F1)/2 */ f2f1 = Math.log(f2f1cosh + Math.sqrt(Math.pow(f2f1cosh,2) - 1)); /* f2f12sinh is the sinh of (F2 – F1)/2 */ f2f12sinh = (Math.exp(f2f1) - Math.exp(-f2f1))/2; /* f2f1 = F2 – F1 */ f2f1 = 2*f2f1; /* f2f1sinh is the sinh of F2 – F1 */ f2f1sinh = (Math.exp(f2f1) - Math.exp(-f2f1))/2; bigx = (f2f1sinh - f2f1)/Math.pow(f2f12sinh,3); } y = 1 + bigx*(l + littlex); counter = counter + 1; if (counter > 100) kgaussfail = 1; } if (kgaussfail == 0) { /* deltaanomaly is COS((E2 – E1)/2) or COSH((F2 – F1)/2) */ deltaanomaly = 1 - 2*littlex; p = radius1*radius2*(1 - nu2nu1cos)/(radius1 + radius2 - 2*Math.sqrt(radius1*radius2)*Math.cos(nu2nu1/2)*deltaanomaly); f = 1 - radius2*(1 - nu2nu1cos)/p; g = radius1*radius2*nu2nu1sin/Math.sqrt(mu*p); y_f_g[1] = y; y_f_g[2] = f; y_f_g[3] = g; } return kgaussfail; } public void differential_correction(double r[],double rprime[],double jdepoch,double Lx[],double Ly[],double Lz[],double x[],double y[],double z[],double time1,double time2,double time3) { /* Refines the state vector calculated by an initial orbit determination method, using differential correction. In the event divergence is detected (by virtue of impossibly large values for r[i], or increasing rms residuals), the r[0] component will be set to 5, and the input solution will be returned. Tested and verified 15 June 1999. */ double[] time = new double[4]; double[] r_variant = new double[4]; double[] rprime_variant = new double[4]; double[] r_input = new double[4]; double[] rprime_input = new double[4]; double[] ra = new double[4]; double[] dec = new double[4]; double[] rho_vector = new double[4]; double[] ra_variant = new double[4]; double[] dec_variant = new double[4]; double[] delta_radec = new double[7]; double[][] pd_matrix = new double[7][7]; double[] delta_r = new double[4]; double[] delta_rprime = new double[4]; double[] lat = new double[4]; double[] longit = new double[4]; double[] latlong = new double[3]; double[] radec = new double[3]; double rho = 0, r_dev = 0, rprime_dev = 0, ra_dev = 0, dec_dev = 0, light_time = 0, residual = 0, old_residual = 0, diff = 0; int i = 0, j = 0, loop_counter = 0; /* Initialize the time array */ time[1] = time1; time[2] = time2; time[3] = time3; /* First, save the input state vector, and calculate the errors in observed vs. computed radecs */ for (i=1; i<=3; i++) { r_input[i] = r[i]; rprime_input[i] = rprime[i]; lat[i] = Math.asin(Lz[i]); longit[i] = Math.atan2(Ly[i],Lx[i]); latlong[1] = lat[i]; latlong[2] = longit[i]; ecliptic_to_radec(latlong,radec); ra[i] = radec[1]; dec[i] = radec[2]; /* Correct for light time-of-flight */ two_body_update(r,rprime,jdepoch,r_variant,rprime_variant,time[i]); light_time = time[i] - Math.sqrt(Math.pow((r_variant[1] + x[i]),2) + Math.pow((r_variant[2] + y[i]),2) + Math.pow((r_variant[3] + z[i]),2))/speed_of_light; two_body_update(r,rprime,jdepoch,r_variant,rprime_variant,light_time); rho_vector[1] = r_variant[1] + x[i]; rho_vector[2] = r_variant[2] + y[i]; rho_vector[3] = r_variant[3] + z[i]; rho = Math.sqrt(Math.pow(rho_vector[1],2) + Math.pow(rho_vector[2],2) + Math.pow(rho_vector[3],2)); lat[i] = Math.asin(rho_vector[3]/rho); longit[i] = Math.atan2(rho_vector[2],rho_vector[1]); latlong[1] = lat[i]; latlong[2] = longit[i]; ecliptic_to_radec(latlong,radec); ra_variant[i] = radec[1]; dec_variant[i] = radec[2]; delta_radec[2*i-1] = ra[i] - ra_variant[i]; while (Math.abs(delta_radec[2*i-1]) > Math.abs(delta_radec[2*i-1] - 2.0*Math.PI)) delta_radec[2*i-1] = delta_radec[2*i-1] - 2.0*Math.PI; while (Math.abs(delta_radec[2*i-1]) > Math.abs(delta_radec[2*i-1] + 2.0*Math.PI)) delta_radec[2*i-1] = delta_radec[2*i-1] + 2.0*Math.PI; delta_radec[2*i] = dec[i] - dec_variant[i]; residual = residual + Math.pow(delta_radec[2*i-1],2) + Math.pow(delta_radec[2*i],2); } residual = Math.sqrt(residual); /* Repeat the differential correction process until successive RMS residuals differ by less than one percent, or until the RMS residuals are sufficiently small. Include a counter so we exit the loop after 10 iterations. End the loop in case of divergence. */ while ((residual > 5e-07) && (Math.abs(residual - old_residual) > 0.10*residual) && (loop_counter < 10) && (r[0] != 5.0)) { System.out.println("residual = " + residual); old_residual = residual; loop_counter = loop_counter + 1; /* Compute elements of the differential correction matrix */ for (i=1; i<=3; i++) { for (j=1;j<=3;j++) { r_dev = 0.00001*r[j]; rprime_dev = 0.000001*rprime[j]; r[j] = r[j] + r_dev; /* Correct for light time-of-flight */ two_body_update(r, rprime, jdepoch, r_variant, rprime_variant, time[i]); light_time = time[i] - Math.sqrt(Math.pow((r_variant[1] + x[i]),2) + Math.pow((r_variant[2] + y[i]),2) + Math.pow((r_variant[3] + z[i]),2))/speed_of_light; two_body_update(r, rprime, jdepoch, r_variant, rprime_variant, light_time); rho_vector[1] = r_variant[1] + x[i]; rho_vector[2] = r_variant[2] + y[i]; rho_vector[3] = r_variant[3] + z[i]; rho = Math.sqrt(Math.pow(rho_vector[1],2) + Math.pow(rho_vector[2],2) + Math.pow(rho_vector[3],2)); lat[i] = Math.asin(rho_vector[3]/rho); longit[i] = Math.atan2(rho_vector[2],rho_vector[1]); latlong[1] = lat[i]; latlong[2] = longit[i]; ecliptic_to_radec(latlong,radec); ra_dev = radec[1]; dec_dev = radec[2]; diff = ra_dev - ra_variant[i]; while (Math.abs(diff) > Math.abs(diff - 2.0*Math.PI)) diff = diff - 2.0*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2.0*Math.PI)) diff = diff + 2.0*Math.PI; pd_matrix[2*i-1][j] = diff/r_dev; pd_matrix[2*i][j] = (dec_dev - dec_variant[i])/r_dev; r[j] = r[j] - r_dev; rprime[j] = rprime[j] + rprime_dev; /* Correct for light time-of-flight */ two_body_update(r, rprime, jdepoch, r_variant, rprime_variant, time[i]); light_time = time[i] - Math.sqrt(Math.pow((r_variant[1] + x[i]),2) + Math.pow((r_variant[2] + y[i]),2) + Math.pow((r_variant[3] + z[i]),2))/speed_of_light; two_body_update(r, rprime, jdepoch, r_variant, rprime_variant, light_time); rho_vector[1] = r_variant[1] + x[i]; rho_vector[2] = r_variant[2] + y[i]; rho_vector[3] = r_variant[3] + z[i]; rho = Math.sqrt(Math.pow(rho_vector[1],2) + Math.pow(rho_vector[2],2) + Math.pow(rho_vector[3],2)); lat[i] = Math.asin(rho_vector[3]/rho); longit[i] = Math.atan2(rho_vector[2],rho_vector[1]); latlong[1] = lat[i]; latlong[2] = longit[i]; ecliptic_to_radec(latlong,radec); ra_dev = radec[1]; dec_dev = radec[2]; diff = ra_dev - ra_variant[i]; while (Math.abs(diff) > Math.abs(diff - 2.0*Math.PI)) diff = diff - 2.0*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2.0*Math.PI)) diff = diff + 2.0*Math.PI; pd_matrix[2*i-1][j+3] = diff/rprime_dev; pd_matrix[2*i][j+3] = (dec_dev - dec_variant[i])/rprime_dev; rprime[j] = rprime[j] - rprime_dev; } } /* Now invert the matrix of partial derivatives, and solve for the improved state vector */ invert_nxn(6,pd_matrix); for (i=1;i<=3;i++) { delta_r[i] = 0; delta_rprime[i] = 0; for (j=1;j<=6;j++) { delta_r[i] = delta_r[i] + pd_matrix[i][j]*delta_radec[j]; delta_rprime[i] = delta_rprime[i] + pd_matrix[i+3][j]*delta_radec[j]; } r[i] = r[i] + delta_r[i]; /* Set flag in case of divergence */ if (Math.abs(r[i]) > 5.84e04) r[0] = 5.0; rprime[i] = rprime[i] + delta_rprime[i]; } /* Now calculate the errors in observed vs. (improved) computed radecs */ residual = 0; for (i=1; i<=3; i++) { /* Correct for light time-of-flight */ two_body_update(r,rprime,jdepoch,r_variant,rprime_variant,time[i]); light_time = time[i] - Math.sqrt(Math.pow((r_variant[1] + x[i]),2) + Math.pow((r_variant[2] + y[i]),2) + Math.pow((r_variant[3] + z[i]),2))/speed_of_light; two_body_update(r,rprime,jdepoch,r_variant,rprime_variant,light_time); rho_vector[1] = r_variant[1] + x[i]; rho_vector[2] = r_variant[2] + y[i]; rho_vector[3] = r_variant[3] + z[i]; rho = Math.sqrt(Math.pow(rho_vector[1],2) + Math.pow(rho_vector[2],2) + Math.pow(rho_vector[3],2)); lat[i] = Math.asin(rho_vector[3]/rho); longit[i] = Math.atan2(rho_vector[2],rho_vector[1]); latlong[1] = lat[i]; latlong[2] = longit[i]; ecliptic_to_radec(latlong,radec); ra_variant[i] = radec[1]; dec_variant[i] = radec[2]; delta_radec[2*i-1] = ra[i] - ra_variant[i]; while (Math.abs(delta_radec[2*i-1]) > Math.abs(delta_radec[2*i-1] - 2.0*Math.PI)) delta_radec[2*i-1] = delta_radec[2*i-1] - 2.0*Math.PI; while (Math.abs(delta_radec[2*i-1]) > Math.abs(delta_radec[2*i-1] + 2.0*Math.PI)) delta_radec[2*i-1] = delta_radec[2*i-1] + 2.0*Math.PI; delta_radec[2*i] = dec[i] - dec_variant[i]; residual = residual + Math.pow(delta_radec[2*i-1],2) + Math.pow(delta_radec[2*i],2); } residual = Math.sqrt(residual); /* Set flag in case of divergence */ if (residual > 1.2*old_residual) r[0] = 5.0; } /* In case of divergence, return input state vector */ if (r[0] == 5.0) { for (i=1;i<=3;i++) { r[i] = r_input[i]; rprime[i] = rprime_input[i]; } } } public double laplace_method(double Lx[],double Ly[],double Lz[],double x[],double y[],double z[],double time1,double time2,double time3,double r2[],double r2prime[]) { /* Procedure to use the Laplace method to determine a preliminary orbit using three sets of optical observations. Algorithm taken from Pedro Escobal, "Methods of Orbit Determination", pp. 261-270. Note: Station coordinates (x, y, and z) should be in heliocentric units, and times in Julian days. The state vector r2 (and r2prime) are given in heliocentric units (per canonical time unit). The Julian day of the epoch to which the elements refer is "jdepoch", which is returned. Note: Station coordinates are inward pointing; that is, vectors x, y, and z point from the observer to the barycenter of the solar system. Note: Due to the vaguaries of the Java compiler, I must reuse variable names, as follows: r2_mag = s1 increment = s2 oldsign = s3 sign = s4 rho2 = s5 rho2dot = s6 Tested and verified 15 June 1999. */ double[] L2dot = new double[4]; double[] L2dotdot = new double[4]; double[] R2dot = new double[4]; double[] R2dotdot = new double[4]; double tau1 = 0, tau3 = 0, s1 = 0, s2 = 0, s3 = 0, s4 = 0, s5 = 0, s6 = 0, delta = 0, Da = 0, Db = 0, Dc = 0, Dd = 0, A2star = 0, B2star = 0, jdepoch = 0; double C2star = 0, D2star = 0, Cpsi = 0, a = 0, b = 0, c = 0; tau1 = kappa*(time1 - time2); tau3 = kappa*(time3 - time2); s1 = -tau3/(tau1*(tau1 - tau3)); s2 = -(tau3 + tau1)/(tau1*tau3); s3 = -tau1/(tau3*(tau3 - tau1)); s4 = 2/(tau1*(tau1 - tau3)); s5 = 2/(tau1*tau3); s6 = 2/(tau3*(tau3 - tau1)); L2dot[1] = s1*Lx[1] + s2*Lx[2] + s3*Lx[3]; L2dot[2] = s1*Ly[1] + s2*Ly[2] + s3*Ly[3]; L2dot[3] = s1*Lz[1] + s2*Lz[2] + s3*Lz[3]; L2dotdot[1] = s4*Lx[1] + s5*Lx[2] + s6*Lx[3]; L2dotdot[2] = s4*Ly[1] + s5*Ly[2] + s6*Ly[3]; L2dotdot[3] = s4*Lz[1] + s5*Lz[2] + s6*Lz[3]; R2dot[1] = s1*x[1] + s2*x[2] + s3*x[3]; R2dot[2] = s1*y[1] + s2*y[2] + s3*y[3]; R2dot[3] = s1*z[1] + s2*z[2] + s3*z[3]; R2dotdot[1] = s4*x[1] + s5*x[2] + s6*x[3]; R2dotdot[2] = s4*y[1] + s5*y[2] + s6*y[3]; R2dotdot[3] = s4*z[1] + s5*z[2] + s6*z[3]; delta = 2*(Lx[2]*(L2dot[2]*L2dotdot[3] - L2dot[3]*L2dotdot[2]) - Ly[2]*(L2dot[1]*L2dotdot[3] - L2dot[3]*L2dotdot[1]) + Lz[2]*(L2dot[1]*L2dotdot[2] - L2dot[2]*L2dotdot[1])); Da = Lx[2]*(L2dot[2]*R2dotdot[3] - L2dot[3]*R2dotdot[2]) - Ly[2]*(L2dot[1]*R2dotdot[3] - L2dot[3]*R2dotdot[1]) + Lz[2]*(L2dot[1]*R2dotdot[2] - L2dot[2]*R2dotdot[1]); Db = Lx[2]*(L2dot[2]*z[2] - L2dot[3]*y[2]) - Ly[2]*(L2dot[1]*z[2] - L2dot[3]*x[2]) + Lz[2]*(L2dot[1]*y[2] - L2dot[2]*x[2]); Dc = Lx[2]*(R2dotdot[2]*L2dotdot[3] - R2dotdot[3]*L2dotdot[2]) - Ly[2]*(R2dotdot[1]*L2dotdot[3] - R2dotdot[3]*L2dotdot[1]) + Lz[2]*(R2dotdot[1]*L2dotdot[2] - R2dotdot[2]*L2dotdot[1]); Dd = Lx[2]*(y[2]*L2dotdot[3] - z[2]*L2dotdot[2]) - Ly[2]*(x[2]*L2dotdot[3] - z[2]*L2dotdot[1]) + Lz[2]*(x[2]*L2dotdot[2] - y[2]*L2dotdot[1]); A2star = 2*Da/delta; B2star = 2*Db/delta; C2star = Dc/delta; D2star = Dd/delta; Cpsi = -2*(Lx[2]*x[2] + Ly[2]*y[2] + Lz[2]*z[2]); a = -(Cpsi*A2star + Math.pow(A2star,2) + (Math.pow(x[2],2) + Math.pow(y[2],2) + Math.pow(z[2],2))); b = -mu*(Cpsi*B2star + 2*A2star*B2star); c = -Math.pow(mu,2)*Math.pow(B2star,2); /* Solve the eighth degree polynomial to find the applicable real root */ s1 = .01; s2 = 1; if ((Math.pow(s1,8) +a*Math.pow(s1,6) + b*Math.pow(s1,3) + c) > 0) s3 = 1; else s3 = -1; s1 = s1 + s2; while (s2 > 5e-09) { if ((Math.pow(s1,8) +a*Math.pow(s1,6) + b*Math.pow(s1,3) + c) > 0) s4 = 1; else s4 = -1; if (s4 != s3) { s1 = s1 - s2/2; s2 = s2/2; } else s1 = s1 + s2; } s5 = A2star + mu*B2star/Math.pow(s1,3); s6 = C2star + mu*D2star/Math.pow(s1,3); r2[1] = s5*Lx[2] - x[2]; r2[2] = s5*Ly[2] - y[2]; r2[3] = s5*Lz[2] - z[2]; r2prime[1] = s6*Lx[2] + s5*L2dot[1] - R2dot[1]; r2prime[2] = s6*Ly[2] + s5*L2dot[2] - R2dot[2]; r2prime[3] = s6*Lz[2] + s5*L2dot[3] - R2dot[3]; jdepoch = time2; return jdepoch; } public void get_classical_uncertainties(double classical_elements[], double uncertainties[]) { /* Method to calculate the heliocentric ecliptical classical orbital elements, and estimate their uncertainties, given the barycentric equatorial epoch state vector and covariance matrix (available as instance variables of the MinorPlanet object class). Tested and verified 11 Nov 1999. */ double[] r_2body = new double[4]; double[] rprime_2body = new double[4]; double[] variant_elements = new double[9]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double[][] rotation_matrix = new double[9][9]; double[][] inter = new double[9][9]; double[][] ecliptic_bigp = new double[9][9]; double[][] partial_matrix = new double[9][9]; double[][] classical_covariance = new double[9][9]; double delta = 0, diff = 0; int i = 0, j = 0, k = 0; /* Initialize the two-body state vector */ get_planet_posvel(epoch_time,11,sun_r,sun_rprime); for (i=1;i<=3;i++) { r_2body[i] = epoch_r[i] - sun_r[i]; rprime_2body[i] = (epoch_rprime[i] - sun_rprime[i])/kappa; } /* Rotate the two-body state vector into ecliptic */ equatorial_to_ecliptic(r_2body); equatorial_to_ecliptic(rprime_2body); /* Rotate the covariances into ecliptic */ rotation_matrix[1][1] = 1.0; rotation_matrix[1][2] = 0.0; rotation_matrix[1][3] = 0.0; rotation_matrix[2][1] = 0.0; rotation_matrix[2][2] = Math.cos(epsilon); rotation_matrix[2][3] = Math.sin(epsilon); rotation_matrix[3][1] = 0.0; rotation_matrix[3][2] = -Math.sin(epsilon); rotation_matrix[3][3] = Math.cos(epsilon); rotation_matrix[4][4] = 1.0/kappa; rotation_matrix[4][5] = 0.0; rotation_matrix[4][6] = 0.0; rotation_matrix[5][4] = 0.0; rotation_matrix[5][5] = Math.cos(epsilon)/kappa; rotation_matrix[5][6] = Math.sin(epsilon)/kappa; rotation_matrix[6][4] = 0.0; rotation_matrix[6][5] = -Math.sin(epsilon)/kappa; rotation_matrix[6][6] = Math.cos(epsilon)/kappa; rotation_matrix[7][7] = 1.0; rotation_matrix[8][8] = 1.0; for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { inter[i][j] = 0; for (k=1;k<=8;k++) inter[i][j] = inter[i][j] + big_p[i][k]*rotation_matrix[j][k]; } } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { ecliptic_bigp[i][j] = 0; for (k=1;k<=8;k++) ecliptic_bigp[i][j] = ecliptic_bigp[i][j] + rotation_matrix[i][k]*inter[k][j]; } } /* Initialize the classical elements */ for (j=1;j<=8;j++) classical_elements[j] = 0.0; /* Find the classical orbital elements */ convert_state_vector_to_Keplerian_elements(r_2body,rprime_2body,classical_elements,epoch_time); /* Calculate the partial derivative matrix */ for (i=1;i<=3;i++) { /* Vary r_2body[i] and determine the changes in the classical elements */ delta = 0.000001*r_2body[i]; /* Avoid dividing by zero */ if (Math.abs(delta) < 1e-15) delta = 0.000001; r_2body[i] = r_2body[i] + delta; convert_state_vector_to_Keplerian_elements(r_2body,rprime_2body,variant_elements,epoch_time); for (j=1;j<=8;j++) { diff = variant_elements[j] - classical_elements[j]; if ((j == 3) || (j == 4) || (j == 5) || (j == 7)) { while (Math.abs(diff) > Math.abs(diff - 2.0*Math.PI)) diff = diff - 2.0*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2.0*Math.PI)) diff = diff + 2.0*Math.PI; } partial_matrix[j][i] = diff/delta; } r_2body[i] = r_2body[i] - delta; /* Vary rprime_2body[i] and determine the changes in the classical elements */ delta = 0.000001*rprime_2body[i]; /* Avoid dividing by zero */ if (Math.abs(delta) < 1e-15) delta = 0.0000001; rprime_2body[i] = rprime_2body[i] + delta; convert_state_vector_to_Keplerian_elements(r_2body,rprime_2body,variant_elements,epoch_time); for (j=1;j<=8;j++) { diff = variant_elements[j] - classical_elements[j]; if ((j == 3) || (j == 4) || (j == 5) || (j == 7)) { while (Math.abs(diff) > Math.abs(diff - 2.0*Math.PI)) diff = diff - 2.0*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2.0*Math.PI)) diff = diff + 2.0*Math.PI; } partial_matrix[j][i+3] = diff/delta; } rprime_2body[i] = rprime_2body[i] - delta; } /* Convert covariances from pos/vel to classical elements */ for (i=1;i<=6;i++) { for (j=1;j<=8;j++) { inter[i][j] = 0; for (k=1;k<=6;k++) inter[i][j] = inter[i][j] + ecliptic_bigp[i][k]*partial_matrix[j][k]; } } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { classical_covariance[i][j] = 0; for (k=1;k<=6;k++) classical_covariance[i][j] = classical_covariance[i][j] + partial_matrix[i][k]*inter[k][j]; } } /* Find the classical uncertainties */ for (j=1;j<=8;j++) uncertainties[j] = Math.sqrt(classical_covariance[j][j]); } void advance_ephemeris_epoch(double oldr[], double oldrprime[], double oldtime, double old_big_p[][], double newr[], double newrprime[], double newtime, double new_big_p[][]) { /* Method to advance an ephemeris state vector and error covariance matrix from Julian TDB oldtime to Julian TDB newtime. Note that this does NOT impact the epoch variables; only those explicitly passed as arguments from within an ephemeris calculation. */ int i = 0, j = 0, k = 0; double[][] phi = new double[9][9]; double[][] inter = new double[9][9]; double[][] collision_variant = new double[2000][9]; /* Advance the state vector */ update(oldr,oldrprime,oldtime,newr,newrprime,newtime,false,collision_variant); /* Advance the error covariance matrix */ get_ephemeris_state_transition_matrix(oldr,oldrprime,oldtime,newr,newrprime,newtime,phi); for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { inter[i][j] = 0; for (k=1;k<=8;k++) inter[i][j] = inter[i][j] + old_big_p[i][k]*phi[j][k]; } } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { new_big_p[i][j] = 0; for (k=1;k<=8;k++) new_big_p[i][j] = new_big_p[i][j] + phi[i][k]*inter[k][j]; } } } void get_ephemeris_state_transition_matrix(double oldr[], double oldrprime[], double oldtime, double rnominal[], double rprimenominal[], double time, double phi[][]) { /* Method to calculate the state transition matrix in going from a prior state vector at oldtime, to the new nominal state vector at time. Note that the purpose of this method is to enable method "advance_ephemeris_epoch" to advance the ephemeris state vector from oldtime to time. It does NOT impact the epoch state vector. */ double[] rvariant = new double[4]; double[] rprimevariant = new double[4]; double[][] collision_variant = new double[2000][9]; double delta = 0; int i = 0, j = 0; /* Calculate the state transition matrix phi at time */ for (i=1;i<=3;i++) { /* Vary oldr[i], and determine the change in the state vector at time */ delta = 0.000001*oldr[i]; oldr[i] = oldr[i] + delta; update(oldr, oldrprime, oldtime, rvariant, rprimevariant, time, false, collision_variant); for (j=1;j<=3;j++) { phi[j][i] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i] = (rprimevariant[j] - rprimenominal[j])/delta; } oldr[i] = oldr[i] - delta; /* Vary oldrprime[i], and determine the change in the state vector at time */ delta = 0.000001*oldrprime[i]; oldrprime[i] = oldrprime[i] + delta; update(oldr, oldrprime, oldtime, rvariant, rprimevariant, time, false, collision_variant); for (j=1;j<=3;j++) { phi[j][i+3] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i+3] = (rprimevariant[j] - rprimenominal[j])/delta; } oldrprime[i] = oldrprime[i] - delta; /* If non-gravitational thrust parameters A1_A2_DT are being utilized, vary A1_A2_DT[i], and determine the change in the state vector at time */ if ((Math.abs(A1_A2_DT[i]) > 0.0) && (i < 3)) { delta = 0.1*A1_A2_DT[i]; A1_A2_DT[i] = A1_A2_DT[i] + delta; update(oldr, oldrprime, oldtime, rvariant, rprimevariant, time, false, collision_variant); for (j=1;j<=3;j++) { phi[j][i+6] = (rvariant[j] - rnominal[j])/delta; phi[j+3][i+6] = (rprimevariant[j] - rprimenominal[j])/delta; } phi[i+6][i+6] = 1.0; A1_A2_DT[i] = A1_A2_DT[i] - delta; } } } public void make_ephemeris(boolean geocentric, double latitude, double longitude, double altitude, double start_time, double end_time, double interval, double ephemeris_radec[][], double magnitudes[], double ephemeris_support_data[][]) { /* Method to create an ephemeris for the minor planet, given the observer location, start and end times, and interval between entries. The inputs: - geocentric is a boolean determining whether the ephemeris will be geocentric or topocentric; - if topocentric, the geodetic latitude (in radians), east longitude (in radians), and altitude (in meters) give the coordinates of the observing site; - the ephemeris start and end times, as well as the interval between entries, are Julian TDT dates and times. The outputs: - the radec in radians - the estimated visual magnitudes - ephemeris_support_data contains the following precovery-oriented data: - ephemeris_support_data[i][1] = Linear 3-sigma Ellipse position angle - ephemeris_support_data[i][2] = Linear 3-sigma Ellipse major axis - ephemeris_support_data[i][3] = Linear 3-sigma Ellipse minor axis - ephemeris_support_data[i][4] = apparent motion in arc seconds per minute - ephemeris_support_data[i][5] = position angle of apparent motion - ephemeris_support_data[i][6] = observer-minor planet distance in AUs (delta) - ephemeris_support_data[i][7] = Sun-minor planet distance in AUs (r) - ephemeris_support_data[i][8] = minor planet elongation in radians Note that this method relies upon the availability of the epoch state vector, error covariance matrix, and epoch time, plus absolute_magnitude_H and slope_parameter_G. The strategy will be to successively advance the state vector and covariance matrix; at each step, calculate the radec, estimated magnitude, and support date. */ double[] ephemeris_r = new double[4]; double[] ephemeris_rprime = new double[4]; double[][] ephemeris_big_p = new double[9][9]; double[] observer_r = new double[4]; double[] observer_rprime = new double[4]; double[] observed_radec = new double[3]; double[] old_ephemeris_r = new double[4]; double[] old_ephemeris_rprime = new double[4]; double[][] old_ephemeris_big_p = new double[9][9]; double[] ephemeris_data = new double[9]; double[][] radec_covariances = new double[3][3]; double current_tdb_time = 0, old_tdb_time = 0, ut1_minus_tai = 0, current_tdt_time = 0, old_tdt_time = 0; int i = 1, j = 0, k = 0, counter_limit = 0; /* Since state vector integrations occur in TDB but ephemeris start-stop-intervals are in TDT, we will iterate over the current_tdt_time */ current_tdt_time = start_time; /* Convert current_tdt_time to TDB for use in state-vector integration */ ut1_minus_tai = -get_leap_second(current_tdt_time); current_tdb_time = tdt_to_tdb(current_tdt_time, latitude, longitude, altitude, ut1_minus_tai); /* Initialize the ephemeris state vector and error covariance matrix */ advance_ephemeris_epoch(epoch_r, epoch_rprime, epoch_time, big_p, ephemeris_r, ephemeris_rprime, current_tdb_time, ephemeris_big_p); /* Loop over current_tdt_time, calculating radecs and uncertainties, until end_time is exceeded. */ while (current_tdt_time <= end_time) { get_observed_radec(ephemeris_r, ephemeris_rprime, current_tdb_time, geocentric, latitude, longitude, altitude, ut1_minus_tai, observer_r, observer_rprime, current_tdt_time, observed_radec); get_radec_covariance(ephemeris_r, ephemeris_rprime, current_tdb_time, ephemeris_big_p, geocentric, latitude, longitude, altitude, ut1_minus_tai, observed_radec, current_tdt_time, radec_covariances); if (comet_identifier == false) magnitudes[i] = apparent_magnitude(current_tdb_time, ephemeris_r); else magnitudes[i] = comet_apparent_magnitude(current_tdb_time, ephemeris_r); /* Calculate the ephemeris_support_data */ get_ephemeris_support_data(ephemeris_r, ephemeris_rprime, current_tdb_time, geocentric, latitude, longitude, altitude, ut1_minus_tai, current_tdt_time, observed_radec, radec_covariances, ephemeris_data); for (j=1;j<=2;j++) { ephemeris_radec[i][j] = observed_radec[j]; } for (j=1;j<=8;j++) { ephemeris_support_data[i][j] = ephemeris_data[j]; } /* Get ready to advance ephemeris epoch */ for (j=1;j<=3;j++) { old_ephemeris_r[j] = ephemeris_r[j]; old_ephemeris_rprime[j] = ephemeris_rprime[j]; observer_r[j] = 0; observer_rprime[j] = 0; } for (j=1;j<=8;j++) { for (k=1;k<=8;k++) old_ephemeris_big_p[j][k] = ephemeris_big_p[j][k]; } old_tdt_time = current_tdt_time; old_tdb_time = current_tdb_time; /* Increment the TDT time and find the corresponding new TDB time */ current_tdt_time = current_tdt_time + interval; i = i + 1; ut1_minus_tai = -get_leap_second(current_tdt_time); current_tdb_time = tdt_to_tdb(current_tdt_time, latitude, longitude, altitude, ut1_minus_tai); advance_ephemeris_epoch(old_ephemeris_r, old_ephemeris_rprime, old_tdb_time, old_ephemeris_big_p, ephemeris_r, ephemeris_rprime, current_tdb_time, ephemeris_big_p); } } void get_radec_covariance(double ephemeris_r[], double ephemeris_rprime[], double ephemeris_time, double ephemeris_big_p[][], boolean geocentric, double latitude, double longitude, double altitude, double ut1_minus_tai, double observed_radec[], double current_tdt_time, double radec_covariance[][]) { /* Method to calculate the covariance matrix for the observed_radec at TDT current_tdt_time, given the state vector and error covariance matrix at TDB ephemeris_time, the nominal observed_radec, and the observer coordinates. The inputs: - ephemeris_r and _rprime are the posvel components (in AU and AU/day) of the state vector at Julian TDB ephemeris_time; - ephemeris_big_p is the error covariance matrix at ephemeris_time; - geocentric is a boolean determining whether the ephemeris will be geocentric or topocentric; - if topocentric, the geodetic latitude (in radians), east longitude (in radians), and altitude (in meters) give the coordinates of the observing site; - ut1_minus_tai gives the value of (UT1-TAI) (in seconds) to be used in computing the ephemeris; - observed_radec are the nominal ra and dec in radians; - current_tdt_time is the current TDT time (corresponding to TDB ephemeris_time). The outputs: - radec_covariance is the 2x2 radec covariance matrix for TDB ephemeris_time. */ double[] variant_radec = new double[3]; double[] observer_r = new double[4]; double[] observer_rprime = new double[4]; double[][] partial_matrix = new double[3][9]; double[][] inter = new double[9][3]; double delta = 0, diff = 0; int i = 0, j = 0, k = 0; for (i=1;i<=3;i++) { /* Vary ephemeris_r[i], and determine the change in the predicted radec */ delta = 0.000001*ephemeris_r[i]; ephemeris_r[i] = ephemeris_r[i] + delta; get_observed_radec(ephemeris_r, ephemeris_rprime, ephemeris_time, geocentric, latitude, longitude, altitude, ut1_minus_tai, observer_r, observer_rprime, current_tdt_time, variant_radec); diff = (variant_radec[1] - observed_radec[1]); while (Math.abs(diff) > Math.abs(diff - 2*Math.PI)) diff = diff - 2*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2*Math.PI)) diff = diff + 2*Math.PI; partial_matrix[1][i] = diff/delta; diff = (variant_radec[2] - observed_radec[2]); while (Math.abs(diff) > Math.abs(diff - 2*Math.PI)) diff = diff - 2*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2*Math.PI)) diff = diff + 2*Math.PI; partial_matrix[2][i] = diff/delta; ephemeris_r[i] = ephemeris_r[i] - delta; /* Vary ephemeris_rprime[i], and determine the change in the predicted radec */ delta = 0.000001*ephemeris_rprime[i]; ephemeris_rprime[i] = ephemeris_rprime[i] + delta; get_observed_radec(ephemeris_r, ephemeris_rprime, ephemeris_time, geocentric, latitude, longitude, altitude, ut1_minus_tai, observer_r, observer_rprime, current_tdt_time, variant_radec); diff = (variant_radec[1] - observed_radec[1]); while (Math.abs(diff) > Math.abs(diff - 2*Math.PI)) diff = diff - 2*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2*Math.PI)) diff = diff + 2*Math.PI; partial_matrix[1][i+3] = diff/delta; diff = (variant_radec[2] - observed_radec[2]); while (Math.abs(diff) > Math.abs(diff - 2*Math.PI)) diff = diff - 2*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2*Math.PI)) diff = diff + 2*Math.PI; partial_matrix[2][i+3] = diff/delta; ephemeris_rprime[i] = ephemeris_rprime[i] - delta; /* If non-gravitational thrust parameters are being utilized, vary A1_A2_DT[i], and determine the change in the predicted radec */ if ((Math.abs(A1_A2_DT[i]) > 0.0) && (i < 3)) { delta = 0.01*A1_A2_DT[i]; A1_A2_DT[i] = A1_A2_DT[i] + delta; get_observed_radec(ephemeris_r, ephemeris_rprime, ephemeris_time, geocentric, latitude, longitude, altitude, ut1_minus_tai, observer_r, observer_rprime, current_tdt_time, variant_radec); diff = (variant_radec[1] - observed_radec[1]); while (Math.abs(diff) > Math.abs(diff - 2*Math.PI)) diff = diff - 2*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2*Math.PI)) diff = diff + 2*Math.PI; partial_matrix[1][i+6] = diff/delta; diff = (variant_radec[2] - observed_radec[2]); while (Math.abs(diff) > Math.abs(diff - 2*Math.PI)) diff = diff - 2*Math.PI; while (Math.abs(diff) > Math.abs(diff + 2*Math.PI)) diff = diff + 2*Math.PI; partial_matrix[2][i+6] = diff/delta; A1_A2_DT[i] = A1_A2_DT[i] - delta; } } /* Convert covariances from pos/vel to ra/dec */ for (i=1;i<=8;i++) { for (j=1;j<=2;j++) { inter[i][j] = 0; for (k=1;k<=8;k++) inter[i][j] = inter[i][j] + ephemeris_big_p[i][k]*partial_matrix[j][k]; } } for (i=1;i<=2;i++) { for (j=1;j<=2;j++) { radec_covariance[i][j] = 0; for (k=1;k<=8;k++) radec_covariance[i][j] = radec_covariance[i][j] + partial_matrix[i][k]*inter[k][j]; } } } void get_ephemeris_support_data(double ephemeris_r[], double ephemeris_rprime[], double current_tdb_time, boolean geocentric, double latitude, double longitude, double altitude, double ut1_minus_tai, double current_tdt_time, double observed_radec[], double radec_covariances[][], double ephemeris_data[]) { /* Method to calculate the secondary ephemeris data for the minor planet, given the state vector and error covariance matrix at TDB current_tdb_time, the nominal observed_radec at TDT current_tdt_time, and the observer coordinates. The inputs: - ephemeris_r and _rprime are the posvel components (in AU and AU/day) of the state vector at Julian TDB current_tdb_time; - geocentric is a boolean determining whether the ephemeris will be geocentric or topocentric; - if topocentric, the geodetic latitude (in radians), east longitude (in radians), and altitude (in meters) give the coordinates of the observing site; - ut1_minus_tai gives the value of (UT1-TAI) (in seconds) to be used in computing the ephemeris; - current_tdt_time is the current TDT time (corresponding to current_tdb_time); - observed_radec are the nominal ra and dec in radians; - radec_covariances is the radec covariance matrix at TDB current_tdb_time. The outputs: - ephemeris_data[1] = Linear 3-sigma Ellipse position angle in radians - ephemeris_data[2] = Linear 3-sigma Ellipse major axis in radians - ephemeris_data[3] = Linear 3-sigma Ellipse minor axis in radians - ephemeris_data[4] = apparent motion in arc seconds per minute - ephemeris_data[5] = position angle of apparent motion in radians - ephemeris_data[6] = observer-minor planet distance in AUs (delta) - ephemeris_data[7] = Sun-minor planet distance in AUs (r) - ephemeris_data[8] = minor planet elongation in radians Note: Position angle calculations based on Peter Van de Kamp, Principles of Astrometry, pp. 4-5, 25. */ double[] observer_r = new double[4]; double[] observer_rprime = new double[4]; double[] variant_radec = new double[3]; double[] sun_r = new double[4]; double[] sun_vel = new double[4]; double[] EOP = new double[8]; double distance = 0, big_r = 0, little_r = 0, delta = 0, root1 = 0, root2 = 0, discriminant = 0, delta_ra = 0, delta_dec = 0, theta = 0; int j = 0; /* I derived general formulas for the eigenvalues and eigenvectors of the 2x2 radec_covariances matrix; these will be applied here to calculate the linear 3-sigma ellipse position angle and major-minor axes. The eigenvalues of the radec covariance matrix represent the major and minor axes of the error ellipse; the corresponding eigenvectors represent the "basis" of the major-minor axes coordinate system, expressed in terms of x and y. The orientation of the semi-major axis with respect to x and y gives the position angle. As part of my testing, I created a transition matrix (P) whose column vectors are the eigenvectors, and verified that P-inverse*A*P is a diagonal matrix whose entries are the original eigenvalues of the covariance matrix. */ /* We will skip these calculations (and avoid division by zero) if the error covariance matrix is empty (i.e., no covariance data exists for the current orbit). */ if ((radec_covariances[1][1] != 0.0) || (radec_covariances[1][2] != 0.0) || (radec_covariances[2][1] != 0.0) || (radec_covariances[2][2] != 0.0)) { /* Begin by finding the eigenvalues of the covariance matrix */ discriminant = Math.pow((radec_covariances[1][1]+radec_covariances[2][2]),2)-4.0*(radec_covariances[1][1]*radec_covariances[2][2]-radec_covariances[1][2]*radec_covariances[2][1]); if (discriminant >= 0.0) { root1 = 0.5*((radec_covariances[1][1]+radec_covariances[2][2]) + Math.sqrt(Math.pow((radec_covariances[1][1]+radec_covariances[2][2]),2)-4.0*(radec_covariances[1][1]*radec_covariances[2][2]-radec_covariances[1][2]*radec_covariances[2][1]))); root2 = 0.5*((radec_covariances[1][1]+radec_covariances[2][2]) - Math.sqrt(Math.pow((radec_covariances[1][1]+radec_covariances[2][2]),2)-4.0*(radec_covariances[1][1]*radec_covariances[2][2]-radec_covariances[1][2]*radec_covariances[2][1]))); } else { root1 = 0.0; root2 = 0.0; } if (Math.abs(root1) > Math.abs(root2)) { ephemeris_data[2] = root1; ephemeris_data[3] = root2; } else { ephemeris_data[2] = root2; ephemeris_data[3] = root1; } if ((ephemeris_data[2] - radec_covariances[1][1]) != 0.0) { ephemeris_data[1] = Math.atan(radec_covariances[1][2]/(ephemeris_data[2] - radec_covariances[1][1])); } else { ephemeris_data[1] = Math.atan((ephemeris_data[2] - radec_covariances[2][2])/radec_covariances[2][1]); } /* In order to get the position angle in the proper quadrant, must account for the fact that ra increases to the west */ ephemeris_data[1] = -ephemeris_data[1]; /* Finally, take the square root of the axes, multiply by three to correspond to the 3-sigma ellipse, and take the tangent to project onto the plane of sky. */ ephemeris_data[2] = Math.tan(3.0*Math.sqrt(Math.abs(ephemeris_data[2]))); ephemeris_data[3] = Math.tan(3.0*Math.sqrt(Math.abs(ephemeris_data[3]))); } else { ephemeris_data[1] = 0.0; ephemeris_data[2] = 0.0; ephemeris_data[3] = 0.0; } /* Begin by calculating the observed radec one minute later */ ut1_minus_tai = -get_leap_second(current_tdt_time+1.0/1440.0); get_observed_radec(ephemeris_r, ephemeris_rprime, current_tdb_time, geocentric, latitude, longitude, altitude, ut1_minus_tai, observer_r, observer_rprime, (current_tdt_time+1.0/1440.0), variant_radec); distance = Math.acos(Math.sin(variant_radec[2])*Math.sin(observed_radec[2]) + Math.cos(variant_radec[2])*Math.cos(observed_radec[2])*Math.cos(variant_radec[1]-observed_radec[1])); /* Project distance travelled onto the plane of sky and convert to arc seconds */ ephemeris_data[4] = Math.tan(distance); ephemeris_data[4] = ephemeris_data[4]*180.0*3600.0/Math.PI; /* Note: position angle measured clockwise from due north. Need to get the correct quadrant, and account for the fact that ra increaes to the west. */ delta_ra = variant_radec[1] - observed_radec[1]; delta_dec = variant_radec[2] - observed_radec[2]; if ((delta_ra > 0) && (delta_dec > 0)) { theta = Math.asin(Math.sin(Math.abs(delta_ra))*Math.cos(observed_radec[2])/Math.sin(distance)); ephemeris_data[5] = -theta; } else if ((delta_ra < 0) && (delta_dec < 0)) { theta = Math.asin(Math.sin(Math.abs(delta_ra))*Math.cos(variant_radec[2])/Math.sin(distance)); ephemeris_data[5] = Math.PI - theta; } else if ((delta_ra > 0) && (delta_dec < 0)) { theta = Math.asin(Math.sin(Math.abs(delta_ra))*Math.cos(observed_radec[2])/Math.sin(distance)); ephemeris_data[5] = theta - Math.PI; } else { theta = Math.asin(Math.sin(Math.abs(delta_ra))*Math.cos(variant_radec[2])/Math.sin(distance)); ephemeris_data[5] = theta; } /* Calculate the positions of the Sun and observer, and use them to calculate delta, r, and elongation. */ get_planet_posvel(current_tdb_time,11,sun_r,sun_vel); if (geocentric) { planetary_ephemeris(current_tdb_time,observer_r,0); for (j=1;j<=3;j++) { observer_r[j] = planet_r[3][j]; observer_rprime[j] = planet_rprime[3][j]; } } else { get_observer_position(latitude,longitude,altitude,current_tdb_time,EOP,observer_r,observer_rprime); } delta = Math.sqrt(Math.pow((observer_r[1] - ephemeris_r[1]),2) + Math.pow((observer_r[2] - ephemeris_r[2]),2) + Math.pow((observer_r[3] - ephemeris_r[3]),2)); little_r = Math.sqrt(Math.pow((sun_r[1] - ephemeris_r[1]),2) + Math.pow((sun_r[2] - ephemeris_r[2]),2) + Math.pow((sun_r[3] - ephemeris_r[3]),2)); big_r = Math.sqrt(Math.pow((sun_r[1] - observer_r[1]),2) + Math.pow((sun_r[2] - observer_r[2]),2) + Math.pow((sun_r[3] - observer_r[3]),2)); ephemeris_data[6] = delta; ephemeris_data[7] = little_r; ephemeris_data[8] = Math.acos((Math.pow(delta,2) + Math.pow(big_r,2) - Math.pow(little_r,2))/(2.0*delta*big_r)); } public void read_data_from_disk(String filename) { /* Procedure to read from disk all observations and state vector data for the current minor planet. In particular, all of the instance variables from class MinorPlanet (except the computational auxiliary variables) are read from file "filename". */ int i = 0, j = 0; try { /* Read instance variables from disk */ FileInputStream file = new FileInputStream(filename); BufferedInputStream buff = new BufferedInputStream(file); DataInputStream data = new DataInputStream(buff); comet_identifier = data.readBoolean(); epoch_time = data.readDouble(); for (i=1;i<=3;i++) { epoch_r[i] = data.readDouble(); epoch_rprime[i] = data.readDouble(); A1_A2_DT[i] = data.readDouble(); } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) big_p[i][j] = data.readDouble(); } absolute_magnitude_H = data.readDouble(); slope_parameter_G = data.readDouble(); comet_absolute_magnitude = data.readDouble(); comet_slope_parameter = data.readDouble(); number_of_optical_observations = data.readInt(); number_of_delay_observations = data.readInt(); number_of_doppler_observations = data.readInt(); for (i=1;i<=(number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations);i++) { observation_type[i] = data.readInt(); incremental_state_vector_epoch[i] = data.readDouble(); for (j=1;j<=3;j++) { incremental_state_vector_r[i][j] = data.readDouble(); incremental_state_vector_rprime[i][j] = data.readDouble(); } optical_ra[i] = data.readDouble(); ra_dev[i] = data.readDouble(); optical_dec[i] = data.readDouble(); dec_dev[i] = data.readDouble(); optical_time[i] = data.readDouble(); visual_magnitude[i] = data.readDouble(); visual_magnitude_dev[i] = data.readDouble(); observation_geocentric[i] = data.readBoolean(); observation_latitude[i] = data.readDouble(); observation_longitude[i] = data.readDouble(); observation_altitude[i] = data.readDouble(); for (j=1;j<=7;j++) observation_EOP[i][j] = data.readDouble(); for (j=1;j<=3;j++) { observation_r[i][j] = data.readDouble(); observation_rprime[i][j] = data.readDouble(); } radar_delay[i] = data.readDouble(); delay_dev[i] = data.readDouble(); radar_delay_receiver_time[i] = data.readDouble(); radar_delay_receiver_latitude[i] = data.readDouble(); radar_delay_receiver_longitude[i] = data.readDouble(); radar_delay_receiver_altitude[i] = data.readDouble(); for (j=1;j<=7;j++) radar_delay_receiver_EOP[i][j] = data.readDouble(); radar_delay_receiver_tdb_minus_utc[i] = data.readDouble(); for (j=1;j<=3;j++) { radar_delay_receiver_r[i][j] = data.readDouble(); radar_delay_receiver_rprime[i][j] = data.readDouble(); } radar_delay_transmitter_latitude[i] = data.readDouble(); radar_delay_transmitter_longitude[i] = data.readDouble(); radar_delay_transmitter_altitude[i] = data.readDouble(); for (j=1;j<=7;j++) radar_delay_transmitter_EOP[i][j] = data.readDouble(); radar_delay_transmitter_tdb_minus_utc[i] = data.readDouble(); radar_delay_transmitter_frequency[i] = data.readDouble(); radar_doppler[i] = data.readDouble(); doppler_dev[i] = data.readDouble(); radar_doppler_receiver_time[i] = data.readDouble(); radar_doppler_receiver_latitude[i] = data.readDouble(); radar_doppler_receiver_longitude[i] = data.readDouble(); radar_doppler_receiver_altitude[i] = data.readDouble(); for (j=1;j<=7;j++) radar_doppler_receiver_EOP[i][j] = data.readDouble(); radar_doppler_receiver_tdb_minus_utc[i] = data.readDouble(); for (j=1;j<=3;j++) { radar_doppler_receiver_r[i][j] = data.readDouble(); radar_doppler_receiver_rprime[i][j] = data.readDouble(); } radar_doppler_transmitter_latitude[i] = data.readDouble(); radar_doppler_transmitter_longitude[i] = data.readDouble(); radar_doppler_transmitter_altitude[i] = data.readDouble(); for (j=1;j<=7;j++) radar_doppler_transmitter_EOP[i][j] = data.readDouble(); radar_doppler_transmitter_tdb_minus_utc[i] = data.readDouble(); radar_doppler_transmitter_frequency[i] = data.readDouble(); } minor_planet_radius = data.readDouble(); data.close(); } catch (FileNotFoundException fnfe) { System.out.println("Error -- " + fnfe.toString()); } catch (IOException ioe) { System.out.println("Error -- " + ioe.toString()); } catch (SecurityException se) { System.out.println("Error -- " + se.toString()); } } public void write_data_to_disk(String filename) { /* Procedure to write to disk all observations and state vector data for the current minor planet. In particular, all of the instance variables from class MinorPlanet (except the computational auxiliary variables) are written to file "filename". */ int i = 0, j = 0; double temp = 0; try { /* Write instance variables to disk */ FileOutputStream file = new FileOutputStream(filename); BufferedOutputStream buff = new BufferedOutputStream(file); DataOutputStream data = new DataOutputStream(buff); data.writeBoolean(comet_identifier); data.writeDouble(epoch_time); for (i=1;i<=3;i++) { data.writeDouble(epoch_r[i]); data.writeDouble(epoch_rprime[i]); data.writeDouble(A1_A2_DT[i]); } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) data.writeDouble(big_p[i][j]); } data.writeDouble(absolute_magnitude_H); data.writeDouble(slope_parameter_G); data.writeDouble(comet_absolute_magnitude); data.writeDouble(comet_slope_parameter); data.writeInt(number_of_optical_observations); data.writeInt(number_of_delay_observations); data.writeInt(number_of_doppler_observations); for (i=1;i<=(number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations);i++) { data.writeInt(observation_type[i]); data.writeDouble(incremental_state_vector_epoch[i]); for (j=1;j<=3;j++) { data.writeDouble(incremental_state_vector_r[i][j]); data.writeDouble(incremental_state_vector_rprime[i][j]); } data.writeDouble(optical_ra[i]); data.writeDouble(ra_dev[i]); data.writeDouble(optical_dec[i]); data.writeDouble(dec_dev[i]); data.writeDouble(optical_time[i]); data.writeDouble(visual_magnitude[i]); data.writeDouble(visual_magnitude_dev[i]); data.writeBoolean(observation_geocentric[i]); data.writeDouble(observation_latitude[i]); data.writeDouble(observation_longitude[i]); data.writeDouble(observation_altitude[i]); for (j=1;j<=7;j++) data.writeDouble(observation_EOP[i][j]); for (j=1;j<=3;j++) { data.writeDouble(observation_r[i][j]); data.writeDouble(observation_rprime[i][j]); } data.writeDouble(radar_delay[i]); data.writeDouble(delay_dev[i]); data.writeDouble(radar_delay_receiver_time[i]); data.writeDouble(radar_delay_receiver_latitude[i]); data.writeDouble(radar_delay_receiver_longitude[i]); data.writeDouble(radar_delay_receiver_altitude[i]); for (j=1;j<=7;j++) data.writeDouble(radar_delay_receiver_EOP[i][j]); data.writeDouble(radar_delay_receiver_tdb_minus_utc[i]); for (j=1;j<=3;j++) { data.writeDouble(radar_delay_receiver_r[i][j]); data.writeDouble(radar_delay_receiver_rprime[i][j]); } data.writeDouble(radar_delay_transmitter_latitude[i]); data.writeDouble(radar_delay_transmitter_longitude[i]); data.writeDouble(radar_delay_transmitter_altitude[i]); for (j=1;j<=7;j++) data.writeDouble(radar_delay_transmitter_EOP[i][j]); data.writeDouble(radar_delay_transmitter_tdb_minus_utc[i]); data.writeDouble(radar_delay_transmitter_frequency[i]); data.writeDouble(radar_doppler[i]); data.writeDouble(doppler_dev[i]); data.writeDouble(radar_doppler_receiver_time[i]); data.writeDouble(radar_doppler_receiver_latitude[i]); data.writeDouble(radar_doppler_receiver_longitude[i]); data.writeDouble(radar_doppler_receiver_altitude[i]); for (j=1;j<=7;j++) data.writeDouble(radar_doppler_receiver_EOP[i][j]); data.writeDouble(radar_doppler_receiver_tdb_minus_utc[i]); for (j=1;j<=3;j++) { data.writeDouble(radar_doppler_receiver_r[i][j]); data.writeDouble(radar_doppler_receiver_rprime[i][j]); } data.writeDouble(radar_doppler_transmitter_latitude[i]); data.writeDouble(radar_doppler_transmitter_longitude[i]); data.writeDouble(radar_doppler_transmitter_altitude[i]); for (j=1;j<=7;j++) data.writeDouble(radar_doppler_transmitter_EOP[i][j]); data.writeDouble(radar_doppler_transmitter_tdb_minus_utc[i]); data.writeDouble(radar_doppler_transmitter_frequency[i]); } data.writeDouble(minor_planet_radius); data.close(); } catch (FileNotFoundException fnfe) { System.out.println("Error -- " + fnfe.toString()); } catch (IOException ioe) { System.out.println("Error -- " + ioe.toString()); } catch (SecurityException se) { System.out.println("Error -- " + se.toString()); } } public void read_mp_list_from_disk() { /* Procedure to read from disk (file "minor_planet_list") the list of minor planets for whom data files exist. */ int i = 1; boolean eof = false; try { /* Read minor planet list from disk */ FileReader file = new FileReader("minor_planet_list"); BufferedReader buff = new BufferedReader(file); while (!eof) { mp_list[i] = buff.readLine(); if (mp_list[i] == null) eof = true; else i++; } number_of_minor_planets = (i-1); buff.close(); } catch (IOException e) { System.out.println("Error -- " + e.toString()); } } public void write_mp_list_to_disk() { /* Procedure to write to disk (file "minor_planet_list") the list of minor planets for whom data files exist. */ int i = 0; try { /* Write minor planet list to disk */ FileWriter file = new FileWriter("minor_planet_list"); BufferedWriter buff = new BufferedWriter(file); for (i=1;i<=number_of_minor_planets;i++) { buff.write(mp_list[i],0,mp_list[i].length()); buff.newLine(); } buff.close(); } catch (IOException e) { System.out.println("Error -- " + e.toString()); } } public void sort_observations() { /* Method to sort the data associated with a minor planet, following the addition of an observation. Since observations must be ordered chronologically from earliest to most recent, it's necessary to ensure that a new observation is placed properly. Initially, the new observation will be put into the last slot in each instance variable. A call to this method will determine if it should actually appear sooner; if so, the new observation is moved up into an earlier slot, and later observations are shifted down one slot. */ double temp = 0, new_observation_date = 0, old_observation_date = 0; int index = (number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations), itemp = 0, i = 0, j = 0, k = 0; boolean btemp = true, stop_now = false; /* Determine the time of the new observation */ if (observation_type[index] == 1) new_observation_date = optical_time[index]; else if (observation_type[index] == 2) new_observation_date = radar_delay_receiver_time[index]; else if (observation_type[index] == 3) new_observation_date = radar_doppler_receiver_time[index]; for (i=1;i=i;j--) observation_type[j+1] = observation_type[j]; observation_type[i] = itemp; temp = incremental_state_vector_epoch[index]; for (j=(index-1);j>=i;j--) incremental_state_vector_epoch[j+1] = incremental_state_vector_epoch[j]; incremental_state_vector_epoch[i] = temp; for (k=1;k<=3;k++) { temp = incremental_state_vector_r[index][k]; for (j=(index-1);j>=i;j--) incremental_state_vector_r[j+1][k] = incremental_state_vector_r[j][k]; incremental_state_vector_r[i][k] = temp; } for (k=1;k<=3;k++) { temp = incremental_state_vector_rprime[index][k]; for (j=(index-1);j>=i;j--) incremental_state_vector_rprime[j+1][k] = incremental_state_vector_rprime[j][k]; incremental_state_vector_rprime[i][k] = temp; } temp = optical_ra[index]; for (j=(index-1);j>=i;j--) optical_ra[j+1] = optical_ra[j]; optical_ra[i] = temp; temp = ra_dev[index]; for (j=(index-1);j>=i;j--) ra_dev[j+1] = ra_dev[j]; ra_dev[i] = temp; temp = optical_dec[index]; for (j=(index-1);j>=i;j--) optical_dec[j+1] = optical_dec[j]; optical_dec[i] = temp; temp = dec_dev[index]; for (j=(index-1);j>=i;j--) dec_dev[j+1] = dec_dev[j]; dec_dev[i] = temp; temp = optical_time[index]; for (j=(index-1);j>=i;j--) optical_time[j+1] = optical_time[j]; optical_time[i] = temp; temp = visual_magnitude[index]; for (j=(index-1);j>=i;j--) visual_magnitude[j+1] = visual_magnitude[j]; visual_magnitude[i] = temp; temp = visual_magnitude_dev[index]; for (j=(index-1);j>=i;j--) visual_magnitude_dev[j+1] = visual_magnitude_dev[j]; visual_magnitude_dev[i] = temp; btemp = observation_geocentric[index]; for (j=(index-1);j>=i;j--) observation_geocentric[j+1] = observation_geocentric[j]; observation_geocentric[i] = btemp; temp = observation_latitude[index]; for (j=(index-1);j>=i;j--) observation_latitude[j+1] = observation_latitude[j]; observation_latitude[i] = temp; temp = observation_longitude[index]; for (j=(index-1);j>=i;j--) observation_longitude[j+1] = observation_longitude[j]; observation_longitude[i] = temp; temp = observation_altitude[index]; for (j=(index-1);j>=i;j--) observation_altitude[j+1] = observation_altitude[j]; observation_altitude[i] = temp; for (k=1;k<=7;k++) { temp = observation_EOP[index][k]; for (j=(index-1);j>=i;j--) observation_EOP[j+1][k] = observation_EOP[j][k]; observation_EOP[i][k] = temp; } for (k=1;k<=3;k++) { temp = observation_r[index][k]; for (j=(index-1);j>=i;j--) observation_r[j+1][k] = observation_r[j][k]; observation_r[i][k] = temp; } for (k=1;k<=3;k++) { temp = observation_rprime[index][k]; for (j=(index-1);j>=i;j--) observation_rprime[j+1][k] = observation_rprime[j][k]; observation_rprime[i][k] = temp; } temp = radar_delay[index]; for (j=(index-1);j>=i;j--) radar_delay[j+1] = radar_delay[j]; radar_delay[i] = temp; temp = delay_dev[index]; for (j=(index-1);j>=i;j--) delay_dev[j+1] = delay_dev[j]; delay_dev[i] = temp; temp = radar_delay_receiver_time[index]; for (j=(index-1);j>=i;j--) radar_delay_receiver_time[j+1] = radar_delay_receiver_time[j]; radar_delay_receiver_time[i] = temp; temp = radar_delay_receiver_latitude[index]; for (j=(index-1);j>=i;j--) radar_delay_receiver_latitude[j+1] = radar_delay_receiver_latitude[j]; radar_delay_receiver_latitude[i] = temp; temp = radar_delay_receiver_longitude[index]; for (j=(index-1);j>=i;j--) radar_delay_receiver_longitude[j+1] = radar_delay_receiver_longitude[j]; radar_delay_receiver_longitude[i] = temp; temp = radar_delay_receiver_altitude[index]; for (j=(index-1);j>=i;j--) radar_delay_receiver_altitude[j+1] = radar_delay_receiver_altitude[j]; radar_delay_receiver_altitude[i] = temp; for (k=1;k<=7;k++) { temp = radar_delay_receiver_EOP[index][k]; for (j=(index-1);j>=i;j--) radar_delay_receiver_EOP[j+1][k] = radar_delay_receiver_EOP[j][k]; radar_delay_receiver_EOP[i][k] = temp; } temp = radar_delay_receiver_tdb_minus_utc[index]; for (j=(index-1);j>=i;j--) radar_delay_receiver_tdb_minus_utc[j+1] = radar_delay_receiver_tdb_minus_utc[j]; radar_delay_receiver_tdb_minus_utc[i] = temp; for (k=1;k<=3;k++) { temp = radar_delay_receiver_r[index][k]; for (j=(index-1);j>=i;j--) radar_delay_receiver_r[j+1][k] = radar_delay_receiver_r[j][k]; radar_delay_receiver_r[i][k] = temp; } for (k=1;k<=3;k++) { temp = radar_delay_receiver_rprime[index][k]; for (j=(index-1);j>=i;j--) radar_delay_receiver_rprime[j+1][k] = radar_delay_receiver_rprime[j][k]; radar_delay_receiver_rprime[i][k] = temp; } temp = radar_delay_transmitter_latitude[index]; for (j=(index-1);j>=i;j--) radar_delay_transmitter_latitude[j+1] = radar_delay_transmitter_latitude[j]; radar_delay_transmitter_latitude[i] = temp; temp = radar_delay_transmitter_longitude[index]; for (j=(index-1);j>=i;j--) radar_delay_transmitter_longitude[j+1] = radar_delay_transmitter_longitude[j]; radar_delay_transmitter_longitude[i] = temp; temp = radar_delay_transmitter_altitude[index]; for (j=(index-1);j>=i;j--) radar_delay_transmitter_altitude[j+1] = radar_delay_transmitter_altitude[j]; radar_delay_transmitter_altitude[i] = temp; for (k=1;k<=7;k++) { temp = radar_delay_transmitter_EOP[index][k]; for (j=(index-1);j>=i;j--) radar_delay_transmitter_EOP[j+1][k] = radar_delay_transmitter_EOP[j][k]; radar_delay_transmitter_EOP[i][k] = temp; } temp = radar_delay_transmitter_tdb_minus_utc[index]; for (j=(index-1);j>=i;j--) radar_delay_transmitter_tdb_minus_utc[j+1] = radar_delay_transmitter_tdb_minus_utc[j]; radar_delay_transmitter_tdb_minus_utc[i] = temp; temp = radar_delay_transmitter_frequency[index]; for (j=(index-1);j>=i;j--) radar_delay_transmitter_frequency[j+1] = radar_delay_transmitter_frequency[j]; radar_delay_transmitter_frequency[i] = temp; temp = radar_doppler[index]; for (j=(index-1);j>=i;j--) radar_doppler[j+1] = radar_doppler[j]; radar_doppler[i] = temp; temp = doppler_dev[index]; for (j=(index-1);j>=i;j--) doppler_dev[j+1] = doppler_dev[j]; doppler_dev[i] = temp; temp = radar_doppler_receiver_time[index]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_time[j+1] = radar_doppler_receiver_time[j]; radar_doppler_receiver_time[i] = temp; temp = radar_doppler_receiver_latitude[index]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_latitude[j+1] = radar_doppler_receiver_latitude[j]; radar_doppler_receiver_latitude[i] = temp; temp = radar_doppler_receiver_longitude[index]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_longitude[j+1] = radar_doppler_receiver_longitude[j]; radar_doppler_receiver_longitude[i] = temp; temp = radar_doppler_receiver_altitude[index]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_altitude[j+1] = radar_doppler_receiver_altitude[j]; radar_doppler_receiver_altitude[i] = temp; for (k=1;k<=7;k++) { temp = radar_doppler_receiver_EOP[index][k]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_EOP[j+1][k] = radar_doppler_receiver_EOP[j][k]; radar_doppler_receiver_EOP[i][k] = temp; } temp = radar_doppler_receiver_tdb_minus_utc[index]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_tdb_minus_utc[j+1] = radar_doppler_receiver_tdb_minus_utc[j]; radar_doppler_receiver_tdb_minus_utc[i] = temp; for (k=1;k<=3;k++) { temp = radar_doppler_receiver_r[index][k]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_r[j+1][k] = radar_doppler_receiver_r[j][k]; radar_doppler_receiver_r[i][k] = temp; } for (k=1;k<=3;k++) { temp = radar_doppler_receiver_rprime[index][k]; for (j=(index-1);j>=i;j--) radar_doppler_receiver_rprime[j+1][k] = radar_doppler_receiver_rprime[j][k]; radar_doppler_receiver_rprime[i][k] = temp; } temp = radar_doppler_transmitter_latitude[index]; for (j=(index-1);j>=i;j--) radar_doppler_transmitter_latitude[j+1] = radar_doppler_transmitter_latitude[j]; radar_doppler_transmitter_latitude[i] = temp; temp = radar_doppler_transmitter_longitude[index]; for (j=(index-1);j>=i;j--) radar_doppler_transmitter_longitude[j+1] = radar_doppler_transmitter_longitude[j]; radar_doppler_transmitter_longitude[i] = temp; temp = radar_doppler_transmitter_altitude[index]; for (j=(index-1);j>=i;j--) radar_doppler_transmitter_altitude[j+1] = radar_doppler_transmitter_altitude[j]; radar_doppler_transmitter_altitude[i] = temp; for (k=1;k<=7;k++) { temp = radar_doppler_transmitter_EOP[index][k]; for (j=(index-1);j>=i;j--) radar_doppler_transmitter_EOP[j+1][k] = radar_doppler_transmitter_EOP[j][k]; radar_doppler_transmitter_EOP[i][k] = temp; } temp = radar_doppler_transmitter_tdb_minus_utc[index]; for (j=(index-1);j>=i;j--) radar_doppler_transmitter_tdb_minus_utc[j+1] = radar_doppler_transmitter_tdb_minus_utc[j]; radar_doppler_transmitter_tdb_minus_utc[i] = temp; temp = radar_doppler_transmitter_frequency[index]; for (j=(index-1);j>=i;j--) radar_doppler_transmitter_frequency[j+1] = radar_doppler_transmitter_frequency[j]; radar_doppler_transmitter_frequency[i] = temp; /* Set a flag to inhibit further shifting. */ stop_now = true; } } } public void comet_least_squares_solution(double[] residuals, double[] rms_residuals) { /* Method to calculate the least squares best-fit orbit of a comet, given optical, radar delay, and or radar doppler observations. The process is repeated until the magnitude of the corrections to the epoch state vector is less than some percentage of their corresponding covariances. The method presumes that an a priori state vector has been selected, and resides in epoch_r, epoch_rprime, and epoch_time. The method also assumes that the observations have been ordered from earliest to most recent. The method presumes that the user has selected a value for non-gravitational thrust parameter DT; the method will initialize values for non-gravitational thrust parameters A1 and A2, and iteratively solve for best-fit values. The output will be the epoch state vector, A1 and A2, and the epoch error covariance matrix (all of which are instance variables of the MinorPlanet class). Provision is made for excluding observations whose chi or residuals exceed user-defined thresholds; although these observations will not be deleted, they will not be used in the final iterations of the algorithm. Adapted from: D.K. Yeomans, S.J. Ostro, and P.W. Chodas (1987). "Radar Astrometry of Near-Earth Asteroids". Astronomical Journal 94, 189-200. Modified to the "Square Root Information Filter" algorithm from Gerald J. Bierman (1974), "Sequential Square Root Filtering and Smoothing of Discrete Linear Systems", Automatica 10, 147-158. NOTE: In case of divergence, epoch_r[0] will be returned as 6.0. Tested and verified 1-6-00. */ int i = 0, j = 0, k = 0, index = 0, retained_index = 0; boolean convergence = false, divergence = false, edit = false; double rms_optical = 0, rms_delay = 0, rms_doppler = 0; double old_rms_optical = 0, old_rms_delay = 0, old_rms_doppler = 0; double convergence_factor = 0.10; double delta = 0, delay = 0, doppler = 0; double observation_residual = 0, observation_chi = 0; int number_of_observations = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; double[] nominal_r = new double[4]; double[] nominal_rprime = new double[4]; double[] initial_r = new double[4]; double[] initial_rprime = new double[4]; double[][] r_variant = new double[9][4]; double[][] rprime_variant = new double[9][4]; double[] this_r = new double[4]; double[] this_rprime = new double[4]; double[] next_r = new double[4]; double[] next_rprime = new double[4]; double[] variant_delta = new double[9]; double[] dev = new double[40002]; double[][] matrix_a = new double[40002][9]; double[][] collision_variant = new double[2000][9]; double[][] phi = new double[9][9]; double[] inter = new double[9]; double[] sensitivity_ra = new double[9]; double[] sensitivity_dec = new double[9]; double[] sensitivity_delay = new double[9]; double[] sensitivity_doppler = new double[9]; double[] radec = new double[3]; double[] row = new double[9]; double[] corrections = new double[9]; double[] observer_r = new double[4]; double[] observer_rprime = new double[4]; double[] delay_doppler = new double[3]; double[] receiver_r = new double[4]; double[] receiver_rprime = new double[4]; double[] EOP = new double[8]; double[][] matrix_R = new double[9][9]; double[] matrix_z = new double[9]; /* How to initialize? */ double[][] matrix_temp = new double[40002][9]; double[] retained_residuals = new double[40002]; double[] initial_residuals = new double[40002]; double[] initial_rms_residuals = new double[4]; double[][] initial_big_p = new double[9][9]; double[] initial_A1_A2_DT = new double[3]; /* Initialize A1 and A2; we will solve for best-fit values */ A1_A2_DT[1] = 2.0E-09; A1_A2_DT[2] = 0.5E-09; /* Initialize all observations as non-excluded for get_residuals */ for (i=1;i<=8000;i++) excluded_observations[i] = 0; /* Using the a priori state vector, determine initial residuals, nominal observables, and the state vector at the tdb time of each observation. Save the a priori state vector in case of divergence. */ get_residuals(residuals,rms_residuals); rms_optical = rms_residuals[1]; rms_delay = rms_residuals[2]; rms_doppler = rms_residuals[3]; for (i=1;i<=3;i++) { initial_r[i] = epoch_r[i]; initial_rprime[i] = epoch_rprime[i]; initial_rms_residuals[i] = rms_residuals[i]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) initial_big_p[i][j] = big_p[i][j]; } initial_A1_A2_DT[1] = 0.0; initial_A1_A2_DT[2] = 0.0; index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) { initial_residuals[i] = residuals[i]; } /* The convergence condition is: The RMS residuals on the current loop must differ by no more than some percentage from the RMS residuals on the preceding loop. (Note that this condition was not my original choice. I tried requiring that the corrections to the epoch state vector must be less than some percentage of their corresponding covariances. However, real-world data is not sufficiently smooth. This new condition uses the observed fact that the RMS residuals decrease with successive loops until the next loop results in almost no change. At this point, the solution has converged.) Clearly, this condition will not be satisfied initially, so we will go through the loop at least once. NOTE: MUST ACCOUNT FOR DIVERGENCE, AS MEASURED BY INCREASING RMS RESIDUALS. If the RMS residuals increase from one loop to the next, the divergence flag will be set, and the original solution returned. */ while ((convergence == false) && (divergence == false)) { /* Clear the counters */ index = 0; retained_index = 0; /* Use the observations to generate rows of matrix_a */ for (i=1;i<=number_of_observations;i++) { /* Assume that the observation will be excluded; if not, the flag will be reset below. */ excluded_observations[i] = 1; /* Generate the state transition matrix corresponding to this observation time */ for (j=1;j<=3;j++) { nominal_r[j] = incremental_state_vector_r[i][j]; nominal_rprime[j] = incremental_state_vector_rprime[i][j]; } /* Initialize the variant trajectories */ if (i == 1) { for (j=1;j<=8;j++) { for (k=1;k<=3;k++) { r_variant[j][k] = epoch_r[k]; rprime_variant[j][k] = epoch_rprime[k]; } } for (j=1;j<=3;j++) { variant_delta[j] = 0.000001*epoch_r[j]; r_variant[j][j] = r_variant[j][j] + variant_delta[j]; variant_delta[j+3] = 0.000001*epoch_rprime[j]; rprime_variant[j+3][j] = rprime_variant[j+3][j] + variant_delta[j+3]; if (j < 3) variant_delta[j+6] = 0.1*A1_A2_DT[j]; } } /* Propogate the variant trajectories, and calculate the transition matrix entries */ for (j=1;j<=8;j++) { for (k=1;k<=3;k++) { this_r[k] = r_variant[j][k]; this_rprime[k] = rprime_variant[j][k]; } if (j > 6) A1_A2_DT[j-6] = A1_A2_DT[j-6] + variant_delta[j]; update(this_r,this_rprime,incremental_state_vector_epoch[i-1],next_r,next_rprime,incremental_state_vector_epoch[i],false,collision_variant); if (j > 6) A1_A2_DT[j-6] = A1_A2_DT[j-6] - variant_delta[j]; for (k=1;k<=3;k++) { r_variant[j][k] = next_r[k]; rprime_variant[j][k] = next_rprime[k]; } for (k=1;k<=3;k++) { phi[k][j] = (r_variant[j][k] - nominal_r[k])/variant_delta[j]; phi[k+3][j] = (rprime_variant[j][k] - nominal_rprime[k])/variant_delta[j]; } if (j > 6) phi[j][j] = 1.0; } /* Generate the sensitivity matrix for this observation */ if (observation_type[i] == 1) { /* Optical observation */ for (j=1;j<=3;j++) { observer_r[j] = observation_r[i][j]; observer_rprime[j] = observation_rprime[i][j]; } for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radec(nominal_r,nominal_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); sensitivity_ra[j] = radec[1] - nominal_observable[index+1]; while (Math.abs(sensitivity_ra[j]) > Math.abs(sensitivity_ra[j] - 2*Math.PI)) sensitivity_ra[j] = sensitivity_ra[j] - 2*Math.PI; while (Math.abs(sensitivity_ra[j]) > Math.abs(sensitivity_ra[j] + 2*Math.PI)) sensitivity_ra[j] = sensitivity_ra[j] + 2*Math.PI; sensitivity_ra[j] = sensitivity_ra[j]/delta; sensitivity_dec[j] = (radec[2] - nominal_observable[index+2])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radec(nominal_r,nominal_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); sensitivity_ra[j+3] = radec[1] - nominal_observable[index+1]; while (Math.abs(sensitivity_ra[j+3]) > Math.abs(sensitivity_ra[j+3] - 2*Math.PI)) sensitivity_ra[j+3] = sensitivity_ra[j+3] - 2*Math.PI; while (Math.abs(sensitivity_ra[j+3]) > Math.abs(sensitivity_ra[j+3] + 2*Math.PI)) sensitivity_ra[j+3] = sensitivity_ra[j+3] + 2*Math.PI; sensitivity_ra[j+3] = sensitivity_ra[j+3]/delta; sensitivity_dec[j+3] = (radec[2] - nominal_observable[index+2])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; if (j < 3) { delta = 0.1*A1_A2_DT[j]; A1_A2_DT[j] = A1_A2_DT[j] + delta; get_observed_radec(nominal_r,nominal_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); sensitivity_ra[j+6] = radec[1] - nominal_observable[index+1]; while (Math.abs(sensitivity_ra[j+6]) > Math.abs(sensitivity_ra[j+6] - 2*Math.PI)) sensitivity_ra[j+6] = sensitivity_ra[j+6] - 2*Math.PI; while (Math.abs(sensitivity_ra[j+6]) > Math.abs(sensitivity_ra[j+6] + 2*Math.PI)) sensitivity_ra[j+6] = sensitivity_ra[j+6] + 2*Math.PI; sensitivity_ra[j+6] = sensitivity_ra[j+6]/delta; sensitivity_dec[j+6] = (radec[2] - nominal_observable[index+2])/delta; A1_A2_DT[j] = A1_A2_DT[j] - delta; } } /* Calculate the residuals and chi; if observations meet the applicable thresholds, calculate the resulting rows of matrix_a */ observation_residual = Math.acos(Math.sin(nominal_observable[index+2])*Math.sin(optical_dec[i]) + Math.cos(nominal_observable[index+2])*Math.cos(optical_dec[i])*Math.cos(nominal_observable[index+1] - optical_ra[i])); if ( (ra_dev[i] > 0.0) && (dec_dev[i] > 0.0) ) observation_chi = Math.sqrt(Math.pow(residuals[index+1]*Math.cos(optical_dec[i])/ra_dev[i],2.0) + Math.pow(residuals[index+2]/dec_dev[i],2.0)); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold) && (ra_dev[i] < 5.0*Math.PI/(3600.0*180.0)) && (dec_dev[i] < 5.0*Math.PI/(3600.0*180.0))) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < optical_residual_threshold) && (ra_dev[i] < 5.0*Math.PI/(3600.0*180.0)) && (dec_dev[i] < 5.0*Math.PI/(3600.0*180.0))) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=8;j++) { row[j] = 0.0; for (k=1;k<=8;k++) row[j] = row[j] + sensitivity_ra[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } for (j=1;j<=8;j++) { row[j] = 0.0; for (k=1;k<=8;k++) row[j] = row[j] + sensitivity_dec[k]*phi[k][j]; matrix_a[retained_index+2][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = ra_dev[i]; dev[retained_index+2] = dec_dev[i]; /* Add these residuals to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; retained_residuals[retained_index+2] = residuals[index+2]; /* Increment the retained observation counter */ retained_index = retained_index + 2; } /* Increment the index */ index = index + 2; } else if (observation_type[i] == 2) { /* Delay observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_delay_receiver_r[i][j]; receiver_rprime[j] = radar_delay_receiver_rprime[i][j]; } for (j=1;j<=7;j++) EOP[j] = radar_delay_transmitter_EOP[i][j]; for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; sensitivity_delay[j] = (delay - nominal_observable[index+1])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; sensitivity_delay[j+3] = (delay - nominal_observable[index+1])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; if (j < 3) { delta = 0.1*A1_A2_DT[j]; A1_A2_DT[j] = A1_A2_DT[j] + delta; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; sensitivity_delay[j+6] = (delay - nominal_observable[index+1])/delta; A1_A2_DT[j] = A1_A2_DT[j] - delta; } } /* Calculate the residual and chi; if observations meet the applicable threshold, calculate the resulting row of matrix_a */ observation_residual = Math.abs(residuals[index+1]); if (delay_dev[i] != 0.0) observation_chi = Math.abs(residuals[index+1]/delay_dev[i]); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold)) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < delay_residual_threshold)) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=8;j++) { row[j] = 0.0; for (k=1;k<=8;k++) row[j] = row[j] + sensitivity_delay[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = delay_dev[i]; /* Add this residual to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; /* Increment the retained observation counter */ retained_index = retained_index + 1; } /* Increment the index */ index = index + 1; } else if (observation_type[i] == 3) { /* Doppler observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_doppler_receiver_r[i][j]; receiver_rprime[j] = radar_doppler_receiver_rprime[i][j]; } for (j=1;j<=7;j++) { EOP[j] = radar_doppler_transmitter_EOP[i][j]; } for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; sensitivity_doppler[j] = (doppler - nominal_observable[index+1])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; sensitivity_doppler[j+3] = (doppler - nominal_observable[index+1])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; if (j < 3) { delta = 0.1*A1_A2_DT[j]; A1_A2_DT[j] = A1_A2_DT[j] + delta; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; sensitivity_doppler[j+6] = (doppler - nominal_observable[index+1])/delta; A1_A2_DT[j] = A1_A2_DT[j] - delta; } } /* Calculate the residual and chi; if observations meet the applicable thresholds, calculate the resulting row of matrix_a */ observation_residual = Math.abs(residuals[index+1]); if (doppler_dev[i] != 0.0) observation_chi = Math.abs(residuals[index+1]/doppler_dev[i]); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold)) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < doppler_residual_threshold)) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=8;j++) { row[j] = 0.0; for (k=1;k<=8;k++) row[j] = row[j] + sensitivity_doppler[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = doppler_dev[i]; /* Add this residual to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; /* Increment the retained observation counter */ retained_index = retained_index + 1; } /* Increment the index */ index = index + 1; } } /* At this point, matrix A is complete. We proceed to convert to a unity covariance form */ /* Should be commented out. */ for (i=1;i<=retained_index;i++) { for (j=1;j<=8;j++) matrix_temp[i][j] = matrix_a[i][j]/dev[i]; retained_residuals[i] = retained_residuals[i]/dev[i]; } for (i=1;i<=retained_index;i++) { for (j=1;j<=8;j++) matrix_a[i][j] = matrix_temp[i][j]; } /* Construct an orthogonal transform T using Givens rotations, and left multiply. matrix_a will now be upper triangular; isolate the upper 8x8 as matrix_R, and isolate the top 8x1 of T x residuals as matrix_z. */ givens_qr(retained_index, 8, matrix_a); read_matrix_Tk(0); for (i=1;i<=8;i++) { matrix_z[i] = 0; for (j=1;j<=retained_index;j++) matrix_z[i] = matrix_z[i] + matrix_Tk[i][j]*retained_residuals[j]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) matrix_R[i][j] = matrix_a[i][j]; } /* Calculate the state vector corrections and covariances. */ invert_nxn(8,matrix_R); for (i=1;i<=8;i++) { corrections[i] = 0; for (k=1;k<=8;k++) /* Square Root Information Filtering assumes that we are in unit covariance form. Since we're often unable to invert matrix_R in that form, we must make the conversion to unit covariance form now, so the results are valid. In this particular calculation, we would multiply matrix_R[i][k] by dev[k] (since matrix_a has been inverted), and we would divide matrix_z by dev[k]; since these precisely cancel, no action is really needed here. */ corrections[i] = corrections[i] + matrix_R[i][k]*matrix_z[k]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) { big_p[i][j] = 0; for (k=1;k<=8;k++) /* Square Root Information Filtering assumes that we are in unit covariance form. Since we're often unable to invert matrix_R in that form, we must make the conversion to unit covariance form now, so the results are valid. In this particular calculation, we would multiply matrix_R[i][k] by dev[k] (since matrix_a has been inverted), and we would multiply matrix_R[j][k] by dev[k] (for the same reason). */ /* big_p[i][j] = big_p[i][j] + matrix_R[i][k]*matrix_R[j][k]*(dev[k]*dev[k]); */ big_p[i][j] = big_p[i][j] + matrix_R[i][k]*matrix_R[j][k]; } } /* Update the epoch state vector */ for (i=1;i<=3;i++) { epoch_r[i] = epoch_r[i] + corrections[i]; System.out.println("epoch_r[i] = " + epoch_r[i]); epoch_rprime[i] = epoch_rprime[i] + corrections[i+3]; System.out.println("epoch_rprime[i] = " + epoch_rprime[i]); if (i < 3) A1_A2_DT[i] = A1_A2_DT[i] + corrections[i+6]; System.out.println("A1_A2_DT[i] = " + A1_A2_DT[i]); } /* Recalculate the residuals using the updated epoch state vector */ get_residuals(residuals,rms_residuals); old_rms_optical = rms_optical; old_rms_delay = rms_delay; old_rms_doppler = rms_doppler; rms_optical = rms_residuals[1]; rms_delay = rms_residuals[2]; rms_doppler = rms_residuals[3]; /* Based on the RMS residuals, evaluate the convergence and divergence condition */ convergence = true; if (Math.abs(old_rms_optical - rms_optical) > (convergence_factor*old_rms_optical)) convergence = false; if (Math.abs(old_rms_delay - rms_delay) > (convergence_factor*old_rms_delay)) convergence = false; if (Math.abs(old_rms_doppler - rms_doppler) > (convergence_factor*old_rms_doppler)) convergence = false; if ((rms_optical >= 1.2*old_rms_optical) && (rms_delay >= 1.2*old_rms_delay) && (rms_doppler >= 1.2*old_rms_doppler)) divergence = true; if ((convergence) && (!edit) && ((chi_testing) || (residual_testing))) { edit = true; convergence = false; } /* We may not have yet converged. However, if the residuals have been reduced, shouldn't we keep this state vector and covariance matrix as the initial data in case the next iteration diverges? */ if ((rms_optical <= old_rms_optical) && (rms_delay <= old_rms_delay) && (rms_doppler <= old_rms_doppler)) { for (i=1;i<=3;i++) { initial_r[i] = epoch_r[i]; initial_rprime[i] = epoch_rprime[i]; initial_rms_residuals[i] = rms_residuals[i]; } initial_A1_A2_DT[1] = A1_A2_DT[1]; initial_A1_A2_DT[2] = A1_A2_DT[2]; for (i=1;i<=8;i++) { for (j=1;j<=8;j++) initial_big_p[i][j] = big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) initial_residuals[i] = residuals[i]; } /* The loop is complete. If the convergence or divergence condition is satisfied, we will exit; if not, we will go around again */ } if (divergence == true) { /* Return initial orbit and residuals, set non-gravitational thrust parameters to zero, and set epoch_r[0] = 6.0. */ for (i=1;i<=3;i++) { epoch_r[i] = initial_r[i]; epoch_rprime[i] = initial_rprime[i]; rms_residuals[i] = initial_rms_residuals[i]; } A1_A2_DT[1] = initial_A1_A2_DT[1]; A1_A2_DT[2] = initial_A1_A2_DT[2]; epoch_r[0] = 6.0; for (i=1;i<=8;i++) { for (j=1;j<=8;j++) big_p[i][j] = initial_big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) { residuals[i] = initial_residuals[i]; } } else if (divergence == false) epoch_r[0] = 0.0; } public void remove_minor_planet_from_list(String removee) { /* Procedure to remove a given minor planet (removee) from the list of minor planets for whom data files exist; the corresponding data file is then erased. */ int i = 0; boolean flag = false; for (i=1;i<=(number_of_minor_planets-1);i++) { if ((mp_list[i] == removee) || (flag == true)) { flag = true; mp_list[i] = mp_list[i+1]; } } number_of_minor_planets = number_of_minor_planets - 1; try { /* Create File object doomed, named by string removee, and delete it */ File doomed = new File(removee); boolean deleted = doomed.delete(); } catch (SecurityException se) { System.out.println("Error -- " + se.toString()); } } public void add_minor_planet_to_list(String addee) { /* Procedure to add a new minor planet (addee) to the list of minor planets for whom data files exist. The new minor planet is added in such a way as to preserve the alphabetical order of the list. */ int i = 0, j = 0, flag = 0; for (i=1;i<=number_of_minor_planets;i++) { if ((addee.compareTo(mp_list[i]) < 0) && (flag == 0)) { for (j=number_of_minor_planets;j>=i;j--) mp_list[j+1] = mp_list[j]; mp_list[i] = addee; flag = 1; } } if (flag == 0) mp_list[number_of_minor_planets + 1] = addee; number_of_minor_planets = number_of_minor_planets + 1; } public void delete_an_observation(int obs_n) { /* Method to remove an observation from the data file of the minor planet */ int i = 0, index = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations, j = 0; if (observation_type[obs_n] == 1) number_of_optical_observations = number_of_optical_observations - 1; else if (observation_type[obs_n] == 2) number_of_delay_observations = number_of_delay_observations - 1; else if (observation_type[obs_n] == 3) number_of_doppler_observations = number_of_doppler_observations - 1; for (i=obs_n;i<=(index-1);i++) { observation_type[i] = observation_type[i+1]; incremental_state_vector_epoch[i] = incremental_state_vector_epoch[i+1]; for (j=1;j<=3;j++) { incremental_state_vector_r[i][j] = incremental_state_vector_r[i+1][j]; incremental_state_vector_rprime[i][j] = incremental_state_vector_rprime[i+1][j]; } optical_ra[i] = optical_ra[i+1]; ra_dev[i] = ra_dev[i+1]; optical_dec[i] = optical_dec[i+1]; dec_dev[i] = dec_dev[i+1]; optical_time[i] = optical_time[i+1]; visual_magnitude[i] = visual_magnitude[i+1]; visual_magnitude_dev[i] = visual_magnitude_dev[i+1]; observation_geocentric[i] = observation_geocentric[i+1]; observation_latitude[i] = observation_latitude[i+1]; observation_longitude[i] = observation_longitude[i+1]; observation_altitude[i] = observation_altitude[i+1]; for (j=1;j<=7;j++) observation_EOP[i][j] = observation_EOP[i+1][j]; for (j=1;j<=3;j++) { observation_r[i][j] = observation_r[i+1][j]; observation_rprime[i][j] = observation_rprime[i+1][j]; } radar_delay[i] = radar_delay[i+1]; delay_dev[i] = delay_dev[i+1]; radar_delay_receiver_time[i] = radar_delay_receiver_time[i+1]; radar_delay_receiver_latitude[i] = radar_delay_receiver_latitude[i+1]; radar_delay_receiver_longitude[i] = radar_delay_receiver_longitude[i+1]; radar_delay_receiver_altitude[i] = radar_delay_receiver_altitude[i+1]; for (j=1;j<=7;j++) radar_delay_receiver_EOP[i][j] = radar_delay_receiver_EOP[i+1][j]; radar_delay_receiver_tdb_minus_utc[i] = radar_delay_receiver_tdb_minus_utc[i+1]; for (j=1;j<=3;j++) { radar_delay_receiver_r[i][j] = radar_delay_receiver_r[i+1][j]; radar_delay_receiver_rprime[i][j] = radar_delay_receiver_rprime[i+1][j]; } radar_delay_transmitter_latitude[i] = radar_delay_transmitter_latitude[i+1]; radar_delay_transmitter_longitude[i] = radar_delay_transmitter_longitude[i+1]; radar_delay_transmitter_altitude[i] = radar_delay_transmitter_altitude[i+1]; for (j=1;j<=7;j++) radar_delay_transmitter_EOP[i][j] = radar_delay_transmitter_EOP[i+1][j]; radar_delay_transmitter_tdb_minus_utc[i] = radar_delay_transmitter_tdb_minus_utc[i+1]; radar_delay_transmitter_frequency[i] = radar_delay_transmitter_frequency[i+1]; radar_doppler[i] = radar_doppler[i+1]; doppler_dev[i] = doppler_dev[i+1]; radar_doppler_receiver_time[i] = radar_doppler_receiver_time[i+1]; radar_doppler_receiver_latitude[i] = radar_doppler_receiver_latitude[i+1]; radar_doppler_receiver_longitude[i] = radar_doppler_receiver_longitude[i+1]; radar_doppler_receiver_altitude[i] = radar_doppler_receiver_altitude[i+1]; for (j=1;j<=7;j++) radar_doppler_receiver_EOP[i][j] = radar_doppler_receiver_EOP[i+1][j]; radar_doppler_receiver_tdb_minus_utc[i] = radar_doppler_receiver_tdb_minus_utc[i+1]; for (j=1;j<=3;j++) { radar_doppler_receiver_r[i][j] = radar_doppler_receiver_r[i+1][j]; radar_doppler_receiver_rprime[i][j] = radar_doppler_receiver_rprime[i+1][j]; } radar_doppler_transmitter_latitude[i] = radar_doppler_transmitter_latitude[i+1]; radar_doppler_transmitter_longitude[i] = radar_doppler_transmitter_longitude[i+1]; radar_doppler_transmitter_altitude[i] = radar_doppler_transmitter_altitude[i+1]; for (j=1;j<=7;j++) radar_doppler_transmitter_EOP[i][j] = radar_doppler_transmitter_EOP[i+1][j]; radar_doppler_transmitter_tdb_minus_utc[i] = radar_doppler_transmitter_tdb_minus_utc[i+1]; radar_doppler_transmitter_frequency[i] = radar_doppler_transmitter_frequency[i+1]; } } double apparent_magnitude(double jultime, double pos[]) { /* Method to predict the apparent magnitude of an asteroid at TDB jultime, given its barycentric equatorial position at that time. Method uses the instance variables absolute_magnitude_H and slope_parameter_G. Algorithm taken from Jean Meeus, Astronomical Algorithms, pp. 216-217. Algorithm is modified in accordance with E. Bowell, "The IAU Two-Parameter Magnitude System for Asteroids", in Gehrels, "Asteroids 2", 1989, pp. 549-554. */ double little_r = 0, big_r = 0, delta = 0, phi1 = 0, phi2 = 0, beta = 0, magnitude = 0, phi1l = 0, phi2l = 0, phi1s = 0, phi2s = 0, w = 0; double[] earth_r = new double[4]; double[] earth_rprime = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; /* If the slope_parameter_G and absolute_magnitude_H are both zero, return '-99.0'. Similarly, apparent magnitude estimate is valid only for slope_parameter_G between zero and one; if G is outside of that range, return '-99.0'. */ if (((slope_parameter_G == 0.0) && (absolute_magnitude_H == 0.0)) || (slope_parameter_G < 0.0) || (slope_parameter_G > 1.0)) magnitude = -99.0; else { /* Proceed with the algorithm. */ /* Get the positions of the Earth and Sun at TDB jultime. Note that I am not accounting for light time-of-flight here. Given the approximate nature of the model and the limited precision of its parameters, I firmly believe that accounting for light time-of-flight would be a waste of CPU cycles, and would introduce an unnecessary delay into overall processing. */ get_planet_posvel(jultime,3,earth_r,earth_rprime); get_planet_posvel(jultime,11,sun_r,sun_rprime); /* Calculate the distances */ big_r = Math.sqrt(Math.pow((earth_r[1] - sun_r[1]),2) + Math.pow((earth_r[2] - sun_r[2]),2) + Math.pow((earth_r[3] - sun_r[3]),2)); little_r = Math.sqrt(Math.pow((pos[1] - sun_r[1]),2) + Math.pow((pos[2] - sun_r[2]),2) + Math.pow((pos[3] - sun_r[3]),2)); delta = Math.sqrt(Math.pow((pos[1] - earth_r[1]),2) + Math.pow((pos[2] - earth_r[2]),2) + Math.pow((pos[3] - earth_r[3]),2)); /* Calculate the phase angle of the minor planet */ beta = Math.acos((Math.pow(little_r,2) + Math.pow(delta,2) - Math.pow(big_r,2))/(2.0*little_r*delta)); /* The apparent magnitude estimate is valid only for phase angles between zero and 120 degrees. If we are outside of that range, return '-99.0'. */ if (beta > 120.0*Math.PI/180.0) magnitude = -99.0; else { /* Calculate the phis */ /* Note that I am using Bowell's formulation here, rather than the IAU Commission 20 (1985) form, due to Bowell's contention that his is more accurate. */ phi1l = Math.exp(-3.332*Math.pow(Math.tan(beta/2.0),0.631)); phi2l = Math.exp(-1.862*Math.pow(Math.tan(beta/2.0),1.218)); phi1s = 1.0 - 0.986*Math.sin(beta)/(0.119 + 1.341*Math.sin(beta) - 0.754*Math.pow(Math.sin(beta),2.0)); phi2s = 1.0 - 0.238*Math.sin(beta)/(0.119 + 1.341*Math.sin(beta) - 0.754*Math.pow(Math.sin(beta),2.0)); w = Math.exp(-90.56*Math.pow(Math.tan(beta/2.0),2.0)); phi1 = w*phi1s + (1.0 - w)*phi1l; phi2 = w*phi2s + (1.0 - w)*phi2l; /* Calculate the apparent magnitude. */ magnitude = absolute_magnitude_H + 5.0*Math.log(little_r*delta)/Math.log(10.0) - 2.5*Math.log((1.0 - slope_parameter_G)*phi1 + slope_parameter_G*phi2)/Math.log(10.0); } } return magnitude; } public void solve_for_absolute_magnitude() { /* Method to solve for the absolute magnitude H and slope parameter G of an asteroid, using visual-range brightness measurements. Once these parameters are known, the future visual-range brightness of the asteroid can be predicted as part of an ephemeris. Note that this method will run after "get_residuals"; thus, incremental_state_vector_r will be available. The instance variables slope_parameter_G and absolute_magnitude_H are directly updated by this method. The method also solves for estimates of asteroid radius; the instance variable minor_planet_radius is directly updated by the method. */ /* One issue must be addressed. Not every optical observation will include a visual-range brightness measurement. A minimum of one measurement is needed to calculate the G and H parameters. So I believe we are faced with three cases. Either we have zero visual-range brightness measurements, or we have exactly one visual-range brightness measurement, or we have two or more visual-range brightness measurements. In the first case, G and H are indeterminate. In the second case, G and H are precisely determined. In the third case, G and H are overdetermined, and we must use least-squares. */ /* Note that this has been a very challenging algorithm to create. The biggest problem is that real-world magnitude measurements are extremely imprecise. Moreover, the sensitivity matrix is ill-conditioned; the partial wrt H is exactly one, while the partial wrt G is approx. -1. As a result, least-squares does not converge well. For this reason, I tried a brute-force alternative approach; for each value of G (in increments of 0.01), calculate the range of H's, then run through each H in the range (in increments of 0.1) and determine which combination gave the lowest rms residuals. This was simply too computationally intensive. */ /* The algorithm I'm now using is due to Dr. Ted Bowell at Lowell Observatory, who helped develop the G,H system in the mid-80's. Reference is Ed. Bowell, "The IAU Two-Parameter Magnitude System for Asteroids" in Gehrels "Asteroids II", 1989, pp. 549-555.*/ int number_of_brightness_observations = 0, index = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; int[] pointer = new int[8001]; double[] phi = new double[3]; double[] earth_r = new double[4]; double[] earth_rprime = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double[] reduced_magnitude = new double[20001]; double[] epsilon = new double[20001]; double[] big_I = new double[20001]; double[] phi1 = new double[20001]; double[] phi2 = new double[20001]; double[] delta_m = new double[20001]; double A = 0, B = 0, big_r = 0, beta = 0, r = 0, delta = 0, phi1l = 0, phi2l = 0, phi1s = 0, phi2s = 0, w = 0, h11 = 0, h12 = 0, h22 = 0, g1 = 0, g2 = 0, big_D = 0, a1 = 0, a2 = 0, top_sum = 0, bottom_sum = 0, albedo = 0, diameter = 0; int i = 0, j = 0, k = 0; /* Survey the observations, and determine the number of visual-range brightness measurements, with phase angle between 0 and 120 degrees. */ for (i=1;i<=index;i++) { if ((observation_type[i] == 1) && (visual_magnitude[i] != -99.0)) { /* Calculate the phase angle */ /* Get the positions of the Earth and Sun at incremental_state_vector_epoch[i]. Note that I am not accounting for light time-of-flight here. Given the approximate nature of the model and the limited precision of its parameters, I firmly believe that accounting for light time-of-flight would be a waste of CPU cycles, and would introduce an unnecessary delay into overall processing. */ get_planet_posvel(incremental_state_vector_epoch[i],3,earth_r,earth_rprime); get_planet_posvel(incremental_state_vector_epoch[i],11,sun_r,sun_rprime); /* Calculate the distances */ big_r = Math.sqrt(Math.pow((earth_r[1] - sun_r[1]),2) + Math.pow((earth_r[2] - sun_r[2]),2) + Math.pow((earth_r[3] - sun_r[3]),2)); r = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - sun_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - sun_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - sun_r[3]),2)); delta = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - earth_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - earth_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - earth_r[3]),2)); /* Calculate the phase angle of the asteroid */ beta = Math.acos((Math.pow(r,2) + Math.pow(delta,2) - Math.pow(big_r,2))/(2.0*r*delta)); if (beta <= 120.0*Math.PI/180.0) { /* Valid visual magnitude observation. Compile quantities for least-squares */ number_of_brightness_observations = number_of_brightness_observations + 1; pointer[number_of_brightness_observations] = i; reduced_magnitude[number_of_brightness_observations] = visual_magnitude[i] - 5.0*Math.log(r*delta)/Math.log(10.0); epsilon[number_of_brightness_observations] = visual_magnitude_dev[i]; big_I[number_of_brightness_observations] = Math.pow(10.0,(-0.4*reduced_magnitude[number_of_brightness_observations])); /* Calculate the phis */ /* Note that I am using Bowell's formulation here, rather than the IAU Commission 20 (1985) form, due to Bowell's contention that his is more accurate. */ phi1l = Math.exp(-3.332*Math.pow(Math.tan(beta/2.0),0.631)); phi2l = Math.exp(-1.862*Math.pow(Math.tan(beta/2.0),1.218)); phi1s = 1.0 - 0.986*Math.sin(beta)/(0.119 + 1.341*Math.sin(beta) - 0.754*Math.pow(Math.sin(beta),2.0)); phi2s = 1.0 - 0.238*Math.sin(beta)/(0.119 + 1.341*Math.sin(beta) - 0.754*Math.pow(Math.sin(beta),2.0)); w = Math.exp(-90.56*Math.pow(Math.tan(beta/2.0),2.0)); phi1[number_of_brightness_observations] = w*phi1s + (1.0 - w)*phi1l; phi2[number_of_brightness_observations] = w*phi2s + (1.0 - w)*phi2l; } } } System.out.println("number_of_brightness_observations = " + number_of_brightness_observations); /* Based on the number_of_brightness_observations, handle each case separately. */ /* Case 1: number_of_brightness_observations = 0 */ if (number_of_brightness_observations == 0) { /* G and H are indeterminate; return zeros. */ slope_parameter_G = 0; absolute_magnitude_H = 0; } /* Case 2: number_of_brightness_observations = 1 */ else if (number_of_brightness_observations == 1) { /* G and H are precisely determined */ /* An earlier version of this algorithm used two observations to solve for G and H. This did not work well in practice, due to the imprecision of magnitude measurements; divide-by-zero errors were quite common. Therefore, I am now assuming that G = 0.15 (the mid-range value for typical asteroids), and solving for H. In fact, this appears to be the strategy employed by Marsden, judging from his MPECs. */ slope_parameter_G = 0.15; absolute_magnitude_H = reduced_magnitude[1] + 2.5*Math.log((1.0 - slope_parameter_G)*phi1[1] + slope_parameter_G*phi2[1])/Math.log(10.0); System.out.println("slope_parameter_G = " + slope_parameter_G); System.out.println("absolute_magnitude_H = " + absolute_magnitude_H); } /* Case 3: number_of_brightness_observations > 1. */ else if (number_of_brightness_observations > 1) { /* G and H are over-determined. Use least-squares. */ for (i=1;i<=number_of_brightness_observations;i++) { h11 = h11 + phi1[i]*phi1[i]/Math.pow(epsilon[i]*big_I[i],2.0); h12 = h12 + phi1[i]*phi2[i]/Math.pow(epsilon[i]*big_I[i],2.0); h22 = h22 + phi2[i]*phi2[i]/Math.pow(epsilon[i]*big_I[i],2.0); /* Possible error in Bowell's paper */ /* g1 = g1 + phi1[i]/Math.pow(epsilon[i]*big_I[i],2.0); g2 = g2 + phi2[i]/Math.pow(epsilon[i]*big_I[i],2.0); */ g1 = g1 + phi1[i]/(Math.pow(epsilon[i],2.0)*big_I[i]); g2 = g2 + phi2[i]/(Math.pow(epsilon[i],2.0)*big_I[i]); } big_D = h11*h22 - Math.pow(h12,2.0); a1 = (h22*g1 - h12*g2)/big_D; a2 = (h11*g2 - h12*g1)/big_D; slope_parameter_G = a2/(a1 + a2); absolute_magnitude_H = -2.5*Math.log(a1 + a2)/Math.log(10.0); } System.out.println("slope_parameter_G = " + slope_parameter_G); System.out.println("absolute_magnitude_H = " + absolute_magnitude_H); /* According to Bowell, G should fall between zero and 0.5. If the solution lies outside that range, we will adopt Marsden's practice of assuming G = 0.15, and calculating H. */ if ((slope_parameter_G < 0.0) || (slope_parameter_G > 0.5)) { slope_parameter_G = 0.15; /* Solve for H as a function of G */ for (i=1;i<=number_of_brightness_observations;i++) { delta_m[i] = -2.5*Math.log((1.0 - slope_parameter_G)*phi1[i] + slope_parameter_G*phi2[i])/Math.log(10.0); top_sum = top_sum + (reduced_magnitude[i] - delta_m[i])/Math.pow(epsilon[i],2.0); bottom_sum = bottom_sum + 1.0/Math.pow(epsilon[i],2.0); } absolute_magnitude_H = top_sum/bottom_sum; } /* Knowing G and H, we can now assume an albedo (based on G), and estimate the asteroid's radius. The albedo will be modeled as a linear function of G. From Tedesco et al., "A Three Parameter Asteroid Taxonomy", The Astronomical Journal, Vol. 97, number 2, pp. 580-607, we have C-class albedo = 0.029-0.075 mean 0.048 (G = 0.09 due to Bowell) S-class albedo = 0.100-0.300 mean 0.186 (G = 0.23 due to Bowell) M-class albedo = 0.080-0.200 mean 0.141 (G = 0.21 due to Bowell) */ if (number_of_brightness_observations > 0) { albedo = 0.048 + (slope_parameter_G - 0.09); if (albedo < 0.029) albedo = 0.029; if (albedo > 0.3) albedo = 0.3; diameter = Math.pow(10.0,((6.259 - Math.log(albedo)/Math.log(10.0) - 0.4*absolute_magnitude_H)/2.0)); minor_planet_radius = (diameter/2.0)/au; } } public void solve_for_comet_absolute_magnitude() { /* Method to solve for the absolute magnitude and slope parameter of a comet, using visual-range brightness measurements. Once these parameters are known, the future visual-range brightness of the comet can be predicted as part of an ephemeris. Note that this method will run after "get_residuals"; thus, incremental_state_vector_r will be available. The instance variables comet_slope_parameter and comet_absolute_magnitude are directly updated by this method. */ /* One issue must be addressed. Not every optical observation will include a visual-range brightness measurement. A minimum of two measurement is needed to calculate the absolute magnitude and slope parameters. So I believe we are faced with two cases. Either we have less than two visual-range brightness measurements, or we have two or more visual-range brightness measurement. In the first case, the parameters are indeterminate. In the second case, the parameters are either precisely or over-determined, and we will use least-squares. */ /* Algorithm based on Jean Meeus, Astronomical Algorithms, pp. 216-217. Uses least-squares formulation from Burden and Faires, "Numerical Analysis" , 3rd edition (1985), p. 364. */ int number_of_brightness_observations = 0, index = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; int[] pointer = new int[20001]; double[] earth_r = new double[4]; double[] earth_rprime = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double r = 0, delta = 0, sumxy = 0, sumx = 0, sumy = 0, sumx2 = 0; int i = 0, j = 0, k = 0; /* Survey the observations, and determine the number of visual-range brightness measurements */ for (i=1;i<=index;i++) { if ((observation_type[i] == 1) && (visual_magnitude[i] != -99.0)) { /* Valid visual magnitude observation. Compile quantities for least-squares */ number_of_brightness_observations = number_of_brightness_observations + 1; pointer[number_of_brightness_observations] = i; /* Get the positions of the Earth and Sun at incremental_state_vector_epoch[i]. Note that I am not accounting for light time-of-flight here. Given the approximate nature of the model and the limited precision of its parameters, I firmly believe that accounting for light time-of-flight would be a waste of CPU cycles, and would introduce an unnecessary delay into overall processing. */ get_planet_posvel(incremental_state_vector_epoch[i],3,earth_r,earth_rprime); get_planet_posvel(incremental_state_vector_epoch[i],11,sun_r,sun_rprime); /* Calculate the distances */ r = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - sun_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - sun_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - sun_r[3]),2)); delta = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - earth_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - earth_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - earth_r[3]),2)); /* Calculate the least-squares quantities */ sumxy = sumxy + (Math.log(r)/Math.log(10.0))*(visual_magnitude[i] - 5.0*Math.log(delta)/Math.log(10.0)); sumx = sumx + Math.log(r)/Math.log(10.0); sumy = sumy + (visual_magnitude[i] - 5.0*Math.log(delta)/Math.log(10.0)); sumx2 = sumx2 + Math.pow(Math.log(r)/Math.log(10.0),2.0); } } System.out.println("number_of_brightness_observations = " + number_of_brightness_observations); /* Based on the number_of_brightness_observations, handle each case separately. */ /* Case 1: number_of_brightness_observations < 2 */ if (number_of_brightness_observations < 2) { /* parameters are indeterminate; return zeros. */ comet_slope_parameter = 0; comet_absolute_magnitude = 0; } /* Case 2: number_of_brightness_observations >= 2 */ else if (number_of_brightness_observations >= 2) { comet_slope_parameter = (number_of_brightness_observations*sumxy - sumx*sumy)/(number_of_brightness_observations*sumx2 - Math.pow(sumx,2.0)); comet_absolute_magnitude = (sumx2*sumy - sumxy*sumx)/(number_of_brightness_observations*sumx2 - Math.pow(sumx,2.0)); System.out.println("comet_slope_parameter = " + comet_slope_parameter); System.out.println("comet_absolute_magnitude = " + comet_absolute_magnitude); } System.out.println("comet_slope_parameter = " + comet_slope_parameter); System.out.println("comet_absolute_magnitude = " + comet_absolute_magnitude); /* According to Marsden and Meeus, comet_slope_parameter should fall between zero and twenty. If the solution lies outside that range, we will assume comet_slope_parameter = 4.0 (appears to be a Marsden default), and calculate comet_absolute_magnitude from the "b normal equation". */ if ((comet_slope_parameter < 0.0) || (comet_slope_parameter > 20.0)) { comet_slope_parameter = 4.0; /* Solve for comet_absolute_magnitude as a function of comet_slope_parameter */ comet_absolute_magnitude = (sumy - comet_slope_parameter*sumx)/number_of_brightness_observations; } } double comet_apparent_magnitude(double jultime, double pos[]) { /* Method to predict the apparent magnitude of a comet at TDB jultime, given its barycentric equatorial position at that time. Method uses the instance variables comet_absolute_magnitude and comet_slope_parameter. Algorithm taken from Jean Meeus, Astronomical Algorithms, pp. 216-217. */ double little_r = 0, delta = 0, magnitude = 0; double[] earth_r = new double[4]; double[] earth_rprime = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; /* If the comet_slope_parameter and comet_absolute_magnitude are both zero, return '-99.0'. Similarly, apparent magnitude estimate is valid only for comet_slope_parameter between zero and twenty; if comet_slope_parameter is outside of that range, return '-99.0'. */ if (((comet_slope_parameter == 0.0) && (comet_absolute_magnitude == 0.0)) || (comet_slope_parameter < 0.0) || (comet_slope_parameter > 20.0)) magnitude = -99.0; else { /* Proceed with the algorithm. */ /* Get the positions of the Earth and Sun at TDB jultime. Note that I am not accounting for light time-of-flight here. Given the approximate nature of the model and the limited precision of its parameters, I firmly believe that accounting for light time-of-flight would be a waste of CPU cycles, and would introduce an unnecessary delay into overall processing. */ get_planet_posvel(jultime,3,earth_r,earth_rprime); get_planet_posvel(jultime,11,sun_r,sun_rprime); /* Calculate the distances */ little_r = Math.sqrt(Math.pow((pos[1] - sun_r[1]),2) + Math.pow((pos[2] - sun_r[2]),2) + Math.pow((pos[3] - sun_r[3]),2)); delta = Math.sqrt(Math.pow((pos[1] - earth_r[1]),2) + Math.pow((pos[2] - earth_r[2]),2) + Math.pow((pos[3] - earth_r[3]),2)); /* Calculate the apparent magnitude. */ magnitude = comet_absolute_magnitude + 5.0*Math.log(delta)/Math.log(10.0) + comet_slope_parameter*Math.log(little_r)/Math.log(10.0); } return magnitude; } void givens_qr(int m, int n, double[][] matrix_a) { /* Method to determine the matrix_T (dimension m x m) that, when left-multiplied against matrix_a (dimension m x n), produces an upper triangular matrix. matrix_T is initialized as the identity; as it is passed through repeated calls to givens_row_rot, matrix_T is accumulated, one Givens rotation at a time. Note that matrix_a is returned as upper triangular. Reference is Gene H. Golub and Charles F. van Loan, "Matrix Computations", 2nd edition (1989) pp. 214-215. */ int i = 0, j = 0, index = 0, new_index = 0, i_index = 0, old_i_index = 0, k_index = 0, old_k_index = 0; double[] cs = new double[3]; /* Initialize matrix_T to the identity */ for (i=1;i<=m;i++) { index = (int)(Math.floor(i/500)); for (j=1;j<=m;j++) { matrix_Tk[i - 500*index][j] = 0.0; if (i == j) matrix_Tk[i - 500*index][j] = 1.0; } new_index = (int)(Math.floor((i+1)/500)); if ((i == m) || (new_index != index)) output_matrix_Tk(index); } /* Execute the algorithm */ for (j=1;j<=n;j++) { old_i_index = -1; old_k_index = -1; for (i=m;i>=j+1;i--) { i_index = (int)(Math.floor((i-1)/500)); if (i_index != old_i_index) read_matrix_Ti(i_index); k_index = (int)(Math.floor(i/500)); if (k_index != old_k_index) read_matrix_Tk(k_index); /* Calculate the required rotation */ givens(matrix_a[i-1][j], matrix_a[i][j], cs); /* Rotate matrix_a and matrix_QT */ givens_row_rot(matrix_a, j, n, m, cs[1], cs[2], i-1, i, i_index, k_index); old_i_index = i_index; old_k_index = k_index; if (i == j+1) output_matrix_Tk(k_index); if (i_index != k_index) { output_matrix_Ti(i_index); output_matrix_Tk(k_index); } } } } void givens_row_rot(double[][] matrix_A, int p, int q, int r, double c, double s, int i, int k, int i_index, int k_index) { /* Method to implement a Givens row rotation. The input matrix_A and matrix_T are overwritten by the rotated output. The only affected rows of matrix_A are i and k, from columns p to q; the only affected rows of matrix_T are i and k, from columns 1 to r. The matrix_T is the cumulative Givens matrix (see method givens_qr). Reference is Gene H. Golub and Charles F. van Loan, "Matrix Computations", 2nd edition (1989) pp. 202-203. */ int j = 0; double tau1 = 0, tau2 = 0; /* Execute the rotation. */ for (j=p;j<=q;j++) { tau1 = matrix_A[i][j]; tau2 = matrix_A[k][j]; matrix_A[i][j] = c*tau1 - s*tau2; if (Math.abs(matrix_A[i][j]) < 2.0E-15) matrix_A[i][j] = 0.0; matrix_A[k][j] = s*tau1 + c*tau2; if (Math.abs(matrix_A[k][j]) < 2.0E-15) matrix_A[k][j] = 0.0; } for (j=1;j<=r;j++) { tau2 = matrix_Tk[k-500*k_index][j]; if (i_index != k_index) { tau1 = matrix_Ti[i-500*i_index][j]; matrix_Ti[i-500*i_index][j] = c*tau1 - s*tau2; if (Math.abs(matrix_Ti[i-500*i_index][j]) < 2.0E-15) matrix_Ti[i-500*i_index][j] = 0.0; } else { tau1 = matrix_Tk[i-500*i_index][j]; matrix_Tk[i-500*i_index][j] = c*tau1 - s*tau2; if (Math.abs(matrix_Tk[i-500*i_index][j]) < 2.0E-15) matrix_Tk[i-500*i_index][j] = 0.0; } matrix_Tk[k-500*k_index][j] = s*tau1 + c*tau2; if (Math.abs(matrix_Tk[k-500*k_index][j]) < 2.0E-15) matrix_Tk[k-500*k_index][j] = 0.0; } } void output_matrix_Tk(int index) { /* Method to send the index'th subsection of matrix_T to indexed text-file output. */ int i = 0, j = 0, j_index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; try{ /* Create output stream */ FileWriter fw = new FileWriter("matrix_T" + Integer.toString(index) + ".txt"); BufferedWriter out = new BufferedWriter(fw); for (i = 0; i <= 499; i++) { for (j = 1; j <= j_index; j++) { out.write(Double.toString(matrix_Tk[i][j])); out.newLine(); } } out.close(); } catch (IOException e) { System.out.println("Error -- " + e.toString()); } catch (SecurityException se) { System.out.println("Error -- " + se.toString()); } } void output_matrix_Ti(int index) { /* Method to send the index'th subsection of matrix_T to indexed text-file output. */ int i = 0, j = 0, j_index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; try{ /* Create output stream */ FileWriter fw = new FileWriter("matrix_T" + Integer.toString(index) + ".txt"); BufferedWriter out = new BufferedWriter(fw); for (i = 0; i <= 499; i++) { for (j = 1; j <= j_index; j++) { out.write(Double.toString(matrix_Ti[i][j])); out.newLine(); } } out.close(); } catch (IOException e) { System.out.println("Error -- " + e.toString()); } catch (SecurityException se) { System.out.println("Error -- " + se.toString()); } } public void read_matrix_Ti(int index) { /* Procedure to read from disk an indexed subsection of matrix_T. */ int i = 0, j = 0, j_index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; String line = " "; try { FileReader file1 = new FileReader("matrix_T" + Integer.toString(index) + ".txt"); BufferedReader buff1 = new BufferedReader(file1); for (i = 0; i <= 499; i++) { for (j = 1; j <= j_index; j++) { line = buff1.readLine(); matrix_Ti[i][j] = Double.valueOf(line).doubleValue(); } } buff1.close(); } catch (FileNotFoundException exceptFNF) { System.out.println("Error -- " + exceptFNF.toString()); } catch (NullPointerException exceptNP) { System.out.println("Error -- " + exceptNP.toString()); } catch (EOFException exceptEOF) { System.out.println("Error -- " + exceptEOF.toString()); } catch (IOException exceptIO) { System.out.println("Error -- " + exceptIO.toString()); } } public void read_matrix_Tk(int index) { /* Procedure to read from disk an indexed subsection of matrix_T. */ int i = 0, j = 0, j_index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; String line = " "; try { FileReader file1 = new FileReader("matrix_T" + Integer.toString(index) + ".txt"); BufferedReader buff1 = new BufferedReader(file1); for (i = 0; i <= 499; i++) { for (j = 1; j <= j_index; j++) { line = buff1.readLine(); matrix_Tk[i][j] = Double.valueOf(line).doubleValue(); } } buff1.close(); } catch (FileNotFoundException exceptFNF) { System.out.println("Error -- " + exceptFNF.toString()); } catch (NullPointerException exceptNP) { System.out.println("Error -- " + exceptNP.toString()); } catch (EOFException exceptEOF) { System.out.println("Error -- " + exceptEOF.toString()); } catch (IOException exceptIO) { System.out.println("Error -- " + exceptIO.toString()); } } void givens(double a, double b, double[] cs) { /* Method to calculate the Givens rotation factors, given scalars a and b; the objective will be to set b to zero. Reference is Gene H. Golub and Charles F. van Loan, "Matrix Computations", 2nd edition (1989) pp. 202. */ double tau = 0; if (b == 0.0) { cs[1] = 1; cs[2] = 0; } else if (Math.abs(b) > Math.abs(a)) { tau = -a/b; cs[2] = 1.0/Math.sqrt(1.0 + tau*tau); cs[1] = cs[2]*tau; } else { tau = -b/a; cs[1] = 1.0/Math.sqrt(1.0 + tau*tau); cs[2] = cs[1]*tau; } } public void asteroid_slope_based_abs_mag() { /* Method to solve for the absolute magnitude H and radius of an asteroid, given an assumed slope_parameter_G, using visual-range brightness measurements. NOTE that this method assumes that incremental_state_vector_r will be available; if it is not, the button permitting use of this method should not appear. (The rationale is that this method requires all available optical observations. If incremental_state_vector_r is available, this implies that least squares would have been run, meaning that all of the observations are available. On the other hand, if incremental_state_vector_r is NOT available, this implies that least squares must not have been run, meaning that all of the observations may not be available.) The instance variables absolute_magnitude_H and minor_planet_radius are directly updated by this method. */ /* The algorithm I'm now using is due to Dr. Ted Bowell at Lowell Observatory, who helped develop the G,H system in the mid-80's. Reference is Ed. Bowell, "The IAU Two-Parameter Magnitude System for Asteroids" in Gehrels "Asteroids II", 1989, pp. 549-555.*/ int number_of_brightness_observations = 0, index = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; int[] pointer = new int[20001]; double[] phi = new double[3]; double[] earth_r = new double[4]; double[] earth_rprime = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double[] reduced_magnitude = new double[20001]; double[] epsilon = new double[20001]; double[] phi1 = new double[20001]; double[] phi2 = new double[20001]; double[] delta_m = new double[20001]; double big_r = 0, beta = 0, r = 0, delta = 0, phi1l = 0, phi2l = 0, phi1s = 0, phi2s = 0, w = 0, top_sum = 0, bottom_sum = 0, albedo = 0, diameter = 0; int i = 0, j = 0, k = 0; /* Survey the observations, and determine the number of visual-range brightness measurements, with phase angle between 0 and 120 degrees. */ for (i=1;i<=index;i++) { if ((observation_type[i] == 1) && (visual_magnitude[i] != -99.0)) { /* Calculate the phase angle */ /* Get the positions of the Earth and Sun at incremental_state_vector_epoch[i]. Note that I am not accounting for light time-of-flight here. Given the approximate nature of the model and the limited precision of its parameters, I firmly believe that accounting for light time-of-flight would be a waste of CPU cycles, and would introduce an unnecessary delay into overall processing. */ get_planet_posvel(incremental_state_vector_epoch[i],3,earth_r,earth_rprime); get_planet_posvel(incremental_state_vector_epoch[i],11,sun_r,sun_rprime); /* Calculate the distances */ big_r = Math.sqrt(Math.pow((earth_r[1] - sun_r[1]),2) + Math.pow((earth_r[2] - sun_r[2]),2) + Math.pow((earth_r[3] - sun_r[3]),2)); r = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - sun_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - sun_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - sun_r[3]),2)); delta = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - earth_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - earth_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - earth_r[3]),2)); /* Calculate the phase angle of the asteroid */ beta = Math.acos((Math.pow(r,2) + Math.pow(delta,2) - Math.pow(big_r,2))/(2.0*r*delta)); if (beta <= 120.0*Math.PI/180.0) { /* Valid visual magnitude observation. Compile quantities for later use */ number_of_brightness_observations = number_of_brightness_observations + 1; pointer[number_of_brightness_observations] = i; reduced_magnitude[number_of_brightness_observations] = visual_magnitude[i] - 5.0*Math.log(r*delta)/Math.log(10.0); epsilon[number_of_brightness_observations] = visual_magnitude_dev[i]; /* Calculate the phis */ /* Note that I am using Bowell's formulation here, rather than the IAU Commission 20 (1985) form, due to Bowell's contention that his is more accurate. */ phi1l = Math.exp(-3.332*Math.pow(Math.tan(beta/2.0),0.631)); phi2l = Math.exp(-1.862*Math.pow(Math.tan(beta/2.0),1.218)); phi1s = 1.0 - 0.986*Math.sin(beta)/(0.119 + 1.341*Math.sin(beta) - 0.754*Math.pow(Math.sin(beta),2.0)); phi2s = 1.0 - 0.238*Math.sin(beta)/(0.119 + 1.341*Math.sin(beta) - 0.754*Math.pow(Math.sin(beta),2.0)); w = Math.exp(-90.56*Math.pow(Math.tan(beta/2.0),2.0)); phi1[number_of_brightness_observations] = w*phi1s + (1.0 - w)*phi1l; phi2[number_of_brightness_observations] = w*phi2s + (1.0 - w)*phi2l; } } } System.out.println("number_of_brightness_observations = " + number_of_brightness_observations); /* Solve for H as a function of G */ for (i=1;i<=number_of_brightness_observations;i++) { delta_m[i] = -2.5*Math.log((1.0 - slope_parameter_G)*phi1[i] + slope_parameter_G*phi2[i])/Math.log(10.0); top_sum = top_sum + (reduced_magnitude[i] - delta_m[i])/Math.pow(epsilon[i],2.0); bottom_sum = bottom_sum + 1.0/Math.pow(epsilon[i],2.0); } absolute_magnitude_H = top_sum/bottom_sum; /* Knowing G and H, we can now assume an albedo (based on G), and estimate the asteroid's radius. The albedo will be modeled as a linear function of G. From Tedesco et al., "A Three Parameter Asteroid Taxonomy", The Astronomical Journal, Vol. 97, number 2, pp. 580-607, we have C-class albedo = 0.029-0.075 mean 0.048 (G = 0.09 due to Bowell) S-class albedo = 0.100-0.300 mean 0.186 (G = 0.23 due to Bowell) M-class albedo = 0.080-0.200 mean 0.141 (G = 0.21 due to Bowell) */ albedo = 0.048 + (slope_parameter_G - 0.09); diameter = Math.pow(10.0,((6.259 - Math.log(albedo)/Math.log(10.0) - 0.4*absolute_magnitude_H)/2.0)); minor_planet_radius = (diameter/2.0)/au; } public void comet_slope_based_abs_mag() { /* Given the comet_slope_parameter, method to solve for the comet_absolute_magnitude, using visual-range brightness measurements. NOTE that this method assumes that incremental_state_vector_r will be available; if it is not, the button permitting use of this method should not appear. (The rationale is that this method requires all available optical observations. If incremental_state_vector_r is available, this implies that least squares would have been run, meaning that all of the observations are available. On the other hand, if incremental_state_vector_r is NOT available, this implies that least squares must not have been run, meaning that all of the observations may not be available.) The instance variables comet_slope_parameter and comet_absolute_magnitude are directly updated by this method. */ /* Algorithm based on Jean Meeus, Astronomical Algorithms, pp. 216-217. Uses least-squares formulation from Burden and Faires, "Numerical Analysis" , 3rd edition (1985), p. 364. */ int number_of_brightness_observations = 0, index = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; int[] pointer = new int[20001]; double[] earth_r = new double[4]; double[] earth_rprime = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double r = 0, delta = 0, sumxy = 0, sumx = 0, sumy = 0, sumx2 = 0; int i = 0, j = 0, k = 0; /* Survey the observations, and determine the number of visual-range brightness measurements */ for (i=1;i<=index;i++) { if ((observation_type[i] == 1) && (visual_magnitude[i] != -99.0)) { /* Valid visual magnitude observation. Compile quantities for least-squares */ number_of_brightness_observations = number_of_brightness_observations + 1; pointer[number_of_brightness_observations] = i; /* Get the positions of the Earth and Sun at incremental_state_vector_epoch[i]. Note that I am not accounting for light time-of-flight here. Given the approximate nature of the model and the limited precision of its parameters, I firmly believe that accounting for light time-of-flight would be a waste of CPU cycles, and would introduce an unnecessary delay into overall processing. */ get_planet_posvel(incremental_state_vector_epoch[i],3,earth_r,earth_rprime); get_planet_posvel(incremental_state_vector_epoch[i],11,sun_r,sun_rprime); /* Calculate the distances */ r = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - sun_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - sun_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - sun_r[3]),2)); delta = Math.sqrt(Math.pow((incremental_state_vector_r[i][1] - earth_r[1]),2) + Math.pow((incremental_state_vector_r[i][2] - earth_r[2]),2) + Math.pow((incremental_state_vector_r[i][3] - earth_r[3]),2)); /* Calculate the least-squares quantities */ sumxy = sumxy + (Math.log(r)/Math.log(10.0))*(visual_magnitude[i] - 5.0*Math.log(delta)/Math.log(10.0)); sumx = sumx + Math.log(r)/Math.log(10.0); sumy = sumy + (visual_magnitude[i] - 5.0*Math.log(delta)/Math.log(10.0)); sumx2 = sumx2 + Math.pow(Math.log(r)/Math.log(10.0),2.0); } } System.out.println("number_of_brightness_observations = " + number_of_brightness_observations); /* Solve for comet_absolute_magnitude as a function of comet_slope_parameter */ comet_absolute_magnitude = (sumy - comet_slope_parameter*sumx)/number_of_brightness_observations; } public void get_state_vector_covariance(double classical_elements[], double classical_elements_covariance[][]) { /* Method to convert the classical orbital elements at epoch_time into a state vector, and to convert the covariances in the orbital elements at epoch_time to state vector covariances. Note: As output, this method directly sets the epoch_r and epoch_rprime state vector, as well as the big_p covariances. */ int i = 0, j = 0, k = 0; double delta = 0.0; double[] r = new double[4]; double[] rprime = new double[4]; double[][] partial_matrix = new double[7][9]; double[][] inter = new double[9][7]; /* Now, knowing the classical elements, and their uncertainties, we use variant calculations to determine the state vector and covariances. In the case of a parabolic orbit, a and M are undefined; thus, trying to determine the variant sensitivity due to e results in a call to convert_Keplerian_elements_to_state_vector with a hyperbolic orbit (e=1+delta) but a and M undefined. The basic problem here is that a parabolic orbit is a discontinuous singular case for the classical elements; for all intents and purposes, the derived posvel covariances associated with a parabolic orbit are undefined. Thus, I have decided the best course would be to set them to zero. If the user believes he/she knows what the posvel covariances should be, they are free to specify them directly. */ get_mp_state_vector(classical_elements, epoch_time, epoch_r, epoch_rprime); /* Reset any divergence flag */ epoch_r[0] = 0.0; if (classical_elements[2] != 1.0) { for (i=1;i<=8;i++) { if (i == 6) /* Since the time of perihelion is a 7-digit Julian date, delta must be correspondingly small */ delta = 0.0000001*classical_elements[i]; else delta = 0.001*classical_elements[i]; /* Prevent division by zero. */ if (Math.abs(delta - 0.0) < 1e-15) delta = 0.0000001; classical_elements[i] = classical_elements[i] + delta; get_mp_state_vector(classical_elements, epoch_time, r, rprime); for (j=1;j<=3;j++) { partial_matrix[j][i] = (r[j] - epoch_r[j])/delta; partial_matrix[j+3][i] = (rprime[j] - epoch_rprime[j])/delta; } classical_elements[i] = classical_elements[i] - delta; } } /* Convert the covariances from classical element into pos/vel */ if (classical_elements[2] == 1.0) { for (i=1;i<=6;i++) { for (j=1;j<=6;j++) big_p[i][j] = 0; } } else { for (i=1;i<=8;i++) { for (j=1;j<=6;j++) { inter[i][j] = 0; for (k=1;k<=8;k++) inter[i][j] = inter[i][j] + classical_elements_covariance[i][k]*partial_matrix[j][k]; } } for (i=1;i<=6;i++) { for (j=1;j<=6;j++) { big_p[i][j] = 0; for (k=1;k<=8;k++) big_p[i][j] = big_p[i][j] + partial_matrix[i][k]*inter[k][j]; } } } } double normal_probability_density(double x, double mean, double sigma) { /* Method to calculate the normal probability density function at x with mean and sigma as given. The value of the pdf will be returned. */ double exponent = 0, prob = 0; exponent = -Math.pow((x-mean),2.0)/(2.0*Math.pow(sigma,2.0)); prob = Math.exp(exponent)/(sigma*Math.sqrt(2.0*Math.PI)); return prob; } public void get_matrix_eigenvalues(int dimension_n, double matrix_nxn[][], double eigenvalues[]) { /* Procedure to diagonalize a dimension_n-by-dimension_n matrix (matrix_nxn[][]) using Gaussian elimination. Note: In this particular implementation, I've dimensioned an intermediate matrix so that n is restricted to n<=8; this is not an intrinsic limitation of the algorithm, and a larger dimensionalization can handle larger values of n. Algorithm taken from Richard L. Burden and J. Douglas Faires, "Numerical Analysis", 3rd edition, p. 300. NOTE: It is assumed that the matrix is diagonalizable!! */ int k = 0, kk = 0, kkk = 0, p = 0; double[][] inter = new double[9][17]; double hold = 0, normalizer = 0; for (k = 1; k <= dimension_n; k++) { for (kk = 1; kk <= dimension_n; kk++) { inter[k][kk] = matrix_nxn[k][kk]; inter[k][kk+dimension_n] = 0; } inter[k][k+dimension_n] = 1; } for (k = 1; k <= (dimension_n - 1); k++) { p = 0; for (kk = k; kk <= dimension_n; kk++) { if ((p == 0) && (inter[kk][k] != 0)) p = kk; } if (p != k) { for (kk = 1; kk <= 2*dimension_n; kk++) { hold = inter[k][kk]; inter[k][kk] = inter[p][kk]; inter[p][kk] = hold; } } for (kk = k+1; kk <= dimension_n; kk++) { hold = inter[kk][k]/inter[k][k]; for (kkk = 1; kkk <= 2*dimension_n; kkk++) inter[kk][kkk] = inter[kk][kkk] - hold*inter[k][kkk]; } } for (k = dimension_n; k >= 2; k--) { for (kk = k-1; kk >= 1; kk--) { hold = inter[kk][k]/inter[k][k]; for (kkk = 1; kkk <= 2*dimension_n; kkk++) inter[kk][kkk] = inter[kk][kkk] - hold*inter[k][kkk]; } } for (k = 1; k <= dimension_n; k++) { eigenvalues[k] = inter[k][k]; } } public double get_MOID() { /* Procedure to estimate the minimum orbital intersection distance between a minor planet and Earth. */ /* The algorithm is due to Sitarski, Acta Astronica, 1968 (Vol 18 pp. 171-181) */ double[][] solution = new double[20][3]; double[][] big_h = new double[4][4]; double[][] little_h = new double[4][4]; double[][] inter1 = new double[4][4]; double[][] inter2 = new double[4][4]; double[][] inter3 = new double[4][4]; double[] big_u = new double[4]; double[] little_u = new double[4]; double[] big_pos = new double[4]; double[] little_pos = new double[4]; double[] rho = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; double[] mp_r_2body = new double[4]; double[] mp_rprime_2body = new double[4]; double[] earth_r_2body = new double[4]; double[] earth_rprime_2body = new double[4]; double[] earth_r = new double[4]; double[] earth_rprime = new double[4]; double[] little_asteroid_elements = new double[9]; double[] big_asteroid_elements = new double[9]; int i = 0, j = 0, big_v_counter = 0, little_v_counter = 0, solution_counter = 0, k = 0; double moid = 1000.0, big_v = 0, little_v = 0, big_r = 0, big_p = 0, little_p = 0, big_x = 0, big_y = 0, big_m = 0, big_n = 0, big_px = 0, big_py = 0, big_pz = 0, big_qx = 0, big_qy = 0, big_qz = 0, little_px = 0, little_py = 0, little_pz = 0, little_qx = 0, little_qy = 0, little_qz = 0, big_k = 0, big_l = 0, s = 0, t = 0, w = 0, little_m = 0, little_l = 0, solution_little_v1 = 0, solution_little_v2 = 0, old_equation_value_1 = 0, old_equation_value_2 = 0, equation_value_1 = 0, equation_value_2 = 0, little_x = 0, little_y = 0, little_r = 0, old_big_v = 0, top = 0, bottom = 0, middle = 0, bisection_equation_value_1 = 0, bisection_equation_value_2 = 0, distance = 0, longitude1 = 0, top_sign = 0, partial_big_v = 0, partial_little_v = 0, mixed_partial = 0; /* Calculate the osculating orbital elements of Earth and the minor planet at epoch */ /* Begin by finding the barycentric equatorial state vectors of the Sun and Earth */ planetary_ephemeris(epoch_time,rho,0); for (i=1;i<=3;i++) { sun_r[i] = planet_r[0][i]; sun_rprime[i] = planet_rprime[0][i]; earth_r[i] = planet_r[3][i]; earth_rprime[i] = planet_rprime[3][i]; } /* Convert the minor planet and Earth state vectors to heliocentric equatorial */ for (i=1;i<=3;i++) { mp_r_2body[i] = epoch_r[i] - sun_r[i]; mp_rprime_2body[i] = (epoch_rprime[i] - sun_rprime[i])/kappa; earth_r_2body[i] = earth_r[i] - sun_r[i]; earth_rprime_2body[i] = (earth_rprime[i] - sun_rprime[i])/kappa; } /* Rotate the heliocentric state vectors into the ecliptic frame */ equatorial_to_ecliptic(mp_r_2body); equatorial_to_ecliptic(mp_rprime_2body); equatorial_to_ecliptic(earth_r_2body); equatorial_to_ecliptic(earth_rprime_2body); /* Find the classical orbital elements */ convert_state_vector_to_Keplerian_elements(mp_r_2body,mp_rprime_2body,little_asteroid_elements,epoch_time); convert_state_vector_to_Keplerian_elements(earth_r_2body,earth_rprime_2body,big_asteroid_elements,epoch_time); /* Create the quantities needed to evaluate the key equation 11 */ big_p = big_asteroid_elements[8]*(1.0 + big_asteroid_elements[2]); little_p = little_asteroid_elements[8]*(1.0 + little_asteroid_elements[2]); big_px = Math.cos(big_asteroid_elements[4]); big_py = Math.sin(big_asteroid_elements[4])*Math.cos(big_asteroid_elements[3]); big_pz = Math.sin(big_asteroid_elements[4])*Math.sin(big_asteroid_elements[3]); big_qx = -Math.sin(big_asteroid_elements[4]); big_qy = Math.cos(big_asteroid_elements[4])*Math.cos(big_asteroid_elements[3]); big_qz = Math.cos(big_asteroid_elements[4])*Math.sin(big_asteroid_elements[3]); little_px = Math.cos(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]) - Math.sin(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); little_py = Math.cos(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]) + Math.sin(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); little_pz = Math.sin(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[3]); little_qx = -Math.sin(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]) - Math.cos(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); little_qy = -Math.sin(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]) + Math.cos(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); little_qz = Math.cos(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[3]); big_k = big_px*little_px + big_py*little_py + big_pz*little_pz; big_l = big_qx*little_px + big_qy*little_py + big_qz*little_pz; big_m = big_px*little_qx + big_py*little_qy + big_pz*little_qz; big_n = big_qx*little_qx + big_qy*little_qy + big_qz*little_qz; /* Calculate cracovians big_h and little_h for use in evaluating the distances */ inter1[1][1] = Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[1][2] = Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[1][3] = 0.0; inter1[2][1] = -Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[2][2] = Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[2][3] = 0.0; inter1[3][1] = 0.0; inter1[3][2] = 0.0; inter1[3][3] = 1.0; inter2[1][1] = 1.0; inter2[1][2] = 0.0; inter2[1][3] = 0.0; inter2[2][1] = 0.0; inter2[2][2] = Math.cos(little_asteroid_elements[3]); inter2[2][3] = -Math.sin(little_asteroid_elements[3]); inter2[3][1] = 0.0; inter2[3][2] = Math.sin(little_asteroid_elements[3]); inter2[3][3] = Math.cos(little_asteroid_elements[3]); /* Multiply the cracovians, using cracovian logic, rather than matrix multiplication */ for (i = 1; i <= 3; i++) { for (j = 1; j <= 3; j++) { inter3[i][j] = 0.0; for (k = 1; k <= 3; k++) inter3[i][j] = inter3[i][j] + inter1[k][j]*inter2[k][i]; } } inter1[1][1] = Math.cos(little_asteroid_elements[4]); inter1[1][2] = -Math.sin(little_asteroid_elements[4]); inter1[1][3] = 0.0; inter1[2][1] = Math.sin(little_asteroid_elements[4]); inter1[2][2] = Math.cos(little_asteroid_elements[4]); inter1[2][3] = 0.0; inter1[3][1] = 0.0; inter1[3][2] = 0.0; inter1[3][3] = 1.0; /* Multiply the cracovians, using cracovian logic, rather than matrix multiplication */ for (i = 1; i <= 3; i++) { for (j = 1; j <= 3; j++) { little_h[i][j] = 0.0; for (k = 1; k <= 3; k++) little_h[i][j] = little_h[i][j] + inter3[k][j]*inter1[k][i]; } } inter2[1][1] = 1.0; inter2[1][2] = 0.0; inter2[1][3] = 0.0; inter2[2][1] = 0.0; inter2[2][2] = Math.cos(big_asteroid_elements[3]); inter2[2][3] = Math.sin(big_asteroid_elements[3]); inter2[3][1] = 0.0; inter2[3][2] = -Math.sin(big_asteroid_elements[3]); inter2[3][3] = Math.cos(big_asteroid_elements[3]); inter1[1][1] = Math.cos(big_asteroid_elements[4]); inter1[1][2] = -Math.sin(big_asteroid_elements[4]); inter1[1][3] = 0.0; inter1[2][1] = Math.sin(big_asteroid_elements[4]); inter1[2][2] = Math.cos(big_asteroid_elements[4]); inter1[2][3] = 0.0; inter1[3][1] = 0.0; inter1[3][2] = 0.0; inter1[3][3] = 1.0; /* Multiply the cracovians, using cracovian logic, rather than matrix multiplication */ for (i = 1; i <= 3; i++) { for (j = 1; j <= 3; j++) { big_h[i][j] = 0.0; for (k = 1; k <= 3; k++) big_h[i][j] = big_h[i][j] + inter2[k][j]*inter1[k][i]; } } big_v = -181*Math.PI/180.0; for (big_v_counter=-180;big_v_counter<=180;big_v_counter++) { old_big_v = big_v; big_v = big_v_counter*Math.PI/180.0; big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(big_v)); big_x = big_r*Math.cos(big_v); big_y = big_r*Math.sin(big_v); s = big_asteroid_elements[2]*big_r*big_y/little_p; t = big_m*big_y - big_n*(big_asteroid_elements[2]*big_r + big_x); w = little_asteroid_elements[2]*s + big_k*big_y - big_l*(big_asteroid_elements[2]*big_r + big_x); little_m = t*t + w*w; little_l = little_m - s*s; solution_little_v1 = Math.atan2((-t*s + w*Math.sqrt(little_l))/little_m, (-w*s - t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v1)); little_x = little_r*Math.cos(solution_little_v1); little_y = little_r*Math.sin(solution_little_v1); equation_value_1 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); solution_little_v2 = Math.atan2((-t*s - w*Math.sqrt(little_l))/little_m, (-w*s + t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v2)); little_x = little_r*Math.cos(solution_little_v2); little_y = little_r*Math.sin(solution_little_v2); equation_value_2 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); if ((big_v_counter > -180) && (((old_equation_value_1 <= 0.0) && (equation_value_1 >= 0.0)) || ((old_equation_value_1 >= 0.0) && (equation_value_1 <= 0.0)))) { top = big_v; bottom = old_big_v; if (equation_value_1 > 0) top_sign = 1.0; else top_sign = -1.0; while (Math.abs(top - bottom) > 1.0e-7) { middle = (top + bottom)/2.0; big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(middle)); big_x = big_r*Math.cos(middle); big_y = big_r*Math.sin(middle); s = big_asteroid_elements[2]*big_r*big_y/little_p; t = big_m*big_y - big_n*(big_asteroid_elements[2]*big_r + big_x); w = little_asteroid_elements[2]*s + big_k*big_y - big_l*(big_asteroid_elements[2]*big_r + big_x); little_m = t*t + w*w; little_l = little_m - s*s; solution_little_v1 = Math.atan2((-t*s + w*Math.sqrt(little_l))/little_m, (-w*s - t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v1)); little_x = little_r*Math.cos(solution_little_v1); little_y = little_r*Math.sin(solution_little_v1); bisection_equation_value_1 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); if ((bisection_equation_value_1 >= 0) && (top_sign > 0)) top = middle; else if ((bisection_equation_value_1 <= 0) && (top_sign > 0)) bottom = middle; else if ((bisection_equation_value_1 >= 0) && (top_sign < 0)) bottom = middle; else if ((bisection_equation_value_1 <= 0) && (top_sign < 0)) top = middle; } solution_counter ++; solution[solution_counter][1] = middle; solution[solution_counter][2] = solution_little_v1; partial_big_v = (big_r/big_p)*((big_asteroid_elements[2]*big_r*big_r/big_p)*(big_asteroid_elements[2]*big_r + big_x) + big_x*(big_k*little_x + big_m*little_y) + big_y*(big_l*little_x + big_n*little_y)); partial_little_v = (little_r/little_p)*((little_asteroid_elements[2]*little_r*little_r/little_p)*(little_asteroid_elements[2]*little_r + little_x) + little_x*(big_k*big_x + big_l*big_y) + little_y*(big_m*big_x + big_n*big_y)); mixed_partial = (big_r/big_p)*(little_r/little_p)*((little_asteroid_elements[2]*little_r + little_x)*(big_n*(big_asteroid_elements[2]*big_r + big_x) - big_m*big_y) - little_y*(big_l*(big_asteroid_elements[2]*big_r + big_x) - big_k*big_y)); } if ((big_v_counter > -180) && (((old_equation_value_2 <= 0.0) && (equation_value_2 >= 0.0)) || ((old_equation_value_2 >= 0.0) && (equation_value_2 <= 0.0)))) { top = big_v; bottom = old_big_v; if (equation_value_2 > 0) top_sign = 1.0; else top_sign = -1.0; while (Math.abs(top - bottom) > 1.0e-7) { middle = (top + bottom)/2.0; big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(middle)); big_x = big_r*Math.cos(middle); big_y = big_r*Math.sin(middle); s = big_asteroid_elements[2]*big_r*big_y/little_p; t = big_m*big_y - big_n*(big_asteroid_elements[2]*big_r + big_x); w = little_asteroid_elements[2]*s + big_k*big_y - big_l*(big_asteroid_elements[2]*big_r + big_x); little_m = t*t + w*w; little_l = little_m - s*s; solution_little_v2 = Math.atan2((-t*s - w*Math.sqrt(little_l))/little_m, (-w*s + t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v2)); little_x = little_r*Math.cos(solution_little_v2); little_y = little_r*Math.sin(solution_little_v2); bisection_equation_value_2 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); if ((bisection_equation_value_2 >= 0) && (top_sign > 0)) top = middle; else if ((bisection_equation_value_2 <= 0) && (top_sign > 0)) bottom = middle; else if ((bisection_equation_value_2 >= 0) && (top_sign < 0)) bottom = middle; else if ((bisection_equation_value_2 <= 0) && (top_sign < 0)) top = middle; } solution_counter ++; solution[solution_counter][1] = middle; solution[solution_counter][2] = solution_little_v2; partial_big_v = (big_r/big_p)*((big_asteroid_elements[2]*big_r*big_r/big_p)*(big_asteroid_elements[2]*big_r + big_x) + big_x*(big_k*little_x + big_m*little_y) + big_y*(big_l*little_x + big_n*little_y)); partial_little_v = (little_r/little_p)*((little_asteroid_elements[2]*little_r*little_r/little_p)*(little_asteroid_elements[2]*little_r + little_x) + little_x*(big_k*big_x + big_l*big_y) + little_y*(big_m*big_x + big_n*big_y)); mixed_partial = (big_r/big_p)*(little_r/little_p)*((little_asteroid_elements[2]*little_r + little_x)*(big_n*(big_asteroid_elements[2]*big_r + big_x) - big_m*big_y) - little_y*(big_l*(big_asteroid_elements[2]*big_r + big_x) - big_k*big_y)); } old_equation_value_1 = equation_value_1; old_equation_value_2 = equation_value_2; } /* We now have several distance minima; determine which is smallest by direct calculation. */ for (i = 1; i <= solution_counter; i++) { big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(solution[i][1])); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution[i][2])); big_u[1] = big_r*Math.cos(solution[i][1]); big_u[2] = big_r*Math.sin(solution[i][1]); big_u[3] = 0.0; little_u[1] = little_r*Math.cos(solution[i][2]); little_u[2] = little_r*Math.sin(solution[i][2]); little_u[3] = 0.0; big_pos[1] = big_u[1]*big_h[1][1] + big_u[2]*big_h[2][1] + big_u[3]*big_h[3][1]; big_pos[2] = big_u[1]*big_h[1][2] + big_u[2]*big_h[2][2] + big_u[3]*big_h[3][2]; big_pos[3] = big_u[1]*big_h[1][3] + big_u[2]*big_h[2][3] + big_u[3]*big_h[3][3]; little_pos[1] = little_u[1]*little_h[1][1] + little_u[2]*little_h[2][1] + little_u[3]*little_h[3][1]; little_pos[2] = little_u[1]*little_h[1][2] + little_u[2]*little_h[2][2] + little_u[3]*little_h[3][2]; little_pos[3] = little_u[1]*little_h[1][3] + little_u[2]*little_h[2][3] + little_u[3]*little_h[3][3]; distance = Math.sqrt(Math.pow((big_pos[1] - little_pos[1]), 2.0) + Math.pow((big_pos[2] - little_pos[2]), 2.0) + Math.pow((big_pos[3] - little_pos[3]), 2.0)); if (distance < moid) moid = distance; } return moid; } public void barycentric_to_two_body_heliocentric(double jultime, double r[], double rprime[]) { /* Procedure to convert the barycentric state vector of a minor planet to heliocentric at jultime. */ double[] rho = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; int i = 0; /* Begin by finding the barycentric equatorial state vector of the Sun */ planetary_ephemeris(jultime,rho,0); for (i=1;i<=3;i++) { sun_r[i] = planet_r[0][i]; sun_rprime[i] = planet_rprime[0][i]; } /* Convert the minor planet state vector to heliocentric equatorial for use in two-body routines */ for (i=1;i<=3;i++) { r[i] = r[i] - sun_r[i]; rprime[i] = (rprime[i] - sun_rprime[i])/kappa; } } public void two_body_heliocentric_to_barycentric(double jultime, double r[], double rprime[]) { /* Procedure to convert the heliocentric state vector of a minor planet to barycentric at jultime. */ double[] rho = new double[4]; double[] sun_r = new double[4]; double[] sun_rprime = new double[4]; int i = 0; /* Begin by finding the barycentric equatorial state vector of the Sun */ planetary_ephemeris(jultime,rho,0); for (i=1;i<=3;i++) { sun_r[i] = planet_r[0][i]; sun_rprime[i] = planet_rprime[0][i]; } /* Convert the minor planet state vector to barycentric equatorial for use in n-body routines */ for (i=1;i<=3;i++) { r[i] = r[i] + sun_r[i]; rprime[i] = kappa*rprime[i] + sun_rprime[i]; } } void two_body_detect_collision(int target, double object_r_at_n[],double object_rprime_at_n[],double jultime_n,double jultime_n1,double event[]) { /* Given the position and velocity of an object at jultime_n and jultime_n1, determine whether the object collides with a planet, or has a near miss with a planet, between time steps n and n+1. Inputs: target = the target planet object_r_at_n[], object_rprime_at_n[] = the barycentric equatorial posvel of the object at time step n; jultime_n, jultime_n1 = the Julian dates corresponding to time steps n and n+1 The output array "event[]" has five entries. "event[1]" gives the Julian date, "event[2]" gives the nominal distance in A.U.s., "event[3]" gives the object's x-position relative to the target at the time of the event, "event[4]" gives the object's y-position relative to the target at the time of the event, and "event[5]" gives the object's z-position relative to the target at the time of the event. */ double[] object_r_at_n1 = new double[4]; double[] object_rprime_at_n1 = new double[4]; double[] rho = new double[4]; double[][] planet_r_at_n = new double[11][4]; double[][] planet_r_at_n1 = new double[11][4]; double[][] planet_rprime_at_n = new double[11][4]; double[][] planet_rprime_at_n1 = new double[11][4]; double[] planet_i_r_at_n = new double[4]; double[] planet_i_rprime_at_n = new double[4]; double[] planet_i_r_at_time = new double[4]; double[] planet_i_rprime_at_time = new double[4]; double[] r_at_time = new double[4]; double[] rprime_at_time = new double[4]; double[] r_at_n = new double[4]; double[] rprime_at_n = new double[4]; double[] r_at_n1 = new double[4]; double[] rprime_at_n1 = new double[4]; double distance_n1 = 0, upper_time = 0, lower_time = 0, time = 0, distance = 0, distance_n = 0, deriv_1_at_n = 0, deriv_1_at_n1 = 0, deriv_1_at_time = 0; int i = 0, j = 0, counter = 0; /* Initialize barycentric object posvel arrays at time step n */ for (j=1;j<=3;j++) { r_at_n[j] = object_r_at_n[j]; rprime_at_n[j] = object_rprime_at_n[j]; } /* Generate the two-body posvel of the object at time n1 */ barycentric_to_two_body_heliocentric(jultime_n,r_at_n,rprime_at_n); two_body_update(r_at_n,rprime_at_n,jultime_n,object_r_at_n1,object_rprime_at_n1,jultime_n1); two_body_heliocentric_to_barycentric(jultime_n1,object_r_at_n1,object_rprime_at_n1); /* Get the ephemerides of the planets at each time step, without correction for light time-of-flight */ planetary_ephemeris(jultime_n,rho,0); /* Initialize barycentric planetary posvel arrays at time step n */ for (i=0;i<=10;i++) { for (j=1;j<=3;j++) { planet_r_at_n[i][j] = planet_r[i][j]; planet_rprime_at_n[i][j] = planet_rprime[i][j]; } } planetary_ephemeris(jultime_n1,rho,0); /* Initialize barycentric planetary posvel arrays at time step n+1 */ for (i=0;i<=10;i++) { for (j=1;j<=3;j++) { planet_r_at_n1[i][j] = planet_r[i][j]; planet_rprime_at_n1[i][j] = planet_rprime[i][j]; } } /* Initialize barycentric object posvel arrays at time step n+1 */ for (j=1;j<=3;j++) { r_at_n1[j] = object_r_at_n1[j]; rprime_at_n1[j] = object_rprime_at_n1[j]; } /* Initialize barycentric posvel arrays for planet target */ for(j=1;j<=3;j++) { planet_i_r_at_n[j] = planet_r_at_n[target][j]; planet_i_rprime_at_n[j] = planet_rprime_at_n[target][j]; } /* Check for collision at time step n+1. */ distance_n1 = Math.sqrt(Math.pow((object_r_at_n1[1] - planet_r_at_n1[target][1]),2) + Math.pow((object_r_at_n1[2] - planet_r_at_n1[target][2]),2) + Math.pow((object_r_at_n1[3] - planet_r_at_n1[target][3]),2)); if (distance_n1 <= planet_radius[target]) { /* Collision. Use bisection to find it. */ /* System.out.println("Collision at n1"); */ upper_time = jultime_n1; lower_time = jultime_n; event[1] = jultime_n1; event[2] = distance_n1; while ((Math.abs(upper_time - lower_time) > event_localization) && (counter < 100)) { counter = counter + 1; time = lower_time + (upper_time - lower_time)/2; /* System.out.println("time = " + time); */ two_body_update(r_at_n,rprime_at_n,jultime_n,r_at_time,rprime_at_time,time); two_body_heliocentric_to_barycentric(time,r_at_time,rprime_at_time); planetary_ephemeris(time,rho,0); for (j=1;j<=3;j++) { planet_i_r_at_time[j] = planet_r[target][j]; planet_i_rprime_at_time[j] = planet_rprime[target][j]; } distance = Math.sqrt(Math.pow((r_at_time[1] - planet_i_r_at_time[1]),2) + Math.pow((r_at_time[2] - planet_i_r_at_time[2]),2) + Math.pow((r_at_time[3] - planet_i_r_at_time[3]),2)); if (distance <= planet_radius[target]) { upper_time = time; } else /* Collision has not yet occurred. Increase lower limit. */ lower_time = time; } event[1] = time; event[2] = distance; event[3] = r_at_time[1] - planet_i_r_at_time[1]; event[4] = r_at_time[2] - planet_i_r_at_time[2]; event[5] = r_at_time[3] - planet_i_r_at_time[3]; } else { /* Not in collision at time step n+1; evaluate intermediate points */ /* Calculate the first derivative of distance at time steps n and n+1; check for a minimum that is less than the planetary radius for collision, or less than near_miss_threshold for a near miss. */ distance_n = Math.sqrt(Math.pow((object_r_at_n[1] - planet_r_at_n[target][1]),2) + Math.pow((object_r_at_n[2] - planet_r_at_n[target][2]),2) + Math.pow((object_r_at_n[3] - planet_r_at_n[target][3]),2)); deriv_1_at_n = ((object_r_at_n[1] - planet_r_at_n[target][1])*(object_rprime_at_n[1] - planet_rprime_at_n[target][1]) + (object_r_at_n[2] - planet_r_at_n[target][2])*(object_rprime_at_n[2] - planet_rprime_at_n[target][2]) + (object_r_at_n[3] - planet_r_at_n[target][3])*(object_rprime_at_n[3] - planet_rprime_at_n[target][3]))/distance_n; deriv_1_at_n1 = ((object_r_at_n1[1] - planet_r_at_n1[target][1])*(object_rprime_at_n1[1] - planet_rprime_at_n1[target][1]) + (object_r_at_n1[2] - planet_r_at_n1[target][2])*(object_rprime_at_n1[2] - planet_rprime_at_n1[target][2]) + (object_r_at_n1[3] - planet_r_at_n1[target][3])*(object_rprime_at_n1[3] - planet_rprime_at_n1[target][3]))/distance_n1; /* System.out.println("distance_n = " + distance_n); System.out.println("deriv_1_at_n = " + deriv_1_at_n); System.out.println("deriv_1_at_n1 = " + deriv_1_at_n1); */ if ((deriv_1_at_n < 0) && (deriv_1_at_n1 > 0)) { /* A relevant minimum has occurred. Use bisection on the first derivative of distance to find it. */ /* System.out.println("Relevant minimum between n and n1"); */ upper_time = jultime_n1; lower_time = jultime_n; while ((Math.abs(upper_time - lower_time) > event_localization) && (counter < 100)) { counter = counter + 1; time = lower_time + (upper_time - lower_time)/2; /* System.out.println("time = " + time); */ two_body_update(r_at_n,rprime_at_n,jultime_n,r_at_time,rprime_at_time,time); two_body_heliocentric_to_barycentric(time,r_at_time,rprime_at_time); planetary_ephemeris(time,rho,0); for (j=1;j<=3;j++) { planet_i_r_at_time[j] = planet_r[target][j]; planet_i_rprime_at_time[j] = planet_rprime[target][j]; } distance = Math.sqrt(Math.pow((r_at_time[1] - planet_i_r_at_time[1]),2) + Math.pow((r_at_time[2] - planet_i_r_at_time[2]),2) + Math.pow((r_at_time[3] - planet_i_r_at_time[3]),2)); /* System.out.println("distance = " + distance); */ deriv_1_at_time = ((r_at_time[1] - planet_i_r_at_time[1])*(rprime_at_time[1] - planet_i_rprime_at_time[1]) + (r_at_time[2] - planet_i_r_at_time[2])*(rprime_at_time[2] - planet_i_rprime_at_time[2]) + (r_at_time[3] - planet_i_r_at_time[3])*(rprime_at_time[3] - planet_i_rprime_at_time[3]))/distance; if (deriv_1_at_time < 0) /* Minimum has not yet occurred. Increase lower limit. */ lower_time = time; else /* Minimum has already occurred. Reduce upper limit. */ upper_time = time; } /* We've now located the minimum to great accuracy. Is it a collision, or a near miss? */ if (distance <= planet_radius[target]) { /* Collision. Use bisection to find it. */ /* System.out.println("Collision between n and n1"); */ upper_time = time; lower_time = jultime_n; event[1] = time; event[2] = distance; while ((Math.abs(upper_time - lower_time) > event_localization) && (counter < 200)) { counter = counter + 1; time = lower_time + (upper_time - lower_time)/2; /* System.out.println("time = " + time); */ two_body_update(r_at_n,rprime_at_n,jultime_n,r_at_time,rprime_at_time,time); two_body_heliocentric_to_barycentric(time,r_at_time,rprime_at_time); planetary_ephemeris(time,rho,0); for (j=1;j<=3;j++) { planet_i_r_at_time[j] = planet_r[target][j]; planet_i_rprime_at_time[j] = planet_rprime[target][j]; } distance = Math.sqrt(Math.pow((r_at_time[1] - planet_i_r_at_time[1]),2) + Math.pow((r_at_time[2] - planet_i_r_at_time[2]),2) + Math.pow((r_at_time[3] - planet_i_r_at_time[3]),2)); if (distance <= planet_radius[target]) { upper_time = time; } else /* Collision has not yet occurred. Increase lower limit. */ lower_time = time; } event[1] = time; event[2] = distance; event[3] = r_at_time[1] - planet_i_r_at_time[1]; event[4] = r_at_time[2] - planet_i_r_at_time[2]; event[5] = r_at_time[3] - planet_i_r_at_time[3]; } else { /* Near miss. Record data. */ /* System.out.println("Near-miss between n and n1"); */ event[1] = time; event[2] = distance; event[3] = r_at_time[1] - planet_i_r_at_time[1]; event[4] = r_at_time[2] - planet_i_r_at_time[2]; event[5] = r_at_time[3] - planet_i_r_at_time[3]; } } } } public void hergets_method(double[] Lx, double[] Ly, double[] Lz, double[] x, double[] y, double[] z, double[] herget_times, double rho1, double rho2) { /* This implementation of Herget's method is experimental, and may become the core of a statistical ranging algorithm */ /* Inputs: rho1 = user-defined slant range at TDB Julian date herget_times[1] rho2 = user-defined slant range at TDB Julian date herget_times[2] Lx, Ly, and Lz = equatorial slant vectors x, y, and z = barycentric equatorial observer's position vectors herget_times[1] and herget_times[2] = TDB time at each observation Outputs: herget_residuals, herget_rms_residuals (created by least-squares) (implicit) epoch_r, epoch_rprime = barycentric equatorial state vector for epoch_time = herget_times[1] (created by least-squares) */ int i = 0; double accel_time = 0; double[] r1 = new double[4]; double[] r2 = new double[4]; double[] r1prime = new double[4]; double[] r1accel = new double[4]; double[] rhalfway = new double[4]; /* Using the user-specified slant ranges, create the corresponding barycentric equatorial position vectors */ r1[1] = rho1*Lx[1] + x[1]; r1[2] = rho1*Ly[1] + y[1]; r1[3] = rho1*Lz[1] + z[1]; r2[1] = rho2*Lx[2] + x[2]; r2[2] = rho2*Ly[2] + y[2]; r2[3] = rho2*Lz[2] + z[2]; /* Create an estimate for the barycentric equatorial velocity vector at herget_times[1] */ accel_time = ((herget_times[1] - rho1/speed_of_light) + (herget_times[2] - rho2/speed_of_light))/2.0; for (i = 1; i <= 3; i++) { r1prime[i] = (r2[i] - r1[i])/((herget_times[2] - rho2/speed_of_light) - (herget_times[1] - rho1/speed_of_light)); rhalfway[i] = (r1[i] + r2[i])/2.0; } get_acceleration(accel_time,rhalfway,r1prime,r1accel); for (i = 1; i <= 3; i++) r1prime[i] = (r2[i] - r1[i])/((herget_times[2] - rho2/speed_of_light) - (herget_times[1] - rho1/speed_of_light)) - r1accel[i]*((herget_times[2] - rho2/speed_of_light) - (herget_times[1] - rho1/speed_of_light))/2.0; /* We now have a state vector for herget_times[1] */ epoch_time = herget_times[1] - rho1/speed_of_light; for (i = 0; i <= 3; i++) { epoch_r[i] = r1[i]; epoch_rprime[i] = r1prime[i]; } } public void lov_least_squares_solution(double[] bvector, double[] residuals, double[] rms_residuals) { /* Method to calculate the least squares best-fit orbit of a minor planet, given optical, radar delay, and or radar doppler observations. The algorithm is only applied once, and the first basis vector is replaced by the input bvector. The method presumes that an a priori state vector has been selected, and resides in epoch_r, epoch_rprime, and epoch_time. The method also assumes that the observations have been ordered from earliest to most recent. The output will be the epoch state vector, and the epoch error covariance matrix. We will assume a maximum of 2000 optical observations, 100 radar delay observations, and 100 radar doppler observations. Adapted from: D.K. Yeomans, S.J. Ostro, and P.W. Chodas (1987). "Radar Astrometry of Near-Earth Asteroids". Astronomical Journal 94, 189-200. Modified to the "Square Root Information Filter" algorithm from Gerald J. Bierman (1974), "Sequential Square Root Filtering and Smoothing of Discrete Linear Systems", Automatica 10, 147-158. */ int i = 0, j = 0, k = 0, index = 0, retained_index = 0; boolean convergence = false, divergence = false, edit = false; double rms_optical = 0, rms_delay = 0, rms_doppler = 0; double old_rms_optical = 0, old_rms_delay = 0, old_rms_doppler = 0; double convergence_factor = 0.10; double delta = 0, delay = 0, doppler = 0; double observation_residual = 0, observation_chi = 0; int number_of_observations = number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; double[] nominal_r = new double[4]; double[] nominal_rprime = new double[4]; double[] initial_r = new double[4]; double[] initial_rprime = new double[4]; double[][] r_variant = new double[7][4]; double[][] rprime_variant = new double[7][4]; double[] this_r = new double[4]; double[] this_rprime = new double[4]; double[] next_r = new double[4]; double[] next_rprime = new double[4]; double[] variant_delta = new double[7]; double[] dev = new double[40002]; double[][] matrix_a = new double[40002][7]; double[][] collision_variant = new double[2000][9]; double[][] phi = new double[7][7]; double[] inter = new double[7]; double[] sensitivity_ra = new double[7]; double[] sensitivity_dec = new double[7]; double[] sensitivity_delay = new double[7]; double[] sensitivity_doppler = new double[7]; double[] radec = new double[3]; double[] row = new double[7]; double[] corrections = new double[7]; double[] observer_r = new double[4]; double[] observer_rprime = new double[4]; double[] delay_doppler = new double[3]; double[] receiver_r = new double[4]; double[] receiver_rprime = new double[4]; double[] EOP = new double[8]; double[][] matrix_R = new double[7][7]; double[] matrix_z = new double[7]; /* How to initialize? */ double[][] matrix_temp = new double[40002][7]; double[] retained_residuals = new double[40002]; double[] initial_residuals = new double[40002]; double[] initial_rms_residuals = new double[4]; double[] temp_r = new double[4]; double[] temp_rprime = new double[4]; double[][] matrix_P = new double[7][7]; double[][] inverse_matrix_P = new double[7][7]; double[] converted_corrections = new double[7]; double[][] initial_big_p = new double[9][9]; /* Initialize all observations as non-excluded for get_residuals */ for (i=1;i<=20000;i++) excluded_observations[i] = 0; /* Using the a priori state vector, determine initial residuals, nominal observables, and the state vector at the tdb time of each observation. Save the a priori state vector in case of divergence. */ get_residuals(residuals,rms_residuals); rms_optical = rms_residuals[1]; rms_delay = rms_residuals[2]; rms_doppler = rms_residuals[3]; for (i=1;i<=3;i++) { initial_r[i] = epoch_r[i]; initial_rprime[i] = epoch_rprime[i]; initial_rms_residuals[i] = rms_residuals[i]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) initial_big_p[i][j] = big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) { initial_residuals[i] = residuals[i]; } /* The convergence condition is: The RMS residuals on the current loop must differ by no more than some percentage from the RMS residuals on the preceding loop. (Note that this condition was not my original choice. I tried requiring that the corrections to the epoch state vector must be less than some percentage of their corresponding covariances. However, real-world data is not sufficiently smooth. This new condition uses the observed fact that the RMS residuals decrease with successive loops until the next loop results in almost no change. At this point, the solution has converged.) Clearly, this condition will not be satisfied initially, so we will go through the loop at least once. NOTE: MUST ACCOUNT FOR DIVERGENCE, AS MEASURED BY INCREASING RMS RESIDUALS. If each of the RMS residuals increase from one loop to the next, the divergence flag will be set, and the original solution returned. */ while ((convergence == false) && (divergence == false)) { /* Clear the counters */ index = 0; retained_index = 0; /* Use the observations to generate rows of matrix_a */ for (i=1;i<=number_of_observations;i++) { /* Assume that the observation will be excluded; if not, the flag will be reset below. */ excluded_observations[i] = 1; /* Generate the state transition matrix corresponding to this observation time */ for (j=1;j<=3;j++) { nominal_r[j] = incremental_state_vector_r[i][j]; nominal_rprime[j] = incremental_state_vector_rprime[i][j]; } /* Initialize the variant trajectories */ if (i == 1) { for (j=1;j<=6;j++) { for (k=1;k<=3;k++) { r_variant[j][k] = epoch_r[k]; rprime_variant[j][k] = epoch_rprime[k]; } } for (j=1;j<=3;j++) { variant_delta[j] = 0.000001*epoch_r[j]; r_variant[j][j] = r_variant[j][j] + variant_delta[j]; variant_delta[j+3] = 0.000001*epoch_rprime[j]; rprime_variant[j+3][j] = rprime_variant[j+3][j] + variant_delta[j+3]; } } /* Propogate the variant trajectories, and calculate the transition matrix entries */ for (j=1;j<=6;j++) { for (k=1;k<=3;k++) { this_r[k] = r_variant[j][k]; this_rprime[k] = rprime_variant[j][k]; } update(this_r,this_rprime,incremental_state_vector_epoch[i-1],next_r,next_rprime,incremental_state_vector_epoch[i],false,collision_variant); for (k=1;k<=3;k++) { r_variant[j][k] = next_r[k]; rprime_variant[j][k] = next_rprime[k]; } for (k=1;k<=3;k++) { phi[k][j] = (r_variant[j][k] - nominal_r[k])/variant_delta[j]; phi[k+3][j] = (rprime_variant[j][k] - nominal_rprime[k])/variant_delta[j]; } } /* Generate the sensitivity matrix for this observation */ if (observation_type[i] == 1) { /* Optical observation */ for (j=1;j<=3;j++) { observer_r[j] = observation_r[i][j]; observer_rprime[j] = observation_rprime[i][j]; } for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radec(nominal_r,nominal_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); sensitivity_ra[j] = radec[1] - nominal_observable[index+1]; while (Math.abs(sensitivity_ra[j]) > Math.abs(sensitivity_ra[j] - 2.0*Math.PI)) sensitivity_ra[j] = sensitivity_ra[j] - 2.0*Math.PI; while (Math.abs(sensitivity_ra[j]) > Math.abs(sensitivity_ra[j] + 2.0*Math.PI)) sensitivity_ra[j] = sensitivity_ra[j] + 2.0*Math.PI; sensitivity_ra[j] = sensitivity_ra[j]/delta; sensitivity_dec[j] = (radec[2] - nominal_observable[index+2])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radec(nominal_r,nominal_rprime,incremental_state_vector_epoch[i],observation_geocentric[i],observation_latitude[i],observation_longitude[i],observation_altitude[i],observation_EOP[i][3],observer_r,observer_rprime,optical_time[i],radec); sensitivity_ra[j+3] = radec[1] - nominal_observable[index+1]; while (Math.abs(sensitivity_ra[j+3]) > Math.abs(sensitivity_ra[j+3] - 2.0*Math.PI)) sensitivity_ra[j+3] = sensitivity_ra[j+3] - 2.0*Math.PI; while (Math.abs(sensitivity_ra[j+3]) > Math.abs(sensitivity_ra[j+3] + 2.0*Math.PI)) sensitivity_ra[j+3] = sensitivity_ra[j+3] + 2.0*Math.PI; sensitivity_ra[j+3] = sensitivity_ra[j+3]/delta; sensitivity_dec[j+3] = (radec[2] - nominal_observable[index+2])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; } /* Calculate the residuals and chi; if observations meet the applicable thresholds, calculate the resulting rows of matrix_a */ observation_residual = Math.acos(Math.sin(nominal_observable[index+2])*Math.sin(optical_dec[i]) + Math.cos(nominal_observable[index+2])*Math.cos(optical_dec[i])*Math.cos(nominal_observable[index+1] - optical_ra[i])); if ( (ra_dev[i] > 0.0) && (dec_dev[i] > 0.0) ) observation_chi = Math.sqrt(Math.pow(residuals[index+1]*Math.cos(optical_dec[i])/ra_dev[i],2.0) + Math.pow(residuals[index+2]/dec_dev[i],2.0)); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold) && (ra_dev[i] < 5.0*Math.PI/(3600.0*180.0)) && (dec_dev[i] < 5.0*Math.PI/(3600.0*180.0))) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < optical_residual_threshold) && (ra_dev[i] < 5.0*Math.PI/(3600.0*180.0)) && (dec_dev[i] < 5.0*Math.PI/(3600.0*180.0))) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_ra[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_dec[k]*phi[k][j]; matrix_a[retained_index+2][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = ra_dev[i]; dev[retained_index+2] = dec_dev[i]; /* Add these residuals to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; retained_residuals[retained_index+2] = residuals[index+2]; /* Increment the retained observation counter */ retained_index = retained_index + 2; } /* Increment the index */ index = index + 2; } else if (observation_type[i] == 2) { /* Delay observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_delay_receiver_r[i][j]; receiver_rprime[j] = radar_delay_receiver_rprime[i][j]; } for (j=1;j<=7;j++) EOP[j] = radar_delay_transmitter_EOP[i][j]; for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; sensitivity_delay[j] = (delay - nominal_observable[index+1])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radar_delay_doppler(radar_delay_receiver_latitude[i],radar_delay_receiver_longitude[i],radar_delay_receiver_altitude[i],receiver_r,receiver_rprime,radar_delay_receiver_time[i],radar_delay_transmitter_latitude[i],radar_delay_transmitter_longitude[i],radar_delay_transmitter_altitude[i],EOP,radar_delay_transmitter_tdb_minus_utc[i],radar_delay_receiver_tdb_minus_utc[i],radar_delay_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); delay = delay_doppler[1]; sensitivity_delay[j+3] = (delay - nominal_observable[index+1])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; } /* Calculate the residual and chi; if observations meet the applicable threshold, calculate the resulting row of matrix_a */ observation_residual = Math.abs(residuals[index+1]); if (delay_dev[i] != 0.0) observation_chi = Math.abs(residuals[index+1]/delay_dev[i]); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold)) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < delay_residual_threshold)) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_delay[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = delay_dev[i]; /* Add this residual to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; /* Increment the retained observation counter */ retained_index = retained_index + 1; } /* Increment the index */ index = index + 1; } else if (observation_type[i] == 3) { /* Doppler observation */ for (j=1;j<=3;j++) { receiver_r[j] = radar_doppler_receiver_r[i][j]; receiver_rprime[j] = radar_doppler_receiver_rprime[i][j]; } for (j=1;j<=7;j++) { EOP[j] = radar_doppler_transmitter_EOP[i][j]; } for (j=1;j<=3;j++) { delta = 0.000001*nominal_r[j]; nominal_r[j] = nominal_r[j] + delta; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; sensitivity_doppler[j] = (doppler - nominal_observable[index+1])/delta; nominal_r[j] = nominal_r[j] - delta; delta = 0.000001*nominal_rprime[j]; nominal_rprime[j] = nominal_rprime[j] + delta; get_observed_radar_delay_doppler(radar_doppler_receiver_latitude[i],radar_doppler_receiver_longitude[i],radar_doppler_receiver_altitude[i],receiver_r,receiver_rprime,radar_doppler_receiver_time[i],radar_doppler_transmitter_latitude[i],radar_doppler_transmitter_longitude[i],radar_doppler_transmitter_altitude[i],EOP,radar_doppler_transmitter_tdb_minus_utc[i],radar_doppler_receiver_tdb_minus_utc[i],radar_doppler_transmitter_frequency[i],minor_planet_radius,nominal_r,nominal_rprime,incremental_state_vector_epoch[i],delay_doppler); doppler = delay_doppler[2]; sensitivity_doppler[j+3] = (doppler - nominal_observable[index+1])/delta; nominal_rprime[j] = nominal_rprime[j] - delta; } /* Calculate the residual and chi; if observations meet the applicable thresholds, calculate the resulting row of matrix_a */ observation_residual = Math.abs(residuals[index+1]); if (doppler_dev[i] != 0.0) observation_chi = Math.abs(residuals[index+1]/doppler_dev[i]); else observation_chi = 0.0; if (((edit) && (chi_testing) && (observation_chi < chi_threshold)) || ((edit) && (residual_testing) && (Math.abs(observation_residual) < doppler_residual_threshold)) || (!edit)) { /* Observation is not excluded; reset flag */ excluded_observations[i] = 0; for (j=1;j<=6;j++) { row[j] = 0.0; for (k=1;k<=6;k++) row[j] = row[j] + sensitivity_doppler[k]*phi[k][j]; matrix_a[retained_index+1][j] = row[j]; } /* Fill the dev array with standard deviations */ dev[retained_index+1] = doppler_dev[i]; /* Add this residual to the list of those being retained */ retained_residuals[retained_index+1] = residuals[index+1]; /* Increment the retained observation counter */ retained_index = retained_index + 1; } /* Increment the index */ index = index + 1; } } /* At this point, matrix A is complete. We proceed to convert to a unity covariance form */ /* Should be commented out */ for (i=1;i<=retained_index;i++) { for (j=1;j<=6;j++) matrix_temp[i][j] = matrix_a[i][j]/dev[i]; retained_residuals[i] = retained_residuals[i]/dev[i]; } for (i=1;i<=retained_index;i++) { for (j=1;j<=6;j++) matrix_a[i][j] = matrix_temp[i][j]; } /* Construct an orthogonal transform T using Givens rotations, and left multiply. matrix_a will now be upper triangular; isolate the upper 6x6 as matrix_R, and isolate the top 6x1 of T x residuals as matrix_z. */ givens_qr(retained_index, 6, matrix_a); read_matrix_Tk(0); for (i=1;i<=6;i++) { matrix_z[i] = 0; for (j=1;j<=retained_index;j++) matrix_z[i] = matrix_z[i] + matrix_Tk[i][j]*retained_residuals[j]; } for (i=1;i<=6;i++) { for (j=1;j<=6;j++) matrix_R[i][j] = matrix_a[i][j]; } /* Calculate the state vector corrections and covariances. */ invert_nxn(6,matrix_R); for (i=1;i<=6;i++) { corrections[i] = 0; for (k=1;k<=6;k++) /* Square Root Information Filtering assumes that we are in unit covariance form. Since we're often unable to invert matrix_R in that form, we must make the conversion to unit covariance form now, so the results are valid. In this particular calculation, we would multiply matrix_R[i][k] by dev[k] (since matrix_a has been inverted), and we would divide matrix_z by dev[k]; since these precisely cancel, no action is really needed here. */ corrections[i] = corrections[i] + matrix_R[i][k]*matrix_z[k]; } for (i=1;i<=6;i++) { for (j=1;j<=6;j++) { big_p[i][j] = 0; for (k=1;k<=6;k++) /* Square Root Information Filtering assumes that we are in unit covariance form. Since we're often unable to invert matrix_R in that form, we must make the conversion to unit covariance form now, so the results are valid. In this particular calculation, we would multiply matrix_R[i][k] by dev[k] (since matrix_a has been inverted), and we would multiply matrix_R[j][k] by dev[k] (for the same reason). */ /* big_p[i][j] = big_p[i][j] + matrix_R[i][k]*matrix_R[j][k]*(dev[k]*dev[k]); */ big_p[i][j] = big_p[i][j] + matrix_R[i][k]*matrix_R[j][k]; } } /* At this point, for LOV sampling, we must convert the state vector corrections to a basis where the first vector is the eigenvector of the current covariance matrix (input as "bvector"). We will then set the 1st entry in the corrections array to zero, so we continue traveling along the LOV. Finally, we'll convert this corrections array back to normal space, and apply the correction. */ if (Math.abs(bvector[1] - 1.0) > 1.0E-10) { /* Create the conversion matrix P and its inverse */ for (i=1;i<=6;i++) { matrix_P[i][1] = bvector[i]; inverse_matrix_P[i][1] = bvector[i]; } /* Column 2 */ delta = 0; for (i=1;i<=6;i++) { if (i == 2) matrix_P[i][2] = 1.0 - bvector[2]*matrix_P[i][1]; else matrix_P[i][2] = -bvector[2]*matrix_P[i][1]; delta = delta + matrix_P[i][2]*matrix_P[i][2]; } for (i=1;i<=6;i++) { matrix_P[i][2] = matrix_P[i][2]/Math.sqrt(delta); inverse_matrix_P[i][2] = matrix_P[i][2]; } /* Column 3 */ delta = 0; for (i=1;i<=6;i++) { if (i == 3) matrix_P[i][3] = 1.0 - bvector[3]*matrix_P[i][1] - matrix_P[3][2]*matrix_P[i][2]; else matrix_P[i][3] = -bvector[3]*matrix_P[i][1] - matrix_P[3][2]*matrix_P[i][2]; delta = delta + matrix_P[i][3]*matrix_P[i][3]; } for (i=1;i<=6;i++) { matrix_P[i][3] = matrix_P[i][3]/Math.sqrt(delta); inverse_matrix_P[i][3] = matrix_P[i][3]; } /* Column 4 */ delta = 0; for (i=1;i<=6;i++) { if (i == 4) matrix_P[i][4] = 1.0 - bvector[4]*matrix_P[i][1] - matrix_P[4][2]*matrix_P[i][2] - matrix_P[4][3]*matrix_P[i][3]; else matrix_P[i][4] = -bvector[4]*matrix_P[i][1] - matrix_P[4][2]*matrix_P[i][2] - matrix_P[4][3]*matrix_P[i][3]; delta = delta + matrix_P[i][4]*matrix_P[i][4]; } for (i=1;i<=6;i++) { matrix_P[i][4] = matrix_P[i][4]/Math.sqrt(delta); inverse_matrix_P[i][4] = matrix_P[i][4]; } /* Column 5 */ delta = 0; for (i=1;i<=6;i++) { if (i == 5) matrix_P[i][5] = 1.0 - bvector[5]*matrix_P[i][1] - matrix_P[5][2]*matrix_P[i][2] - matrix_P[5][3]*matrix_P[i][3] - matrix_P[5][4]*matrix_P[i][4]; else matrix_P[i][5] = -bvector[5]*matrix_P[i][1] - matrix_P[5][2]*matrix_P[i][2] - matrix_P[5][3]*matrix_P[i][3] - matrix_P[5][4]*matrix_P[i][4]; delta = delta + matrix_P[i][5]*matrix_P[i][5]; } for (i=1;i<=6;i++) { matrix_P[i][5] = matrix_P[i][5]/Math.sqrt(delta); inverse_matrix_P[i][5] = matrix_P[i][5]; } /* Column 6 */ delta = 0; for (i=1;i<=6;i++) { if (i == 6) matrix_P[i][6] = 1.0 - bvector[6]*matrix_P[i][1] - matrix_P[6][2]*matrix_P[i][2] - matrix_P[6][3]*matrix_P[i][3] - matrix_P[6][4]*matrix_P[i][4] - matrix_P[6][5]*matrix_P[i][5]; else matrix_P[i][6] = -bvector[6]*matrix_P[i][1] - matrix_P[6][2]*matrix_P[i][2] - matrix_P[6][3]*matrix_P[i][3] - matrix_P[6][4]*matrix_P[i][4] - matrix_P[6][5]*matrix_P[i][5]; delta = delta + matrix_P[i][6]*matrix_P[i][6]; } for (i=1;i<=6;i++) { matrix_P[i][6] = matrix_P[i][6]/Math.sqrt(delta); inverse_matrix_P[i][6] = matrix_P[i][6]; } invert_nxn(6,inverse_matrix_P); for (i=1;i<=6;i++) { converted_corrections[i] = inverse_matrix_P[i][1]*corrections[1] + inverse_matrix_P[i][2]*corrections[2] + inverse_matrix_P[i][3]*corrections[3] + inverse_matrix_P[i][4]*corrections[4] + inverse_matrix_P[i][5]*corrections[5] + inverse_matrix_P[i][6]*corrections[6]; } converted_corrections[1] = 0.0; for (i=1;i<=6;i++) { corrections[i] = matrix_P[i][1]*converted_corrections[1] + matrix_P[i][2]*converted_corrections[2] + matrix_P[i][3]*converted_corrections[3] + matrix_P[i][4]*converted_corrections[4] + matrix_P[i][5]*converted_corrections[5] + matrix_P[i][6]*converted_corrections[6]; } } /* Update the epoch state vector */ for (i=1;i<=3;i++) { epoch_r[i] = epoch_r[i] + corrections[i]; epoch_rprime[i] = epoch_rprime[i] + corrections[i+3]; } /* Recalculate the residuals using the updated epoch state vector */ get_residuals(residuals,rms_residuals); old_rms_optical = rms_optical; old_rms_delay = rms_delay; old_rms_doppler = rms_doppler; rms_optical = rms_residuals[1]; rms_delay = rms_residuals[2]; rms_doppler = rms_residuals[3]; System.out.println("old_rms_optical = " + old_rms_optical); System.out.println("old_rms_delay = " + old_rms_delay); System.out.println("old_rms_doppler = " + old_rms_doppler); /* Calculate the asteroid's absolute magnitude, slope parameter, and radius. This last is especially important when radar observations are used, to account for the "bounce point". */ solve_for_absolute_magnitude(); /* Based on the RMS residuals, evaluate the convergence and divergence conditions */ convergence = true; if ((Math.abs(old_rms_optical - rms_optical) > (convergence_factor*old_rms_optical)) || (Math.abs(old_rms_delay - rms_delay) > (convergence_factor*old_rms_delay)) || (Math.abs(old_rms_doppler - rms_doppler) > (convergence_factor*old_rms_doppler))) convergence = false; if ((rms_optical >= 1.2*old_rms_optical) && (rms_delay >= 1.2*old_rms_delay) && (rms_doppler >= 1.2*old_rms_doppler)) divergence = true; if ((convergence) && (!edit) && ((chi_testing) || (residual_testing))) { edit = true; convergence = false; } /* We may not have yet converged. However, if the residuals have been reduced, shouldn't we keep this state vector and covariance matrix as the initial data in case the next iteration diverges? */ if ((rms_optical <= old_rms_optical) && (rms_delay <= old_rms_delay) && (rms_doppler <= old_rms_doppler)) { for (i=1;i<=3;i++) { initial_r[i] = epoch_r[i]; initial_rprime[i] = epoch_rprime[i]; initial_rms_residuals[i] = rms_residuals[i]; } for (i=1;i<=8;i++) { for (j=1;j<=8;j++) initial_big_p[i][j] = big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) initial_residuals[i] = residuals[i]; } System.out.println("convergence = " + convergence); System.out.println("divergence = " + divergence); /* The loop is complete. If the convergence or divergence condition is satisfied, we will exit; if not, we will go around again */ } if (divergence == true) { /* Return initial orbit and residuals, and set epoch_r[0] = 6.0. */ for (i=1;i<=3;i++) { epoch_r[i] = initial_r[i]; epoch_rprime[i] = initial_rprime[i]; rms_residuals[i] = initial_rms_residuals[i]; } epoch_r[0] = 6.0; for (i=1;i<=8;i++) { for (j=1;j<=8;j++) big_p[i][j] = initial_big_p[i][j]; } index = 2*number_of_optical_observations + number_of_delay_observations + number_of_doppler_observations; for (i=1;i<=index;i++) residuals[i] = initial_residuals[i]; } else if (divergence == false) epoch_r[0] = 0.0; } public void mp_get_covariance_eigenvector(double[] eigenvector, double[][] covariance_matrix) { /* Procedure to calculate the dominant eigenvalue and (unit) eigenvector corresponding to the weak direction of the covariance matrix using the Power Method. Note: The eigenvalue is returned as eigenvector[0]. Algorithm taken from Richard L. Burden and J. Douglas Faires, "Numerical Analysis", 3rd edition, pp. 454-455. */ int k = 0, counter = 0; double[] y = new double[7]; double error = 1.0, normalizer = 0; for (k = 1; k <= 6; k++) eigenvector[k] = 1.0; while ((counter < 100) && (error > 1.0E-8)) { for (k = 1; k <= 6; k++) y[k] = covariance_matrix[k][1]*eigenvector[1] + covariance_matrix[k][2]*eigenvector[2] + covariance_matrix[k][3]*eigenvector[3] + covariance_matrix[k][4]*eigenvector[4] + covariance_matrix[k][5]*eigenvector[5] + covariance_matrix[k][6]*eigenvector[6]; normalizer = 0; for (k = 1; k <= 6; k++) if (Math.abs(y[k]) > normalizer) normalizer = Math.abs(y[k]); error = 0; for (k = 1; k <= 6; k++) { if (Math.abs(eigenvector[k] - y[k]/normalizer) > error) error = Math.abs(eigenvector[k] - y[k]/normalizer); eigenvector[k] = y[k]/normalizer; } counter = counter + 1; } eigenvector[0] = normalizer; normalizer = Math.sqrt(Math.pow(eigenvector[1],2.0) + Math.pow(eigenvector[2],2.0) + Math.pow(eigenvector[3],2.0) + Math.pow(eigenvector[4],2.0) + Math.pow(eigenvector[5],2.0) + Math.pow(eigenvector[6],2.0)); for (k = 1; k <= 6; k++) { eigenvector[k] = eigenvector[k]/normalizer; } } double covariance_second_eigenvalue(double[][] covariance) { /* Procedure to calculate the second largest eigenvalue of the covariance matrix. This corresponds to the width of the uncertainty region, and can be used to determine whether a collision is possible. Algorithm taken from Howard Anton, "Elementary Linear Algebra", 4th edition, pg. 348. */ int i = 0, j = 0, k = 0; double[] first_eigenvector = new double[7]; double[] eigenvector = new double[7]; double[][] vvt = new double[7][7]; double[][] deflated_cov = new double[7][7]; /* Determine the largest eigenvalue (and the corresponding eigenvector) of the covariance matrix */ mp_get_covariance_eigenvector(first_eigenvector, covariance); /* Create the deflated covariance matrix */ for (i=1;i<=6;i++) { for (j=1;j<=6;j++) vvt[i][j] = first_eigenvector[i]*first_eigenvector[j]; } for (i=1;i<=6;i++) { for (j=1;j<=6;j++) deflated_cov[i][j] = covariance[i][j] - first_eigenvector[0]*vvt[i][j]; } /* Determine the largest eigenvalue (and the corresponding eigenvector) of the propogated covariance matrix */ mp_get_covariance_eigenvector(eigenvector, deflated_cov); return eigenvector[0]; } public double get_relative_MOID(double[] big_asteroid_elements, double[] little_asteroid_elements) { /* Procedure to estimate the minimum orbital intersection distance between two minor planets. */ /* The algorithm is due to Sitarski, Acta Astronica, 1968 (Vol 18 pp. 171-181) */ double[][] solution = new double[800][3]; double[][] big_h = new double[4][4]; double[][] little_h = new double[4][4]; double[][] inter1 = new double[4][4]; double[][] inter2 = new double[4][4]; double[][] inter3 = new double[4][4]; double[] big_u = new double[4]; double[] little_u = new double[4]; double[] big_pos = new double[4]; double[] little_pos = new double[4]; int i = 0, j = 0, big_v_counter = 0, little_v_counter = 0, solution_counter = 0, k = 0; double moid = 1000.0, big_v = 0, little_v = 0, big_r = 0, big_p = 0, little_p = 0, big_x = 0, big_y = 0, big_m = 0, big_n = 0, big_px = 0, big_py = 0, big_pz = 0, big_qx = 0, big_qy = 0, big_qz = 0, little_px = 0, little_py = 0, little_pz = 0, little_qx = 0, little_qy = 0, little_qz = 0, big_k = 0, big_l = 0, s = 0, t = 0, w = 0, little_m = 0, little_l = 0, solution_little_v1 = 0, solution_little_v2 = 0, old_equation_value_1 = 0, old_equation_value_2 = 0, equation_value_1 = 0, equation_value_2 = 0, little_x = 0, little_y = 0, little_r = 0, old_big_v = 0, top = 0, bottom = 0, middle = 0, bisection_equation_value_1 = 0, bisection_equation_value_2 = 0, distance = 0, longitude1 = 0, top_sign = 0, partial_big_v = 0, partial_little_v = 0, mixed_partial = 0; /* Create the quantities needed to evaluate the key equation 11 */ big_p = big_asteroid_elements[8]*(1.0 + big_asteroid_elements[2]); little_p = little_asteroid_elements[8]*(1.0 + little_asteroid_elements[2]); big_px = Math.cos(big_asteroid_elements[4]); big_py = Math.sin(big_asteroid_elements[4])*Math.cos(big_asteroid_elements[3]); big_pz = Math.sin(big_asteroid_elements[4])*Math.sin(big_asteroid_elements[3]); big_qx = -Math.sin(big_asteroid_elements[4]); big_qy = Math.cos(big_asteroid_elements[4])*Math.cos(big_asteroid_elements[3]); big_qz = Math.cos(big_asteroid_elements[4])*Math.sin(big_asteroid_elements[3]); little_px = Math.cos(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]) - Math.sin(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); little_py = Math.cos(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]) + Math.sin(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); little_pz = Math.sin(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[3]); little_qx = -Math.sin(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]) - Math.cos(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); little_qy = -Math.sin(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]) + Math.cos(little_asteroid_elements[4])*Math.cos(little_asteroid_elements[3])*Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); little_qz = Math.cos(little_asteroid_elements[4])*Math.sin(little_asteroid_elements[3]); big_k = big_px*little_px + big_py*little_py + big_pz*little_pz; big_l = big_qx*little_px + big_qy*little_py + big_qz*little_pz; big_m = big_px*little_qx + big_py*little_qy + big_pz*little_qz; big_n = big_qx*little_qx + big_qy*little_qy + big_qz*little_qz; /* Calculate cracovians big_h and little_h for use in evaluating the distances */ inter1[1][1] = Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[1][2] = Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[1][3] = 0.0; inter1[2][1] = -Math.sin(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[2][2] = Math.cos(little_asteroid_elements[5] - big_asteroid_elements[5]); inter1[2][3] = 0.0; inter1[3][1] = 0.0; inter1[3][2] = 0.0; inter1[3][3] = 1.0; inter2[1][1] = 1.0; inter2[1][2] = 0.0; inter2[1][3] = 0.0; inter2[2][1] = 0.0; inter2[2][2] = Math.cos(little_asteroid_elements[3]); inter2[2][3] = -Math.sin(little_asteroid_elements[3]); inter2[3][1] = 0.0; inter2[3][2] = Math.sin(little_asteroid_elements[3]); inter2[3][3] = Math.cos(little_asteroid_elements[3]); /* Multiply the cracovians, using cracovian logic, rather than matrix multiplication */ for (i = 1; i <= 3; i++) { for (j = 1; j <= 3; j++) { inter3[i][j] = 0.0; for (k = 1; k <= 3; k++) inter3[i][j] = inter3[i][j] + inter1[k][j]*inter2[k][i]; } } inter1[1][1] = Math.cos(little_asteroid_elements[4]); inter1[1][2] = -Math.sin(little_asteroid_elements[4]); inter1[1][3] = 0.0; inter1[2][1] = Math.sin(little_asteroid_elements[4]); inter1[2][2] = Math.cos(little_asteroid_elements[4]); inter1[2][3] = 0.0; inter1[3][1] = 0.0; inter1[3][2] = 0.0; inter1[3][3] = 1.0; /* Multiply the cracovians, using cracovian logic, rather than matrix multiplication */ for (i = 1; i <= 3; i++) { for (j = 1; j <= 3; j++) { little_h[i][j] = 0.0; for (k = 1; k <= 3; k++) little_h[i][j] = little_h[i][j] + inter3[k][j]*inter1[k][i]; } } inter2[1][1] = 1.0; inter2[1][2] = 0.0; inter2[1][3] = 0.0; inter2[2][1] = 0.0; inter2[2][2] = Math.cos(big_asteroid_elements[3]); inter2[2][3] = Math.sin(big_asteroid_elements[3]); inter2[3][1] = 0.0; inter2[3][2] = -Math.sin(big_asteroid_elements[3]); inter2[3][3] = Math.cos(big_asteroid_elements[3]); inter1[1][1] = Math.cos(big_asteroid_elements[4]); inter1[1][2] = -Math.sin(big_asteroid_elements[4]); inter1[1][3] = 0.0; inter1[2][1] = Math.sin(big_asteroid_elements[4]); inter1[2][2] = Math.cos(big_asteroid_elements[4]); inter1[2][3] = 0.0; inter1[3][1] = 0.0; inter1[3][2] = 0.0; inter1[3][3] = 1.0; /* Multiply the cracovians, using cracovian logic, rather than matrix multiplication */ for (i = 1; i <= 3; i++) { for (j = 1; j <= 3; j++) { big_h[i][j] = 0.0; for (k = 1; k <= 3; k++) big_h[i][j] = big_h[i][j] + inter2[k][j]*inter1[k][i]; } } big_v = -181*Math.PI/180.0; for (big_v_counter=-180;big_v_counter<=180;big_v_counter++) { old_big_v = big_v; big_v = big_v_counter*Math.PI/180.0; big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(big_v)); big_x = big_r*Math.cos(big_v); big_y = big_r*Math.sin(big_v); s = big_asteroid_elements[2]*big_r*big_y/little_p; t = big_m*big_y - big_n*(big_asteroid_elements[2]*big_r + big_x); w = little_asteroid_elements[2]*s + big_k*big_y - big_l*(big_asteroid_elements[2]*big_r + big_x); little_m = t*t + w*w; little_l = little_m - s*s; solution_little_v1 = Math.atan2((-t*s + w*Math.sqrt(little_l))/little_m, (-w*s - t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v1)); little_x = little_r*Math.cos(solution_little_v1); little_y = little_r*Math.sin(solution_little_v1); equation_value_1 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); solution_little_v2 = Math.atan2((-t*s - w*Math.sqrt(little_l))/little_m, (-w*s + t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v2)); little_x = little_r*Math.cos(solution_little_v2); little_y = little_r*Math.sin(solution_little_v2); equation_value_2 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); if ((big_v_counter > -180) && (((old_equation_value_1 <= 0.0) && (equation_value_1 >= 0.0)) || ((old_equation_value_1 >= 0.0) && (equation_value_1 <= 0.0)))) { top = big_v; bottom = old_big_v; if (equation_value_1 > 0) top_sign = 1.0; else top_sign = -1.0; while (Math.abs(top - bottom) > 1.0e-7) { middle = (top + bottom)/2.0; big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(middle)); big_x = big_r*Math.cos(middle); big_y = big_r*Math.sin(middle); s = big_asteroid_elements[2]*big_r*big_y/little_p; t = big_m*big_y - big_n*(big_asteroid_elements[2]*big_r + big_x); w = little_asteroid_elements[2]*s + big_k*big_y - big_l*(big_asteroid_elements[2]*big_r + big_x); little_m = t*t + w*w; little_l = little_m - s*s; solution_little_v1 = Math.atan2((-t*s + w*Math.sqrt(little_l))/little_m, (-w*s - t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v1)); little_x = little_r*Math.cos(solution_little_v1); little_y = little_r*Math.sin(solution_little_v1); bisection_equation_value_1 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); if ((bisection_equation_value_1 >= 0) && (top_sign > 0)) top = middle; else if ((bisection_equation_value_1 <= 0) && (top_sign > 0)) bottom = middle; else if ((bisection_equation_value_1 >= 0) && (top_sign < 0)) bottom = middle; else if ((bisection_equation_value_1 <= 0) && (top_sign < 0)) top = middle; } solution_counter ++; solution[solution_counter][1] = middle; solution[solution_counter][2] = solution_little_v1; partial_big_v = (big_r/big_p)*((big_asteroid_elements[2]*big_r*big_r/big_p)*(big_asteroid_elements[2]*big_r + big_x) + big_x*(big_k*little_x + big_m*little_y) + big_y*(big_l*little_x + big_n*little_y)); partial_little_v = (little_r/little_p)*((little_asteroid_elements[2]*little_r*little_r/little_p)*(little_asteroid_elements[2]*little_r + little_x) + little_x*(big_k*big_x + big_l*big_y) + little_y*(big_m*big_x + big_n*big_y)); mixed_partial = (big_r/big_p)*(little_r/little_p)*((little_asteroid_elements[2]*little_r + little_x)*(big_n*(big_asteroid_elements[2]*big_r + big_x) - big_m*big_y) - little_y*(big_l*(big_asteroid_elements[2]*big_r + big_x) - big_k*big_y)); } if ((big_v_counter > -180) && (((old_equation_value_2 <= 0.0) && (equation_value_2 >= 0.0)) || ((old_equation_value_2 >= 0.0) && (equation_value_2 <= 0.0)))) { top = big_v; bottom = old_big_v; if (equation_value_2 > 0) top_sign = 1.0; else top_sign = -1.0; while (Math.abs(top - bottom) > 1.0e-7) { middle = (top + bottom)/2.0; big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(middle)); big_x = big_r*Math.cos(middle); big_y = big_r*Math.sin(middle); s = big_asteroid_elements[2]*big_r*big_y/little_p; t = big_m*big_y - big_n*(big_asteroid_elements[2]*big_r + big_x); w = little_asteroid_elements[2]*s + big_k*big_y - big_l*(big_asteroid_elements[2]*big_r + big_x); little_m = t*t + w*w; little_l = little_m - s*s; solution_little_v2 = Math.atan2((-t*s - w*Math.sqrt(little_l))/little_m, (-w*s + t*Math.sqrt(little_l))/little_m); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution_little_v2)); little_x = little_r*Math.cos(solution_little_v2); little_y = little_r*Math.sin(solution_little_v2); bisection_equation_value_2 = (little_r/little_p)*(little_asteroid_elements[2]*little_r*little_y + little_y*(big_k*big_x + big_l*big_y) - (little_asteroid_elements[2]*little_r + little_x)*(big_m*big_x + big_n*big_y)); if ((bisection_equation_value_2 >= 0) && (top_sign > 0)) top = middle; else if ((bisection_equation_value_2 <= 0) && (top_sign > 0)) bottom = middle; else if ((bisection_equation_value_2 >= 0) && (top_sign < 0)) bottom = middle; else if ((bisection_equation_value_2 <= 0) && (top_sign < 0)) top = middle; } solution_counter ++; solution[solution_counter][1] = middle; solution[solution_counter][2] = solution_little_v2; partial_big_v = (big_r/big_p)*((big_asteroid_elements[2]*big_r*big_r/big_p)*(big_asteroid_elements[2]*big_r + big_x) + big_x*(big_k*little_x + big_m*little_y) + big_y*(big_l*little_x + big_n*little_y)); partial_little_v = (little_r/little_p)*((little_asteroid_elements[2]*little_r*little_r/little_p)*(little_asteroid_elements[2]*little_r + little_x) + little_x*(big_k*big_x + big_l*big_y) + little_y*(big_m*big_x + big_n*big_y)); mixed_partial = (big_r/big_p)*(little_r/little_p)*((little_asteroid_elements[2]*little_r + little_x)*(big_n*(big_asteroid_elements[2]*big_r + big_x) - big_m*big_y) - little_y*(big_l*(big_asteroid_elements[2]*big_r + big_x) - big_k*big_y)); } old_equation_value_1 = equation_value_1; old_equation_value_2 = equation_value_2; } /* We now have several distance minima; determine which is smallest by direct calculation. */ for (i = 1; i <= solution_counter; i++) { big_r = big_p/(1.0 + big_asteroid_elements[2]*Math.cos(solution[i][1])); little_r = little_p/(1.0 + little_asteroid_elements[2]*Math.cos(solution[i][2])); big_u[1] = big_r*Math.cos(solution[i][1]); big_u[2] = big_r*Math.sin(solution[i][1]); big_u[3] = 0.0; little_u[1] = little_r*Math.cos(solution[i][2]); little_u[2] = little_r*Math.sin(solution[i][2]); little_u[3] = 0.0; big_pos[1] = big_u[1]*big_h[1][1] + big_u[2]*big_h[2][1] + big_u[3]*big_h[3][1]; big_pos[2] = big_u[1]*big_h[1][2] + big_u[2]*big_h[2][2] + big_u[3]*big_h[3][2]; big_pos[3] = big_u[1]*big_h[1][3] + big_u[2]*big_h[2][3] + big_u[3]*big_h[3][3]; little_pos[1] = little_u[1]*little_h[1][1] + little_u[2]*little_h[2][1] + little_u[3]*little_h[3][1]; little_pos[2] = little_u[1]*little_h[1][2] + little_u[2]*little_h[2][2] + little_u[3]*little_h[3][2]; little_pos[3] = little_u[1]*little_h[1][3] + little_u[2]*little_h[2][3] + little_u[3]*little_h[3][3]; distance = Math.sqrt(Math.pow((big_pos[1] - little_pos[1]), 2.0) + Math.pow((big_pos[2] - little_pos[2]), 2.0) + Math.pow((big_pos[3] - little_pos[3]), 2.0)); if (distance < moid) moid = distance; } return moid; } }