Skip to content

Satellite transfer

A satellite moved between Kepler orbits of equal angular momentum by radial thrust.

  • Name: satellite_transfer
  • Field: engineering/control
  • Formalism: ordinary differential equations
  • References: Dilão et al. 2022; Dilão 2023

Factory

@register_model(
    field='engineering/control',
    tags=('control', 'orbits', 'celestial mechanics', 'ode'),
    references=('Dilão et al. 2022', 'Dilão 2023'),
    defaults={'until': 20.0, 'dt': 0.05, 'solver': RK4Solver(dt=0.005)},
    observables=lambda system: [ObservableSpec('energy', _energy_of_satellite)],
)
def satellite_transfer(
    s: float = 0.314,
    velocity: float = 0.0,
    theta: float = 0.0,
    *,
    Lz: float = 0.7,
    epsilon: float = 0.1,
    H_final: float = -0.2,
) -> System:
    """Return a satellite moved between Kepler orbits of equal angular momentum by radial thrust.

    In normalised polar coordinates the satellite follows `s'' = L_z^2 / s^3 - 1 / s^2 - ε sigma` and
    `θ' = L_z / s^2`. Radial thrust keeps the angular momentum and changes the effective energy
    `H = s'^2 / 2 + L_z^2 / (2 s^2) - 1 / s` at the rate `-ε sigma s'`, so the controller pushes inwards, `sigma = 1`,
    while the satellite falls and outwards, `sigma = -1`, while it rises, until `H` reaches the final energy. From the
    periapsis of the orbit with `H = -0.7`, the transfer to `H = -0.2` takes about 9.81.

    Args:
        s:
            The initial distance to the planet.

        velocity:
            The initial radial velocity.

        theta:
            The initial angle.

        Lz:
            The angular momentum.

        epsilon:
            The thrust.

        H_final:
            The effective energy of the final orbit.

    Returns:
        system:
            The model, with the distance `satellite.s`, the radial velocity `satellite.s_d1`, the angle
            `satellite.theta`, and the observable `energy`.

    Examples:
        >>> from skcomplex.models.engineering.control import satellite_transfer
        >>> satellite_transfer().elements[0].Lz
        0.7
    """
    satellite = SatelliteElement(
        'satellite',
        s=s,
        s_d1=velocity,
        theta=theta,
        Lz=Lz,
        epsilon=epsilon,
        H_final=H_final,
    )
    return System(elements=[satellite])

Components

class SatelliteElement(Element):
    """Satellite on a planar Kepler orbit whose radial thrust moves it to an orbit of a final energy."""

    def __init__(
        self,
        label: str,
        visual: VisualSpec | None = None,
        *,
        s: Annotated[object, StateSpec(domain=RealSet.open(0, oo), order=2)] = 0.314,
        s_d1: float = 0.0,
        theta: Annotated[object, StateSpec()] = 0.0,
        Lz: Annotated[object, ParameterSpec(domain=RealSet.open(0, oo))] = 0.7,
        epsilon: Annotated[object, ParameterSpec(domain=RealSet.closed_open(0, oo))] = 0.1,
        H_final: Annotated[object, ParameterSpec()] = -0.2,
    ) -> None:
        super().__init__(label, visual=visual)
        self.s = s
        self.s_d1 = s_d1
        self.theta = theta
        self.Lz = Lz
        self.epsilon = epsilon
        self.H_final = H_final

    def _get_laws(self, s: SimpleNamespace) -> list[BaseLaw]:
        """Return the radial rate under thrust and the rate of the angle."""
        return [
            RateLaw(s.s, s.Lz**2 / s.s**3 - 1 / s.s**2 - s.epsilon * SatelliteElement._thrust_direction(s)),
            RateLaw(s.theta, s.Lz / s.s**2),
        ]

    @staticmethod
    def _satellite_energy(s: SimpleNamespace) -> Expression:
        """Return the total effective energy of the satellite, `s'^2 / 2 + L_z^2 / (2 s^2) - 1 / s`."""
        return derivative(s.s, 1) ** 2 / 2 + s.Lz**2 / (2 * s.s**2) - 1 / s.s

    @staticmethod
    def _thrust_direction(s: SimpleNamespace) -> Expression:
        """Return the thrust `sigma`: inwards while the satellite falls, outwards while it rises, to the end energy."""
        return where(SatelliteElement._satellite_energy(s) < s.H_final, where(derivative(s.s, 1) < 0, 1, -1), 0)

Equations

satellite.s: diff(s_satellite(t), t, t) == -epsilon_satellite*where(1/2*diff(s_satellite(t), t)^2 + 1/2*Lz_satellite^2/s_satellite(t)^2 - 1/s_satellite(t) < H_final_satellite, where(diff(s_satellite(t), t) < 0, 1, -1), 0) + Lz_satellite^2/s_satellite(t)^3 - 1/s_satellite(t)^2
satellite.theta: diff(theta_satellite(t), t) == Lz_satellite/s_satellite(t)^2