Exemple #1
0
def filter_expected(function_name: str, data):
    xsuccess = []
    xfail = []

    for test_input, test_output in data:

        input_str = str(test_input)

        if isinstance(test_output, str):
            # Expect fail --> Make pytest.param marked with xfai
            # e.g. pytest.param("6*9", 42, marks=pytest.mark.xfail)
            xfail.append(
                pytest.param(
                    f"{function_name}(ephemerides,{input_str})",
                    None,
                    marks=pytest.mark.xfail(raises=ValueError),
                ))

        else:
            # Expect success -> make tuple list ("function(input)", test_output)
            eph = get_ephemerides_on_day(get_ephemerides(), test_input)
            sun = list(eph["sun"])
            earth = list(eph["earth"])
            mars = list(eph["mars"])

            del sun[2]
            del earth[2]
            del mars[2]

            xsuccess.append(([sun, earth, mars], test_output))

    return xsuccess, xfail
Exemple #2
0
def test_invalid_days(test_input, expected):
    """
    Tests of get_ephemerides_on_day"""

    # 1. We have to use pytest.approx since there is a difference on the rounding of the
    #    very last decimal in reading in the .csv between Python and Mathematica.

    # 2. We have to iterate over the input/output with for loop since pytest.approx does
    #    not support nested structures.

    ephemerides = get_ephemerides()  # pylint: disable=W0612
    assert eval(test_input) == expected  # pylint: disable=W0123
Exemple #3
0
def process_ephemerides_for_coordinate_function(function_name: str, data):
    coords = []

    for test_input, expected in data:
        # Expect success -> make tuple list ("function(input)", test_output)
        output = list(
            get_coordinates_on_day_rad(
                get_ephemerides_on_day(get_ephemerides(), test_input)))

        coords.append((output, expected))

    return coords
Exemple #4
0
def get_leo_position_and_velocity(day=0, altitude=160, max_year="2020"):
    """Calculate direction of initial velocity vector.
    Assumes ephemerides are given with 1 day interval. With a series of cross products,
    calculate a LEO position perpendicular and velocity parallel to Earth's velocity
    vector in same direction and plane as Earth.

    Arguments:
        ephemerides {[dict of pandas dataframe]} -- ephemerides table.
        day {[int]} -- day (day=0 at 2019-01-01 00:00:00)

    Keyword Arguments:
        orientation {str} -- Wanted LEO orbit orientation (default: {'ecliptic'})

    Returns:
        List[List[int]] -- Initial LEO position vector, spherical [AU/y, rad, rad]
                           initial LEO velocity vector, spherical [AU/y, rad/y, rad/y]
    """

    # region --- 1 Calculate Earth velocity and speed (cartesian & spherical)

    # Earth velocity at day = dr/dt ≈ Δr/Δt, where Δt = 1 day
    # i.e. Earth velocity estimated by difference of position vector 1 day apart / 1 day
    ephemerides = get_ephemerides(max_year=max_year)
    eph_day = get_ephemerides_on_day(ephemerides, day)
    eph_daym1 = get_ephemerides_on_day(ephemerides, day - 1)

    earth_day = eph_day["earth"]
    earth_daym1 = eph_daym1["earth"]
    earth_diff = earth_day - earth_daym1

    # Get positions of Earth at {day, day-1} in spherical and cartesian coordinates
    earth_q0_spherical_AU_deg = [earth_day["r"], earth_day["theta"], earth_day["phi"]]
    earth_q0_cartesian_AU = [earth_day["x"], earth_day["y"], earth_day["z"]]

    earth_qm1_spherical_AU = [
        earth_daym1["r"],
        earth_daym1["theta"],
        earth_daym1["phi"],
    ]
    earth_qm1_cartesian_AU = [earth_daym1["x"], earth_daym1["y"], earth_daym1["z"]]

    # Average r and theta needed for speed computation in spherical coordinates
    r_average_AU = np.mean([earth_day["r"], earth_daym1["r"]])
    r_average_km = r_average_AU * UNIT_LENGTH
    logging.debug(f"r_average: {r_average_AU} AU = {r_average_km} km")

    theta_average_deg = np.mean([earth_day["theta"], earth_daym1["theta"]])
    theta_average_rad = radians(theta_average_deg)
    logging.debug(f"theta_average: {theta_average_deg} deg = {theta_average_rad} rad")

    # Earth velocity vector in spherical and cartesian coordinates and convert units
    earth_qdot0_spherical_au_day_deg = [
        earth_diff["r"],
        earth_diff["theta"],
        earth_diff["phi"],
    ]

    earth_qdot0_spherical_km_s_rad = [
        earth_qdot0_spherical_au_day_deg[0] / (24 * 3600) * UNIT_LENGTH,
        radians(earth_qdot0_spherical_au_day_deg[1]) / (24 * 3600),
        radians(earth_qdot0_spherical_au_day_deg[2]) / (24 * 3600),
    ]

    earth_qdot0_cartesian_au_day = [earth_diff["x"], earth_diff["y"], earth_diff["z"]]

    earth_qdot0_cartesian_km_s = [
        earth_qdot0_cartesian_au_day[0] / (24 * 3600) * UNIT_LENGTH,
        earth_qdot0_cartesian_au_day[1] / (24 * 3600) * UNIT_LENGTH,
        earth_qdot0_cartesian_au_day[2] / (24 * 3600) * UNIT_LENGTH,
    ]

    # Speeds
    earth_qdot0_spherical_km_s_rad_speed = get_speed_spherical(
        r_average_km,
        theta_average_rad,
        earth_qdot0_spherical_km_s_rad[0],  # rdot
        earth_qdot0_spherical_km_s_rad[1],  # thetadot
        earth_qdot0_spherical_km_s_rad[2],  # phidot
    )

    earth_qdot0_cartesian_au_day_speed = np.linalg.norm(earth_qdot0_cartesian_au_day)

    earth_qdot0_cartesian_km_s_speed = np.linalg.norm(earth_qdot0_cartesian_km_s)

    # Logs
    logging.debug(
        f"Earth initial position at day {day} (cartesian, AU): "
        f"{earth_q0_cartesian_AU}"
    )
    logging.debug(
        f"Earth initial position at day {day-1} (cartesian, AU): "
        f"{earth_qm1_cartesian_AU}"
    )

    logging.debug(
        f"Earth initial position at day {day} (spherical, AU & deg): "
        f"{earth_q0_spherical_AU_deg}"
    )
    logging.debug(
        f"Earth initial position at day {day-1} (spherical, AU & deg): "
        f"{earth_qm1_spherical_AU}"
    )

    logging.debug(
        f"Earth initial velocity (spherical, AU/d & deg/d): "
        f"{earth_qdot0_spherical_au_day_deg}"
    )
    logging.debug(
        f"Earth initial velocity (spherical, km/s & rad/s): "
        f"{earth_qdot0_spherical_km_s_rad} "
        f"(speed: {earth_qdot0_spherical_km_s_rad_speed})"
    )

    logging.debug(
        f"Earth initial velocity (cartesian, AU/d): {earth_qdot0_cartesian_au_day}"
        f" (speed: {earth_qdot0_cartesian_au_day_speed})"
    )
    logging.debug(
        f"Earth initial velocity (cartesian, km/s): {earth_qdot0_cartesian_km_s}"
        f"        (speed: {earth_qdot0_cartesian_km_s_speed})"
    )

    # endregion

    # region --- 2 Earth plane vector

    earth_orbital_plane = np.cross(earth_q0_cartesian_AU, earth_qdot0_cartesian_au_day)
    earth_orbital_plane /= np.linalg.norm(earth_orbital_plane)

    logging.debug(f"Ecliptic plane vector: {earth_orbital_plane}")

    # endregion

    # region --- 3 Spacecraft initial position

    # Spacecraft geocentric position: perpendicular to earth velocity pointing outwards
    # (i.e. chosen such that it's the one pointing outwards from elliptical orbit)
    # (note this means spacecraft speed != earth speed (helio) + spacecraft speed (geo))
    q0_geocentric_cartesian_unit = np.cross(
        earth_qdot0_cartesian_km_s, earth_orbital_plane
    )
    q0_geocentric_cartesian_unit /= np.linalg.norm(q0_geocentric_cartesian_unit)

    q0_geocentric_cartesian_km = q0_geocentric_cartesian_unit * (
        EARTH_RADIUS + altitude
    )
    q0_geocentric_cartesian_AU = q0_geocentric_cartesian_km / UNIT_LENGTH

    q0_cartesian_AU = earth_q0_cartesian_AU + q0_geocentric_cartesian_AU
    q0_cartesian_km = q0_cartesian_AU * UNIT_LENGTH

    q0_spherical_AU_rad = list(get_position_spherical_from_cartesian(*q0_cartesian_AU))

    q0_spherical_AU_deg = list(q0_spherical_AU_rad)  # copy, not reference
    q0_spherical_AU_deg[1] = degrees(q0_spherical_AU_deg[1])
    q0_spherical_AU_deg[2] = degrees(q0_spherical_AU_deg[2])

    logging.debug(
        f"Spacecraft initial position unit vector (geocentric, cartesian): "
        f"{q0_geocentric_cartesian_unit}"
    )

    logging.debug(
        f"Spacecraft initial position (geocentric, cartesian, km): "
        f"{q0_geocentric_cartesian_km}"
        f" (distance from Earth center: {np.linalg.norm(q0_geocentric_cartesian_km)})"
    )

    logging.debug(
        f"Spacecraft initial position (geocentric, cartesian, AU): "
        f"{q0_geocentric_cartesian_AU}"
        f" (distance from Earth center: {np.linalg.norm(q0_geocentric_cartesian_AU)})"
    )
    logging.debug(
        f"Spacecraft initial position (heliocentric, cartesian, AU): "
        f"{q0_cartesian_AU}"
    )
    logging.debug(
        f"Spacecraft initial position (heliocentric, cartesian, km): "
        f"{q0_cartesian_km}"
    )

    logging.debug(
        f"Spacecraft initial position (heliocentric, spherical, AU & rad): "
        f"{q0_spherical_AU_rad}"
    )
    logging.debug(
        f"Spacecraft initial position (heliocentric, spherical, AU & deg): "
        f"{q0_spherical_AU_deg}"
    )

    # endregion

    # region --- 4 Spacecraft initial velocity
    # Spacecraft velocity = Earth velocity + leo speed (same direction as Earth)

    leo_speed = get_circular_orbit_speed("Earth", altitude)

    qdot0_cartesian_unit = list(earth_qdot0_cartesian_km_s)
    qdot0_cartesian_unit /= np.linalg.norm(qdot0_cartesian_unit)

    qdot0_cartesian_km_s = earth_qdot0_cartesian_km_s + qdot0_cartesian_unit * leo_speed
    qdot0_cartesian_km_s_speed = np.linalg.norm(qdot0_cartesian_km_s)

    # Get spherical velocity vector from cartesian velocity vector
    qdot0_spherical_km_s_rad = get_velocity_spherical_from_cartesian(
        q0_cartesian_km, qdot0_cartesian_km_s
    )

    qdot0_spherical_km_s_rad_speed = get_speed_spherical(
        r_average_km,
        theta_average_rad,
        qdot0_spherical_km_s_rad[0],  # rdot
        qdot0_spherical_km_s_rad[1],  # thetadot
        qdot0_spherical_km_s_rad[2],  # phidot
    )

    qdot0_spherical_AU_rad_y = [
        qdot0_spherical_km_s_rad[0] / UNIT_VELOCITY,  # AU/y
        qdot0_spherical_km_s_rad[1] * UNIT_TIME,  # rad/y
        qdot0_spherical_km_s_rad[2] * UNIT_TIME,  # rad/y
    ]

    qdot0_spherical_AU_rad_year_speed = get_speed_spherical(
        r_average_AU,
        theta_average_rad,
        qdot0_spherical_AU_rad_y[0],  # rdot
        qdot0_spherical_AU_rad_y[1],  # thetadot
        qdot0_spherical_AU_rad_y[2],  # phidot
    )

    logging.debug(
        f"Spacecraft initial velocity unit vector (cartesian, km/s): "
        f"{qdot0_cartesian_unit}"
    )
    logging.debug(
        f"Spacecraft initial velocity vector (cartesian, km/s): {qdot0_cartesian_km_s}"
        f" (speed: {qdot0_cartesian_km_s_speed})"
    )

    logging.debug(
        f"Spacecraft initial velocity vector (spherical, km/s & rad/s): "
        f"{qdot0_spherical_km_s_rad}"
        f" (speed: {qdot0_spherical_km_s_rad_speed})"
    )

    logging.debug(
        f"Spacecraft initial velocity vector (spherical, AU/y & rad/y): "
        f"{qdot0_spherical_AU_rad_y}"
        f" (speed: {qdot0_spherical_AU_rad_year_speed})"
    )

    # endregion

    # region --- 5 Calculate B0 from Q0, Qdot0

    # FINAL OUTPUT: Initial coordinates (Q)
    Q0 = q0_spherical_AU_rad

    # FINAL OUTPUT: Initial momenta per mass (B)
    R, theta, _ = Q0
    Rdot, thetadot, phidot = qdot0_spherical_AU_rad_y

    B_R = get_B_R(Rdot)
    B_theta = get_B_theta(R, thetadot)
    B_phi = get_B_phi(R, theta, phidot)

    B0 = [B_R, B_theta, B_phi]

    # Info log output
    logging.info(
        f"Spacecraft initial position vector, Q0 (spherical, AU & rad): " f"{Q0}"
    )
    logging.info(
        f"Spacecraft initial momentum per mass vector, B0 (AU/y & rad/y): {B0}"
    )

    # endregion

    return Q0, B0
Exemple #5
0
def test_invalid_end_year():
    """Tests of get_ephemerides (INVALID YEARS)"""

    get_ephemerides(end_year="2042")
Exemple #6
0
def test_invalid_planets():
    """Tests of get_ephemerides (INVALID PLANETS)"""

    get_ephemerides(planets=("earth", "mercury"))
Exemple #7
0
def simulate(
        psi,
        max_year="2039",
        h=1 / UNIT_TIME,
        max_duration=1 * 3600 * 24 / UNIT_TIME,
        max_iter=int(1e6),
):
    """Simple simulator that will run a LEO until duration or max_iter is reached.

    Keyword Arguments:
        psi {tuple} -- Initial conditions: (day, Q0, B0, burn)
        max_year {string} -- Max year for ephemerides table (default: "2020")
        h {float} -- Initial time step size (default: 1/UNIT_LENGTH = 1 second in years)
        max_duration {int} -- Max duration of simulation (in years) (default: {1 day})
        max_iter {int} -- Max number of iterations of simulation (default: {1e6})
                          (1e6 iterations corresponds to ~11 days with h = 1 s)

    Returns:
        [type] -- [description]
    """
    logging.info("STARTING: Simple simulation.")
    t_start = time.time()
    max_iter = int(max_iter)

    # Unpack psi
    day = psi[0]
    Q = psi[1]
    B = psi[2]
    delta_v0 = psi[3]

    t = day * 3600 * 24 / UNIT_TIME
    t0 = t

    # Read ephemerides
    logging.debug("Getting ephemerides tables")
    ephemerides = get_ephemerides(max_year=max_year)

    # Apply initial burn if delta_v input is provided
    if delta_v0:
        B = apply_delta_v(Q, B, delta_v0)

    # Unpack initial position (Q) and momenta (B)
    R, theta, phi = Q
    B_R, B_theta, B_phi = B

    logging.info(f"Initial coordinates: Q = {B} (R, theta, phi)")
    logging.info(f"Initial momenta: B = {B} (B_R, B_theta, B_phi")

    logging.info(
        f"Starting simulation at time {t} ({day} days) with step size h = {h} "
        f"({h*UNIT_TIME} s)"
        f", max {max_iter} iterations and max {max_duration*UNIT_TIME/3600/24} days"
    )

    # List initialization
    i = 0

    ts = []
    days = []
    Qs = []
    Bs = []
    q_p_list = []
    eph_body_coords = []
    body_distances = []

    # Run iteration 0 manually
    ts.append(t)
    days.append(day)
    Qs.append([R, theta, phi])
    Bs.append([B_R, B_theta, B_phi])
    q_p_list.append((Qs[0], Bs[0]))
    t1 = time.time()
    sim_time = t1 - t_start
    logging.info(f"Iteration {str(i).rjust(len(str(max_iter)))} / {max_iter}"
                 f", in-sim time {format_time(t, time_unit='years')} / "
                 f"{format_time(max_duration, time_unit='years')}"
                 f"   (out-of-sim elapsed time: {format_time(sim_time)})")

    # Iteration loop
    while True:
        i += 1
        t += h
        day = t * UNIT_TIME / (3600 * 24)

        eph_on_day = get_ephemerides_on_day(ephemerides, day)
        eph_coords = get_coordinates_on_day_rad(eph_on_day)

        # Save ephemerides coords on day into array
        R_ks, theta_ks, phi_ks = eph_coords
        sun = [R_ks[0], theta_ks[0], phi_ks[0]]
        earth = [R_ks[1], theta_ks[1], phi_ks[1]]
        mars = [R_ks[2], theta_ks[2], phi_ks[2]]
        eph_body_coords.append([sun, earth, mars])

        dist_sun = get_distance_spherical(Q, sun) * UNIT_LENGTH
        dist_earth = get_distance_spherical(Q, earth) * UNIT_LENGTH
        dist_mars = get_distance_spherical(Q, mars) * UNIT_LENGTH
        body_distances.append([dist_sun, dist_earth, dist_mars])

        if dist_earth < EARTH_RADIUS:
            logging.info(
                f"STOP: Collision with earth {dist_earth:.6f} "
                f"({format_time(max_duration, time_unit='years')}) "
                f"reached at t = {t:.6f} ({format_time(t, time_unit='years')})"
                f" at iteration: {i}/{max_iter} ~ {i/max_iter*100:.3f} %")
            break

        Q, B = euler_step_symplectic(h, Q, B, eph_coords)
        # Q, B = verlet_step_symplectic(h, Q, B, eph_coords)

        ts.append(t)
        days.append(day)
        Qs.append(Q)
        Bs.append(B)
        q_p_list.append((Q, B))

        # Log status every 1000 iterations.
        if i % 1000 == 0:
            t1 = time.time()
            sim_time = t1 - t_start

            # if i > 10 ^ 4:
            #     h = 60 / UNIT_TIME
            #     logging.info(f"At iteration {i}, h now set to {h}")

            # if i > 10 ^ 5:
            #     h = 3600 / UNIT_TIME
            #     logging.info(f"At iteration {i}, h now set to {h}")

            # if i > 10 ^ 6:
            #     h = 3600 * 12 / UNIT_TIME
            #     logging.info(f"At iteration {i}, h now set to {h}")

            logging.info(
                f"Iteration {str(i).rjust(len(str(max_iter)))} / {max_iter}"
                f", in-sim time {format_time(t, time_unit='years')} / "
                f"{format_time(max_duration, time_unit='years')}"
                f"   (out-of-sim elapsed time: {format_time(sim_time)})")

        # Stop simulation of max duration reached
        if (t - t0) >= max_duration:
            logging.info(
                f"STOP: Max time of {max_duration:.6f} "
                f"({format_time(max_duration, time_unit='years')}) "
                f"reached at t = {t:.6f} ({format_time(t, time_unit='years')})"
                f" at iteration: {i}/{max_iter} ~ {i/max_iter*100:.3f} %")
            break

        # Stop simulation of max iterations reached
        if i >= max_iter:
            logging.info(f"STOP: Max iter of {max_iter} reached (i={i}) "
                         f"at t = {format_time(t, time_unit='years')}/"
                         f"{format_time(max_duration, time_unit='years')} ~ "
                         f"{t/max_duration:.3f} %)")
            break

        # Check for body collision

    t_s = (t - t0) * UNIT_TIME  # final in-sim time in seconds
    t_end = time.time()
    T = t_end - t_start  # final out-of-sim time in seconds

    # Post simulator run logging
    logging.info(f"SIMULATOR PERFORMANCE: Sim/Real time ratio:    "
                 f"{Decimal(t_s / T):.2E} ({(t_s / T):.2f})")
    logging.info(f"SIMULATOR PERFORMANCE: 1 second can simulate:  "
                 f"{format_time(t_s / T)} (DDD:HH:MM:SS)")
    logging.info(f"SIMULATOR PERFORMANCE: Time to simulate 1 day: "
                 f"{format_time(T / t_s * 3600 * 24)} (DDD:HH:MM:SS)")
    logging.info(
        f"TIME ELAPSED: In-sim time duration:     {format_time(t,time_unit='years')} "
        f"(DDD:HH:MM:SS)")
    logging.info(
        f"TIME ELAPSED: Out-of-sim time duration: {format_time(T)} (DDD:HH:MM:SS)"
    )

    return (ts, Qs, Bs, (t, i), ephemerides, eph_body_coords, body_distances)
Exemple #8
0
    def __init__(self, qs, ts, t_final, max_year, ax):
        eph = get_ephemerides(max_year=max_year)
        earth = eph["earth"]
        mars = eph["mars"]
        # ts is in years, and is as such very small. we scale it to days, to fit with eph
        days_ts = [(t * UNIT_TIME) / 3600 / 24 for t in ts]

        qs = [get_position_cartesian_from_spherical(x, y, z) for x, y, z in qs]
        self.xs, self.ys, self.zs = np.array(
            qs
        ).T  # get individual coordinate sets for plotting

        self.t_final = t_final
        ax.set_title("animation")

        self.ani_ax = ax

        xs_earth = []
        ys_earth = []
        zs_earth = []
        xs_mars = []
        ys_mars = []
        zs_mars = []
        for t in days_ts:
            t_eph = get_ephemerides_on_day(eph, day_index=t)
            xs_earth.append(t_eph["earth"]["x"])
            ys_earth.append(t_eph["earth"]["y"])
            zs_earth.append(t_eph["earth"]["z"])
            xs_mars.append(t_eph["mars"]["x"])
            ys_mars.append(t_eph["mars"]["y"])
            zs_mars.append(t_eph["mars"]["z"])

        self.xs_earth = xs_earth
        self.ys_earth = ys_earth
        self.zs_earth = zs_earth

        self.xs_mars = xs_mars
        self.ys_mars = ys_mars
        self.zs_mars = zs_mars

        self.earth_xdata = [self.xs_earth[0]]
        self.earth_ydata = [self.ys_earth[0]]
        self.earth_zdata = [self.zs_earth[0]]
        self.earth_line = Line3D(
            self.earth_xdata, self.earth_ydata, self.earth_zdata, color="deepskyblue"
        )
        self.ani_ax.add_line(self.earth_line)

        self.mars_xdata = [self.xs_mars[0]]
        self.mars_ydata = [self.ys_mars[0]]
        self.mars_zdata = [self.zs_mars[0]]
        self.mars_line = Line3D(
            self.mars_xdata, self.mars_ydata, self.mars_zdata, color="orange"
        )
        self.ani_ax.add_line(self.mars_line)

        self.xdata = [self.xs[0]]
        self.ydata = [self.ys[0]]
        self.zdata = [self.zs[0]]
        self.traj_line = Line3D(self.xdata, self.ydata, self.zdata, color="black")
        self.ani_ax.add_line(self.traj_line)
        # -- SUN --
        ax.scatter(0, 0, 0, c="gold", marker="o")

        self.ani_ax.set_xlim(-1.5, 1.5)
        self.ani_ax.set_ylim(-1.5, 1.5)
        self.ani_ax.set_zlim(-1, 1)

        self.ani_terminate = False