Beispiel #1
0
def change_ecc_inc(k, a, ecc_0, ecc_f, inc_0, inc_f, argp, r, v, f):
    # We fix the inertial direction at the beginning
    if ecc_0 > 0.001:  # Arbitrary tolerance
        e_vec = eccentricity_vector(k, r, v)
        ref_vec = e_vec / ecc_0
    else:
        ref_vec = r / norm(r)

    h_vec = cross(r, v)  # Specific angular momentum vector
    h_unit = h_vec / norm(h_vec)
    thrust_unit = cross(h_unit, ref_vec) * np.sign(ecc_f - ecc_0)

    beta_0 = beta(ecc_0, ecc_f, inc_0, inc_f, argp)

    @jit
    def a_d(t0, u_, k_):
        r_ = u_[:3]
        v_ = u_[3:]
        nu = rv2coe(k_, r_, v_)[-1]
        beta_ = beta_0 * np.sign(
            np.cos(nu))  # The sign of ß reverses at minor axis crossings

        w_ = cross(r_, v_) / norm(cross(r_, v_))
        accel_v = f * (np.cos(beta_) * thrust_unit + np.sin(beta_) * w_)
        return accel_v

    delta_v = delta_V(circular_velocity(k, a), ecc_0, ecc_f, beta_0)
    t_f = delta_t(delta_v, f)

    return a_d, delta_v, t_f
Beispiel #2
0
def third_body(t0, state, k, k_third, perturbation_body):
    r"""Calculate third body acceleration (km/s2).

    .. math::

        \vec{p} = \mu_{m}\left ( \frac{\vec{r_{m/s}}}{r_{m/s}^3} - \frac{\vec{r_{m}}}{r_{m}^3} \right )

    Parameters
    ----------
    t0 : float
        Current time (s).
    state : numpy.ndarray
        Six component state vector [x, y, z, vx, vy, vz] (km, km/s).
    k : float
        Standard Gravitational parameter of the attractor (km^3/s^2).
    k_third : float
        Standard Gravitational parameter of the third body (km^3/s^2).
    perturbation_body : callable
        A callable object returning the position of the body that causes the perturbation
        in the attractor frame.

    Notes
    -----
    This formula is taken from Howard Curtis, section 12.10. As an example, a third body could be
    the gravity from the Moon acting on a small satellite.

    """
    body_r = perturbation_body(t0)
    delta_r = body_r - state[:3]
    return k_third * delta_r / norm(delta_r)**3 - k_third * body_r / norm(
        body_r)**3
Beispiel #3
0
def line_of_sight(r1, r2, R):
    """Calculates the line of sight condition between two position vectors, r1 and r2.

    Parameters
    ----------
    r1 : numpy.ndarray
        The position vector of the first object with respect to a central attractor.
    r2 : numpy.ndarray
        The position vector of the second object with respect to a central attractor.
    R : float
        The radius of the central attractor.

    Returns
    -------
    delta_theta: float
        Greater than or equal to zero, if there exists a LOS between two objects
        located by r1 and r2, else negative.

    """
    r1_norm = norm(r1)
    r2_norm = norm(r2)

    theta = np.arccos((r1 @ r2) / r1_norm / r2_norm)
    theta_1 = np.arccos(R / r1_norm)
    theta_2 = np.arccos(R / r2_norm)

    return (theta_1 + theta_2) - theta
Beispiel #4
0
def hohmann(k, rv, r_f):
    r"""Calculate the Hohmann maneuver velocities and the duration of the maneuver.

    By defining the relationship between orbit radius:

    .. math::
        a_{trans} = \frac{r_{i} + r_{f}}{2}

    The Hohmann maneuver velocities can be expressed as:

    .. math::
        \begin{align}
            \Delta v_{a} &= \sqrt{\frac{2\mu}{r_{i}} - \frac{\mu}{a_{trans}}} - v_{i}\\
            \Delta v_{b} &= \sqrt{\frac{\mu}{r_{f}}} - \sqrt{\frac{2\mu}{r_{f}} - \frac{\mu}{a_{trans}}}
        \end{align}

    The time that takes to complete the maneuver can be computed as:

    .. math::
        \tau_{trans} = \pi \sqrt{\frac{(a_{trans})^{3}}{\mu}}

    Parameters
    ----------
    k : float
        Standard Gravitational parameter
    rv : numpy.ndarray, numpy.ndarray
        Position and velocity vectors
    r_f : float
        Final orbital radius

    """
    _, ecc, inc, raan, argp, nu = rv2coe(k, *rv)
    h_i = norm(cross(*rv))
    p_i = h_i**2 / k

    r_i, v_i = rv_pqw(k, p_i, ecc, nu)

    r_i = norm(r_i)
    v_i = norm(v_i)
    a_trans = (r_i + r_f) / 2

    dv_a = np.sqrt(2 * k / r_i - k / a_trans) - v_i
    dv_b = np.sqrt(k / r_f) - np.sqrt(2 * k / r_f - k / a_trans)

    dv_a = np.array([0, dv_a, 0])
    dv_b = np.array([0, -dv_b, 0])

    rot_matrix = coe_rotation_matrix(inc, raan, argp)

    dv_a = rot_matrix @ dv_a
    dv_b = rot_matrix @ dv_b

    t_trans = np.pi * np.sqrt(a_trans**3 / k)

    return dv_a, dv_b, t_trans
Beispiel #5
0
    def a_d(t0, u_, k):
        r = u_[:3]
        v = u_[3:]

        # Change sign of beta with the out-of-plane velocity
        beta_ = beta(t0, V_0, f, beta_0_) * np.sign(r[0] * (inc_f - inc_0))

        t_ = v / norm(v)
        w_ = cross(r, v) / norm(cross(r, v))
        accel_v = f * (np.cos(beta_) * t_ + np.sin(beta_) * w_)
        return accel_v
Beispiel #6
0
    def a_d(t0, u_, k):
        r = u_[:3]
        v = u_[3:]
        nu = rv2coe(k, r, v)[-1]

        alpha_ = nu - np.pi / 2

        r_ = r / norm(r)
        w_ = cross(r, v) / norm(cross(r, v))
        s_ = cross(w_, r_)
        accel_v = f * (np.cos(alpha_) * s_ + np.sin(alpha_) * r_)
        return accel_v
Beispiel #7
0
def J3_perturbation(t0, state, k, J3, R):
    r"""Calculates J3_perturbation acceleration (km/s2)

    Parameters
    ----------
    t0 : float
        Current time (s)
    state : numpy.ndarray
        Six component state vector [x, y, z, vx, vy, vz] (km, km/s).
    k : float
        Standard Gravitational parameter. (km^3/s^2)
    J3 : float
        Oblateness factor
    R : float
        Attractor radius

    Notes
    -----
    The J3 accounts for the oblateness of the attractor. The formula is given in
    Howard Curtis, problem 12.8
    This perturbation has not been fully validated, see https://github.com/poliastro/poliastro/pull/398

    """
    r_vec = state[:3]
    r = norm(r_vec)

    factor = (1.0 / 2.0) * k * J3 * (R**3) / (r**5)
    cos_phi = r_vec[2] / r

    a_x = 5.0 * r_vec[0] / r * (7.0 * cos_phi**3 - 3.0 * cos_phi)
    a_y = 5.0 * r_vec[1] / r * (7.0 * cos_phi**3 - 3.0 * cos_phi)
    a_z = 3.0 * (35.0 / 3.0 * cos_phi**4 - 10.0 * cos_phi**2 + 1)
    return np.array([a_x, a_y, a_z]) * factor
Beispiel #8
0
def atmospheric_drag(t0, state, k, C_D, A_over_m, rho):
    r"""Calculates atmospheric drag acceleration (km/s2)

    .. math::

        \vec{p} = -\frac{1}{2}\rho v_{rel}\left ( \frac{C_{d}A}{m} \right )\vec{v_{rel}}

    .. versionadded:: 1.14

    Parameters
    ----------
    t0 : float
        Current time (s).
    state : numpy.ndarray
        Six component state vector [x, y, z, vx, vy, vz] (km, km/s).
    k : float
        Standard Gravitational parameter (km^3/s^2)
    C_D : float
        Dimensionless drag coefficient ()
    A_over_m : float
        Frontal area/mass of the spacecraft (km^2/kg)
    rho : float
        Air density at corresponding state (kg/m^3)

    Notes
    -----
    This function provides the acceleration due to atmospheric drag, as
    computed by a model from poliastro.earth.atmosphere

    """
    v_vec = state[3:]
    v = norm(v_vec)
    B = C_D * A_over_m

    return -(1.0 / 2.0) * rho * B * v * v_vec
Beispiel #9
0
    def __call__(self, t, u_, k):
        self._last_t = t
        pos_on_body = (u_[:3] / norm(u_[:3])) * self._R
        lat_, _, _ = cartesian_to_ellipsoidal_fast(self._R, self._R_polar,
                                                   *pos_on_body)

        return np.rad2deg(lat_) - self._lat
Beispiel #10
0
def project_point_on_ellipsoid(x, y, z, a, b, c):
    """Return the projection of a point on an ellipsoid.

    Parameters
    ----------
    x, y, z : float
        Cartesian coordinates of point
    a, b, c : float
        Semi-axes of the ellipsoid

    """
    p1, p2 = intersection_ellipsoid_line(x, y, z, x, y, z, a, b, c)

    norm_1 = norm(np.array([p1[0] - x, p1[1] - y, p1[2] - z]))
    norm_2 = norm(np.array([p2[0] - x, p2[1] - y, p2[2] - z]))

    return p1 if norm_1 <= norm_2 else p2
Beispiel #11
0
def atmospheric_drag_exponential(t0, state, k, R, C_D, A_over_m, H0, rho0):
    r"""Calculates atmospheric drag acceleration (km/s2)

    .. math::

        \vec{p} = -\frac{1}{2}\rho v_{rel}\left ( \frac{C_{d}A}{m} \right )\vec{v_{rel}}

    .. versionadded:: 0.9.0

    Parameters
    ----------
    t0 : float
        Current time (s)
    state : numpy.ndarray
        Six component state vector [x, y, z, vx, vy, vz] (km, km/s).
    k : float
        Standard Gravitational parameter (km^3/s^2).
    R : float
        Radius of the attractor (km)
    C_D : float
        Dimensionless drag coefficient ()
    A_over_m : float
        Frontal area/mass of the spacecraft (km^2/kg)
    H0 : float
        Atmospheric scale height, (km)
    rho0 : float
        Exponent density pre-factor, (kg / km^3)

    Notes
    -----
    This function provides the acceleration due to atmospheric drag
    using an overly-simplistic exponential atmosphere model. We follow
    Howard Curtis, section 12.4
    the atmospheric density model is rho(H) = rho0 x exp(-H / H0)

    """
    H = norm(state[:3])

    v_vec = state[3:]
    v = norm(v_vec)
    B = C_D * A_over_m
    rho = rho0 * np.exp(-(H - R) / H0)

    return -(1.0 / 2.0) * rho * B * v * v_vec
Beispiel #12
0
    def a_d(t0, u_, k_):
        r_ = u_[:3]
        v_ = u_[3:]
        nu = rv2coe(k_, r_, v_)[-1]
        beta_ = beta_0 * np.sign(
            np.cos(nu))  # The sign of ß reverses at minor axis crossings

        w_ = cross(r_, v_) / norm(cross(r_, v_))
        accel_v = f * (np.cos(beta_) * thrust_unit + np.sin(beta_) * w_)
        return accel_v
Beispiel #13
0
def radiation_pressure(t0, state, k, R, C_R, A_over_m, Wdivc_s, star):
    r"""Calculates radiation pressure acceleration (km/s2)

    .. math::

        \vec{p} = -\nu \frac{S}{c} \left ( \frac{C_{r}A}{m} \right )\frac{\vec{r}}{r}

    Parameters
    ----------
    t0 : float
        Current time (s).
    state : numpy.ndarray
        Six component state vector [x, y, z, vx, vy, vz] (km, km/s).
    k : float
        Standard Gravitational parameter (km^3/s^2).
    R : float
        Radius of the attractor.
    C_R : float
        Dimensionless radiation pressure coefficient, 1 < C_R < 2 ().
    A_over_m : float
        Effective spacecraft area/mass of the spacecraft (km^2/kg).
    Wdivc_s : float
        Total star emitted power divided by the speed of light (W * s / km).
    star : callable
        A callable object returning the position of radiating star
        in the attractor frame.

    Notes
    -----
    This function provides the acceleration due to star light pressure. We follow
    Howard Curtis, section 12.9

    """
    r_star = star(t0)
    r_sat = state[:3]
    P_s = Wdivc_s / (norm(r_star)**2)

    nu = float(line_of_sight_fast(r_sat, r_star, R) > 0)
    return -nu * P_s * (C_R * A_over_m) * r_star / norm(r_star)
Beispiel #14
0
def correct_pericenter(k, R, J2, max_delta_r, v, a, inc, ecc):
    """Calculates the time before burning and the velocity vector in direction of the burn.

    Parameters
    ----------
    k : float
        Standard Gravitational parameter
    R : float
        Radius of the attractor
    J2 : float
        Oblateness factor
    max_delta_r : float
        Maximum satellite’s geocentric distance
    v : numpy.ndarray
        Velocity vector
    a : float
        Semi-major axis
    inc : float
        Inclination
    ecc : float
        Eccentricity

    Notes
    -----
    The algorithm was obtained from "Fundamentals of Astrodynamics and Applications, 4th ed (2013)" by David A.
    Vallado, page 885.
    Given a max_delta_r, we determine the maximum perigee drift before we do an orbit-adjustment burn
    to restore the perigee to its nominal value. We estimate the time until this burn using the allowable drift
    delta_w and the drift rate :math:`|dw|`.
    For positive delta_v, the change in the eccentricity is positive for perigee burns and negative for apogee burns.
    The opposite holds for a delta_v applied against the velocity vector, which decreases the satellite’s velocity.
    Perigee drift are mainly due to the zonal harmonics, which cause variations in the altitude by changing the
    argument of perigee.
    Please note that ecc ≈ 0.001, so the error incurred by assuming a small eccentricity is on the order of 0.1%.
    This is smaller than typical variations in thruster performance between burns.

    """
    p = a * (1 - ecc**2)
    n = (k / a**3)**0.5

    dw = ((3 * n * R**2 * J2) / (4 * p**2)) * (4 - 5 * np.sin(inc)**2)

    delta_w = 2 * (1 + ecc) * max_delta_r
    delta_w /= a * ecc * (1 - ecc)
    delta_w **= 0.5
    delta_t = abs(delta_w / dw)
    delta_v = 0.5 * n * a * ecc * abs(delta_w)

    vf_ = v / norm(v) * delta_v

    return delta_t, vf_
Beispiel #15
0
    def __call__(self, t, u_, k):
        self._last_t = t

        if norm(u_[:3]) < self._R:
            warn(
                "The norm of the position vector of the primary body is less than the radius of the attractor."
            )

        pos_coord = self._pos_coords.pop(
            0) if self._pos_coords else self._last_coord

        # Need to cast `pos_coord` to array since `norm` inside numba only works for arrays, not lists.
        delta_angle = line_of_sight_fast(u_[:3], np.array(pos_coord), self._R)
        return delta_angle
Beispiel #16
0
def tangential_vecs(N):
    """Returns orthonormal vectors tangential to the ellipsoid at the given location.

    Parameters
    ----------
    N : numpy.ndarray
        Normal vector of the ellipsoid

    """
    u = np.array([1.0, 0, 0])
    u -= (u @ N) * N
    u /= norm(u)
    v = np.cross(N, u)

    return u, v
Beispiel #17
0
def eclipse_function(k, u_, r_sec, R_sec, R_primary, umbra=True):
    """Calculates a continuous shadow function.

    Parameters
    ----------
    k : float
        Standard gravitational parameter (km^3 / s^2).
    u_ : numpy.ndarray
        Satellite position and velocity vector with respect to the primary body.
    r_sec : numpy.ndarray
        Position vector of the secondary body with respect to the primary body.
    R_sec : float
        Equatorial radius of the secondary body.
    R_primary : float
        Equatorial radius of the primary body.
    umbra : bool
        Whether to calculate the shadow function for umbra or penumbra, defaults to True
        i.e. calculates for umbra.

    Notes
    -----
    The shadow function is taken from Escobal, P. (1985). Methods of orbit determination.
    The current implementation assumes circular bodies and doesn't account for flattening.

    """
    # Plus or minus condition
    pm = 1 if umbra else -1
    p, ecc, inc, raan, argp, nu = rv2coe(k, u_[:3], u_[3:])

    PQW = coe_rotation_matrix(inc, raan, argp)
    # Make arrays contiguous for faster dot product with numba.
    P_, Q_ = np.ascontiguousarray(PQW[:, 0]), np.ascontiguousarray(PQW[:, 1])

    r_sec_norm = norm(r_sec)
    beta = (P_ @ r_sec) / r_sec_norm
    zeta = (Q_ @ r_sec) / r_sec_norm

    sin_delta_shadow = np.sin((R_sec - pm * R_primary) / r_sec_norm)

    cos_psi = beta * np.cos(nu) + zeta * np.sin(nu)
    shadow_function = (((R_primary**2) *
                        (1 + ecc * np.cos(nu))**2) + (p**2) * (cos_psi**2) -
                       p**2 + pm * (2 * p * R_primary * cos_psi) *
                       (1 + ecc * np.cos(nu)) * sin_delta_shadow)

    return shadow_function
Beispiel #18
0
def eccentricity_vector(k, r, v):
    r"""Eccentricity vector.

    .. math::

       \vec{e} = \frac{1}{\mu} \left(
           \left( v^{2} - \frac{\mu}{r}\right ) \vec{r}  - (\vec{r} \cdot \vec{v})\vec{v}
       \right)

    Parameters
    ----------
    k : float
        Standard gravitational parameter (km^3 / s^2).
    r : numpy.ndarray
        Position vector (km)
    v : numpy.ndarray
        Velocity vector (km / s)
    """
    return ((v @ v - k / norm(r)) * r - (r @ v) * v) / k
Beispiel #19
0
def N(a, b, c, cartesian_cords):
    """Normal vector of the ellipsoid at the given location.

    Parameters
    ----------
    a : float
        Semi-major axis
    b : float
        Equatorial radius
    c : float
        Semi-minor axis
    cartesian_cords : numpy.ndarray
        Cartesian coordinates

    """
    x, y, z = cartesian_cords
    N = np.array([2 * x / a**2, 2 * y / b**2, 2 * z / c**2])
    N /= norm(N)
    return N
Beispiel #20
0
def distance(cartesian_cords, px, py, pz):
    """Calculates the distance from an arbitrary point to the given location (Cartesian coordinates).

    Parameters
    ----------
    cartesian_cords : numpy.ndarray
        Cartesian coordinates
    px : float
        x-coordinate of the point
    py : float
        y-coordinate of the point
    pz : float
        z-coordinate of the point

    """
    c = cartesian_cords
    u = np.array([px, py, pz])
    d = norm(c - u)
    return d
Beispiel #21
0
def J2_perturbation(t0, state, k, J2, R):
    r"""Calculates J2_perturbation acceleration (km/s2)

    .. math::

        \vec{p} = \frac{3}{2}\frac{J_{2}\mu R^{2}}{r^{4}}\left [\frac{x}{r}\left ( 5\frac{z^{2}}{r^{2}}-1 \right )\vec{i} + \frac{y}{r}\left ( 5\frac{z^{2}}{r^{2}}-1 \right )\vec{j} + \frac{z}{r}\left ( 5\frac{z^{2}}{r^{2}}-3 \right )\vec{k}\right]

    .. versionadded:: 0.9.0

    Parameters
    ----------
    t0 : float
        Current time (s)
    state : numpy.ndarray
        Six component state vector [x, y, z, vx, vy, vz] (km, km/s).
    k : float
        Standard Gravitational parameter. (km^3/s^2)
    J2 : float
        Oblateness factor
    R : float
        Attractor radius

    Notes
    -----
    The J2 accounts for the oblateness of the attractor. The formula is given in
    Howard Curtis, (12.30)

    """
    r_vec = state[:3]
    r = norm(r_vec)

    factor = (3.0 / 2.0) * k * J2 * (R**2) / (r**5)

    a_x = 5.0 * r_vec[2]**2 / r**2 - 1
    a_y = 5.0 * r_vec[2]**2 / r**2 - 1
    a_z = 5.0 * r_vec[2]**2 / r**2 - 3
    return np.array([a_x, a_y, a_z]) * r_vec * factor
Beispiel #22
0
    def __call__(self, t, u, k):
        self._last_t = t
        r_norm = norm(u[:3])

        return (r_norm - self._R - self._alt
                )  # If this goes from +ve to -ve, altitude is decreasing.
Beispiel #23
0
def bielliptic(k, r_b, r_f, rv):
    r"""Calculate the increments in the velocities and the time of flight of the maneuver

    The bielliptic maneuver employs two Hohmann transfers, therefore two
    intermediate orbits are established. We define the different radius
    relationships as follows:

    .. math::
        \begin{align}
            a_{trans1} &= \frac{r_{i} + r_{b}}{2}\\
            a_{trans2} &= \frac{r_{b} + r_{f}}{2}\\
        \end{align}

    The increments in the velocity are:

    .. math::
        \begin{align}
            \Delta v_{a} &= \sqrt{\frac{2\mu}{r_{i}} - \frac{\mu}{a_{trans1}}} - v_{i}\\
            \Delta v_{b} &= \sqrt{\frac{2\mu}{r_{b}} - \frac{\mu}{a_{trans2}}} - \sqrt{\frac{2\mu}{r_{b}} - \frac{\mu}{a_trans{1}}}\\
            \Delta v_{c} &= \sqrt{\frac{\mu}{r_{f}}} - \sqrt{\frac{2\mu}{r_{f}} - \frac{\mu}{a_{trans2}}}\\
        \end{align}

    The time of flight for this maneuver is the addition of the time needed for both transition orbits, following the same formula as
    Hohmann:

    .. math::
        \begin{align}
            \tau_{trans1} &= \pi \sqrt{\frac{a_{trans1}^{3}}{\mu}}\\
            \tau_{trans2} &= \pi \sqrt{\frac{a_{trans2}^{3}}{\mu}}\\
        \end{align}

    Parameters
    ----------
    k : float
        Standard Gravitational parameter
    r_b : float
        Altitude of the intermediate orbit
    r_f : float
        Final orbital radius
    rv : numpy.ndarray, numpy.ndarray
        Position and velocity vectors

    """
    _, ecc, inc, raan, argp, nu = rv2coe(k, *rv)
    h_i = norm(cross(*rv))
    p_i = h_i**2 / k

    r_i, v_i = rv_pqw(k, p_i, ecc, nu)

    r_i = norm(r_i)
    v_i = norm(v_i)
    a_trans1 = (r_i + r_b) / 2
    a_trans2 = (r_b + r_f) / 2

    dv_a = np.sqrt(2 * k / r_i - k / a_trans1) - v_i
    dv_b = np.sqrt(2 * k / r_b - k / a_trans2) - np.sqrt(2 * k / r_b -
                                                         k / a_trans1)
    dv_c = np.sqrt(k / r_f) - np.sqrt(2 * k / r_f - k / a_trans2)

    dv_a = np.array([0, dv_a, 0])
    dv_b = np.array([0, -dv_b, 0])
    dv_c = np.array([0, dv_c, 0])

    rot_matrix = coe_rotation_matrix(inc, raan, argp)

    dv_a = rot_matrix @ dv_a
    dv_b = rot_matrix @ dv_b
    dv_c = rot_matrix @ dv_c

    t_trans1 = np.pi * np.sqrt(a_trans1**3 / k)
    t_trans2 = np.pi * np.sqrt(a_trans2**3 / k)

    return dv_a, dv_b, dv_c, t_trans1, t_trans2
Beispiel #24
0
def rv2coe(k, r, v, tol=1e-8):
    r"""Converts from vectors to classical orbital elements.

    Parameters
    ----------
    k : float
        Standard gravitational parameter (km^3 / s^2)
    r : numpy.ndarray
        Position vector (km)
    v : numpy.ndarray
        Velocity vector (km / s)
    tol : float, optional
        Tolerance for eccentricity and inclination checks, default to 1e-8

    Returns
    -------
    p : float
        Semi-latus rectum of parameter (km)
    ecc: float
        Eccentricity
    inc: float
        Inclination (rad)
    raan: float
        Right ascension of the ascending nod (rad)
    argp: float
        Argument of Perigee (rad)
    nu: float
        True Anomaly (rad)

    Notes
    -----
    This example is a real exercise from Orbital Mechanics for Engineering
    students by Howard D.Curtis. This exercise is 4.3 of 3rd. Edition, page 200.

    1. First the angular momentum is computed:

    .. math::
        \vec{h} = \vec{r} \times \vec{v}

    2. With it the eccentricity can be solved:

    .. math::
        \begin{align}
        \vec{e} &= \frac{1}{\mu}\left [ \left ( v^{2} - \frac{\mu}{r}\right ) \vec{r}  - (\vec{r} \cdot \vec{v})\vec{v} \right ] \\
        e &= \sqrt{\vec{e}\cdot\vec{e}} \\
        \end{align}

    3. The node vector line is solved:

    .. math::
        \begin{align}
        \vec{N} &= \vec{k} \times \vec{h} \\
        N &= \sqrt{\vec{N}\cdot\vec{N}}
        \end{align}

    4. The rigth ascension node is computed:

    .. math::
        \Omega = \left\{ \begin{array}{lcc}
         cos^{-1}{\left ( \frac{N_{x}}{N} \right )} &   if  & N_{y} \geq  0 \\
         \\ 360^{o} -cos^{-1}{\left ( \frac{N_{x}}{N} \right )} &  if & N_{y} < 0 \\
         \end{array}
        \right.

    5. The argument of perigee:

    .. math::
        \omega  = \left\{ \begin{array}{lcc}
         cos^{-1}{\left ( \frac{\vec{N}\vec{e}}{Ne} \right )} &   if  & e_{z} \geq  0 \\
         \\ 360^{o} -cos^{-1}{\left ( \frac{\vec{N}\vec{e}}{Ne} \right )} &  if & e_{z} < 0 \\
         \end{array}
        \right.

    6. And finally the true anomaly:

    .. math::
        \nu  = \left\{ \begin{array}{lcc}
         cos^{-1}{\left ( \frac{\vec{e}\vec{r}}{er} \right )} &   if  & v_{r} \geq  0 \\
         \\ 360^{o} -cos^{-1}{\left ( \frac{\vec{e}\vec{r}}{er} \right )} &  if & v_{r} < 0 \\
         \end{array}
        \right.

    Examples
    --------
    >>> from poliastro.bodies import Earth
    >>> from astropy import units as u
    >>> k = Earth.k.to_value(u.km ** 3 / u.s ** 2)
    >>> r = np.array([-6045., -3490., 2500.])
    >>> v = np.array([-3.457, 6.618, 2.533])
    >>> p, ecc, inc, raan, argp, nu = rv2coe(k, r, v)
    >>> print("p:", p, "[km]")  # doctest: +FLOAT_CMP
    p: 8530.47436396927 [km]
    >>> print("ecc:", ecc)  # doctest: +FLOAT_CMP
    ecc: 0.17121118195416898
    >>> print("inc:", np.rad2deg(inc), "[deg]")  # doctest: +FLOAT_CMP
    inc: 153.2492285182475 [deg]
    >>> print("raan:", np.rad2deg(raan), "[deg]")  # doctest: +FLOAT_CMP
    raan: 255.27928533439618 [deg]
    >>> print("argp:", np.rad2deg(argp), "[deg]")  # doctest: +FLOAT_CMP
    argp: 20.068139973005362 [deg]
    >>> print("nu:", np.rad2deg(nu), "[deg]")  # doctest: +FLOAT_CMP
    nu: 28.445804984192122 [deg]

    """

    h = cross(r, v)
    n = cross([0, 0, 1], h)
    e = ((v @ v - k / norm(r)) * r - (r @ v) * v) / k
    ecc = norm(e)
    p = (h @ h) / k
    inc = np.arccos(h[2] / norm(h))

    circular = ecc < tol
    equatorial = abs(inc) < tol

    if equatorial and not circular:
        raan = 0
        argp = np.arctan2(e[1], e[0]) % (2 * np.pi)  # Longitude of periapsis
        nu = np.arctan2((h @ cross(e, r)) / norm(h), r @ e)
    elif not equatorial and circular:
        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        argp = 0
        # Argument of latitude
        nu = np.arctan2((r @ cross(h, n)) / norm(h), r @ n)
    elif equatorial and circular:
        raan = 0
        argp = 0
        nu = np.arctan2(r[1], r[0]) % (2 * np.pi)  # True longitude
    else:
        a = p / (1 - (ecc**2))
        ka = k * a
        if a > 0:
            e_se = (r @ v) / sqrt(ka)
            e_ce = norm(r) * (v @ v) / k - 1
            nu = E_to_nu(np.arctan2(e_se, e_ce), ecc)
        else:
            e_sh = (r @ v) / sqrt(-ka)
            e_ch = norm(r) * (norm(v)**2) / k - 1
            nu = F_to_nu(np.log((e_ch + e_sh) / (e_ch - e_sh)) / 2, ecc)

        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        px = r @ n
        py = (r @ cross(h, n)) / norm(h)
        argp = (np.arctan2(py, px) - nu) % (2 * np.pi)

    nu = (nu + np.pi) % (2 * np.pi) - np.pi

    return p, ecc, inc, raan, argp, nu
Beispiel #25
0
def izzo(k, r1, r2, tof, M, prograde, lowpath, numiter, rtol):
    """Aplies izzo algorithm to solve Lambert's problem.

    Parameters
    ----------
    k : float
        Gravitational Constant
    r1 : numpy.ndarray
        Initial position vector
    r2 : numpy.ndarray
        Final position vector
    tof : float
        Time of flight between both positions
    M : int
        Number of revolutions
    prograde: boolean
        Controls the desired inclination of the transfer orbit.
    lowpath: boolean
        If `True` or `False`, gets the transfer orbit whose vacant focus is
        below or above the chord line, respectively.
    numiter : int
        Number of iterations
    rtol : float
        Error tolerance

    Returns
    -------
    v1: numpy.ndarray
        Initial velocity vector
    v2: numpy.ndarray
        Final velocity vector

    """

    # Check preconditions
    assert tof > 0
    assert k > 0

    # Check collinearity of r1 and r2
    if not cross(r1, r2).any():
        raise ValueError(
            "Lambert solution cannot be computed for collinear vectors")

    # Chord
    c = r2 - r1
    c_norm, r1_norm, r2_norm = norm(c), norm(r1), norm(r2)

    # Semiperimeter
    s = (r1_norm + r2_norm + c_norm) * 0.5

    # Versors
    i_r1, i_r2 = r1 / r1_norm, r2 / r2_norm
    i_h = cross(i_r1, i_r2)
    i_h = i_h / norm(i_h)  # Fixed from paper

    # Geometry of the problem
    ll = np.sqrt(1 - min(1.0, c_norm / s))

    # Compute the fundamental tangential directions
    if i_h[2] < 0:
        ll = -ll
        i_t1, i_t2 = cross(i_r1, i_h), cross(i_r2, i_h)
    else:
        i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2)

    # Correct transfer angle parameter and tangential vectors if required
    ll, i_t1, i_t2 = (ll, i_t1, i_t2) if prograde else (-ll, -i_t1, -i_t2)

    # Non dimensional time of flight
    T = np.sqrt(2 * k / s**3) * tof

    # Find solutions
    x, y = _find_xy(ll, T, M, numiter, lowpath, rtol)

    # Reconstruct
    gamma = np.sqrt(k * s / 2)
    rho = (r1_norm - r2_norm) / c_norm
    sigma = np.sqrt(1 - rho**2)

    # Compute the radial and tangential components at r0 and r
    V_r1, V_r2, V_t1, V_t2 = _reconstruct(x, y, r1_norm, r2_norm, ll, gamma,
                                          rho, sigma)

    # Solve for the initial and final velocity
    v1 = V_r1 * (r1 / r1_norm) + V_t1 * i_t1
    v2 = V_r2 * (r2 / r2_norm) + V_t2 * i_t2

    return v1, v2
Beispiel #26
0
def vallado(k, r0, r, tof, M, prograde, lowpath, numiter, rtol):
    r"""Solves the Lambert's problem.

    The algorithm returns the initial velocity vector and the final one, these are
    computed by the following expresions:

    .. math::

        \vec{v_{o}} &= \frac{1}{g}(\vec{r} - f\vec{r_{0}}) \\
        \vec{v} &= \frac{1}{g}(\dot{g}\vec{r} - \vec{r_{0}})

    Therefore, the lagrange coefficients need to be computed. For the case of
    Lamber's problem, they can be expressed by terms of the initial and final vector:

    .. math::

        \begin{align}
            f = 1 -\frac{y}{r_{o}} \\
            g = A\sqrt{\frac{y}{\mu}} \\
            \dot{g} = 1 - \frac{y}{r} \\
        \end{align}

    Where y(z) is a function that depends on the :py:mod:`poliastro.core.stumpff` coefficients:

    .. math::

        y = r_{o} + r + A\frac{zS(z)-1}{\sqrt{C(z)}} \\
        A = \sin{(\Delta \nu)}\sqrt{\frac{rr_{o}}{1 - \cos{(\Delta \nu)}}}

    The value of z to evaluate the stump functions is solved by applying a Numerical method to
    the following equation:

    .. math::

        z_{i+1} = z_{i} - \frac{F(z_{i})}{F{}'(z_{i})}

    Function F(z)  to the expression:

    .. math::

        F(z) = \left [\frac{y(z)}{C(z)}  \right ]^{\frac{3}{2}}S(z) + A\sqrt{y(z)} - \sqrt{\mu}\Delta t

    Parameters
    ----------
    k : float
        Gravitational Parameter
    r0 : numpy.ndarray
        Initial position vector
    r : numpy.ndarray
        Final position vector
    tof : float
        Time of flight
    M : int
        Number of revolutions
    prograde: boolean
        Controls the desired inclination of the transfer orbit.
    lowpath: boolean
        If `True` or `False`, gets the transfer orbit whose vacant focus is
        below or above the chord line, respectively.
    numiter : int
        Number of iterations to
    rtol : int
        Number of revolutions

    Returns
    -------
    v0: numpy.ndarray
        Initial velocity vector
    v: numpy.ndarray
        Final velocity vector

    Examples
    --------
    >>> from poliastro.core.iod import vallado
    >>> from astropy import units as u
    >>> import numpy as np
    >>> from poliastro.bodies import Earth
    >>> k = Earth.k.to(u.km ** 3 / u.s ** 2)
    >>> r1 = np.array([5000, 10000, 2100]) * u.km # Initial position vector
    >>> r2 = np.array([-14600, 2500, 7000]) * u.km # Final position vector
    >>> tof = 3600 * u.s # Time of flight
    >>> v1, v2 = vallado(k.value, r1.value, r2.value, tof.value, M=0, prograde=True, lowpath=True, numiter=35, rtol=1e-8)
    >>> v1 = v1 * u.km / u.s
    >>> v2 = v2 * u.km / u.s
    >>> print(v1, v2)
    [-5.99249503  1.92536671  3.24563805] km / s [-3.31245851 -4.19661901 -0.38528906] km / s

    Notes
    -----
    This procedure can be found in section 5.3 of Curtis, with all the
    theoretical description of the problem. Analytical example can be found
    in the same book under name Example 5.2.

    """

    # TODO: expand for the multi-revolution case.
    # Issue: https://github.com/poliastro/poliastro/issues/858
    if M > 0:
        raise NotImplementedError(
            "Multi-revolution scenario not supported for Vallado. See issue https://github.com/poliastro/poliastro/issues/858"
        )

    t_m = 1 if prograde else -1

    norm_r0 = norm(r0)
    norm_r = norm(r)
    norm_r0_times_norm_r = norm_r0 * norm_r
    norm_r0_plus_norm_r = norm_r0 + norm_r

    cos_dnu = (r0 @ r) / norm_r0_times_norm_r

    A = t_m * (norm_r * norm_r0 * (1 + cos_dnu))**0.5

    if A == 0.0:
        raise RuntimeError("Cannot compute orbit, phase angle is 180 degrees")

    psi = 0.0
    psi_low = -4 * np.pi**2
    psi_up = 4 * np.pi**2

    count = 0

    while count < numiter:
        y = norm_r0_plus_norm_r + A * (psi * c3(psi) - 1) / c2(psi)**0.5
        if A > 0.0:
            # Readjust xi_low until y > 0.0
            # Translated directly from Vallado
            while y < 0.0:
                psi_low = psi
                psi = (0.8 * (1.0 / c3(psi)) *
                       (1.0 - norm_r0_times_norm_r * np.sqrt(c2(psi)) / A))
                y = norm_r0_plus_norm_r + A * (psi * c3(psi) -
                                               1) / c2(psi)**0.5

        xi = np.sqrt(y / c2(psi))
        tof_new = (xi**3 * c3(psi) + A * np.sqrt(y)) / np.sqrt(k)

        # Convergence check
        if np.abs((tof_new - tof) / tof) < rtol:
            break
        count += 1
        # Bisection check
        condition = tof_new <= tof
        psi_low = psi_low + (psi - psi_low) * condition
        psi_up = psi_up + (psi - psi_up) * (not condition)

        psi = (psi_up + psi_low) / 2
    else:
        raise RuntimeError("Maximum number of iterations reached")

    f = 1 - y / norm_r0
    g = A * np.sqrt(y / k)

    gdot = 1 - y / norm_r

    v0 = (r - f * r0) / g
    v = (gdot * r - r0) / g

    return v0, v
Beispiel #27
0
def vallado(k, r0, v0, tof, numiter):
    r"""Solves Kepler's Equation by applying a Newton-Raphson method.

    If the position of a body along its orbit wants to be computed
    for a specific time, it can be solved by terms of the Kepler's Equation:

    .. math::
        E = M + e\sin{E}

    In this case, the equation is written in terms of the Universal Anomaly:

    .. math::

        \sqrt{\mu}\Delta t = \frac{r_{o}v_{o}}{\sqrt{\mu}}\chi^{2}C(\alpha \chi^{2}) + (1 - \alpha r_{o})\chi^{3}S(\alpha \chi^{2}) + r_{0}\chi

    This equation is solved for the universal anomaly by applying a Newton-Raphson numerical method.
    Once it is solved, the Lagrange coefficients are returned:

    .. math::

        \begin{align}
            f &= 1 \frac{\chi^{2}}{r_{o}}C(\alpha \chi^{2}) \\
            g &= \Delta t - \frac{1}{\sqrt{\mu}}\chi^{3}S(\alpha \chi^{2}) \\
            \dot{f} &= \frac{\sqrt{\mu}}{rr_{o}}(\alpha \chi^{3}S(\alpha \chi^{2}) - \chi) \\
            \dot{g} &= 1 - \frac{\chi^{2}}{r}C(\alpha \chi^{2}) \\
        \end{align}

    Lagrange coefficients can be related then with the position and velocity vectors:

    .. math::
        \begin{align}
            \vec{r} &= f\vec{r_{o}} + g\vec{v_{o}} \\
            \vec{v} &= \dot{f}\vec{r_{o}} + \dot{g}\vec{v_{o}} \\
        \end{align}

    Parameters
    ----------
    k : float
        Standard gravitational parameter.
    r0 : numpy.ndarray
        Initial position vector.
    v0 : numpy.ndarray
        Initial velocity vector.
    tof : float
        Time of flight.
    numiter : int
        Number of iterations.

    Returns
    -------
    f: float
        First Lagrange coefficient
    g: float
        Second Lagrange coefficient
    fdot: float
        Derivative of the first coefficient
    gdot: float
        Derivative of the second coefficient

    Notes
    -----
    The theoretical procedure is explained in section 3.7 of Curtis in really
    deep detail. For analytical example, check in the same book for example 3.6.

    """

    # Cache some results
    dot_r0v0 = r0 @ v0
    norm_r0 = norm(r0)
    sqrt_mu = k**0.5
    alpha = -(v0 @ v0) / k + 2 / norm_r0

    # First guess
    if alpha > 0:
        # Elliptic orbit
        xi_new = sqrt_mu * tof * alpha
    elif alpha < 0:
        # Hyperbolic orbit
        xi_new = (
            np.sign(tof)
            * (-1 / alpha) ** 0.5
            * np.log(
                (-2 * k * alpha * tof)
                / (
                    dot_r0v0
                    + np.sign(tof) * np.sqrt(-k / alpha) * (1 - norm_r0 * alpha)
                )
            )
        )
    else:
        # Parabolic orbit
        # (Conservative initial guess)
        xi_new = sqrt_mu * tof / norm_r0

    # Newton-Raphson iteration on the Kepler equation
    count = 0
    while count < numiter:
        xi = xi_new
        psi = xi * xi * alpha
        c2_psi = c2(psi)
        c3_psi = c3(psi)
        norm_r = (
            xi * xi * c2_psi
            + dot_r0v0 / sqrt_mu * xi * (1 - psi * c3_psi)
            + norm_r0 * (1 - psi * c2_psi)
        )
        xi_new = (
            xi
            + (
                sqrt_mu * tof
                - xi * xi * xi * c3_psi
                - dot_r0v0 / sqrt_mu * xi * xi * c2_psi
                - norm_r0 * xi * (1 - psi * c3_psi)
            )
            / norm_r
        )
        if abs(xi_new - xi) < 1e-7:
            break
        else:
            count += 1
    else:
        raise RuntimeError("Maximum number of iterations reached")

    # Compute Lagrange coefficients
    f = 1 - xi**2 / norm_r0 * c2_psi
    g = tof - xi**3 / sqrt_mu * c3_psi

    gdot = 1 - xi**2 / norm_r * c2_psi
    fdot = sqrt_mu / (norm_r * norm_r0) * xi * (psi * c3_psi - 1)

    return f, g, fdot, gdot
Beispiel #28
0
def compute_flyby(v_spacecraft, v_body, k, r_p, theta):
    """Computes outbound velocity after a flyby and the turn angle

    Parameters
    ----------
    k : float
        Standard Gravitational parameter.
    r_p : float
        Radius of periapsis, measured from the center of the body.
    v_spacecraft : float
        Velocity of the spacecraft, relative to the attractor of the body.
    v_body : float
        Velocity of the body, relative to its attractor.
    theta : float
        Aim angle of the B vector.

    Returns
    -------
    v_spacecraft_out : float
        Outbound velocity of the spacecraft.
    delta : float
        Turn angle.

    """
    v_inf_1 = v_spacecraft - v_body  # Hyperbolic excess velocity
    v_inf = norm(v_inf_1)

    ecc = 1 + r_p * v_inf**2 / k  # Eccentricity of the entry hyperbola
    delta = 2 * np.arcsin(1 / ecc)  # Turn angle

    b = k / v_inf**2 * np.sqrt(ecc**2 - 1)  # Magnitude of the B vector

    # Now we compute the unit vectors in which to return the outbound hyperbolic excess velocity:
    # * S goes along the hyperbolic excess velocity and is perpendicular to the B-Plane,
    # * T goes along the B-Plane and is parallel to _some_ fundamental plane - in this case, the plane in which
    #   the velocities are computed
    # * R completes the orthonormal set
    S_vec = v_inf_1 / v_inf
    c_vec = np.array([0, 0, 1])
    T_vec = cross(S_vec, c_vec)
    T_vec = T_vec / norm(T_vec)
    R_vec = cross(S_vec, T_vec)

    # This vector defines the B-Plane
    B_vec = b * (np.cos(theta) * T_vec + np.sin(theta) * R_vec)

    # We have to rotate the inbound hyperbolic excess velocity
    # an angle delta (turn angle) around a vector that is orthogonal to
    # the B-Plane and trajectory plane
    rot_v = cross(B_vec / b, S_vec)

    # And now we rotate the outbound hyperbolic excess velocity
    # u_vec = v_inf_1 / norm(v_inf) = S_vec
    v_vec = cross(rot_v, v_inf_1)
    v_vec = v_vec / norm(v_vec)

    v_inf_2 = v_inf * (np.cos(delta) * S_vec + np.sin(delta) * v_vec)

    v_spacecraft_out = v_inf_2 + v_body

    return v_spacecraft_out, delta