Python Lab Reference
Every library, method, and pattern you need for the Orbital Mechanics labs.
65 entries across 8 libraries
🔢NumPy
The numerical workhorse. Every lab uses it for vectors, trigonometry and roots. Angles in NumPy are always in radians — convert deliberately.
import numpy as npCreating & shaping arrays
np.array→ ndarrayBuild an array (a vector or matrix) from a Python list.
r = np.array([7000.0, 0.0, 0.0]) # position vector, km
v = np.array([0.0, 7.5, 1.0]) # velocity vector, km/snp.concatenate→ ndarrayJoin arrays end-to-end — the standard way to pack a state vector.
y0 = np.concatenate([r0, v0]) # 6-element state [x,y,z, vx,vy,vz]np.zeros→ ndarrayAn array of zeros — handy for pre-allocating results.
accel = np.zeros(3) # [0., 0., 0.]np.linspace→ ndarraynum evenly spaced values from start to stop (both ends included).
times = np.linspace(0, T, 500) # 500 sample times over one periodPowers, roots & exponentials
np.sqrt→ float or ndarraySquare root. Works on scalars and whole arrays.
v = np.sqrt(mu * (2/r - 1/a)) # vis-viva speednp.piThe constant π (≈ 3.14159). Not a function — no parentheses.
T = 2 * np.pi * np.sqrt(a**3 / mu) # orbital period** (power)Python’s exponent operator — use it, not np.power, for readability.
a_cubed = a ** 3np.exp / np.loge^x and the natural logarithm (base e).
rho = rho0 * np.exp(-h / H) # exponential atmosphere densityTrigonometry & angles
np.radians / np.degreesConvert between degrees and radians. Your bridge to every trig call.
theta = np.radians(45) # 0.785...
deg = np.degrees(theta) # back to 45.0np.sin / np.cos / np.tan→ float or ndarrayStandard trig functions.
x = r * np.cos(nu)
y = r * np.sin(nu) # perifocal position from true anomalynp.arctan2→ radians in (−π, π]Angle of the vector (x, y), correct in all four quadrants. Prefer over arctan.
Plain np.arctan(y/x) loses the quadrant and blows up when x=0. arctan2 takes y and x separately and returns the true angle in (−π, π].
lon = np.degrees(np.arctan2(r[1], r[0]))np.arccos / np.arcsin / np.arctanInverse trig — return radians.
inc = np.degrees(np.arccos(h[2] / np.linalg.norm(h))) # inclinationVectors & linear algebra
np.linalg.norm→ floatMagnitude (length) of a vector — the single most-used call in these labs.
r_mag = np.linalg.norm(r) # distance from Earth centre
speed = np.linalg.norm(v) # scalar speed from velocity vectornp.dot→ floatDot product — projects one vector onto another; zero means perpendicular.
radial_speed = np.dot(r, v) / np.linalg.norm(r)np.cross→ ndarray (3-vector)Cross product — gives a vector perpendicular to both. The gateway to orbital elements.
The specific angular momentum h = r × v defines the orbital plane. Its direction gives inclination and node; its magnitude gives the orbit’s shape.
h = np.cross(r, v) # angular momentum vector
h_mag = np.linalg.norm(h)📐Astropy Units
poliastro speaks in *quantities* — a number glued to a physical unit. This kills “km vs m” bugs, but you must attach units on the way in and strip them on the way out.
from astropy import units as uAttaching units
value * u.<unit>→ QuantityMultiply a number by a unit to make a Quantity.
a = 7000 * u.km
inc = 51.6 * u.deg
dt = 90 * u.minCommon unitsThe units the labs use most.
u.km u.m # length
u.s u.min u.hour # time
u.deg u.rad # angle
u.kg # mass
u.one # dimensionless (eccentricity!)Compound units→ QuantityBuild derived units by dividing/multiplying.
speed = 7.5 * u.km / u.s
mu = 398600 * u.km**3 / u.s**2Converting & extracting
.to(unit)→ Quantity (in the new unit)Convert a Quantity to different units. Errors loudly if units are incompatible.
v_kms = np.linalg.norm(orb.v).to(u.km / u.s)
T_hr = orb.period.to(u.hour).value→ float or ndarrayStrip the unit and get the raw float/array back.
e = orb.ecc.value # 0.0006 as a plain float
print(f"e = {e:.4f}").to_value(unit)→ float or ndarrayConvert *and* strip in one step — .to(unit).value combined.
r_km = orb.r_p.to_value(u.km) # plain number in km🌍Astropy Constants
Physical constants that already carry units and cited values — no more copy-pasting 398600.
from astropy.constants import GM_earth, R_earthThe constants you’ll use
GM_earthEarth’s gravitational parameter μ = G·M (≈ 3.986×10¹⁴ m³/s²).
mu = GM_earth.to(u.km**3 / u.s**2) # convert to km-based units
mu = GM_earth.value # raw number (SI, m³/s²)R_earthEarth’s equatorial radius (≈ 6378 km).
altitude = orb.r_p.to(u.km) - R_earth.to(u.km).value / .to(...)Constants are Quantities — same rules as astropy units apply.
print(GM_earth) # 3.986e+14 m3 / s2 (value + unit + reference)
print(GM_earth.value) # 3.986004418e+14🛰️poliastro
The high-level orbit toolkit. An Orbit object bundles the six classical elements and lets you read any derived property, propagate in time, and apply maneuvers — all unit-safe.
from poliastro.bodies import Earth
from poliastro.twobody.orbit import Orbit
from poliastro.maneuver import Maneuver
from poliastro.iod import izzoCreating an orbit
Orbit.from_classical→ OrbitBuild an orbit from the six classical orbital elements (COEs).
Arguments in order: semi-major axis a, eccentricity ecc, inclination inc, right ascension of ascending node (RAAN), argument of periapsis argp, true anomaly nu. Every argument is a Quantity.
orb = Orbit.from_classical(
Earth,
7000 * u.km, # a
0.01 * u.one, # ecc
51.6 * u.deg, # inc
0 * u.deg, # raan
0 * u.deg, # argp
0 * u.deg, # nu (true anomaly)
)Orbit.circular→ OrbitShortcut for a circular orbit at a given altitude above the surface.
iss = Orbit.circular(Earth, alt=420 * u.km, inc=51.6 * u.deg)Orbit.from_vectors→ OrbitBuild an orbit directly from position and velocity vectors.
r = [7000, 0, 0] * u.km
v = [0, 7.5, 1] * u.km / u.s
orb = Orbit.from_vectors(Earth, r, v)Reading orbit properties
orb.r · orb.v→ Quantity vectorsCurrent position and velocity vectors (as Quantities).
speed = np.linalg.norm(orb.v).to(u.km / u.s)orb.a · orb.ecc · orb.incThe classical elements: semi-major axis, eccentricity, inclination.
print(f"a = {orb.a:.1f}, e = {orb.ecc.value:.4f}")orb.r_p · orb.r_a→ QuantityPeriapsis and apoapsis radius (closest / farthest distance from centre).
print(f"perigee {orb.r_p.to(u.km):.0f}, apogee {orb.r_a.to(u.km):.0f}")orb.period (orb.T)→ Quantity (time)Time to complete one full orbit.
print(f"period = {orb.period.to(u.min):.1f}")orb.energy→ QuantitySpecific orbital energy — negative for bound orbits, zero at escape.
print(f"energy = {orb.energy:.3f}")Propagating & maneuvering
orb.propagate→ Orbit (a new one — the original is unchanged)Advance the orbit forward (or back) in time; returns a new Orbit.
later = orb.propagate(45 * u.min)
print(later.nu.to(u.deg)) # where the satellite is nowManeuver.hohmann→ ManeuverBuild the classic two-burn Hohmann transfer to a target circular radius.
man = Maneuver.hohmann(orb, 42164 * u.km) # LEO → GEO
print(man.get_total_cost().to(u.km / u.s)) # total Δv
print(man.get_total_time().to(u.hour)) # transfer timeManeuver.impulse→ ManeuverA single instantaneous velocity change (Δv burn).
burn = Maneuver.impulse([0, 0.1, 0] * u.km / u.s)orb.apply_maneuver→ OrbitApply a maneuver and get the resulting orbit.
geo = orb.apply_maneuver(man)izzo.lambert→ (v1, v2) velocity QuantitiesSolve Lambert’s problem: the two velocities that fly from r1 to r2 in a given time.
k is the gravitational parameter (Earth.k), r1/r2 are position vectors, tof is time of flight. Returns the departure and arrival velocity vectors.
v1, v2 = izzo.lambert(Earth.k, r1, r2, tof)∫SciPy · solve_ivp
The numerical integrator behind “propagate it yourself” labs. You write the equations of motion as a function; solve_ivp marches them through time.
from scipy.integrate import solve_ivpIntegrating equations of motion
solve_ivp→ a solution object (see below)Integrate dy/dt = fun(t, y) from t_span[0] to t_span[1], starting at y0.
fun(t, y) must return the derivative of the state. For two-body motion the state is [x,y,z, vx,vy,vz] and its derivative is [vx,vy,vz, ax,ay,az].
def two_body(t, y):
r = y[:3]
v = y[3:]
a = -mu * r / np.linalg.norm(r)**3
return np.concatenate([v, a])
sol = solve_ivp(two_body, [0, T], y0,
t_eval=times, rtol=1e-10, atol=1e-10)sol.t · sol.yThe results: sol.t is the array of times, sol.y is the state at each time.
x = sol.y[0] # x-position over time
vx = sol.y[3] # x-velocity over time
final = sol.y[:, -1] # full state at the last instantt_evalThe specific times you want the answer sampled at (for smooth plots).
t_eval = np.linspace(0, T, 1000)rtol / atolRelative and absolute error tolerances — smaller = more accurate but slower.
rtol=1e-10, atol=1e-10 # tight, good for orbit energy conservation📡SGP4 & Skyfield
Real-satellite tracking from TLEs (Two-Line Element sets). sgp4 is the low-level propagator; skyfield wraps it with friendly time, coordinate and ground-track handling.
from sgp4.api import Satrec
from skyfield.api import load, EarthSatellite, wgs84sgp4 — raw TLE propagation
Satrec.twoline2rv→ SatrecParse a two-line element set into a propagatable satellite record.
sat = Satrec.twoline2rv(line1, line2)sat.sgp4→ (error_code, r, v) — error 0 means successPropagate to a Julian date (jd + fractional day fr); returns error, r, v.
e, r, v = sat.sgp4(jd, fr)
if e == 0:
print("position (km):", r)skyfield — friendly tracking
load.timescale→ TimescaleCreate a timescale, then build times from it.
ts = load.timescale()
t = ts.utc(2026, 7, 9, 12, 0, 0)EarthSatellite→ EarthSatelliteA satellite built from a TLE, tied to a timescale.
sat = EarthSatellite(line1, line2, "ISS", ts)sat.at(t)→ Geocentric positionThe satellite’s geocentric position at time t.
geo = sat.at(t)
print(geo.position.km) # [x, y, z] in kmwgs84.subpoint→ GeographicPosition (latitude, longitude, elevation)The point on Earth directly below the satellite — its ground track.
sp = wgs84.subpoint(sat.at(t))
print(sp.latitude.degrees, sp.longitude.degrees)🐍Python Essentials
The plain-Python patterns that show up in every lab — defining functions, printing clean output, and formatting numbers so the auto-grader sees what it expects.
f-strings & number formatting
f"..."Format string — drop a variable straight into text with {var}.
a = 7000.0
print(f"semi-major axis = {a} km"):.2f (fixed decimals)Show a number with a set number of decimal places.
print(f"speed = {v:.2f} km/s") # 7.55
print(f"e = {ecc:.4f}") # 0.0006:.3e (scientific)Scientific notation with N significant digits.
print(f"mu = {mu:.3e}") # 3.986e+05:>10 / :<10 (alignment)Pad to a width for tidy columns — right (>) or left (<) aligned.
print(f"{name:<12}{value:>8.1f}")Functions & structure
def→ whatever you `return`Define a reusable function. Indentation (4 spaces) marks the body.
def period(a, mu=398600):
return 2 * np.pi * np.sqrt(a**3 / mu)
print(period(7000))returnHand a value back to the caller and exit the function.
return v_periapsis, v_apoapsis # returns a tuple of two valuesprintWrite to stdout — this is what the auto-grader reads to check your answer.
print(f"Result: {answer:.2f}")for loopRepeat a block over a list, range, or array.
for alt in [200, 400, 800]:
orb = Orbit.circular(Earth, alt * u.km)
print(alt, orb.period.to(u.min))📖Orbital Cookbook
Recurring recipes and the traps that cost students the most time. Skim this before you start debugging — the bug is usually here.
Go-to recipes
Vis-viva (speed at any point)The single most useful orbital equation: speed from radius and semi-major axis.
v = np.sqrt(mu * (2 / r - 1 / a))Angular momentum vectorh = r × v defines the orbital plane; its z-component sets inclination.
h = np.cross(r, v)
inc = np.degrees(np.arccos(h[2] / np.linalg.norm(h)))Circular orbit speedSpeed to hold a circular orbit at radius r.
v_circ = np.sqrt(mu / r)Escape velocitySpeed needed to just barely escape from radius r.
v_esc = np.sqrt(2 * mu / r) # = sqrt(2) * v_circPeriod (Kepler’s third law)Orbital period from semi-major axis.
T = 2 * np.pi * np.sqrt(a**3 / mu)Common traps
Degrees vs radiansNumPy trig is radians; poliastro wants u.deg. Mixing them silently wrecks answers.
# WRONG: np.sin(45) → sine of 45 radians
# RIGHT: np.sin(np.radians(45))arccos domain (NaN)Rounding can push arccos input just past ±1, producing NaN.
inc = np.degrees(np.arccos(np.clip(h[2] / h_mag, -1, 1)))Forgetting .valueA Quantity won’t accept an f-string format spec like :.4f.
# ERROR: f"{orb.ecc:.4f}" (unless it happens to work)
print(f"{orb.ecc.value:.4f}") # strip the unit firstradius vs altituder is measured from Earth’s centre; altitude is above the surface.
altitude = r - R_earth.to(u.km) # subtract the radiuskm vs m consistencyPick one length unit and keep mu in matching units.
mu = 398600 # km³/s² → use km everywhere
mu = 3.986e14 # m³/s² → use m everywhere