astra.maneuver module

ASTRA Core Maneuver Modeling — Frame Transformations & Thrust Application. Implements the mathematics required to convert spacecraft-centric thrust vectors (VNB / RTN) into the inertial frame and to validate maneuver definitions before integration. Frame definitions (all right-handed orthonormal triads):

VNB (Velocity, Normal, Binormal):
    V̂ = v / |v|
    N̂ = (r × v) / |r × v|
    B̂ = V̂ × N̂
RTN (Radial, Transverse, Normal):
    R̂ = r / |r|
    N̂ = (r × v) / |r × v|
    T̂ = N̂ × R̂

References - Vallado, D. A. (2013). Fundamentals of Astrodynamics and Applications, §4.7. - Schaub & Junkins (2018). Analytical Mechanics of Space Systems, §14.2.

astra.maneuver.rotation_vnb_to_inertial(r_eci, v_eci)[source]

Build the 3×3 rotation matrix whose rows are the VNB unit vectors in ECI. Convention (important): _build_vnb_matrix_njit stores the VNB basis vectors as rows:

T[0, :] = V̂ (velocity unit vector) T[1, :] = N̂ (orbit-normal unit vector) T[2, :] = B̂ (binormal unit vector)

This makes T an VNB→ECI rotation matrix, so:

a_inertial = T @ a_vnb # VNB direction → ECI components

Parameters:
  • r_eci – Shape (3,) inertial position [km].

  • v_eci – Shape (3,) inertial velocity [km/s].

Returns:

Shape (3, 3) matrix whose columns are [V̂, N̂, B̂] in ECI (i.e. VNB→ECI).

Raises:

ManeuverError – If velocity magnitude < 1e-12 km/s or if r ∥ v (angular momentum near-zero), making the VNB frame undefined.

Example::

T = rotation_vnb_to_inertial(r_eci, v_eci) # Prograde burn of 10 m/s: dv_eci = T @ np.array([0.01, 0.0, 0.0]) # V̂ direction → ECI

astra.maneuver.rotation_rtn_to_inertial(r_eci, v_eci)[source]

Build the 3×3 rotation matrix from RTN to inertial (ECI/TEME). Columns of the returned matrix are the RTN unit vectors expressed in the inertial frame:

T = [R̂ | T̂ | N̂]

so that a_inertial = T @ a_rtn. :param r_eci: Shape (3,) inertial position [km]. :param v_eci: Shape (3,) inertial velocity [km/s].

Returns:

Shape (3, 3) rotation matrix.

Raises:

ManeuverError – If position magnitude is degenerate (< 1e-12 km), making the frame undefined.

astra.maneuver.frame_to_inertial(r_eci, v_eci, frame)[source]

Return the appropriate frame-to-inertial rotation matrix. Convenience dispatcher that selects VNB or RTN based on the ManeuverFrame enum value. :param r_eci: Shape (3,) inertial position [km]. :param v_eci: Shape (3,) inertial velocity [km/s]. :param frame: ManeuverFrame.VNB or ManeuverFrame.RTN.

Returns:

Shape (3, 3) rotation matrix T such that a_inertial = T @ a_frame.

astra.maneuver.thrust_acceleration_inertial(r_eci, v_eci, mass_kg, burn)[source]

Compute the inertial thrust acceleration vector at a single instant. This function is called at every Runge-Kutta sub-step during a powered arc. It re-computes the frame transformation matrix from the instantaneous position and velocity, ensuring that dynamically steered burns (e.g. gravity-turn, velocity-aligned orbit-raise) perfectly track the commanded attitude. :param r_eci: Shape (3,) inertial position [km]. :param v_eci: Shape (3,) inertial velocity [km/s]. :param mass_kg: Instantaneous spacecraft mass [kg] (must be > 0). :param burn: Active FiniteBurn definition.

Returns:

Shape (3,) inertial acceleration [km/s²].

Raises:

ManeuverError – If mass is non-positive (propellant exhausted).

astra.maneuver.validate_burn(burn, initial_mass_kg)[source]

Pre-flight validation of a FiniteBurn definition. Checks physical consistency before handing the burn off to the integrator. Raises ManeuverError on the first detected issue. Checks performed:

  1. Duration is strictly positive.

  2. Thrust is strictly positive.

  3. Specific impulse is strictly positive.

  4. Direction vector has unit magnitude (within 1e-6 tolerance).

  5. Total propellant consumed does not exceed available mass.

Parameters:
  • burn – The FiniteBurn to validate.

  • initial_mass_kg – Spacecraft wet mass at ignition [kg].

Raises:

ManeuverError – Descriptive error on validation failure.

astra.maneuver.validate_burn_sequence(burns)[source]

Ensure that a list of FiniteBurn objects does not contain temporal overlaps. This function detects unphysical “dual-thrust” arcs (remediates PHY-F/SE-G). It assumes the burns list is already sorted by ignition time. :param burns: Sorted list of FiniteBurn objects.

Raises:

ManeuverError – If an overlap is detected between any two burns.

astra.maneuver.plan_hohmann(r_initial_km, r_target_km, isp_s, mass_kg, thrust_N, t_ignition_jd, frame=ManeuverFrame.VNB)[source]

Plan a two-burn Hohmann transfer between two circular orbits. Computes the two impulsive delta-V maneuvers required for a classical Hohmann transfer, then converts each impulsive delta-V to a finite-burn arc using the Tsiolkovsky rocket equation and the specified engine parameters. For raising transfers, both burns are prograde. For lowering transfers, both burns are retrograde because the transfer ellipse is slower than the initial circular orbit at apoapsis and faster than the final circular orbit at periapsis. Assumptions:

  • Both initial and target orbits are circular.

  • Earth’s gravitational parameter μ = 398600.4418 km³/s².

  • The transfer uses instantaneous acceleration (finite-burn duration is computed from thrust and Isp but the impulsive ΔV is exact).

  • Coasting time on the transfer arc (half the ellipse period) is computed and used to schedule the second burn epoch.

Parameters:
  • r_initial_km – Geocentric radius of the initial circular orbit (km). This is altitude + EARTH_EQUATORIAL_RADIUS_KM.

  • r_target_km – Geocentric radius of the target circular orbit (km).

  • isp_s – Engine specific impulse (seconds).

  • mass_kg – Spacecraft mass at the start of the transfer (kg).

  • thrust_N – Engine thrust in Newtons.

  • t_ignition_jd – Julian Date of the first burn ignition.

  • frame – ManeuverFrame for thrust direction (default VNB).

Returns:

List of two FiniteBurn objects — [burn_1, burn_2].

Raises:

ManeuverError – If the orbit radii are non-positive, target equals initial (null transfer), thrust or Isp are non-positive, or the spacecraft runs out of propellant before completing the transfer.

Example::

import astra, math from astra.constants import EARTH_EQUATORIAL_RADIUS_KM as Re burns = astra.plan_hohmann(

r_initial_km = Re + 400.0, # 400 km LEO r_target_km = Re + 600.0, # 600 km target isp_s = 300.0, mass_kg = 1000.0, thrust_N = 10.0, t_ignition_jd= 2460000.5,

) traj = astra.propagate_cowell(state, maneuvers=burns, …)

astra.maneuver.plan_bielliptic(r_initial_km, r_target_km, r_intermediate_km, isp_s, mass_kg, thrust_N, t_ignition_jd, frame=ManeuverFrame.VNB)[source]

Plan a three-burn bi-elliptic transfer between two circular orbits.

A bi-elliptic transfer is more efficient than Hohmann when the ratio r_target / r_initial > 11.94 (Vallado §6.3.2). It uses three burns:

  1. Burn 1: At initial orbit, raise apoapsis to r_intermediate.

  2. Burn 2: At r_intermediate, adjust periapsis to r_target.

  3. Burn 3: At r_target, circularize.

Assumptions:
  • Initial and target orbits are circular.

  • Intermediate radius must exceed both r_initial and r_target.

  • Earth’s gravitational parameter μ = 398600.4418 km³/s².

Parameters:
  • r_initial_km – Geocentric radius of the initial circular orbit (km).

  • r_target_km – Geocentric radius of the target circular orbit (km).

  • r_intermediate_km – Geocentric radius of the intermediate apoapsis (km). Must be ≥ max(r_initial_km, r_target_km).

  • isp_s – Engine specific impulse (seconds).

  • mass_kg – Spacecraft mass at the start of the transfer (kg).

  • thrust_N – Engine thrust in Newtons.

  • t_ignition_jd – Julian Date of the first burn ignition.

  • frame – ManeuverFrame for thrust direction (default VNB).

Returns:

List of three FiniteBurn objects — [burn_1, burn_2, burn_3].

Raises:

ManeuverError – If radii are invalid, intermediate is too small, or the spacecraft runs out of propellant.

Example:

import astra
from astra.constants import EARTH_EQUATORIAL_RADIUS_KM as Re
burns = astra.plan_bielliptic(
    r_initial_km     = Re + 300.0,
    r_target_km      = Re + 35786.0,  # GEO
    r_intermediate_km= Re + 100000.0, # high intermediate
    isp_s            = 300.0,
    mass_kg          = 2000.0,
    thrust_N         = 50.0,
    t_ignition_jd    = 2460000.5,
)
astra.maneuver.plan_inclination_change(r_km, delta_inc_deg, isp_s, mass_kg, thrust_N, t_ignition_jd, frame=ManeuverFrame.VNB)[source]

Plan a single-burn inclination change at the ascending/descending node.

Uses the exact plane-change ΔV formula for circular orbits:

ΔV = 2 · v_circular · sin(Δi / 2)

where v_circular = √(μ / r) is the orbital velocity at radius r.

The burn is applied normal to the orbital plane (along the N-axis in both VNB and RTN frames). Positive delta_inc_deg increases inclination (burns at ascending node); negative decreases it (burns at descending node).

Parameters:
  • r_km – Geocentric orbit radius (km). Must be positive.

  • delta_inc_deg – Desired inclination change (degrees). Can be negative.

  • isp_s – Engine specific impulse (seconds).

  • mass_kg – Spacecraft mass at burn start (kg).

  • thrust_N – Engine thrust in Newtons.

  • t_ignition_jd – Julian Date of burn ignition (should be at the ascending or descending node for optimal efficiency).

  • frame – ManeuverFrame for thrust direction (default VNB).

Returns:

List containing one FiniteBurn with thrust along the normal axis.

Raises:

ManeuverError – If radius, thrust, or Isp are non-positive, or if delta_inc_deg is zero, or if the burn requires more propellant than available.

Example:

import astra
from astra.constants import EARTH_EQUATORIAL_RADIUS_KM as Re
burns = astra.plan_inclination_change(
    r_km          = Re + 35786.0,  # GEO radius
    delta_inc_deg = -0.5,          # reduce inclination by 0.5°
    isp_s         = 300.0,
    mass_kg       = 3000.0,
    thrust_N      = 22.0,
    t_ignition_jd = 2460000.5,
)
class astra.maneuver.DeltaVBudget(total_delta_v_m_s, total_propellant_kg, final_mass_kg, burns)[source]

Bases: object

Pre-propagation ΔV budget summary for a sequence of finite burns.

Provides the total ΔV, total propellant consumption, final mass, and per-burn breakdown without requiring a full numerical propagation.

total_delta_v_m_s

Total ΔV across all burns (m/s).

Type:

float

total_propellant_kg

Total propellant consumed (kg).

Type:

float

final_mass_kg

Spacecraft mass after all burns (kg).

Type:

float

burns

Per-burn breakdown as a list of dicts, each containing: index, delta_v_m_s, propellant_kg, mass_before_kg, mass_after_kg, duration_s, epoch_ignition_jd.

Type:

list[dict]

total_delta_v_m_s
total_propellant_kg
final_mass_kg
burns
astra.maneuver.compute_delta_v_budget(burns, initial_mass_kg)[source]

Compute a pre-propagation ΔV and propellant budget for a burn sequence.

Uses the Tsiolkovsky rocket equation sequentially on each burn to compute ΔV, propellant consumed, and remaining mass — without running a full numerical propagation.

The ΔV for each burn is computed as:

ΔV = Isp · g₀ · ln(m_before / m_after)

where m_after = m_before - F · duration / (Isp · g₀).

Parameters:
  • burns – Ordered list of FiniteBurn objects. Burns are processed sequentially; mass depleted by burn N reduces the starting mass for burn N+1.

  • initial_mass_kg – Spacecraft mass before the first burn (kg).

Returns:

DeltaVBudget with total ΔV, propellant, final mass, and per-burn breakdown.

Raises:

ManeuverError – If initial_mass_kg is non-positive, if burns is empty, or if the spacecraft runs out of mass mid-sequence.

Example:

import astra
burns = astra.plan_hohmann(
    r_initial_km=6778.0, r_target_km=7178.0,
    isp_s=300.0, mass_kg=1000.0, thrust_N=10.0,
    t_ignition_jd=2460000.5,
)
budget = astra.compute_delta_v_budget(burns, initial_mass_kg=1000.0)
print(f"Total ΔV: {budget.total_delta_v_m_s:.1f} m/s")
print(f"Propellant: {budget.total_propellant_kg:.2f} kg")
print(f"Final mass: {budget.final_mass_kg:.2f} kg")