Source code for ADCS.orbits.helpers.orbit_factory

# Without __all__, autodoc treats the imported Orbit / Orbital_State below as
# members of this module and documents them a second time, which Sphinx reports
# as a duplicate object description -- fatal under the docs build's -W.
__all__ = ["create_random_circular_os", "create_random_circular_orbit"]

import numpy as np
from typing import Optional

from ADCS.orbits.ephemeris import Ephemeris
from ADCS.orbits.orbital_state import Orbital_State
from ADCS.orbits.orbit import Orbit
from ADCS.orbits.universal_constants import TimeConstants, EarthConstants
from ADCS.helpers.math_helpers import normalize

[docs] def create_random_circular_os( radius_km: float, J2000: float = 0.22, ephem: Optional[Ephemeris] = None, rng: Optional[np.random.Generator] = None ) -> Orbital_State: r""" Generates a random orbital state for a circular orbit with a specified radius. The position vector :math:`\mathbf{R}` is generated by sampling a random vector :math:`\mathbf{x}` from a standard normal distribution :math:`\mathcal{N}(0, I_3)` and normalizing it to lie on the unit sphere, then scaling by the radius :math:`r`: .. math:: \hat{\mathbf{r}} = \frac{\mathbf{x}}{\|\mathbf{x}\|} \mathbf{R} = r \cdot \hat{\mathbf{r}} The velocity vector :math:`\mathbf{V}` is generated by creating a random tangent direction. A second random vector :math:`\mathbf{v}_{rand}` is sampled and projected onto the plane tangent to :math:`\hat{\mathbf{r}}`: .. math:: \mathbf{v}_{tan} = \mathbf{v}_{rand} - (\mathbf{v}_{rand} \cdot \hat{\mathbf{r}}) \hat{\mathbf{r}} \hat{\mathbf{v}} = \frac{\mathbf{v}_{tan}}{\|\mathbf{v}_{tan}\|} The magnitude is set to the circular velocity :math:`v_{circ}`: .. math:: v_{circ} = \sqrt{\frac{\mu}{r}} \mathbf{V} = v_{circ} \cdot \hat{\mathbf{v}} :param radius_km: The orbital radius in kilometers. :type radius_km: float :param J2000: The current time in J2000 centuries. Defaults to 0.22. :type J2000: float :param ephem: Optional ephemeris object. If None, a new one is created. :type ephem: :class:`~ADCS.orbits.ephemeris.Ephemeris`, optional :return: The generated orbital state. :rtype: :class:`~ADCS.orbits.orbital_state.Orbital_State` """ if ephem is None: ephem = Ephemeris() # Fallback to default generator if none provided if rng is None: rng = np.random.default_rng() mu = EarthConstants.mu_e # Use rng.standard_normal instead of np.random.standard_normal r_hat = normalize(rng.standard_normal(3)) R = radius_km * r_hat v_rand = rng.standard_normal(3) v_tan = v_rand - np.dot(v_rand, r_hat) * r_hat v_hat = normalize(v_tan) v_circ = float(np.sqrt(mu / radius_km)) V = v_circ * v_hat return Orbital_State( ephem=ephem, J2000=J2000, R=R, V=V, )
[docs] def create_random_circular_orbit( radius_km: float, dt: float, tf: float, J2000: float = 0.22, fast: bool = False, rng: Optional[np.random.Generator] = None, zonal_J: int = 2, ) -> Orbit: r""" Creates an initialized Orbit object based on a random circular orbital state. This function utilizes :func:`~create_random_circular_os` to generate the initial conditions :math:`\mathbf{R}` and :math:`\mathbf{V}`. It then initializes the orbit propagator. The end time is calculated as: .. math:: t_{end} = t_{start} + t_{f} \cdot C_{sec2cent} where :math:`C_{sec2cent}` is the conversion factor from seconds to centuries. :param radius_km: The orbital radius in kilometers. :type radius_km: float :param dt: The simulation time step in seconds. :type dt: float :param tf: The total simulation duration in seconds. :type tf: float :param J2000: The start time in J2000 centuries. Defaults to 0.22. :type J2000: float :param fast: Flag to enable fast propagation mode (reduced precision). :type fast: bool :param zonal_J: Highest zonal harmonic degree to include. ``0`` disables zonals, ``2`` includes only J2, and larger values include every zonal term up to that degree. :type zonal_J: int :return: The fully initialized orbit object. :rtype: :class:`~ADCS.orbits.orbit.Orbit` """ os0 = create_random_circular_os(radius_km=radius_km, J2000=J2000, rng=rng) return Orbit( os0=os0, end_time=J2000 + tf * TimeConstants.sec2cent, dt=dt, fast=fast, verbose=False, zonal_J=zonal_J, )