Ejemplo n.º 1
0
def sample_closed(min_nu, ecc, max_nu=None, num_values=100):
    """Sample a closed orbit

    If ``max_nu`` is given, the sampling interval will go
    from the minimum to the maximum true anomaly in the direction of the orbit.
    If not given, it will do a full revolution starting in the minimum true anomaly.

    Notes
    -----
    First sample the eccentric anomaly uniformly,
    then transform into true anomaly
    to minimize error in the apocenter,
    see https://apps.dtic.mil/dtic/tr/fulltext/u2/a605040.pdf

    """
    # Because how nu_to_E works, we don't need to wrap the angle here!
    # It will do the right thing
    min_E = nu_to_E(min_nu, ecc)

    # This linspace will always increase positively,
    # even though it might contain out of range values
    E_values = alinspace(min_E,
                         nu_to_E(max_nu, ecc) if max_nu is not None else None,
                         num=num_values)

    # Because how E_to_nu works, we don't need to wrap the angles here!
    # It will do the right thing
    nu_values = E_to_nu(E_values, ecc)

    # We wrap the angles on return
    return (nu_values + np.pi * u.rad) % (2 * np.pi * u.rad) - np.pi * u.rad
Ejemplo n.º 2
0
    def sample(self, values=None, function=propagate):
        """Samples an orbit to some specified time values.

        .. versionadded:: 0.8.0

        Parameters
        ----------
        values : Multiple options
            Number of interval points (default to 100),
            True anomaly values,
            Time values.

        Returns
        -------
        (Time, CartesianRepresentation)
            A tuple containing Time and Position vector in each
            given value.

        Notes
        -----
        When specifying a number of points, the initial and final
        position is present twice inside the result (first and
        last row). This is more useful for plotting.

        Examples
        --------
        >>> from astropy import units as u
        >>> from poliastro.examples import iss
        >>> iss.sample()
        >>> iss.sample(10)
        >>> iss.sample([0, 180] * u.deg)
        >>> iss.sample([0, 10, 20] * u.minute)
        >>> iss.sample([iss.epoch + iss.period / 2])

        """
        if values is None:
            return self.sample(100, function)

        elif isinstance(values, int):
            if self.ecc < 1:
                # first sample eccentric anomaly, then transform into true anomaly
                # why sampling eccentric anomaly uniformly to minimize error in the apocenter, see
                # http://www.dtic.mil/dtic/tr/fulltext/u2/a605040.pdf
                E_values = np.linspace(0, 2 * np.pi, values) * u.rad
                nu_values = E_to_nu(E_values, self.ecc)
            else:
                # Select a sensible limiting value for non-closed orbits
                # This corresponds to r = 3p
                nu_limit = np.arccos(-(1 - 1 / 3.) / self.ecc)
                nu_values = np.linspace(-nu_limit, nu_limit, values)
                nu_values = np.insert(nu_values, 0, self.ecc)

            return self.sample(nu_values, function)

        elif hasattr(values, "unit") and values.unit in ('rad', 'deg'):
            values = self._generate_time_values(values)
        return (values, self._sample(values, function))
Ejemplo n.º 3
0
def test_mean_to_true():
    for row in ELLIPTIC_ANGLES_DATA:
        ecc, M, expected_nu = row
        ecc = ecc * u.one
        M = M * u.deg
        expected_nu = expected_nu * u.deg

        nu = E_to_nu(M_to_E(M, ecc), ecc)

        assert_quantity_allclose(nu, expected_nu, rtol=1e-4)
Ejemplo n.º 4
0
    def sample(self, values=100, method=mean_motion):
        """Samples an orbit to some specified time values.

        .. versionadded:: 0.8.0

        Parameters
        ----------
        values : int
            Number of interval points (default to 100).

        method : function, optional
            Method used for propagation

        Returns
        -------
        positions: ~astropy.coordinates.BaseCoordinateFrame
            Array of x, y, z positions, with proper times as the frame attributes if supported.

        Notes
        -----
        When specifying a number of points, the initial and final
        position is present twice inside the result (first and
        last row). This is more useful for plotting.

        Examples
        --------
        >>> from astropy import units as u
        >>> from poliastro.examples import iss
        >>> iss.sample()  # doctest: +ELLIPSIS
        <GCRS Coordinate ...>
        >>> iss.sample(10)  # doctest: +ELLIPSIS
        <GCRS Coordinate ...>

        """
        if self.ecc < 1:
            # first sample eccentric anomaly, then transform into true anomaly
            # why sampling eccentric anomaly uniformly to minimize error in the apocenter, see
            # http://www.dtic.mil/dtic/tr/fulltext/u2/a605040.pdf
            # Start from pericenter
            E_values = np.linspace(0, 2 * np.pi, values) * u.rad
            nu_values = E_to_nu(E_values, self.ecc)
        else:
            # Select a sensible limiting value for non-closed orbits
            # This corresponds to max(r = 3p, r = self.r)
            # We have to wrap nu in [-180, 180) to compare it with the output of
            # the arc cosine, which is in the range [0, 180)
            # Start from -nu_limit
            wrapped_nu = self.nu if self.nu < 180 * u.deg else self.nu - 360 * u.deg
            nu_limit = max(np.arccos(-(1 - 1 / 3.0) / self.ecc),
                           abs(wrapped_nu))
            nu_values = np.linspace(-nu_limit, nu_limit, values)

        time_values = self._generate_time_values(nu_values)
        return propagate(self, time_values, method=method)
Ejemplo n.º 5
0
    def _sample_closed(self, values, min_anomaly, max_anomaly):
        limits = [
            min_anomaly.to(u.rad).value if min_anomaly is not None else 0,
            max_anomaly.to(u.rad).value if max_anomaly is not None else 2 * np.pi,
        ] * u.rad

        # First sample eccentric anomaly, then transform into true anomaly
        # to minimize error in the apocenter, see
        # http://www.dtic.mil/dtic/tr/fulltext/u2/a605040.pdf
        # Start from pericenter
        E_values = np.linspace(*limits, values)
        nu_values = E_to_nu(E_values, self.ecc)
        return nu_values
Ejemplo n.º 6
0
def orbit_from_sbdb(name, **kwargs):
    obj = SBDB.query(name, full_precision=True, **kwargs)

    if "count" in obj:
        # No error till now ---> more than one object has been found
        # Contains all the name of the objects
        objects_name = obj["list"]["name"]
        objects_name_in_str = ""  # Used to store them in string form each in new line
        for i in objects_name:
            objects_name_in_str += i + "\n"

        raise ValueError(
            str(obj["count"]) + " different objects found: \n" +
            objects_name_in_str)

    if "object" not in obj.keys():
        raise ValueError(f"Object {name} not found")

    a = obj["orbit"]["elements"]["a"]
    ecc = float(obj["orbit"]["elements"]["e"]) * u.one
    inc = obj["orbit"]["elements"]["i"]
    raan = obj["orbit"]["elements"]["om"]
    argp = obj["orbit"]["elements"]["w"]

    # Since JPL provides Mean Anomaly (M) we need to make
    # the conversion to the true anomaly (nu)
    M = obj["orbit"]["elements"]["ma"].to(u.rad)
    # NOTE: It is unclear how this conversion should happen,
    # see https://ssd-api.jpl.nasa.gov/doc/sbdb.html
    if ecc < 1:
        M = (M + np.pi * u.rad) % (2 * np.pi * u.rad) - np.pi * u.rad
        nu = E_to_nu(M_to_E(M, ecc), ecc)
    elif ecc == 1:
        nu = D_to_nu(M_to_D(M))
    else:
        nu = F_to_nu(M_to_F(M, ecc), ecc)

    epoch = Time(obj["orbit"]["epoch"].to(u.d), format="jd")

    return Orbit.from_classical(
        attractor=Sun,
        a=a,
        ecc=ecc,
        inc=inc,
        raan=raan,
        argp=argp,
        nu=nu,
        epoch=epoch.tdb,
        plane=Planes.EARTH_ECLIPTIC,
    )
Ejemplo n.º 7
0
def orbit_from_record(record):
    """Return :py:class:`~poliastro.twobody.orbit.Orbit` given a record.

    Retrieve info from JPL DASTCOM5 database.

    Parameters
    ----------
    record : int
        Object record.

    Returns
    -------
    orbit : ~poliastro.twobody.orbit.Orbit
        NEO orbit.

    """
    body_data = read_record(record)
    a = body_data["A"].item() * u.au
    ecc = body_data["EC"].item() * u.one
    inc = body_data["IN"].item() * u.deg
    raan = body_data["OM"].item() * u.deg
    argp = body_data["W"].item() * u.deg
    M = body_data["MA"].item() * u.deg
    epoch = Time(body_data["EPOCH"].item(), format="jd", scale="tdb")

    # NOTE: It is unclear how this conversion should happen,
    # see https://ssd-api.jpl.nasa.gov/doc/sbdb.html
    if ecc < 1:
        M = (M + np.pi * u.rad) % (2 * np.pi * u.rad) - np.pi * u.rad
        nu = E_to_nu(M_to_E(M, ecc), ecc)
    elif ecc == 1:
        nu = D_to_nu(M_to_D(M))
    else:
        nu = F_to_nu(M_to_F(M, ecc), ecc)

    orbit = Orbit.from_classical(Sun, a, ecc, inc, raan, argp, nu, epoch)
    orbit._frame = HeliocentricEclipticJ2000(obstime=epoch)
    return orbit
Ejemplo n.º 8
0
def newton(func, x0, ecc, M, fprime=None, maxiter=50):
    EFD = 1.0 * x0
    delta = 1e-2
    nu_prev = 1e+10
    converged = False
    # Newton-Rapheson method
    with u.set_enabled_equivalencies(u.dimensionless_angles()):
        for iter in range(maxiter):
            if ecc < 1.0 - delta:
                nu = E_to_nu(EFD, ecc)
                E_plus = nu_to_E(nu + variation, ecc)
                E_minus = nu_to_E(nu - variation, ecc)
                M_actual = _kepler_equation(EFD, M, ecc, count=False)
                M_plus = _kepler_equation(E_plus, M, ecc, count=False)
                M_minus = _kepler_equation(E_minus, M, ecc, count=False)
            elif ecc > 1.0 + delta:
                nu = F_to_nu(EFD, ecc)
                F_plus = nu_to_F(nu + variation, ecc)
                F_minus = nu_to_F(nu - variation, ecc)
                M_actual = _kepler_equation_hyper(EFD, M, ecc, count=False)
                M_plus = _kepler_equation_hyper(F_plus, M, ecc, count=False)
                M_minus = _kepler_equation_hyper(F_minus, M, ecc, count=False)
            else:
                nu = D_to_nu(EFD, ecc)
                D_plus = nu_to_D(nu + variation, ecc)
                D_minus = nu_to_D(nu - variation, ecc)
                M_actual = _kepler_equation_parabolic(EFD, M, ecc, count=False)
                M_plus = _kepler_equation_parabolic(D_plus, M, ecc, count=False)
                M_minus = _kepler_equation_parabolic(D_minus, M, ecc, count=False)
            converged = (np.abs(nu_prev - nu) < variation) and (M_actual * M_plus <= 0 or M_minus * M_actual <= 0)
            nu_prev = nu
            EFD_new = EFD - func(EFD, M, ecc) / fprime(EFD, M, ecc)
            if converged:
                return EFD_new
            EFD = EFD_new
    print(ecc, "fail")
    return -1
Ejemplo n.º 9
0
def test_eccentric_to_true_range(E, ecc):
    nu = E_to_nu(E, ecc)
    E_result = nu_to_E(nu, ecc)
    assert_quantity_allclose(E_result, E, rtol=1e-8)
Ejemplo n.º 10
0
    def from_sbdb(cls, name, **kwargs):
        """Return osculating `Orbit` by using `SBDB` from Astroquery.

        Parameters
        ----------
        name: str
            Name of the body to make the request.

        Returns
        -------
        ss: poliastro.twobody.orbit.Orbit
            Orbit corresponding to body_name

        Examples
        --------
        >>> from poliastro.twobody.orbit import Orbit
        >>> apophis_orbit = Orbit.from_sbdb('apophis')  # doctest: +REMOTE_DATA

        """
        from poliastro.bodies import Sun

        obj = SBDB.query(name, full_precision=True, **kwargs)

        if "count" in obj:
            # No error till now ---> more than one object has been found
            # Contains all the name of the objects
            objects_name = obj["list"]["name"]
            objects_name_in_str = (
                ""  # Used to store them in string form each in new line
            )
            for i in objects_name:
                objects_name_in_str += i + "\n"

            raise ValueError(
                str(obj["count"]) + " different objects found: \n" + objects_name_in_str
            )

        if "object" not in obj.keys():
            raise ValueError(f"Object {name} not found")

        a = obj["orbit"]["elements"]["a"]
        ecc = float(obj["orbit"]["elements"]["e"]) * u.one
        inc = obj["orbit"]["elements"]["i"]
        raan = obj["orbit"]["elements"]["om"]
        argp = obj["orbit"]["elements"]["w"]

        # Since JPL provides Mean Anomaly (M) we need to make
        # the conversion to the true anomaly (nu)
        M = obj["orbit"]["elements"]["ma"].to(u.rad)
        # NOTE: It is unclear how this conversion should happen,
        # see https://ssd-api.jpl.nasa.gov/doc/sbdb.html
        if ecc < 1:
            M = (M + np.pi * u.rad) % (2 * np.pi * u.rad) - np.pi * u.rad
            nu = E_to_nu(M_to_E(M, ecc), ecc)
        elif ecc == 1:
            nu = D_to_nu(M_to_D(M))
        else:
            nu = F_to_nu(M_to_F(M, ecc), ecc)

        epoch = time.Time(obj["orbit"]["epoch"].to(u.d), format="jd")

        ss = cls.from_classical(
            Sun,
            a,
            ecc,
            inc,
            raan,
            argp,
            nu,
            epoch=epoch.tdb,
            plane=Planes.EARTH_ECLIPTIC,
        )

        return ss