std.astro — Bahnmechanik, Lage und Guidance

<WRAP tip> Seit PR #1890 (2026-08-30) auf develop. Fünf Units unter std/astro/, ausgeliefert nach /usr/include/lyx/units/std/astro/ (.lyx und .lyu synchron), abgesichert durch 189 Prüfungen in tests/astro_{orbit,insertion,entry,launch}_test.lyx. Alle Beispiele dieser Seiten sind mit lyxc 1.1.15B gebaut und ausgeführt. </WRAP>

→ Für den atmosphärischen Flug (Standardatmosphäre, Flugleistung, Flugführung): std.aero.

std.astro ist die Schicht oberhalb der Vektor- und Quaternionen-Algebra: Bahnenergie, Transfermanöver, Einfang, Atmosphäreneintritt, Startauslegung. Der Zuschnitt entstand als Issue #1900 und ist mit PR #1890 umgesetzt.

Vollständig seit 2026-08-31: Lageregelung und Guidance sind mit std.astro.attitude und std.astro.guidance dazugekommen (#1900, 83 Prüfungen). Der Weg über std.quat und std.matrix in Abschnitt 3 bleibt als Hintergrund stehen — gebraucht wird er dafür nicht mehr.

Kurs und Lage im 3D-Raum · std.quat · std.matrix · Mathematik-Units


1. Die fünf Units

Unit Inhalt Seite
std.astro.state Zustandsvektoren, Vektorrechnung, Rahmenkennungen, Frenet-Dreibein, NaN als Fehlerwert std.astro.state
std.astro.orbit Vis-Viva, Energie, Halbachse, Umlaufzeit, Bahnelemente aus einem Zustand, Hohmann, Fluchtbahnen, Ebenenänderung std.astro.orbit
std.astro.insertion Einfang, Zirkularisieren, Perizentrum anheben, Deorbit, Korrekturschübe aus zwei Zuständen std.astro.insertion
std.astro.entry Atmosphären (Erde, Mars, Venus, Titan), Allen-Eggers, Sutton-Graves, Eintrittskorridor std.astro.entry
std.astro.launch Ziolkowski, Stufung, Schub-Gewicht, Aufstiegsverluste, Startazimut und Inklination std.astro.launch
std.astro.attitude Lagefehler (Quaternion, Winkel, Achse, Fehlervektor), Winkelgeschwindigkeit, Propagation, Schwenkzeiten und Ratengrenzen std.astro.attitude
std.astro.guidance Abstand, Annäherungsrate, dichteste Annäherung, Quer- und Längsabweichung, Restflugzeit, Sichtlinienrate, Proportional Navigation std.astro.guidance
std.astro.groundtrack Bodenspur: Greenwich-Stundenwinkel, ECI↔ECEF, Unterpunkt in Breite, Länge und Höhe std.astro.groundtrack
std.astro.cr3bp eingeschränktes Dreikörperproblem: die fünf Librationspunkte, Hill-Radius, Jacobi-Konstante std.astro.cr3bp

Dazu vier Datenseiten ohne eigene Unit:

  • Startplätze — Breiten der aktiven Rampen (SpaceX-Rampen, Kourou, Baikonur, Andøya, die asiatischen Plätze, Wostotschny, Plessezk) mit Wikidata-Quelle, dazu Drehgewinn, Aufstiegs-dv und Ebenenstrafe je Platz.
  • Trägerraketen — fünf Referenzsysteme (Spectrum, Falcon 9, Ariane 64, Angara A5, Long March 5) mit gerechnetem Schub-Gewicht-Verhältnis, Nutzlastanteil, Austrittsgeschwindigkeit und Brenndauer.
  • Orbit-Matrix — welcher Träger von welcher Rampe welchen Orbit erreicht: dv-Bedarf für 16 Rampen und sechs Zielbahnen (darunter ISS, Tiangong, Hubble) sowie die modellierte Nutzlast je Träger und Ziel.
  • Bahntypen — Katalog der Umlaufbahnen (LEO, polar, SSO, MEO, GTO, GEO, Molniya, Tundra) mit gerechneten Kennwerten und den Bahnfamilien, die ohne J2 nicht auslegbar sind.

Die Abhängigkeiten laufen nur nach unten: state steht allein, orbit baut darauf, insertion/entry/launch auf beiden; attitude und guidance brauchen nur state (und std.quat). groundtrack und cr3bp stehen daneben: die eine bringt eine Zeitachse ins Spiel, die andere verlässt das Zweikörperproblem.

1b. Zwei Entwurfsentscheidungen, die überall durchgehalten sind

NaN statt Ersatzwert. Jede Funktion gibt bei unmöglicher Eingabe AError() zurück, geprüft mit AIsError. Bei Bahnrechnungen sehen 0 und 1 beide wie gültige Ergebnisse aus, und ein stiller Ersatzwert wandert unbemerkt in die nächste Rechnung. Beispiele, an denen das trägt: AHyperbolicExcess unterhalb der Fluchtgeschwindigkeit (0 sähe aus wie eine gerade erreichte Flucht), ARaan bei äquatorialer Bahn (0 wäre ein gültiger Winkel), AUnitVec beim Nullvektor, ATrueAnomaly auf der Kreisbahn.

SI durchgehend, Meter und Sekunden, Winkel in Radiant. Die Units mischen nirgends — ein Zahlenwert ohne Einheit ist in der Bahnmechanik wertlos.

 
Freie Funktionen, keine Methoden. So ist es umgesetzt — der Rechenkern steht als freie Funktion mit dem Zustand als Parameter. Zwei Gründe:

* Sachlich: viele Größen brauchen zwei Zustände (Annäherung, Relativbewegung) oder Zustand plus Zielbahn — als Methode an einem der beiden wäre die Zuordnung willkürlich.
* Technisch: eine Methode mit f64-Rückgabe liefert derzeit das Bitmuster statt des Werts, sobald das Ergebnis in eine Variable oder einen Ausdruck geht (#1887). Genau daran ist der erste Entwurf gescheitert: Halbachse() lieferte 0.0 statt 6678000.0, während Energie() richtig rechnete. Der Quelltext hält ausdrücklich fest, dass der Zuschnitt auch nach einem Fix richtig bleibt — die sachliche Begründung ist von dem Fehler unabhängig.

2. Beispiel: Bahnmechanik von Hand (ohne die Units)

Das folgende Programm rechnet dieselben Größen ohne std.astro — es zeigt, was die Units abnehmen, und dient als Gegenprobe zu ihren Ergebnissen.

import std.quat;
import std.matrix;
import std.math;
import std.io;

type Zustand = struct { pos: Vec3; vel: Vec3; lage: Quat; t_ns: int64; }

con MU_ERDE: f64 := 398600441800000.0;   // m^3/s^2

fn Energie(z: Zustand, mu: f64): f64 {
    return Vec3LengthSq(z.vel) / 2.0 - mu / Vec3Length(z.pos);
}
fn Halbachse(z: Zustand, mu: f64): f64 { return 0.0 - mu / (2.0 * Energie(z, mu)); }
fn Umlaufzeit(a: f64, mu: f64): f64 {
    return 2.0 * 3.14159265358979 * SqrtF64(a * a * a / mu);
}
fn VisViva(mu: f64, r: f64, a: f64): f64 { return SqrtF64(mu * (2.0 / r - 1.0 / a)); }

fn HohmannDeltaV(mu: f64, r1: f64, r2: f64): f64 {
    var at: f64 := (r1 + r2) / 2.0;
    return AbsF64(VisViva(mu, r1, at) - SqrtF64(mu / r1))
         + AbsF64(SqrtF64(mu / r2) - VisViva(mu, r2, at));
}
fn HohmannZeit(mu: f64, r1: f64, r2: f64): f64 {
    var at: f64 := (r1 + r2) / 2.0;
    return 3.14159265358979 * SqrtF64(at * at * at / mu);
}
fn InklinationDeltaV(v: f64, winkelRad: f64): f64 {
    return 2.0 * v * SinF64(winkelRad / 2.0);
}
fn Raketengleichung(m0: f64, dv: f64, isp: f64): f64 {
    return m0 * (1.0 - ExpF64(0.0 - dv / (isp * 9.80665)));
}

fn main(): int64 {
    var leo: f64 := 6678000.0;    // 300 km Hoehe
    var geo: f64 := 42164000.0;
    var z: Zustand;
    z.pos := Vec3New(leo, 0.0, 0.0);
    z.vel := Vec3New(0.0, SqrtF64(MU_ERDE / leo), 0.0);

    Print("spez. Energie   : "); PrintLn(FloatToStr(Energie(z, MU_ERDE), 1));
    Print("grosse Halbachse: "); PrintLn(FloatToStr(Halbachse(z, MU_ERDE), 1));
    Print("Umlaufzeit  min : "); PrintLn(FloatToStr(Umlaufzeit(Halbachse(z, MU_ERDE), MU_ERDE) / 60.0, 2));
    Print("Hohmann dv  m/s : "); PrintLn(FloatToStr(HohmannDeltaV(MU_ERDE, leo, geo), 1));
    Print("Hohmann t   h   : "); PrintLn(FloatToStr(HohmannZeit(MU_ERDE, leo, geo) / 3600.0, 2));
    Print("Inkl 28.5 Grad  : "); PrintLn(FloatToStr(InklinationDeltaV(3074.7, 28.5 * 3.14159265358979 / 180.0), 1));
    Print("Treibstoff kg   : "); PrintLn(FloatToStr(Raketengleichung(1000.0, 3892.6, 320.0), 1));
    return 0;
}

spez. Energie   : -29844297.8
grosse Halbachse: 6678000.0
Umlaufzeit  min : 90.52
Hohmann dv  m/s : 3892.6
Hohmann t   h   : 5.28
Inkl 28.5 Grad  : 1513.7
Treibstoff kg   : 710.7

Jede dieser Zahlen ist gegen bekannte Werte prüfbar — und genau deshalb gehört die Bahnmechanik in eine eigene Unit: 90,5 Minuten Umlaufzeit in 300 km Höhe, 3,9 km/s für LEO→GEO, 5,3 Stunden Transferzeit, 1,5 km/s für 28,5 Grad Inklinationsänderung aus dem Geostationärorbit. Wer die Unit ändert, merkt an diesen Zahlen sofort, wenn etwas nicht mehr stimmt.


3. Beispiel: Lage und Guidance von Hand

Lageregelung und Guidance sind in std.astro noch nicht enthalten (Fehlerquaternion, CPA, Sichtlinienrate); der folgende Weg über std.quat und std.matrix bleibt dafür der aktuelle.

import std.quat;
import std.matrix;
import std.math;
import std.io;

fn Grad(rad: f64): f64 { return rad * 180.0 / 3.14159265358979; }

fn LageFehler(ist: Quat, ziel: Quat): Quat { return QuatMul(ziel, QuatInverse(ist)); }

// Zeit bis zur groessten Annaeherung:  t = -(r.v) / (v.v)
fn ZeitBisCpa(rRel: Vec3, vRel: Vec3): f64 {
    var vv: f64 := Vec3Dot(vRel, vRel);
    if (vv < 0.000000001) { return 0.0; }          // gleiche Geschwindigkeit: nie naeher
    return 0.0 - Vec3Dot(rRel, vRel) / vv;
}
fn AbstandBeiCpa(rRel: Vec3, vRel: Vec3): f64 {
    var t: f64 := ZeitBisCpa(rRel, vRel);
    if (t < 0.0) { return Vec3Length(rRel); }      // entfernt sich bereits
    return Vec3Length(Vec3Add(rRel, Vec3Scale(vRel, t)));
}
// Querabweichung von der Sollbahn durch a in Richtung d (Einheitsvektor)
fn Querabweichung(p: Vec3, a: Vec3, d: Vec3): f64 {
    var w: Vec3 := Vec3Sub(p, a);
    return Vec3Length(Vec3Sub(w, Vec3Scale(d, Vec3Dot(w, d))));
}
// Sichtlinien-Drehrate fuer Proportional Navigation:  omega = (r x v) / (r.r)
fn Sichtlinienrate(rRel: Vec3, vRel: Vec3): Vec3 {
    var rr: f64 := Vec3Dot(rRel, rRel);
    if (rr < 0.000000001) { return Vec3Zero(); }
    return Vec3Scale(Vec3Cross(rRel, vRel), 1.0 / rr);
}

fn main(): int64 {
    var ist:  Quat := QuatIdentity();
    var ziel: Quat := QuatFromAxisAngle(0.0, 0.0, 1.0, 30.0 * 3.14159265358979 / 180.0);
    Print("Lagefehler Grad   : "); PrintLn(FloatToStr(Grad(QuatAngle(LageFehler(ist, ziel))), 2));

    // Objekt 1000 m voraus, 50 m seitlich versetzt, kommt mit 10 m/s entgegen
    var rRel: Vec3 := Vec3New(1000.0, 50.0, 0.0);
    var vRel: Vec3 := Vec3New(0.0 - 10.0, 0.0, 0.0);
    Print("Zeit bis CPA  s   : "); PrintLn(FloatToStr(ZeitBisCpa(rRel, vRel), 1));
    Print("Abstand bei CPA m : "); PrintLn(FloatToStr(AbstandBeiCpa(rRel, vRel), 1));

    Print("Querabweichung  m : ");
    PrintLn(FloatToStr(Querabweichung(Vec3New(500.0, 30.0, 0.0), Vec3Zero(), Vec3UnitX()), 1));

    var w: Vec3 := Sichtlinienrate(rRel, vRel);
    Print("Sichtlinienrate z : "); PrintLn(FloatToStr(w.z, 6));
    return 0;
}

Lagefehler Grad   : 30.00
Zeit bis CPA  s   : 100.0
Abstand bei CPA m : 50.0
Querabweichung  m : 30.0
Sichtlinienrate z : 0.000499

Auch hier ist jedes Ergebnis von Hand nachrechenbar: 1000 m Vorsprung bei 10 m/s Annäherung sind 100 Sekunden, und der seitliche Versatz von 50 m bleibt — das ist der Abstand bei größter Annäherung. Die Sichtlinienrate ist die Größe, auf die Proportional Navigation regelt: bleibt sie null, ist der Kurs ein Kollisionskurs.


4. Was beim Bau zu beachten ist

  • Bezugsrahmen in den Namen. pos_eci gegen pos_ecef — der Compiler kann die beiden nicht unterscheiden, weder über dim/utype (beide sind Längen) noch über eigene Structs (#1886: Struct-Typen werden nicht geprüft). Siehe Kurs und Lage im 3D-Raum, Abschnitt 1.
  • acos klemmen. Vec3Dot zweier Einheitsvektoren kann 1.0000000002 ergeben; AcosF64 liefert daraus NaN, und NaN wandert stumm durch die ganze Rechnung.
  • Division durch fast null abfangen. Zwei Objekte mit derselben Geschwindigkeit haben keine Annäherungszeit; ohne Prüfung kommt Unendlich heraus statt einer Aussage.
  • Quaternionen normalisieren. Nach jeder Verkettung; QuatIsUnit als Prüfung.
  • SI-Einheiten durchgängig, Meter und Sekunden. Kilometer sind der klassische Weg zu einem Faktor 1000 an der falschen Stelle.

Letzte Aktualisierung: 2026-09-01 — std.astro.attitude und std.astro.guidance aufgenommen; die Lücke „Lageregelung und Guidance fehlen„ ist damit geschlossen.

Zuvor 2026-08-31 — Datenseite Startplätze aufgenommen (SpaceX-Rampen mit Wikidata-Quellen, alle Kennzahlen gerechnet).

Zuvor 2026-08-31 — Seite auf den ausgelieferten Stand gebracht (PR #1890): fünf Unit-Seiten verlinkt, Zuschnitt-Tabelle durch die tatsächlichen Units ersetzt.

Zuvor 2026-08-31 — Seite neu angelegt, als der Zuschnitt noch ein Vorschlag war.