Example #1
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
Example #2
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
Example #3
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=V_0, f=f, beta_0=beta_0_) * np.sign(r[0] * (inc_f - inc_0))

        t_ = v / norm(v)
        w_ = cross(r, v) / norm(cross(r, v))
        # n_ = cross(t_, w_)
        accel_v = f * (np.cos(beta_) * t_ + np.sin(beta_) * w_)
        return accel_v
Example #4
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
Example #5
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
Example #6
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=V_0, f=f, beta_0=beta_0_) * np.sign(
            r[0] * (inc_f - inc_0))

        t_ = v / norm(v)
        w_ = cross(r, v) / norm(cross(r, v))
        # n_ = cross(t_, w_)
        accel_v = f * (np.cos(beta_) * t_ + np.sin(beta_) * w_)
        return accel_v
Example #7
0
def change_ecc_quasioptimal(ss_0, ecc_f, f):
    """Guidance law from the model.
    Thrust is aligned with an inertially fixed direction perpendicular to the
    semimajor axis of the orbit.

    Parameters
    ----------
    ss_0 : Orbit
        Initial orbit, containing all the information.
    ecc_f : float
        Final eccentricity.
    f : float
        Magnitude of constant acceleration
    """
    # We fix the inertial direction at the beginning
    k = ss_0.attractor.k.to(u.km**3 / u.s**2).value
    a = ss_0.a.to(u.km).value
    ecc_0 = ss_0.ecc.value
    if ecc_0 > 0.001:  # Arbitrary tolerance
        ref_vec = ss_0.e_vec / ecc_0
    else:
        ref_vec = ss_0.r / norm(ss_0.r)

    h_unit = ss_0.h_vec / norm(ss_0.h_vec)
    thrust_unit = cross(h_unit, ref_vec) * np.sign(ecc_f - ecc_0)

    def a_d(t0, u_, k):
        accel_v = f * thrust_unit
        return accel_v

    delta_V, t_f = extra_quantities(k, a, ecc_0, ecc_f, f)
    return a_d, delta_V, t_f
Example #8
0
def izzo(k, r1, r2, tof, M, numiter, rtol):
    # Check preconditions
    assert tof > 0
    assert k > 0

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

    # Semiperimeter
    s = (r1_norm + r2_norm + c_norm) * .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 - c_norm / s)

    if i_h[2] < 0:
        ll = -ll
        i_h = -i_h

    i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2)  # Fixed from paper

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

    # Find solutions
    xy = _find_xy(ll, T, M, numiter, rtol)

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

    for x, y in xy:
        V_r1, V_r2, V_t1, V_t2 = _reconstruct(x, y, r1_norm, r2_norm, ll,
                                              gamma, rho, sigma)
        v1 = V_r1 * i_r1 + V_t1 * i_t1
        v2 = V_r2 * i_r2 + V_t2 * i_t2
        yield v1, v2
Example #9
0
def izzo(k, r1, r2, tof, M, numiter, rtol):
    # Check preconditions
    assert tof > 0
    assert k > 0

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

    # Semiperimeter
    s = (r1_norm + r2_norm + c_norm) * .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 - c_norm / s)

    if i_h[2] < 0:
        ll = -ll
        i_h = -i_h

    i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2)  # Fixed from paper

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

    # Find solutions
    xy = _find_xy(ll, T, M, numiter, rtol)

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

    for x, y in xy:
        V_r1, V_r2, V_t1, V_t2 = _reconstruct(x, y, r1_norm, r2_norm, ll,
                                              gamma, rho, sigma)
        v1 = V_r1 * i_r1 + V_t1 * i_t1
        v2 = V_r2 * i_r2 + V_t2 * i_t2
        yield v1, v2
Example #10
0
def change_inc_ecc(ss_0, ecc_f, inc_f, f):
    """Guidance law from the model.
    Thrust is aligned with an inertially fixed direction perpendicular to the
    semimajor axis of the orbit.

    Parameters
    ----------
    ss_0 : Orbit
        Initial orbit, containing all the information.
    ecc_f : float
        Final eccentricity.
    inc_f : float
        Final inclination.
    f : float
        Magnitude of constant acceleration.
    """

    # We fix the inertial direction at the beginning
    ecc_0 = ss_0.ecc.value
    if ecc_0 > 0.001:  # Arbitrary tolerance
        ref_vec = ss_0.e_vec / ecc_0
    else:
        ref_vec = ss_0.r / norm(ss_0.r)

    h_unit = ss_0.h_vec / norm(ss_0.h_vec)
    thrust_unit = cross(h_unit, ref_vec) * np.sign(ecc_f - ecc_0)

    inc_0 = ss_0.inc.to(u.rad).value
    argp = ss_0.argp.to(u.rad).value

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

    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, beta_, t_f = extra_quantities(
        ss_0.attractor.k.to(u.km ** 3 / u.s ** 2).value,
        ss_0.a.to(u.km).value,
        ecc_0,
        ecc_f,
        inc_0,
        inc_f,
        argp,
        f,
    )
    return a_d, delta_V, beta_, t_f
Example #11
0
def change_inc_ecc(ss_0, ecc_f, inc_f, f):
    """Guidance law from the model.
    Thrust is aligned with an inertially fixed direction perpendicular to the
    semimajor axis of the orbit.

    Parameters
    ----------
    ss_0 : Orbit
        Initial orbit, containing all the information.
    ecc_f : float
        Final eccentricity.
    inc_f : float
        Final inclination.
    f : float
        Magnitude of constant acceleration.
    """

    # We fix the inertial direction at the beginning
    ecc_0 = ss_0.ecc.value
    if ecc_0 > 0.001:  # Arbitrary tolerance
        ref_vec = ss_0.e_vec / ecc_0
    else:
        ref_vec = ss_0.r / norm(ss_0.r)

    h_unit = ss_0.h_vec / norm(ss_0.h_vec)
    thrust_unit = cross(h_unit, ref_vec) * np.sign(ecc_f - ecc_0)

    inc_0 = ss_0.inc.to(u.rad).value
    argp = ss_0.argp.to(u.rad).value

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

    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, beta_, t_f = extra_quantities(
        ss_0.attractor.k.to(u.km**3 / u.s**2).value,
        ss_0.a.to(u.km).value,
        ecc_0,
        ecc_f,
        inc_0,
        inc_f,
        argp,
        f,
    )
    return a_d, delta_V, beta_, t_f
Example #12
0
def wxyz(v1, v2):
    """
    Calculates w, x, y, z coefficients for quaternion from v1 to v2
    Parameters
    ----------
    v1 :
    v2 :
    """

    n1 = np.linalg.norm(v1)
    n2 = np.linalg.norm(v2)

    u1 = v1 / n1
    u2 = v2 / n2

    xyz = cross(u1, u2)
    w = np.dot(u1, u2) + 1

    return w, xyz[0], xyz[1], xyz[2]
Example #13
0
def rv2coe(k, r, v, tol=1e-8):
    """Converts from vectors to classical orbital elements.

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

    """
    h = cross(r, v)
    n = cross([0, 0, 1], h) / norm(h)
    e = ((v.dot(v) - k / (norm(r))) * r - r.dot(v) * v) / k
    ecc = norm(e)
    p = h.dot(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.dot(cross(e, r)) / norm(h), r.dot(e)) %
              (2 * np.pi))
    elif not equatorial and circular:
        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        argp = 0
        # Argument of latitude
        nu = (np.arctan2(r.dot(cross(h, n)) / norm(h), r.dot(n)) %
              (2 * np.pi))
    elif equatorial and circular:
        raan = 0
        argp = 0
        nu = np.arctan2(r[1], r[0]) % (2 * np.pi)  # True longitude
    else:
        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        argp = (np.arctan2(e.dot(cross(h, n)) / norm(h), e.dot(n)) %
                (2 * np.pi))
        nu = (np.arctan2(r.dot(cross(h, e)) / norm(h), r.dot(e))
              % (2 * np.pi))

    return p, ecc, inc, raan, argp, nu
Example #14
0
def rv2coe(k, r, v, tol=1e-8):
    """Converts from vectors to classical orbital elements.

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

    """
    h = cross(r, v)
    n = cross([0, 0, 1], h) / norm(h)
    e = ((v.dot(v) - k / (norm(r))) * r - r.dot(v) * v) / k
    ecc = norm(e)
    p = h.dot(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.dot(cross(e, r)) / norm(h), r.dot(e)) % (2 * np.pi))
    elif not equatorial and circular:
        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        argp = 0
        # Argument of latitude
        nu = (np.arctan2(r.dot(cross(h, n)) / norm(h), r.dot(n)) % (2 * np.pi))
    elif equatorial and circular:
        raan = 0
        argp = 0
        nu = np.arctan2(r[1], r[0]) % (2 * np.pi)  # True longitude
    else:
        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        argp = (np.arctan2(e.dot(cross(h, n)) / norm(h), e.dot(n)) %
                (2 * np.pi))
        nu = (np.arctan2(r.dot(cross(h, e)) / norm(h), r.dot(e)) % (2 * np.pi))

    return p, ecc, inc, raan, argp, nu
Example #15
0
def rv2coe(k, r, v, tol=1e-8):
    r"""Converts from vectors to classical orbital elements.

    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.

    Parameters
    ----------
    k : float
        Standard gravitational parameter (km^3 / s^2)
    r : array
        Position vector (km)
    v : array
        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)

    Examples
    --------
    >>> from poliastro.core.elements import rv2coe
    >>> from poliastro.constants import GM_earth
    >>> from astropy import units as u
    >>> import numpy as np
    >>> k = GM_earth.to(u.km**3 / u.s**2) #Earth gravitational parameter
    >>> r = [-6045, -3490, 2500] * u.km
    >>> v = [-3.457, 6.618, 2.533] * u.km / u.s
    >>> p, ecc, inc, raan, argp, nu = rv2coe(k, r, v)
    >>> print("p:", p, "[km]")
    p: 8530.47436396927 [km]
    >>> print("ecc:", ecc)
    ecc: 0.17121118195416898
    >>> print("inc:", np.rad2deg(inc), "[deg]")
    inc: 153.2492285182475 [deg]
    >>> print("raan:", np.rad2deg(raan), "[deg]")
    raan: 255.27928533439618 [deg]
    >>> print("argp:", np.rad2deg(argp), "[deg]")
    argp: 20.068139973005362 [deg]
    >>> print("nu:", np.rad2deg(nu), "[deg]")
    nu: 28.445804984192108 [deg]


    Note
    ----

    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.
    """

    h = cross(r, v)
    n = cross([0, 0, 1], h) / norm(h)
    e = ((v.dot(v) - k / (norm(r))) * r - r.dot(v) * v) / k
    ecc = norm(e)
    p = h.dot(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.dot(cross(e, r)) / norm(h), r.dot(e)) % (2 * np.pi)
    elif not equatorial and circular:
        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        argp = 0
        # Argument of latitude
        nu = np.arctan2(r.dot(cross(h, n)) / norm(h), r.dot(n)) % (2 * np.pi)
    elif equatorial and circular:
        raan = 0
        argp = 0
        nu = np.arctan2(r[1], r[0]) % (2 * np.pi)  # True longitude
    else:
        raan = np.arctan2(n[1], n[0]) % (2 * np.pi)
        argp = np.arctan2(e.dot(cross(h, n)) / norm(h), e.dot(n)) % (2 * np.pi)
        nu = np.arctan2(r.dot(cross(h, e)) / norm(h), r.dot(e)) % (2 * np.pi)

    return p, ecc, inc, raan, argp, nu
Example #16
0
    def hohmann(cls, orbit_i, r_f):
        r"""Compute a Hohmann transfer between two circular orbits.

        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
        ----------
        orbit_i: poliastro.twobody.orbit.Orbit
            Initial orbit
        r_f: astropy.unit.Quantity
            Final altitude of the orbit
        """

        if orbit_i.nu is not 0 * u.deg:
            orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg)

        # Initial orbit data
        k = orbit_i.attractor.k
        r_i = orbit_i.r
        v_i = orbit_i.v
        h_i = norm(cross(r_i.to(u.m).value, v_i.to(u.m / u.s).value) * u.m ** 2 / u.s)
        p_i = h_i ** 2 / k.to(u.m ** 3 / u.s ** 2)

        # Hohmann is defined always from the PQW frame, since it is the
        # natural plane of the orbit
        r_i, v_i = rv_pqw(
            k.to(u.m ** 3 / u.s ** 2).value,
            p_i.to(u.m).value,
            orbit_i.ecc.value,
            orbit_i.nu.to(u.rad).value,
        )

        # Now, we apply Hohmman maneuver
        r_i = norm(r_i * u.m)
        v_i = norm(v_i * u.m / u.s)
        a_trans = (r_i + r_f) / 2

        # This is the modulus of the velocities
        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)

        # Write them in PQW frame
        dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0])
        dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0])

        # Transform to IJK frame
        rot_matrix = coe_rotation_matrix(
            orbit_i.inc.to(u.rad).value,
            orbit_i.raan.to(u.rad).value,
            orbit_i.argp.to(u.rad).value,
        )

        dv_a = (rot_matrix @ dv_a) * u.m / u.s
        dv_b = (rot_matrix @ dv_b) * u.m / u.s

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

        return cls((0 * u.s, dv_a), (t_trans, dv_b))
Example #17
0
    def bielliptic(cls, orbit_i, r_b, r_f):
        r"""Compute a bielliptic transfer between two circular orbits.

        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
        ----------
        orbit_i: poliastro.twobody.orbit.Orbit
            Initial orbit
        r_b: astropy.unit.Quantity
            Altitude of the intermediate orbit
        r_f: astropy.unit.Quantity
            Final altitude of the orbit
        """
        if orbit_i.nu is not 0 * u.deg:
            orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg)

        # Initial orbit data
        k = orbit_i.attractor.k
        r_i = orbit_i.r
        v_i = orbit_i.v
        h_i = norm(cross(r_i.to(u.m).value, v_i.to(u.m / u.s).value) * u.m ** 2 / u.s)
        p_i = h_i ** 2 / k.to(u.m ** 3 / u.s ** 2)

        # Bielliptic is defined always from the PQW frame, since it is the
        # natural plane of the orbit
        r_i, v_i = rv_pqw(
            k.to(u.m ** 3 / u.s ** 2).value,
            p_i.to(u.m).value,
            orbit_i.ecc.value,
            orbit_i.nu.to(u.rad).value,
        )

        # Define the transfer radius
        r_i = norm(r_i * u.m)
        v_i = norm(v_i * u.m / u.s)
        a_trans1 = (r_i + r_b) / 2
        a_trans2 = (r_b + r_f) / 2

        # Compute impulses
        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)

        # Write impulses in PQW frame
        dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0])
        dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0])
        dv_c = np.array([0, dv_c.to(u.m / u.s).value, 0])

        rot_matrix = coe_rotation_matrix(
            orbit_i.inc.to(u.rad).value,
            orbit_i.raan.to(u.rad).value,
            orbit_i.argp.to(u.rad).value,
        )

        # Transform to IJK frame
        dv_a = (rot_matrix @ dv_a) * u.m / u.s
        dv_b = (rot_matrix @ dv_b) * u.m / u.s
        dv_c = (rot_matrix @ dv_c) * u.m / u.s

        # Compute time for maneuver
        t_trans1 = np.pi * np.sqrt(a_trans1 ** 3 / k)
        t_trans2 = np.pi * np.sqrt(a_trans2 ** 3 / k)

        return cls((0 * u.s, dv_a), (t_trans1, dv_b), (t_trans2, dv_c))
Example #18
0
    def hohmann(cls, orbit_i, r_f):
        r"""Compute a Hohmann transfer between two circular orbits.

        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
        ----------
        orbit_i: poliastro.twobody.orbit.Orbit
            Initial orbit
        r_f: astropy.unit.Quantity
            Final altitude of the orbit
        """

        if orbit_i.nu is not 0 * u.deg:
            orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg)

        # Initial orbit data
        k = orbit_i.attractor.k
        r_i = orbit_i.r
        v_i = orbit_i.v
        h_i = norm(
            cross(r_i.to(u.m).value,
                  v_i.to(u.m / u.s).value) * u.m**2 / u.s)
        p_i = h_i**2 / k.to(u.m**3 / u.s**2)

        # Hohmann is defined always from the PQW frame, since it is the
        # natural plane of the orbit
        r_i, v_i = rv_pqw(
            k.to(u.m**3 / u.s**2).value,
            p_i.to(u.m).value,
            orbit_i.ecc.value,
            orbit_i.nu.to(u.rad).value,
        )

        # Now, we apply Hohmman maneuver
        r_i = norm(r_i * u.m)
        v_i = norm(v_i * u.m / u.s)
        a_trans = (r_i + r_f) / 2

        # This is the modulus of the velocities
        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)

        # Write them in PQW frame
        dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0])
        dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0])

        # Transform to IJK frame
        rot_matrix = coe_rotation_matrix(
            orbit_i.inc.to(u.rad).value,
            orbit_i.raan.to(u.rad).value,
            orbit_i.argp.to(u.rad).value,
        )

        dv_a = (rot_matrix @ dv_a) * u.m / u.s
        dv_b = (rot_matrix @ dv_b) * u.m / u.s

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

        return cls((0 * u.s, dv_a), (t_trans, dv_b))
Example #19
0
    def bielliptic(cls, orbit_i, r_b, r_f):
        r"""Compute a bielliptic transfer between two circular orbits.

        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
        ----------
        orbit_i: poliastro.twobody.orbit.Orbit
            Initial orbit
        r_b: astropy.unit.Quantity
            Altitude of the intermediate orbit
        r_f: astropy.unit.Quantity
            Final altitude of the orbit
        """
        if orbit_i.nu is not 0 * u.deg:
            orbit_i = orbit_i.propagate_to_anomaly(0 * u.deg)

        # Initial orbit data
        k = orbit_i.attractor.k
        r_i = orbit_i.r
        v_i = orbit_i.v
        h_i = norm(
            cross(r_i.to(u.m).value,
                  v_i.to(u.m / u.s).value) * u.m**2 / u.s)
        p_i = h_i**2 / k.to(u.m**3 / u.s**2)

        # Bielliptic is defined always from the PQW frame, since it is the
        # natural plane of the orbit
        r_i, v_i = rv_pqw(
            k.to(u.m**3 / u.s**2).value,
            p_i.to(u.m).value,
            orbit_i.ecc.value,
            orbit_i.nu.to(u.rad).value,
        )

        # Define the transfer radius
        r_i = norm(r_i * u.m)
        v_i = norm(v_i * u.m / u.s)
        a_trans1 = (r_i + r_b) / 2
        a_trans2 = (r_b + r_f) / 2

        # Compute impulses
        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)

        # Write impulses in PQW frame
        dv_a = np.array([0, dv_a.to(u.m / u.s).value, 0])
        dv_b = np.array([0, -dv_b.to(u.m / u.s).value, 0])
        dv_c = np.array([0, dv_c.to(u.m / u.s).value, 0])

        rot_matrix = coe_rotation_matrix(
            orbit_i.inc.to(u.rad).value,
            orbit_i.raan.to(u.rad).value,
            orbit_i.argp.to(u.rad).value,
        )

        # Transform to IJK frame
        dv_a = (rot_matrix @ dv_a) * u.m / u.s
        dv_b = (rot_matrix @ dv_b) * u.m / u.s
        dv_c = (rot_matrix @ dv_c) * u.m / u.s

        # Compute time for maneuver
        t_trans1 = np.pi * np.sqrt(a_trans1**3 / k)
        t_trans2 = np.pi * np.sqrt(a_trans2**3 / k)

        return cls((0 * u.s, dv_a), (t_trans1, dv_b), (t_trans2, dv_c))
Example #20
0
def izzo(k, r1, r2, tof, M, numiter, rtol):
    """ Aplies izzo algorithm to solve Lambert's problem.

    Parameters
    ----------
    k: float
        Gravitational Constant
    r1: ~numpy.array
        Initial position vector
    r2: ~numpy.array
        Final position vector
    tof: float
        Time of flight between both positions
    M: int
        Number of revolutions
    numiter: int
        Numbert of iterations
    rtol: float
        Error tolerance

    Returns
    -------

    v1: ~numpy.array
        Initial velocity vector
    v2: ~numpy.array
        Final velocity vector

    """

    # Check preconditions
    assert tof > 0
    assert k > 0

    # Check collinearity of r1 and r2
    if np.all(cross(r1, r2) == 0):
        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))

    if i_h[2] < 0:
        ll = -ll
        i_h = -i_h

    i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2)  # Fixed from paper

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

    # Find solutions
    xy = _find_xy(ll, T, M, numiter, rtol)

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

    for x, y in xy:
        V_r1, V_r2, V_t1, V_t2 = _reconstruct(
            x, y, r1_norm, r2_norm, ll, gamma, rho, sigma
        )
        v1 = V_r1 * i_r1 + V_t1 * i_t1
        v2 = V_r2 * i_r2 + V_t2 * i_t2
        yield v1, v2
Example #21
0
def izzo(k, r1, r2, tof, M, numiter, rtol):
    """ Aplies izzo algorithm to solve Lambert's problem.

    Parameters
    ----------
    k: float
        Gravitational Constant
    r1: ~numpy.array 
        Initial position vector
    r2: ~numpy.array
        Final position vector
    tof: float
        Time of flight between both positions
    M: int
        Number of revolutions
    numiter: int
        Numbert of iterations
    rotl: float
        Error tolerance
    
    Returns
    -------

    v1: ~numpy.array
        Initial velocity vector
    v2: ~numpy.array
        FInal velocity vector

    """

    # Check preconditions
    assert tof > 0
    assert k > 0

    # Check collinearity of r1 and r2
    if np.all(cross(r1, r2) == 0):
        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))

    if i_h[2] < 0:
        ll = -ll
        i_h = -i_h

    i_t1, i_t2 = cross(i_h, i_r1), cross(i_h, i_r2)  # Fixed from paper

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

    # Find solutions
    xy = _find_xy(ll, T, M, numiter, rtol)

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

    for x, y in xy:
        V_r1, V_r2, V_t1, V_t2 = _reconstruct(x, y, r1_norm, r2_norm, ll,
                                              gamma, rho, sigma)
        v1 = V_r1 * i_r1 + V_t1 * i_t1
        v2 = V_r2 * i_r2 + V_t2 * i_t2
        yield v1, v2
Example #22
0
def rv2coe(k, r, v, tol=1e-8):
    r"""Converts from vectors to classical orbital elements.

    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.

    Parameters
    ----------
    k : float
        Standard gravitational parameter (km^3 / s^2)
    r : array
        Position vector (km)
    v : array
        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)

    Examples
    --------
    >>> from poliastro.constants import GM_earth
    >>> from astropy import units as u
    >>> k = GM_earth.to(u.km ** 3 / u.s ** 2).value  # Earth gravitational parameter
    >>> 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]")
    p: 8530.47436396927 [km]
    >>> print("ecc:", ecc)
    ecc: 0.17121118195416898
    >>> print("inc:", np.rad2deg(inc), "[deg]")
    inc: 153.2492285182475 [deg]
    >>> print("raan:", np.rad2deg(raan), "[deg]")
    raan: 255.27928533439618 [deg]
    >>> print("argp:", np.rad2deg(argp), "[deg]")
    argp: 20.068139973005366 [deg]
    >>> print("nu:", np.rad2deg(nu), "[deg]")
    nu: 28.445804984192122 [deg]

    Note
    ----
    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.
    """

    h = cross(r, v)
    n = cross([0, 0, 1], h)
    e = ((v.dot(v) - k / (norm(r))) * r - r.dot(v) * v) / k
    ecc = norm(e)
    p = h.dot(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.dot(cross(e, r)) / norm(h), r.dot(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.dot(cross(h, n)) / norm(h), r.dot(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.dot(v) / sqrt(ka)
            e_ce = norm(r) * (norm(v) ** 2) / k - 1
            nu = E_to_nu(np.arctan2(e_se, e_ce), ecc)
        else:
            e_sh = r.dot(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.dot(n)
        py = r.dot(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