diff --git a/CMakeLists.txt b/CMakeLists.txt index 45dc97d..3e1cde3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,6 +72,14 @@ target_link_libraries(flux_sweep PRIVATE adsc_core adsc_flags) add_executable(decay_trade src/main_decay_trade.cpp) target_link_libraries(decay_trade PRIVATE adsc_core adsc_flags) +# WP13 kit-class trade CSV emitter: regenerates generated/wp13_kit_trade.{csv,md} +# (sail/EDT/hybrid per-class A/B/C trade) and generated/wp13_classC.{csv,md} +# (controlled-reentry mission-class comparison). Reuses src/decay.cpp's +# existing sail model plus the new EDT-v1 physics core; no other physics +# change. +add_executable(kit_trade src/main_kit_trade.cpp) +target_link_libraries(kit_trade PRIVATE adsc_core adsc_flags) + # WP7 reference-metrics emitter: re-computes the pinned WP1/F1/WP2/WP3/WP4 # demo numbers as generated/reference_metrics.csv so the evidence pack quotes # committed data only (R6). Reuses existing code paths; no physics change. diff --git a/README.md b/README.md index 20c86fa..acd03a1 100644 --- a/README.md +++ b/README.md @@ -488,13 +488,20 @@ adsc-specification-v4.md retired v4.2 spec (superseded by v5; kept because v5 keep-out violations re-verified at L1/L2 [ds-v1/ds-v2]; the CW-safe-but-higher-level-unsafe case does NOT materialize for this dispersion set — measured, see `generated/wp12_ladder.md`). -- **WP13–WP15 (v5)** — not started; `adsc-specification-v5.md` defines the - kit-class trade + EDT physics, cost ranges + FoM, and the proposal package. - WP9 remains the only path to TRL 5, unchanged by v5. +- **WP13 — Kit-class trade + EDT physics** ✅ implemented (aligned-dipole + EDT-v1 band model with mandatory η(i) = |cos i|..cos²i — derivation: + `wp13-edt-derivation.md`, citations: `wp13-literature.md`; per-class + recommended-kit table `generated/wp13_kit_trade.csv`; class-C + controlled-reentry comparison `generated/wp13_classC.md`; libration T7 + explicitly **unresolved**; SL-16 EDT is a *candidate with open risks*, + not a closed recommendation). +- **WP14–WP15 (v5)** — not started; `adsc-specification-v5.md` defines the + cost ranges + FoM upgrade and the proposal package. WP9 remains the only + path to TRL 5, unchanged by v5. **Roadmap end state: WP1–WP8 complete; Phase 0 (WP10), WP11 safety -hardening, and WP12 fidelity ladder complete.** The package is regenerable -end-to-end with one command +hardening, WP12 fidelity ladder, and WP13 kit-class trade + EDT physics +complete.** The package is regenerable end-to-end with one command (`bash tools/regenerate_all.sh build`) and CI enforces byte-identity of every committed artifact on every push. @@ -528,8 +535,13 @@ handoff (range = solar max .. solar min) makes the honest structure concrete: Sail-only closes for the lighter/lower class B (tens of m²) but **not** for the heavy, high stage A (hundreds-to-thousands of m² — impractical): the honest negative that brackets open trade T1 (drag sail vs electrodynamic tether). The -EDT parametric study spans, e.g., 36.1 yr at 50 m/day down to 1.2 yr at -1500 m/day of along-track decay. (The 25-year column is the IADC guideline; +legacy EDT *parametric* study spans, e.g., 36.1 yr at 50 m/day down to 1.2 yr at +1500 m/day of along-track decay; since WP13 the EDT branch also has a *physics* +band (aligned-dipole, capped-current, mandatory η(i) = |cos i|..cos²i): the SL-16 +class works out at **2.98 .. 9.17 yr** with a 3 km / 2 A (PLACEHOLDER) tether — +an **EDT candidate with open risks** (libration T7 unresolved, plasma Ne pair +PLACEHOLDER), *not* a closed recommendation — see `generated/wp13_kit_trade.csv` +and `wp13-edt-derivation.md`. (The 25-year column is the IADC guideline; under a US FCC license the current rule is **5 years** — FCC 22-74, 2022 — which makes the class-A sail-only negative strictly harder. See the WP8 rulepacks.) diff --git a/evidence/adsc_evidence_pack.md b/evidence/adsc_evidence_pack.md index 4ccc778..3e32b15 100644 --- a/evidence/adsc_evidence_pack.md +++ b/evidence/adsc_evidence_pack.md @@ -263,6 +263,60 @@ per-orbit table for both geometries is in `generated/wp12_ladder.md`. with a deliberately coarse altitude-independent solar factor marked PLACEHOLDER. +### Kit-class trade + EDT physics (WP13) + +Data source: `generated/wp13_kit_trade.csv` (schema 1.0; column +definitions in `generated/wp13_kit_trade_schema.md`), regenerated by +`kit_trade` (src/main_kit_trade.cpp) by combining the EXISTING WP3 +sail-decay model with the NEW WP13 EDT-v1 aligned-dipole physics core +(src/decay.cpp: edt_deorbit_years) -- no hand-written numbers. + +| catalog | recommended kit | sail area, 25-yr [m^2] | EDT deorbit time [yr] | EDT eta(i) band | +|---|---|---:|---:|---:| +| SL-16 / Zenit-2 second stage | edt-candidate-open-risks | 135..2155 | 3.0..9.2 | 0.106..0.326 | +| SL-8 / Kosmos-3M second stage | sail | 7..115 | 0.6..3.0 | 0.043..0.208 | +| Envisat-class massive SSO payload | controlled-reentry-mission-class | 61..983 | 5.5..37.5 | 0.021..0.146 | + +**EDT-v1 model scope (R14 fidelity tag), quoted verbatim from the +committed CSV -- never hand-transcribed, so the generator and the data +cannot drift:** + +> [model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor] + +**Honesty check (T7, D12/D13 - never claim closed).** Libration / +dynamic tether stability is explicitly UNRESOLVED (T7; Pelaez, J., et +al., 2000, cited in `wp13-literature.md`); the edt_years band above +folds it in only as a flat PLACEHOLDER duty-cycle knob +(`EdtConfig::eta_libration`), never claimed solved. Plasma electron +density is a cited PLACEHOLDER solar-min/max pair, not an IRI +implementation (spec non-goal; `wp13-literature.md` Topic 3 citation +gap). Every band above is an aligned-dipole idealization (SPENVIS +centred dipole, IGRF epoch 2000), not a tilted-dipole or full-IGRF +field. Deploy risk is reported, not applied: deploy_risk = 0.0500 [-] +(PLACEHOLDER `EdtConfig::deploy_failure_prob`) is quoted for the +record and is NOT folded into the edt_years band above. Bottom line, +EDT is a candidate for the SL-16 class, not a closed recommendation -- +quoted verbatim from the committed CSV's own recommended-kit +rationale: "sail infeasible (135..2155 m2 for the 25-yr guideline, solar max..min); EDT (band 3.0..9.2 yr) subject to T7; libration (T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/max pair, wp13-literature.md Topic 3) remain open -- an honest EDT candidate, not a closed recommendation" + +**Class C (controlled reentry, a separate mission class - not a +sail/EDT kit choice).** Massive/high-risk derelicts where the +deliverable is a controlled reentry, evaluated on casualty risk, +ground footprint, delta-v, consent and cost in +`generated/wp13_classC.md` (data: `generated/wp13_classC.csv`, schema +1.0), not on a sail/EDT kit trade. Computed controlled-deorbit delta-v +(single impulsive perigee-lowering burn from the catalog's circular +orbit to a target perigee of 6371+40 km): + +| candidate | v_c [m/s] | delta-v [m/s] | +|---|---:|---:| +| Zenit-2 stage (catalog_A) | 7431.2 | 223.4 | +| Envisat-class (catalog_C) | 7462.2 | 208.3 | + +Cross-reference: the eta(i) closed-form derivation is +`wp13-edt-derivation.md`; the citation pack is `wp13-literature.md` +(both committed at the repo root). + ## 5. Campaign statistics and cost/FoM WP5 Monte Carlo: 500 dispersed missions per catalog at fixed master seed @@ -422,7 +476,7 @@ in `include/`, `src/` and `tools/` carrying the uppercase PLACEHOLDER mark only in order to collect and audit it). If a value is listed here, treat it as unvalidated until a cited source replaces it. -Total marks: **113** +Total marks: **130** | location | line | |---|---| @@ -476,8 +530,11 @@ Total marks: **113** | `include/adsc/cost.hpp:86` | double cu_to_musd_high = 0.0; // PLACEHOLDER (cited range, WP7) | | `include/adsc/cost.hpp:88` | // Normalized congestion-weight table (PLACEHOLDER; cite on fill). Peaks | | `include/adsc/cost.hpp:101` | // Launch band factor for a target band (PLACEHOLDER model). | -| `include/adsc/decay.hpp:33` | DebrisCatalog catalog_C(); // CZ upper-stage class (PLACEHOLDER) | | `include/adsc/decay.hpp:34` | DebrisCatalog catalog_D(); // US Delta-class stage (PLACEHOLDER) | +| `include/adsc/decay.hpp:111` | double kit_mass_kg = 20.0; // PLACEHOLDER EDT kit mass (tether + deployer + electron... | +| `include/adsc/decay.hpp:112` | double avg_current_a = 2.0; // PLACEHOLDER average bare-tether collected current, BET... | +| `include/adsc/decay.hpp:113` | double eta_libration = 0.75; // PLACEHOLDER libration/duty-cycle efficiency penalty [-... | +| `include/adsc/decay.hpp:114` | double deploy_failure_prob = 0.05; // PLACEHOLDER deployment-failure probability [-] (report... | | `include/adsc/flux.hpp:21` | // them. All debris-population figures are PLACEHOLDER, marked below and to be | | `include/adsc/flux.hpp:26` | // PLACEHOLDER-marked flux parameters (R10); the physical constants (aluminium | | `include/adsc/flux.hpp:34` | // Spatial number density of >=1 cm debris [objects / km^3]. PLACEHOLDER -- | @@ -521,12 +578,26 @@ Total marks: **113** | `src/cost.cpp:448` | "/ notes / provenance / PLACEHOLDER caveats /\n" | | `src/cost.cpp:460` | " sum m_i*w(h_i)/C_campaign (spec ?4). The two weightings are PLACEHOLDER and\n" | | `src/cost.cpp:467` | "- **currency_anchor** (global): `cu_to_musd_range` -- a PLACEHOLDER cited\n" | -| `src/decay.cpp:63` | return {"CZ upper stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; | -| `src/decay.cpp:66` | return {"US Delta-class stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; | +| `src/decay.cpp:83` | return {"US Delta-class stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; | | `src/flux.cpp:81` | "figures are PLACEHOLDER (MASTER-8 / ESA spatial-density class; cite at " | | `src/flux.cpp:94` | std::fprintf(f, "PLACEHOLDER spatial densities: average %.1e /km^3, peak " | | `src/main.cpp:137` | std::printf(" target inertia diag: %.2f / %.2f / %.2f kg m^2 (PLACEHOLDER ratios)\n", | | `src/main_flux.cpp:33` | std::printf("\n[T6] collector exposure (>= 1 cm; PLACEHOLDER densities, cite MASTER-8)\n"); | +| `src/main_kit_trade.cpp:38` | "/cos i/..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]"; | +| `src/main_kit_trade.cpp:117` | // kit_mass (edt): NEW EdtConfig::kit_mass_kg, PLACEHOLDER. | +| `src/main_kit_trade.cpp:120` | "\"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)\"\n", | +| `src/main_kit_trade.cpp:124` | // deploy_risk: EdtConfig::deploy_failure_prob, PLACEHOLDER. Reported | +| `src/main_kit_trade.cpp:128` | "\"PLACEHOLDER EDT deployment-failure probability " | +| `src/main_kit_trade.cpp:203` | " `EdtConfig::kit_mass_kg` (PLACEHOLDER). value = value_lo = value_hi (a\n" | +| `src/main_kit_trade.cpp:205` | "- `deploy_risk`: `EdtConfig::deploy_failure_prob`, PLACEHOLDER. Reported\n" | +| `src/main_kit_trade.cpp:219` | "libration T7 OPEN - PLACEHOLDER duty factor]`. This states the model scope\n" | +| `src/main_kit_trade.cpp:228` | "PLACEHOLDER duty-cycle knob (`EdtConfig::eta_libration`) -- never claimed\n" | +| `src/main_kit_trade.cpp:229` | "solved. Plasma electron density is a cited PLACEHOLDER parameter, not an\n" | +| `src/main_kit_trade.cpp:366` | "**[CITATION NEEDED - PLACEHOLDER: per-object Ec analysis]**. Both " | +| `src/main_kit_trade.cpp:427` | std::fprintf(f, "/ Zenit-2 / SL-16 (catalog_A) / PLACEHOLDER until WP14 / WP14 cost-range ite... | +| `src/main_kit_trade.cpp:428` | std::fprintf(f, "/ Envisat-class (catalog_C) / PLACEHOLDER until WP14 / WP14 cost-range itemi... | +| `src/main_kit_trade.cpp:466` | // open risks (T7 libration, plasma Ne PLACEHOLDER). ------------------- | +| `src/main_kit_trade.cpp:479` | "(T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/" | | `src/main_ladder.cpp:75` | // PLACEHOLDER bare-stage drag cross-sections (WP12 L2). Kept local to this | | `src/main_ladder.cpp:78` | constexpr double kSL16AreaM2 = 33.0; // PLACEHOLDER SL-16 / Zenit-2 bare-stage cross-section... | | `src/main_ladder.cpp:79` | constexpr double kSL8AreaM2 = 7.5; // PLACEHOLDER SL-8 / Kosmos-3M bare-stage cross-sectio... | diff --git a/generated/viz/wp3_decay_trade.svg b/generated/viz/wp3_decay_trade.svg index 4043fb4..43f34a5 100644 --- a/generated/viz/wp3_decay_trade.svg +++ b/generated/viz/wp3_decay_trade.svg @@ -1 +1 @@ -WP3 sail-only decay: area vs years (solar min..max band)0.1110100100010000510255010020040080016003200sail area [m^2] (log)years to 180 km (log)25-yr guidelineSL-16 (25-yr area 135..2155 m^2)SL-8 (25-yr area 7..115 m^2)band = solar max (fast) .. solar min (slow); the heavy high SL-16 class needs an impractically large sail to meet 25 yr while the light SL-8 class closes (motivates T1).source: wp3_decay_trade.csv schema 1.0 seed n/a (deterministic decay model) +WP3 sail-only decay: area vs years (solar min..max band)0.11101001000100005102550100200400800100016003200sail area [m^2] (log)years to 180 km (log)25-yr guidelineSL-16 (25-yr area 135..2155 m^2)SL-8 (25-yr area 7..115 m^2)band = solar max (fast) .. solar min (slow); the heavy high SL-16 class needs an impractically large sail to meet 25 yr while the light SL-8 class closes (motivates T1).source: wp3_decay_trade.csv schema 1.0 seed n/a (deterministic decay model) diff --git a/generated/wp13_classC.csv b/generated/wp13_classC.csv new file mode 100644 index 0000000..a293fee --- /dev/null +++ b/generated/wp13_classC.csv @@ -0,0 +1,7 @@ +schema_version,candidate,mass_kg,altitude_km,inclination_deg,metric,value,units,notes +1.0,Zenit-2 stage (catalog_A),9000.0,840.0,71.0,v_circular_initial,7431.1551,m/s,"circular-orbit speed at the catalog altitude, v_c = sqrt(mu/r_a)" +1.0,Zenit-2 stage (catalog_A),9000.0,840.0,71.0,target_perigee_radius,6411.0,km,"controlled-deorbit target perigee, mean-Earth-radius convention 6371+40 km (distinct from the WGS-84 kEarthRadius used for r_a)" +1.0,Zenit-2 stage (catalog_A),9000.0,840.0,71.0,controlled_deorbit_dv,223.3998,m/s,"impulsive perigee-lowering burn from circular (RE+alt) to perigee 6371+40 km: dv = v_c*(1-sqrt(2*r_p/(r_a+r_p)))" +1.0,Envisat-class (catalog_C),8211.0,780.0,98.4,v_circular_initial,7462.2344,m/s,"circular-orbit speed at the catalog altitude, v_c = sqrt(mu/r_a)" +1.0,Envisat-class (catalog_C),8211.0,780.0,98.4,target_perigee_radius,6411.0,km,"controlled-deorbit target perigee, mean-Earth-radius convention 6371+40 km (distinct from the WGS-84 kEarthRadius used for r_a)" +1.0,Envisat-class (catalog_C),8211.0,780.0,98.4,controlled_deorbit_dv,208.3495,m/s,"impulsive perigee-lowering burn from circular (RE+alt) to perigee 6371+40 km: dv = v_c*(1-sqrt(2*r_p/(r_a+r_p)))" diff --git a/generated/wp13_classC.md b/generated/wp13_classC.md new file mode 100644 index 0000000..7cf0769 --- /dev/null +++ b/generated/wp13_classC.md @@ -0,0 +1,50 @@ +# WP13 Class-C controlled-reentry mission-class comparison + +Class C (spec WP13) is a **separate mission class**, not a sail/EDT kit choice: massive/high-risk derelicts where the deliverable is a controlled reentry, evaluated on casualty risk, ground footprint, delta-v, consent, and cost. The comparison against tug architectures is allowed **here only**, and only on the **same target set** (spec WP13/WP14, instr Sec 0.4). Data source: `generated/wp13_classC.csv` (schema 1.0), regenerated by `kit_trade` -- no hand-written numbers. + +## Candidates (same target set) + +| candidate | mass [kg] | altitude [km] | inclination [deg] | source | +|---|---:|---:|---:|---| +| Zenit-2 / SL-16 second stage (`catalog_A`) | 9000 | 840 | 71.0 | web-verified ~8300(-9000) kg, ~845x857 km, 71.01 deg (Tselina-2 family), `wp13-literature.md` Topic 5a; also class A's sail/EDT anchor -- massive by the same McKnight et al. 2021 top-50 ranking that motivates class C | +| Envisat-class massive SSO payload (`catalog_C`) | 8211 | 780 | 98.4 | web-verified 8211 kg, ~765-800 km sun-synchronous, ~98.4-98.55 deg, eoPortal / ESA Earth Online, `wp13-literature.md` Topic 6 item 1 | + +## Uncontrolled-reentry casualty framing (cited, not computed) + +NASA-STD-8719.14A (with Change 1), Requirement 4.7-1: the expected worldwide human-casualty risk from reentering debris shall not exceed **Ec < 1e-4** (1 in 10,000); casualty is assumed for any surviving fragment with impact kinetic energy **> 15 J**; a controlled reentry must additionally ensure no surviving >15 J fragment lands within **370 km of foreign landmasses** (soma.larc.nasa.gov/SIMPLEx/pdf_files/871914.pdf; ESA applies the same 1e-4 limit via DRAMA/SARA, technology.esa.int/page/re-entry-safety -- both web-verified, `wp13-literature.md` Topic 7). This repo does **not** compute a per-object Ec for either candidate: that requires a survivability / ground-footprint tool (NASA DAS/ORSAT or ESA SARA class), out of scope here: **[CITATION NEEDED - PLACEHOLDER: per-object Ec analysis]**. Both candidates are multi-tonne, so an UNCONTROLLED reentry is the presumptive risk driver the 370 km / Ec<1e-4 rule exists for; that presumption is not itself a computed Ec value. + +## Controlled-deorbit delta-v (COMPUTED) + +Single impulsive perigee-lowering burn from the catalog's circular orbit (radius r_a = R_E + altitude) to a target perigee r_p = 6371 + 40 km (mean-Earth-radius convention for the reentry perigee, distinct from the WGS-84 kEarthRadius used for r_a elsewhere in this repo -- a deliberate, documented choice, not a silent inconsistency): dv = v_c * (1 - sqrt(2 r_p / (r_a + r_p))), v_c = sqrt(mu / r_a). This is the minimum single-burn delta-v to commit the stage to reentry; it excludes targeting/footprint-control burns and any margin. Full precision in `generated/wp13_classC.csv`. + +| candidate | v_c [m/s] | dv [m/s] | +|---|---:|---:| +| Zenit-2 / SL-16 (catalog_A) | 7431.2 | **223.4** | +| Envisat-class (catalog_C) | 7462.2 | **208.3** | + +## Consent gate + +Legal accessibility enters as a **gate and metadata flags, never a multiplier** (D12): the WP8 compliance engine BLOCKs unconsented active debris removal, and target prioritization consumes its PASS/BLOCK output and flags rather than folding a subjective legal weight into any score (`tools/compliance/check_compliance.py`, policy `ADSC-POL-01`, consent-missing -> BLOCK). Any controlled-reentry mission on either candidate above is subject to that same gate: no consent, no operation, regardless of the casualty-risk or delta-v numbers on this page. + +## Kit-installer vs tug (same target set) + +Steelmanned: a tug architecture also amortizes fixed costs (launch, ground segment, ops) across a batch of targets visited in one plane, exactly like the kit-installer's own batch amortization (WP6 amortization curve). The comparison is fair only because it is the SAME target set (spec WP14 rule) -- comparing a tug against a different, easier target set would be meaningless. What does NOT amortize for a tug, and is the installer's structural edge (D1: installer-not-tug): (1) a tug must carry propellant to change EACH target's orbit itself, so its per-target delta-v cost scales with target count, while an installer's kit-carrying stages do their own deorbit after release -- the installer pays no per-target deorbit delta-v; (2) a tug must detumble, dock/grapple, and physically tow each multi-tonne stage through its own controlled-reentry burn, repeating the highest-risk contact event per target, while an installer's own contact event is bounded by the WP3 low-energy capture approach (0.333 J contact-energy budget at 0.15 m/s, README/evidence pack Section on WP3) and does not itself carry a target through reentry. Both architectures still need SOME per-target consent/compliance gate (D12) and SOME reentry delta-v paid by someone (either pre-installed as a kit, or delivered by the tug) -- the installer's edge is where that delta-v and that repeated high-risk contact event are paid, not that they vanish. + +## Cost + +| candidate | cost | source | +|---|---|---| +| Zenit-2 / SL-16 (catalog_A) | PLACEHOLDER until WP14 | WP14 cost-range itemization | +| Envisat-class (catalog_C) | PLACEHOLDER until WP14 | WP14 cost-range itemization | + +## `wp13_classC.csv` columns + +| column | meaning | +|---|---| +| schema_version | class-C schema id (`1.0`) | +| candidate | candidate label (Zenit-2 stage / Envisat-class) | +| mass_kg, altitude_km, inclination_deg | catalog parameters (constant per candidate) | +| metric | v_circular_initial / target_perigee_radius / controlled_deorbit_dv | +| value | metric value at full precision (the md table above rounds to 0.1) | +| units | m/s or km | +| notes | formula / provenance | diff --git a/generated/wp13_kit_trade.csv b/generated/wp13_kit_trade.csv new file mode 100644 index 0000000..8347a17 --- /dev/null +++ b/generated/wp13_kit_trade.csv @@ -0,0 +1,25 @@ +schema_version,catalog,mass_kg,altitude_km,inclination_deg,record_type,kit_option,value,value_lo,value_hi,units,notes +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,sail_area_25yr,sail,0.0,134.6904,2155.0459,m2,"sail area for the 25-yr IADC guideline (solar max .. solar min); existing area_for_target_years model, see wp3_decay_trade.csv area_for_25yr" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,edt_years,edt,0.0,2.9844,9.1668,years,"[model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,edt_eta,edt,0.0,0.105995,0.325568,[-],"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = |cos i| (power/current-capped current); wp13-edt-derivation.md Section 5" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,edt_emf_power,edt,150.3125,0.0,300.6250,"V (value); W (value_hi)","value = open-circuit motional EMF at the catalog altitude [V]; value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a [W]; value_lo unused (0.0); not a deorbit-time metric" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,kit_mass,sail,2.4000,2.4000,2.4000,kg,"installed drag-sail kit mass (Config::kit_mass_kg)" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,kit_mass,edt,20.0000,20.0000,20.0000,kg,"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,deploy_risk,edt,0.0500,0.0500,0.0500,[-],"PLACEHOLDER EDT deployment-failure probability (EdtConfig::deploy_failure_prob); reported only, not applied to the edt_years band above" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,71.0,recommended_kit,edt-candidate-open-risks,0.0,0.0,0.0,[-],"sail infeasible (135..2155 m2 for the 25-yr guideline, solar max..min); EDT (band 3.0..9.2 yr) subject to T7; libration (T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/max pair, wp13-literature.md Topic 3) remain open -- an honest EDT candidate, not a closed recommendation" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,sail_area_25yr,sail,0.0,7.2046,115.2728,m2,"sail area for the 25-yr IADC guideline (solar max .. solar min); existing area_for_target_years model, see wp3_decay_trade.csv area_for_25yr" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,edt_years,edt,0.0,0.6291,3.0259,years,"[model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,edt_eta,edt,0.0,0.043227,0.207912,[-],"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = |cos i| (power/current-capped current); wp13-edt-derivation.md Section 5" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,edt_emf_power,edt,100.3007,0.0,200.6013,"V (value); W (value_hi)","value = open-circuit motional EMF at the catalog altitude [V]; value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a [W]; value_lo unused (0.0); not a deorbit-time metric" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,kit_mass,sail,2.4000,2.4000,2.4000,kg,"installed drag-sail kit mass (Config::kit_mass_kg)" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,kit_mass,edt,20.0000,20.0000,20.0000,kg,"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,deploy_risk,edt,0.0500,0.0500,0.0500,[-],"PLACEHOLDER EDT deployment-failure probability (EdtConfig::deploy_failure_prob); reported only, not applied to the edt_years band above" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,78.0,recommended_kit,sail,0.0,0.0,0.0,[-],"sail closes (7..115 m2 for the 25-yr guideline, solar max..min); no EDT kit needed for this class" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,sail_area_25yr,sail,0.0,61.4390,983.0247,m2,"sail area for the 25-yr IADC guideline (solar max .. solar min); existing area_for_target_years model, see wp3_decay_trade.csv area_for_25yr" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,edt_years,edt,0.0,5.4814,37.5223,years,"[model: EDT-v1 aligned-dipole, capped-current; eta band |cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,edt_eta,edt,0.0,0.021340,0.146083,[-],"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = |cos i| (power/current-capped current); wp13-edt-derivation.md Section 5; retrograde (i > 90 deg): the EMF polarity reverses but the induced current always drags (Lenz), so the magnitude |cos i| is used" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,edt_emf_power,edt,69.4450,0.0,138.8899,"V (value); W (value_hi)","value = open-circuit motional EMF at the catalog altitude [V]; value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a [W]; value_lo unused (0.0); not a deorbit-time metric" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,kit_mass,sail,2.4000,2.4000,2.4000,kg,"installed drag-sail kit mass (Config::kit_mass_kg)" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,kit_mass,edt,20.0000,20.0000,20.0000,kg,"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,deploy_risk,edt,0.0500,0.0500,0.0500,[-],"PLACEHOLDER EDT deployment-failure probability (EdtConfig::deploy_failure_prob); reported only, not applied to the edt_years band above" +1.0,Envisat-class massive SSO payload,8211.0,780.0,98.4,recommended_kit,controlled-reentry-mission-class,0.0,0.0,0.0,[-],"massive (8211 kg) high-inclination payload -- sail/EDT kit-only deorbit is not the recommended mission class; see generated/wp13_classC.md for the controlled-reentry comparison" diff --git a/generated/wp13_kit_trade_schema.md b/generated/wp13_kit_trade_schema.md new file mode 100644 index 0000000..18fbec6 --- /dev/null +++ b/generated/wp13_kit_trade_schema.md @@ -0,0 +1,97 @@ +# WP13 kit-class trade CSV schema (version 1.0) + +`generated/wp13_kit_trade.csv` is the committed per-class (A/B/C) kit-trade +table emitted by `kit_trade` (src/main_kit_trade.cpp). It combines the +EXISTING WP3 sail-decay model (src/decay.cpp: sail_decay_years, +area_for_target_years) with the NEW WP13 EDT-v1 aligned-dipole physics core +(src/decay.cpp: edt_deorbit_years) into one deterministic, timestamp-free +table (R6): sail area, EDT deorbit-time band, EDT diagnostics, kit mass +(sail and EDT), EDT deployment risk, and a generated (never hand-written) +recommended-kit row with a one-line rationale per class. Class-C +controlled-reentry mission-class detail lives in the separate +`wp13_classC.{csv,md}` pair, not here (see that file's own column notes). + +## Columns + +| column | meaning | +|---|---| +| schema_version | WP13 kit-trade schema id (`1.0`) | +| catalog | class-level preset name (`DebrisCatalog::name`) | +| mass_kg | catalog stage mass [kg] | +| altitude_km | catalog altitude [km] | +| inclination_deg | catalog inclination [deg] (drives the mandatory EDT eta(i) band) | +| record_type | see record_type rows below | +| kit_option | sail / edt, or the recommendation label (recommended_kit only) | +| value | single-value metric, or 0.0 for band-only / not-applicable rows | +| value_lo | band lower edge (see per-row meaning below) | +| value_hi | band upper edge (see per-row meaning below) | +| units | value / value_lo / value_hi units | +| notes | provenance, model-scope tag, and caveats | + +## record_type rows + +- `sail_area_25yr`: sail area for the 25-yr IADC guideline from the + EXISTING WP3 model (`area_for_target_years`, unchanged). value_lo/hi are + the solar-max/solar-min band (mirrors `wp3_decay_trade.csv`'s + `area_for_25yr` row; this does not repeat the full area sweep -- see + that file for the sweep, now including the 1000 m^2 row). +- `edt_years`: EDT-v1 deorbit-time band from `edt_deorbit_years`. + value_lo = years_optimistic (eta_hi = |cos i|, power/current-capped + edge); value_hi = years_conservative (eta_lo = cos(i)^2, + EMF/collection-limited edge) -- NEVER a point value. notes carries the + EDT-v1 model-scope tag (below). Exactly-polar catalogs (|cos i| ~= 0: + the aligned-dipole orbit-normal field vanishes) print the literal + text `n/a (polar: aligned-dipole avg force -> 0)` in value_lo/value_hi + instead of a number: this is the honest, physically-correct null result + for that idealization (see the `edt_deorbit_years` doc comment in + decay.hpp), not a missing-data placeholder. Retrograde catalogs + (i > 90 deg) get FINITE bands: the passive tether's induced current + always drags (Lenz), so the force magnitude scales with |cos i|. +- `edt_eta`: the mandatory inclination-dependent v x B efficiency band + (spec:240-243, never omitted). value_lo = eta_lo = cos(i)^2, value_hi = + eta_hi = |cos i| (see `wp13-edt-derivation.md` Section 5 for + both current-limit regimes; for i > 90 deg the EMF polarity reverses + and the magnitude is reported, with a note in the row). +- `edt_emf_power`: value = open-circuit motional EMF at the catalog + altitude [V]; value_hi = diagnostic power = emf_v * avg_current_a [W]; + value_lo is unused (0.0). Diagnostic only, not a deorbit-time metric. +- `kit_mass`: installed kit mass. kit_option = sail uses the EXISTING + `Config::kit_mass_kg`; kit_option = edt uses the NEW + `EdtConfig::kit_mass_kg` (PLACEHOLDER). value = value_lo = value_hi (a + point value, not a band). +- `deploy_risk`: `EdtConfig::deploy_failure_prob`, PLACEHOLDER. Reported + only -- NOT applied to the edt_years band above (see the + `edt_deorbit_years` doc comment in decay.hpp). +- `recommended_kit`: value = 0.0 (a label row, not a numeric metric); + kit_option carries the recommendation label; notes carries the + one-line, generated (never hand-written) rationale, quoting the actual + computed sail-area and/or EDT-years numbers for that catalog. Class C's + recommendation is `controlled-reentry-mission-class`, pointing at + `wp13_classC.md` rather than a sail/EDT kit choice. + +## EDT-v1 model scope (R14 fidelity tag) + +Every `edt_years` row's notes column carries the literal tag +`[model: EDT-v1 aligned-dipole, capped-current; eta band cos(i)..cos^2(i); +libration T7 OPEN - PLACEHOLDER duty factor]`. This states the model scope +in-line with the number it qualifies (R14): aligned (untilted) dipole +geomagnetic field (SPENVIS centred dipole, IGRF epoch 2000); force capped +by either a fixed/power-limited current (optimistic edge) or an +EMF/collection-limited current (conservative edge); the along-track +efficiency factor is the orbit-averaged eta(i) in [cos(i)^2, cos(i)], +MANDATORY per spec WP13 (never a point value, never omitted for a +high-inclination catalog). Libration/dynamic stability (T7, Pelaez et al. +2000) is explicitly UNRESOLVED and is folded in only as a flat +PLACEHOLDER duty-cycle knob (`EdtConfig::eta_libration`) -- never claimed +solved. Plasma electron density is a cited PLACEHOLDER parameter, not an +IRI implementation (spec non-goal); see +`wp13-literature.md` Topic 3 for the citation and the open +solar-min/max pair gap. + +## Schema changes (R15) + +Additive columns to this file bump the minor version (1.0 -> 1.1) with +this file and all consumers updated in the same PR, the same rule as +`wp3_decay_trade_schema.md`'s own closing paragraph. Additive rows (a new +catalog, a new record_type) do NOT need a version bump under this +project's own rule. diff --git a/generated/wp3_decay_trade.csv b/generated/wp3_decay_trade.csv index de32042..89824bb 100644 --- a/generated/wp3_decay_trade.csv +++ b/generated/wp3_decay_trade.csv @@ -7,6 +7,7 @@ schema_version,catalog,mass_kg,altitude_km,record_type,sail_area_m2,value_solar_ 1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,200.0000,16.8363,269.3807,years,"" 1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,400.0000,8.4181,134.6904,years,"" 1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,800.0000,4.2091,67.3452,years,"" +1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,1000.0000,3.3673,53.8761,years,"" 1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,1600.0000,2.1045,33.6726,years,"" 1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,decay_years,3200.0000,1.0523,16.8363,years,"" 1.0,SL-16 / Zenit-2 second stage,9000.0,840.0,area_for_25yr,0.0,134.6904,2155.0459,m2,"sail area for the 25-yr guideline (solar max .. min)" @@ -18,6 +19,7 @@ schema_version,catalog,mass_kg,altitude_km,record_type,sail_area_m2,value_solar_ 1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,200.0000,0.9006,14.4091,years,"" 1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,400.0000,0.4503,7.2046,years,"" 1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,800.0000,0.2251,3.6023,years,"" +1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,1000.0000,0.1801,2.8818,years,"" 1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,1600.0000,0.1126,1.8011,years,"" 1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,decay_years,3200.0000,0.0563,0.9006,years,"" 1.0,SL-8 / Kosmos-3M second stage,1400.0,750.0,area_for_25yr,0.0,7.2046,115.2728,m2,"sail area for the 25-yr guideline (solar max .. min)" diff --git a/include/adsc/decay.hpp b/include/adsc/decay.hpp index 9b721c8..22fe2be 100644 --- a/include/adsc/decay.hpp +++ b/include/adsc/decay.hpp @@ -30,7 +30,7 @@ struct DebrisCatalog { DebrisCatalog catalog_A(); // SL-16 / Zenit-2 second-stage class DebrisCatalog catalog_B(); // SL-8 / Kosmos-3M second-stage class -DebrisCatalog catalog_C(); // CZ upper-stage class (PLACEHOLDER) +DebrisCatalog catalog_C(); // Envisat-class massive SSO payload (WP13, cited) DebrisCatalog catalog_D(); // US Delta-class stage (PLACEHOLDER) // Piecewise-exponential atmospheric density [kg/m^3] at geodetic altitude [m]. @@ -91,4 +91,91 @@ double area_for_target_years(const DebrisCatalog& target, double target_years, // specific tether. double edt_deorbit_days(double a0_m, double a_stop_m, double delta_a_per_day_m); +// ============================================================================ +// Electrodynamic-tether (EDT) physics core (WP13) +// ---------------------------------------------------------------------------- +// Aligned-dipole v x B model for a radial (nadir-aligned) bare tether. This is +// a NEW model alongside edt_deorbit_days above (kept byte-for-byte, R15 pin) -- +// it does not replace or edit that legacy parametric knob or any sail/ +// atmosphere function in this file. See wp13-edt-derivation.md +// for the full eta(i) derivation and wp13-literature.md for +// citations (Sanmartin, Martinez-Sanchez & Ahedo 1993 OML bare-tether +// baseline; BETs FP7 GA 262972 tape-tether reference geometry; SPENVIS +// centred-dipole field; Pelaez et al. 2000 EDT libration/dynamic-instability +// finding). Every output is reported as a MANDATORY [conservative, +// optimistic] band -- never a point value (spec:247-249). +// ============================================================================ + +struct EdtConfig { + double tether_length_m = 3000.0; // BETs FP7 tape-tether reference length [m] (2 km bare + 1 km inert; thebetsproject.com / oa.upm.es/39287, cited wp13-literature.md Topic 1b) + double kit_mass_kg = 20.0; // PLACEHOLDER EDT kit mass (tether + deployer + electronics) [kg] + double avg_current_a = 2.0; // PLACEHOLDER average bare-tether collected current, BETs-scale [A] + double eta_libration = 0.75; // PLACEHOLDER libration/duty-cycle efficiency penalty [-] (T7 open; Pelaez et al. 2000 dynamic-instability finding, never claimed solved) + double deploy_failure_prob = 0.05; // PLACEHOLDER deployment-failure probability [-] (reported only, not applied to years_* below) +}; + +// Deorbit-time band and diagnostics returned by edt_deorbit_years(). Report +// the [years_optimistic, years_conservative] pair together -- never +// years_optimistic alone (spec:247-249). +struct EdtResult { + double years_optimistic; // deorbit time [yr], eta_hi = |cos i| edge (fixed / power-capped current) + double years_conservative; // deorbit time [yr], eta_lo = cos(i)^2 edge (EMF / collection-limited current) + double eta_hi; // |cos i|: optimistic orbit-averaged inclination efficiency [-] + double eta_lo; // cos(i)^2: conservative orbit-averaged inclination efficiency [-] + double emf_v; // open-circuit motional EMF at the initial altitude [V] + double power_w; // emf_v * avg_current_a, diagnostic only [W] +}; + +// General EDT quasi-circular spiral integrator: an along-track Lorentz drag +// force F(a) [N] (any function of semi-major axis a, not necessarily a power +// law) drives, for a circular orbit (Gauss planetary equation), +// da/dt = -2 * F(a) * a^1.5 / (m * sqrt(mu)) +// Integrates from a0_m down to a_stop_m and returns elapsed time [s]. Same +// trapezoidal-in-a quadrature style as integrate_decay_seconds above. For the +// aligned-dipole force law F(a) = C / a^3 this cross-checks the elementary +// closed form used internally by edt_deorbit_years (WP13 task-3 requirement). +template +double integrate_edt_seconds(double a0_m, double a_stop_m, double m, + ForceFn force_at_a, int steps = 6000) { + if (a0_m <= a_stop_m || steps < 1 || m <= 0.0) { + return 0.0; + } + const double da = (a0_m - a_stop_m) / steps; + const auto integrand = [&](double a) { + return m * std::sqrt(kEarthMu) / (2.0 * force_at_a(a) * std::pow(a, 1.5)); + }; + double sum = 0.5 * (integrand(a_stop_m) + integrand(a0_m)); + for (int i = 1; i < steps; ++i) { + sum += integrand(a_stop_m + static_cast(i) * da); + } + return sum * da; +} + +// Aligned-dipole EDT deorbit-time band for a debris target fitted with an EDT +// kit (see EdtConfig). Orbit-normal field magnitude B_n(a) = kB0 * +// (kDipoleRefRadius_m / a)^3 * |cos i| (SPENVIS centred dipole, IGRF epoch +// 2000; i = target's catalog inclination -- this is the MANDATORY +// inclination-dependent v x B efficiency, spec:240-243). |cos i|, not signed +// cos i: the passive tether's induced-current direction follows the +// motional-EMF sign (Lenz), so the along-track Lorentz force is always a +// drag; a retrograde orbit (i > 90 deg, e.g. the class-C sun-synchronous +// candidate) reverses the EMF polarity but keeps a finite deorbit drag +// scaling with |cos i|. Along-track force at semi-major axis a: F(a) = +// avg_current_a * tether_length_m * B_n(a) * eta_libration is the optimistic +// edge (eta_hi = |cos i|, fixed/power-capped current); the conservative edge +// is F(a) * |cos i| (eta_lo = cos(i)^2, EMF/collection-limited current) -- +// see wp13-edt-derivation.md Section 5 for both current-limit regimes. +// Solved in closed form (elementary power-law integral, exact); +// integrate_edt_seconds above cross-checks the algebra. Exactly-polar orbits +// (|cos i| <= 1e-6): the aligned-dipole orbit-normal field is zero +// everywhere on the orbit (derivation note Section 6, limit L3) -- +// years_optimistic/years_conservative are +infinity, a physically correct +// null result for this idealization (not a numerical artifact); emitters +// should print "n/a (polar: aligned-dipole avg force -> 0)" for it. +// Libration (T7) is folded in only as the flat eta_libration duty-cycle knob +// above -- dynamic stability itself is explicitly unresolved (never claimed +// solved). +EdtResult edt_deorbit_years(const DebrisCatalog& target, const EdtConfig& cfg, + double stop_altitude_km); + } // namespace adsc diff --git a/src/decay.cpp b/src/decay.cpp index 043b355..573685a 100644 --- a/src/decay.cpp +++ b/src/decay.cpp @@ -1,6 +1,7 @@ #include "adsc/decay.hpp" #include +#include namespace adsc { @@ -34,6 +35,16 @@ constexpr Band kBands[] = { constexpr int kNumBands = static_cast(sizeof(kBands) / sizeof(kBands[0])); constexpr double kSecondsPerYear = 365.25 * 86400.0; +// SPENVIS (ESA/BIRA-IASB Space Environment Information System), "Dipole +// approximations of the geomagnetic field", centred-dipole IGRF epoch-2000 +// values: equatorial surface field B0 and the reference (mean) Earth radius +// used in the dipole formula (https://www.spenvis.oma.be/help/background/ +// magfield/cd.html, web-verified 2026-07-11, wp13-literature.md Topic 4). +// Deliberately NOT kEarthRadius (WGS-84 equatorial, relmotion.hpp): the cited +// dipole formula is defined against this specific mean-radius reference. +constexpr double kB0 = 3.01153e-5; // T +constexpr double kDipoleRefRadius_m = 6371200.0; // m + } // namespace double atmospheric_density(double altitude_m, double solar_factor) { @@ -60,7 +71,13 @@ DebrisCatalog catalog_B() { return {"SL-8 / Kosmos-3M second stage", 1400.0, 750.0, 78.0, false}; } DebrisCatalog catalog_C() { - return {"CZ upper stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; + // Envisat (ESA): mass 8211 kg (Service Module 2673 + PEB 1021 + Payload + // Carrier 2078 + fuel 319 + instruments 2118 kg); sun-synchronous orbit + // ~765-800 km, inclination ~98.4-98.55 deg; defunct since April 2012 -- + // the archetypal massive / controlled-reentry-class Class-C target. + // eoPortal (https://www.eoportal.org/satellite-missions/envisat), + // web-verified 2026-07-11 (wp13-literature.md Topic 6, item 1). + return {"Envisat-class massive SSO payload", 8211.0, 780.0, 98.4, false}; } DebrisCatalog catalog_D() { return {"US Delta-class stage (PLACEHOLDER)", 0.0, 0.0, 0.0, true}; @@ -96,4 +113,67 @@ double edt_deorbit_days(double a0_m, double a_stop_m, double delta_a_per_day_m) return (a0_m - a_stop_m) / delta_a_per_day_m; } +EdtResult edt_deorbit_years(const DebrisCatalog& target, const EdtConfig& cfg, + double stop_altitude_km) { + EdtResult result{}; + + // Magnitude |cos i|: the passive tether's induced-current direction is set + // by the motional-EMF sign (Lenz), so the along-track Lorentz force is + // ALWAYS a drag. For a retrograde orbit (i > 90 deg, e.g. the class-C + // sun-synchronous candidate) the EMF polarity reverses but the deorbit + // force magnitude scales with |cos i|, not signed cos i. + const double cos_i = std::cos(target.inclination_deg * kPi / 180.0); + const double abs_cos_i = std::fabs(cos_i); + result.eta_hi = abs_cos_i; + result.eta_lo = cos_i * cos_i; + + const double a0 = kEarthRadius + target.altitude_km * 1000.0; + const double Bn0 = kB0 * std::pow(kDipoleRefRadius_m / a0, 3.0) * abs_cos_i; + const double v0 = std::sqrt(kEarthMu / a0); + // EMF/power reported as magnitudes; polarity reverses for retrograde + // orbits (diagnostic only -- the drag sign is unaffected, see above). + result.emf_v = v0 * Bn0 * cfg.tether_length_m; + result.power_w = result.emf_v * cfg.avg_current_a; + + if (abs_cos_i <= 1e-6) { + // Exactly-polar orbit: the aligned-dipole orbit-normal field is zero + // everywhere on the orbit (wp13-edt-derivation.md Section 6, limit + // L3), so the along-track EDT force -> 0 identically, not just on + // average. This is the correct physics for the aligned-dipole + // idealization, not a numerical artifact. (Retrograde orbits with + // |cos i| > 0, e.g. i = 98.4 deg, are handled normally above.) + result.years_optimistic = std::numeric_limits::infinity(); + result.years_conservative = std::numeric_limits::infinity(); + return result; + } + + const double a_stop = kEarthRadius + stop_altitude_km * 1000.0; + const double m = target.mass_kg + cfg.kit_mass_kg; + if (a0 <= a_stop || m <= 0.0) { + result.years_optimistic = 0.0; + result.years_conservative = 0.0; + return result; + } + + // Optimistic edge: F_hi(a) = I * L * B_n(a) * eta_lib + // = (I * L * kB0 * kDipoleRefRadius_m^3 * |cos i| * eta_lib) / a^3 + // = C_hi / a^3. + // Conservative edge: F_lo(a) = F_hi(a) * |cos i| = C_lo / a^3 + // (wp13-edt-derivation.md Section 5: the two current-limit regimes). + const double C_hi = cfg.avg_current_a * cfg.tether_length_m * kB0 * + std::pow(kDipoleRefRadius_m, 3.0) * abs_cos_i * cfg.eta_libration; + const double C_lo = C_hi * abs_cos_i; + + // Quasi-circular spiral under F(a) = C / a^3: + // da/dt = -2*F(a)*a^1.5/(m*sqrt(mu)) = -(2*C/(m*sqrt(mu))) * a^-1.5 + // => a^1.5 da = -(2*C/(m*sqrt(mu))) dt + // => (2/5)(a_stop^2.5 - a0^2.5) = -(2*C/(m*sqrt(mu))) * t + // => t = m*sqrt(mu)*(a0^2.5 - a_stop^2.5) / (5*C) + const double sqrt_mu = std::sqrt(kEarthMu); + const double delta_a25 = std::pow(a0, 2.5) - std::pow(a_stop, 2.5); + result.years_optimistic = (m * sqrt_mu * delta_a25 / (5.0 * C_hi)) / kSecondsPerYear; + result.years_conservative = (m * sqrt_mu * delta_a25 / (5.0 * C_lo)) / kSecondsPerYear; + return result; +} + } // namespace adsc diff --git a/src/main_decay_trade.cpp b/src/main_decay_trade.cpp index b95029c..fdbfd63 100644 --- a/src/main_decay_trade.cpp +++ b/src/main_decay_trade.cpp @@ -25,9 +25,12 @@ namespace { const char* kSchema = "1.0"; // Sail areas swept for the trade curve [m^2]. Spans B's ~7 m^2 and A's -// ~135-2155 m^2 25-year crossings so both are visible. +// ~135-2155 m^2 25-year crossings so both are visible. 1000.0 added (WP13 +// review minor) so it lines up with main.cpp scenario-6's areas[] and the +// README's 1000 m^2 table column is byte-checkable against this CSV -- an +// additive row, not a column, so no schema bump (R15 clause below). const double kAreas[] = {5.0, 10.0, 25.0, 50.0, 100.0, 200.0, - 400.0, 800.0, 1600.0, 3200.0}; + 400.0, 800.0, 1000.0, 1600.0, 3200.0}; // Writes generated/wp3_decay_trade_schema.md documenting the wp3_decay_trade.csv // columns. A single fputs of one string literal -- no printf format specifiers, diff --git a/src/main_kit_trade.cpp b/src/main_kit_trade.cpp new file mode 100644 index 0000000..4f929be --- /dev/null +++ b/src/main_kit_trade.cpp @@ -0,0 +1,538 @@ +// WP13 kit-class trade CSV emitter: writes generated/wp13_kit_trade.{csv,md} +// combining the EXISTING WP3 sail-decay model (src/decay.cpp: +// sail_decay_years, area_for_target_years) with the NEW WP13 EDT-v1 +// aligned-dipole physics core (src/decay.cpp: edt_deorbit_years) into one +// per-class (A/B/C) recommended-kit trade table. Also writes the WP13 +// class-C controlled-reentry mission-class comparison, generated/ +// wp13_classC.{csv,md} (Zenit-2 stage / catalog_A vs Envisat-class / +// catalog_C -- same-target-set rule, spec:237/244-246). This changes no +// existing physics and no existing pinned number: it is a pure consumer of +// catalog_A/B/C and the pre-existing decay.cpp functions. Deterministic and +// timestamp-free (R6). +// +// kit_trade [out_dir] +// +// Default out_dir = "generated". +#include +#include +#include +#include +#include + +#include "adsc/decay.hpp" +#include "adsc/mission.hpp" // Config + +using namespace adsc; + +namespace { + +// Stable schema id for wp13_kit_trade.csv and wp13_classC.csv (independent +// of wp3's schema id; both new files start at 1.0). +const char* kSchema = "1.0"; + +// R14 model-scope tag, carried verbatim in every edt_years row's notes: the +// aligned-dipole force model, the mandatory eta(i) band, and the T7 +// libration caveat, stated in-line with the number it qualifies. +const char* kEdtModelTag = + "[model: EDT-v1 aligned-dipole, capped-current; eta band " + "|cos i|..cos^2 i; libration T7 OPEN - PLACEHOLDER duty factor]"; + +// decay.hpp's own documented print for the exactly-polar (|cos i| ~= 0) +// null-coupling case: years_optimistic/years_conservative are +infinity, a +// physically correct null result, not a numerical artifact (see the +// edt_deorbit_years doc comment). Retrograde catalogs (i > 90 deg) get +// FINITE numbers: the drag magnitude scales with |cos i| (Lenz). +const char* kEdtPolarNote = "n/a (polar: aligned-dipole avg force -> 0)"; + +// Writes the 7 record_type rows shared by every catalog (sail_area_25yr, +// edt_years, edt_eta, edt_emf_power, kit_mass x2, deploy_risk). The 8th row +// (recommended_kit) is catalog-specific prose and is written by the caller. +void write_common_rows(std::FILE* f, const DebrisCatalog& c, const Config& cfg, + const EdtConfig& ecfg, double a25_max, double a25_min, + const EdtResult& edt) { + // sail_area_25yr: EXISTING WP3 model, unchanged. value_lo/value_hi = + // solar-max/solar-min band (mirrors wp3_decay_trade.csv's area_for_25yr). + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,sail_area_25yr,sail,0.0,%.4f,%.4f,m2," + "\"sail area for the 25-yr IADC guideline (solar max .. solar min); " + "existing area_for_target_years model, see wp3_decay_trade.csv " + "area_for_25yr\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + a25_max, a25_min); + + // edt_years: WP13 EDT-v1 band. value_lo = years_optimistic (eta_hi = + // |cos i|), value_hi = years_conservative (eta_lo = cos(i)^2) -- never a + // point value. The exactly-polar null case prints text, not a number. + if (std::isinf(edt.years_optimistic)) { + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,edt_years,edt,0.0,%s,%s,years," + "\"%s inclination %.1f deg gives |cos i| ~= 0 (exactly polar), " + "so the aligned-dipole model reports null orbit-normal " + "coupling %s\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + kEdtPolarNote, kEdtPolarNote, kEdtModelTag, c.inclination_deg, + kEdtPolarNote); + } else { + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,edt_years,edt,0.0,%.4f,%.4f,years,\"%s\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + edt.years_optimistic, edt.years_conservative, kEdtModelTag); + } + + // edt_eta: the mandatory inclination-dependent v x B efficiency band. + // value_lo = eta_lo = cos(i)^2, value_hi = eta_hi = |cos i|. + const char* sign_note = + (c.inclination_deg > 90.0) + ? "; retrograde (i > 90 deg): the EMF polarity reverses but the " + "induced current always drags (Lenz), so the magnitude " + "|cos i| is used" + : ""; + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,edt_eta,edt,0.0,%.6f,%.6f,[-]," + "\"eta_lo = cos^2(i) (EMF/collection-limited current), eta_hi = " + "|cos i| (power/current-capped current); " + "wp13-edt-derivation.md Section 5%s\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + edt.eta_lo, edt.eta_hi, sign_note); + + // edt_emf_power: value = open-circuit motional EMF [V]; value_hi = + // diagnostic power = emf_v * avg_current_a [W]; value_lo unused (0.0). + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,edt_emf_power,edt,%.4f,0.0,%.4f," + "\"V (value); W (value_hi)\"," + "\"value = open-circuit motional EMF at the catalog altitude [V]; " + "value_hi = diagnostic power = emf_v * EdtConfig::avg_current_a " + "[W]; value_lo unused (0.0); not a deorbit-time metric\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + edt.emf_v, edt.power_w); + + // kit_mass (sail): EXISTING Config::kit_mass_kg. Point value: value = + // value_lo = value_hi. + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,kit_mass,sail,%.4f,%.4f,%.4f,kg," + "\"installed drag-sail kit mass (Config::kit_mass_kg)\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + cfg.kit_mass_kg, cfg.kit_mass_kg, cfg.kit_mass_kg); + + // kit_mass (edt): NEW EdtConfig::kit_mass_kg, PLACEHOLDER. + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,kit_mass,edt,%.4f,%.4f,%.4f,kg," + "\"installed EDT kit mass, PLACEHOLDER (EdtConfig::kit_mass_kg)\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + ecfg.kit_mass_kg, ecfg.kit_mass_kg, ecfg.kit_mass_kg); + + // deploy_risk: EdtConfig::deploy_failure_prob, PLACEHOLDER. Reported + // only -- NOT applied to the edt_years band above. + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,deploy_risk,edt,%.4f,%.4f,%.4f,[-]," + "\"PLACEHOLDER EDT deployment-failure probability " + "(EdtConfig::deploy_failure_prob); reported only, not applied to " + "the edt_years band above\"\n", + kSchema, c.name, c.mass_kg, c.altitude_km, c.inclination_deg, + ecfg.deploy_failure_prob, ecfg.deploy_failure_prob, + ecfg.deploy_failure_prob); +} + +// Writes generated/wp13_kit_trade_schema.md documenting the wp13_kit_trade.csv +// columns. A single fputs of one string literal -- no printf format +// specifiers, so there is zero formatting risk (mirrors main_decay_trade.cpp's +// write_schema_md). +void write_kit_trade_schema_md(const std::string& out_dir) { + std::FILE* f = std::fopen((out_dir + "/wp13_kit_trade_schema.md").c_str(), "w"); + if (!f) return; + std::fputs( +"# WP13 kit-class trade CSV schema (version 1.0)\n" +"\n" +"`generated/wp13_kit_trade.csv` is the committed per-class (A/B/C) kit-trade\n" +"table emitted by `kit_trade` (src/main_kit_trade.cpp). It combines the\n" +"EXISTING WP3 sail-decay model (src/decay.cpp: sail_decay_years,\n" +"area_for_target_years) with the NEW WP13 EDT-v1 aligned-dipole physics core\n" +"(src/decay.cpp: edt_deorbit_years) into one deterministic, timestamp-free\n" +"table (R6): sail area, EDT deorbit-time band, EDT diagnostics, kit mass\n" +"(sail and EDT), EDT deployment risk, and a generated (never hand-written)\n" +"recommended-kit row with a one-line rationale per class. Class-C\n" +"controlled-reentry mission-class detail lives in the separate\n" +"`wp13_classC.{csv,md}` pair, not here (see that file's own column notes).\n" +"\n" +"## Columns\n" +"\n" +"| column | meaning |\n" +"|---|---|\n" +"| schema_version | WP13 kit-trade schema id (`1.0`) |\n" +"| catalog | class-level preset name (`DebrisCatalog::name`) |\n" +"| mass_kg | catalog stage mass [kg] |\n" +"| altitude_km | catalog altitude [km] |\n" +"| inclination_deg | catalog inclination [deg] (drives the mandatory EDT eta(i) band) |\n" +"| record_type | see record_type rows below |\n" +"| kit_option | sail / edt, or the recommendation label (recommended_kit only) |\n" +"| value | single-value metric, or 0.0 for band-only / not-applicable rows |\n" +"| value_lo | band lower edge (see per-row meaning below) |\n" +"| value_hi | band upper edge (see per-row meaning below) |\n" +"| units | value / value_lo / value_hi units |\n" +"| notes | provenance, model-scope tag, and caveats |\n" +"\n" +"## record_type rows\n" +"\n" +"- `sail_area_25yr`: sail area for the 25-yr IADC guideline from the\n" +" EXISTING WP3 model (`area_for_target_years`, unchanged). value_lo/hi are\n" +" the solar-max/solar-min band (mirrors `wp3_decay_trade.csv`'s\n" +" `area_for_25yr` row; this does not repeat the full area sweep -- see\n" +" that file for the sweep, now including the 1000 m^2 row).\n" +"- `edt_years`: EDT-v1 deorbit-time band from `edt_deorbit_years`.\n" +" value_lo = years_optimistic (eta_hi = |cos i|, power/current-capped\n" +" edge); value_hi = years_conservative (eta_lo = cos(i)^2,\n" +" EMF/collection-limited edge) -- NEVER a point value. notes carries the\n" +" EDT-v1 model-scope tag (below). Exactly-polar catalogs (|cos i| ~= 0:\n" +" the aligned-dipole orbit-normal field vanishes) print the literal\n" +" text `n/a (polar: aligned-dipole avg force -> 0)` in value_lo/value_hi\n" +" instead of a number: this is the honest, physically-correct null result\n" +" for that idealization (see the `edt_deorbit_years` doc comment in\n" +" decay.hpp), not a missing-data placeholder. Retrograde catalogs\n" +" (i > 90 deg) get FINITE bands: the passive tether's induced current\n" +" always drags (Lenz), so the force magnitude scales with |cos i|.\n" +"- `edt_eta`: the mandatory inclination-dependent v x B efficiency band\n" +" (spec:240-243, never omitted). value_lo = eta_lo = cos(i)^2, value_hi =\n" +" eta_hi = |cos i| (see `wp13-edt-derivation.md` Section 5 for\n" +" both current-limit regimes; for i > 90 deg the EMF polarity reverses\n" +" and the magnitude is reported, with a note in the row).\n" +"- `edt_emf_power`: value = open-circuit motional EMF at the catalog\n" +" altitude [V]; value_hi = diagnostic power = emf_v * avg_current_a [W];\n" +" value_lo is unused (0.0). Diagnostic only, not a deorbit-time metric.\n" +"- `kit_mass`: installed kit mass. kit_option = sail uses the EXISTING\n" +" `Config::kit_mass_kg`; kit_option = edt uses the NEW\n" +" `EdtConfig::kit_mass_kg` (PLACEHOLDER). value = value_lo = value_hi (a\n" +" point value, not a band).\n" +"- `deploy_risk`: `EdtConfig::deploy_failure_prob`, PLACEHOLDER. Reported\n" +" only -- NOT applied to the edt_years band above (see the\n" +" `edt_deorbit_years` doc comment in decay.hpp).\n" +"- `recommended_kit`: value = 0.0 (a label row, not a numeric metric);\n" +" kit_option carries the recommendation label; notes carries the\n" +" one-line, generated (never hand-written) rationale, quoting the actual\n" +" computed sail-area and/or EDT-years numbers for that catalog. Class C's\n" +" recommendation is `controlled-reentry-mission-class`, pointing at\n" +" `wp13_classC.md` rather than a sail/EDT kit choice.\n" +"\n" +"## EDT-v1 model scope (R14 fidelity tag)\n" +"\n" +"Every `edt_years` row's notes column carries the literal tag\n" +"`[model: EDT-v1 aligned-dipole, capped-current; eta band cos(i)..cos^2(i);\n" +"libration T7 OPEN - PLACEHOLDER duty factor]`. This states the model scope\n" +"in-line with the number it qualifies (R14): aligned (untilted) dipole\n" +"geomagnetic field (SPENVIS centred dipole, IGRF epoch 2000); force capped\n" +"by either a fixed/power-limited current (optimistic edge) or an\n" +"EMF/collection-limited current (conservative edge); the along-track\n" +"efficiency factor is the orbit-averaged eta(i) in [cos(i)^2, cos(i)],\n" +"MANDATORY per spec WP13 (never a point value, never omitted for a\n" +"high-inclination catalog). Libration/dynamic stability (T7, Pelaez et al.\n" +"2000) is explicitly UNRESOLVED and is folded in only as a flat\n" +"PLACEHOLDER duty-cycle knob (`EdtConfig::eta_libration`) -- never claimed\n" +"solved. Plasma electron density is a cited PLACEHOLDER parameter, not an\n" +"IRI implementation (spec non-goal); see\n" +"`wp13-literature.md` Topic 3 for the citation and the open\n" +"solar-min/max pair gap.\n" +"\n" +"## Schema changes (R15)\n" +"\n" +"Additive columns to this file bump the minor version (1.0 -> 1.1) with\n" +"this file and all consumers updated in the same PR, the same rule as\n" +"`wp3_decay_trade_schema.md`'s own closing paragraph. Additive rows (a new\n" +"catalog, a new record_type) do NOT need a version bump under this\n" +"project's own rule.\n" + , f); + std::fclose(f); +} + +// ---------------------------------------------------------------------------- +// WP13 class-C controlled-reentry mission-class comparison. +// ---------------------------------------------------------------------------- + +// Controlled-deorbit target perigee: 6371 + 40 km, a mean-Earth-radius +// reentry-perigee convention (per WP13 task spec) -- deliberately distinct +// from the WGS-84 kEarthRadius used below for the starting circular orbit +// radius r_a. This is a stated choice, not a silent inconsistency: r_a +// matches every other altitude-to-radius conversion in this codebase +// (kEarthRadius + altitude_km*1000), while r_p uses the literal 6371+40 km +// perigee target exactly as specified. +constexpr double kReentryPerigeeRadius_m = (6371.0 + 40.0) * 1000.0; + +struct ClassCCandidate { + const char* label; + DebrisCatalog cat; +}; + +// Single impulsive perigee-lowering burn from the catalog's circular orbit +// (radius r_a) to the target perigee r_p: dv = v_c*(1 - sqrt(2*r_p/(r_a+r_p))), +// v_c = sqrt(mu/r_a). Writes the circular speed to *v_c_out for reuse by the +// caller (both the CSV and the md table report v_c alongside dv). +double controlled_deorbit_dv_ms(const DebrisCatalog& cat, double* v_c_out) { + const double r_a = kEarthRadius + cat.altitude_km * 1000.0; + const double r_p = kReentryPerigeeRadius_m; + const double v_c = std::sqrt(kEarthMu / r_a); + const double dv = v_c * (1.0 - std::sqrt(2.0 * r_p / (r_a + r_p))); + if (v_c_out) *v_c_out = v_c; + return dv; +} + +void write_classc_csv(const std::string& out_dir, const DebrisCatalog& zenit, + const DebrisCatalog& envisat) { + std::FILE* f = std::fopen((out_dir + "/wp13_classC.csv").c_str(), "w"); + if (!f) return; + std::fprintf(f, + "schema_version,candidate,mass_kg,altitude_km,inclination_deg," + "metric,value,units,notes\n"); + + const ClassCCandidate cands[] = { + {"Zenit-2 stage (catalog_A)", zenit}, + {"Envisat-class (catalog_C)", envisat}, + }; + for (const ClassCCandidate& cd : cands) { + double v_c = 0.0; + const double dv = controlled_deorbit_dv_ms(cd.cat, &v_c); + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,v_circular_initial,%.4f,m/s," + "\"circular-orbit speed at the catalog altitude, " + "v_c = sqrt(mu/r_a)\"\n", + kSchema, cd.label, cd.cat.mass_kg, cd.cat.altitude_km, + cd.cat.inclination_deg, v_c); + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,target_perigee_radius,%.1f,km," + "\"controlled-deorbit target perigee, mean-Earth-radius " + "convention 6371+40 km (distinct from the WGS-84 kEarthRadius " + "used for r_a)\"\n", + kSchema, cd.label, cd.cat.mass_kg, cd.cat.altitude_km, + cd.cat.inclination_deg, kReentryPerigeeRadius_m / 1000.0); + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,controlled_deorbit_dv,%.4f,m/s," + "\"impulsive perigee-lowering burn from circular (RE+alt) to " + "perigee 6371+40 km: dv = v_c*(1-sqrt(2*r_p/(r_a+r_p)))\"\n", + kSchema, cd.label, cd.cat.mass_kg, cd.cat.altitude_km, + cd.cat.inclination_deg, dv); + } + std::fclose(f); +} + +void write_classc_md(const std::string& out_dir, const DebrisCatalog& zenit, + const DebrisCatalog& envisat) { + std::FILE* f = std::fopen((out_dir + "/wp13_classC.md").c_str(), "w"); + if (!f) return; + + double v_c_z = 0.0, v_c_e = 0.0; + const double dv_z = controlled_deorbit_dv_ms(zenit, &v_c_z); + const double dv_e = controlled_deorbit_dv_ms(envisat, &v_c_e); + + std::fprintf(f, "# WP13 Class-C controlled-reentry mission-class comparison\n\n"); + std::fprintf(f, + "Class C (spec WP13) is a **separate mission class**, not a sail/EDT " + "kit choice: massive/high-risk derelicts where the deliverable is a " + "controlled reentry, evaluated on casualty risk, ground footprint, " + "delta-v, consent, and cost. The comparison against tug architectures " + "is allowed **here only**, and only on the **same target set** (spec " + "WP13/WP14, instr Sec 0.4). Data source: `generated/wp13_classC.csv` " + "(schema %s), regenerated by `kit_trade` -- no hand-written numbers.\n\n", + kSchema); + + std::fprintf(f, "## Candidates (same target set)\n\n"); + std::fprintf(f, "| candidate | mass [kg] | altitude [km] | inclination [deg] | source |\n"); + std::fprintf(f, "|---|---:|---:|---:|---|\n"); + std::fprintf(f, + "| Zenit-2 / SL-16 second stage (`catalog_A`) | %.0f | %.0f | %.1f | " + "web-verified ~8300(-9000) kg, ~845x857 km, 71.01 deg (Tselina-2 " + "family), `wp13-literature.md` Topic 5a; also class A's " + "sail/EDT anchor -- massive by the same McKnight et al. 2021 top-50 " + "ranking that motivates class C |\n", + zenit.mass_kg, zenit.altitude_km, zenit.inclination_deg); + std::fprintf(f, + "| Envisat-class massive SSO payload (`catalog_C`) | %.0f | %.0f | %.1f | " + "web-verified 8211 kg, ~765-800 km sun-synchronous, ~98.4-98.55 deg, " + "eoPortal / ESA Earth Online, `wp13-literature.md` " + "Topic 6 item 1 |\n\n", + envisat.mass_kg, envisat.altitude_km, envisat.inclination_deg); + + std::fprintf(f, "## Uncontrolled-reentry casualty framing (cited, not computed)\n\n"); + std::fprintf(f, + "NASA-STD-8719.14A (with Change 1), Requirement 4.7-1: the expected " + "worldwide human-casualty risk from reentering debris shall not " + "exceed **Ec < 1e-4** (1 in 10,000); casualty is assumed for any " + "surviving fragment with impact kinetic energy **> 15 J**; a " + "controlled reentry must additionally ensure no surviving >15 J " + "fragment lands within **370 km of foreign landmasses** " + "(soma.larc.nasa.gov/SIMPLEx/pdf_files/871914.pdf; ESA applies the " + "same 1e-4 limit via DRAMA/SARA, technology.esa.int/page/" + "re-entry-safety -- both web-verified, " + "`wp13-literature.md` Topic 7). This repo does **not** " + "compute a per-object Ec for either candidate: that requires a " + "survivability / ground-footprint tool (NASA DAS/ORSAT or ESA " + "SARA class), out of scope here: " + "**[CITATION NEEDED - PLACEHOLDER: per-object Ec analysis]**. Both " + "candidates are multi-tonne, so an UNCONTROLLED reentry is the " + "presumptive risk driver the 370 km / Ec<1e-4 rule exists for; that " + "presumption is not itself a computed Ec value.\n\n"); + + std::fprintf(f, "## Controlled-deorbit delta-v (COMPUTED)\n\n"); + std::fprintf(f, + "Single impulsive perigee-lowering burn from the catalog's circular " + "orbit (radius r_a = R_E + altitude) to a target perigee r_p = " + "6371 + 40 km (mean-Earth-radius convention for the reentry perigee, " + "distinct from the WGS-84 kEarthRadius used for r_a elsewhere in " + "this repo -- a deliberate, documented choice, not a silent " + "inconsistency): dv = v_c * (1 - sqrt(2 r_p / (r_a + r_p))), v_c = " + "sqrt(mu / r_a). This is the minimum single-burn delta-v to commit " + "the stage to reentry; it excludes targeting/footprint-control burns " + "and any margin. Full precision in `generated/wp13_classC.csv`.\n\n"); + std::fprintf(f, "| candidate | v_c [m/s] | dv [m/s] |\n"); + std::fprintf(f, "|---|---:|---:|\n"); + std::fprintf(f, "| Zenit-2 / SL-16 (catalog_A) | %.1f | **%.1f** |\n", v_c_z, dv_z); + std::fprintf(f, "| Envisat-class (catalog_C) | %.1f | **%.1f** |\n\n", v_c_e, dv_e); + + std::fprintf(f, "## Consent gate\n\n"); + std::fprintf(f, + "Legal accessibility enters as a **gate and metadata flags, never a " + "multiplier** (D12): the WP8 compliance engine BLOCKs unconsented " + "active debris removal, and target prioritization consumes its " + "PASS/BLOCK output and flags rather than folding a subjective legal " + "weight into any score (`tools/compliance/check_compliance.py`, " + "policy `ADSC-POL-01`, consent-missing -> BLOCK). Any controlled-" + "reentry mission on either candidate above is subject to that same " + "gate: no consent, no operation, regardless of the casualty-risk or " + "delta-v numbers on this page.\n\n"); + + std::fprintf(f, "## Kit-installer vs tug (same target set)\n\n"); + std::fprintf(f, + "Steelmanned: a tug architecture also amortizes fixed costs (launch, " + "ground segment, ops) across a batch of targets visited in one plane, " + "exactly like the kit-installer's own batch amortization (WP6 " + "amortization curve). The comparison is fair only because it is the " + "SAME target set (spec WP14 rule) -- comparing a tug against a " + "different, easier target set would be meaningless. What does NOT " + "amortize for a tug, and is the installer's structural edge (D1: " + "installer-not-tug): (1) a tug must carry propellant to change EACH " + "target's orbit itself, so its per-target delta-v cost scales with " + "target count, while an installer's kit-carrying stages do their own " + "deorbit after release -- the installer pays no per-target deorbit " + "delta-v; (2) a tug must detumble, dock/grapple, and physically tow " + "each multi-tonne stage through its own controlled-reentry burn, " + "repeating the highest-risk contact event per target, while an " + "installer's own contact event is bounded by the WP3 low-energy " + "capture approach (0.333 J contact-energy budget at 0.15 m/s, " + "README/evidence pack Section on WP3) and does not itself carry a " + "target through reentry. Both architectures still need SOME per-" + "target consent/compliance gate (D12) and SOME reentry delta-v paid " + "by someone (either pre-installed as a kit, or delivered by the tug) " + "-- the installer's edge is where that delta-v and that repeated " + "high-risk contact event are paid, not that they vanish.\n\n"); + + std::fprintf(f, "## Cost\n\n"); + std::fprintf(f, "| candidate | cost | source |\n"); + std::fprintf(f, "|---|---|---|\n"); + std::fprintf(f, "| Zenit-2 / SL-16 (catalog_A) | PLACEHOLDER until WP14 | WP14 cost-range itemization |\n"); + std::fprintf(f, "| Envisat-class (catalog_C) | PLACEHOLDER until WP14 | WP14 cost-range itemization |\n\n"); + + std::fprintf(f, "## `wp13_classC.csv` columns\n\n"); + std::fprintf(f, "| column | meaning |\n"); + std::fprintf(f, "|---|---|\n"); + std::fprintf(f, "| schema_version | class-C schema id (`%s`) |\n", kSchema); + std::fprintf(f, "| candidate | candidate label (Zenit-2 stage / Envisat-class) |\n"); + std::fprintf(f, "| mass_kg, altitude_km, inclination_deg | catalog parameters (constant per candidate) |\n"); + std::fprintf(f, "| metric | v_circular_initial / target_perigee_radius / controlled_deorbit_dv |\n"); + std::fprintf(f, "| value | metric value at full precision (the md table above rounds to 0.1) |\n"); + std::fprintf(f, "| units | m/s or km |\n"); + std::fprintf(f, "| notes | formula / provenance |\n"); + + std::fclose(f); +} + +} // namespace + +int main(int argc, char** argv) { + const std::string out_dir = (argc > 1) ? argv[1] : "generated"; + std::error_code ec; + std::filesystem::create_directories(out_dir, ec); + + const Config cfg; + const EdtConfig ecfg; + const double stop_km = cfg.reentry_handoff_altitude_km; + + const DebrisCatalog A = catalog_A(); + const DebrisCatalog B = catalog_B(); + const DebrisCatalog C = catalog_C(); + + std::FILE* f = std::fopen((out_dir + "/wp13_kit_trade.csv").c_str(), "w"); + if (!f) return 1; + std::fprintf(f, + "schema_version,catalog,mass_kg,altitude_km,inclination_deg,record_type," + "kit_option,value,value_lo,value_hi,units,notes\n"); + + // --- Catalog A: SL-16/Zenit-2 -- sail infeasible, EDT candidate with + // open risks (T7 libration, plasma Ne PLACEHOLDER). ------------------- + { + const double a25_max = area_for_target_years(A, 25.0, cfg.kit_mass_kg, + cfg.drag_cd, stop_km, cfg.solar_max_density_factor); + const double a25_min = area_for_target_years(A, 25.0, cfg.kit_mass_kg, + cfg.drag_cd, stop_km, cfg.solar_min_density_factor); + const EdtResult edt = edt_deorbit_years(A, ecfg, stop_km); + write_common_rows(f, A, cfg, ecfg, a25_max, a25_min, edt); + + char notes[512]; + std::snprintf(notes, sizeof(notes), + "sail infeasible (%.0f..%.0f m2 for the 25-yr guideline, solar " + "max..min); EDT (band %.1f..%.1f yr) subject to T7; libration " + "(T7, Pelaez et al. 2000) and plasma Ne (PLACEHOLDER solar-min/" + "max pair, wp13-literature.md Topic 3) remain open -- an honest " + "EDT candidate, not a closed recommendation", + a25_max, a25_min, edt.years_optimistic, edt.years_conservative); + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,recommended_kit,edt-candidate-open-risks," + "0.0,0.0,0.0,[-],\"%s\"\n", + kSchema, A.name, A.mass_kg, A.altitude_km, A.inclination_deg, notes); + } + + // --- Catalog B: SL-8/Kosmos-3M -- sail closes, no EDT needed. ----------- + { + const double a25_max = area_for_target_years(B, 25.0, cfg.kit_mass_kg, + cfg.drag_cd, stop_km, cfg.solar_max_density_factor); + const double a25_min = area_for_target_years(B, 25.0, cfg.kit_mass_kg, + cfg.drag_cd, stop_km, cfg.solar_min_density_factor); + const EdtResult edt = edt_deorbit_years(B, ecfg, stop_km); + write_common_rows(f, B, cfg, ecfg, a25_max, a25_min, edt); + + char notes[256]; + std::snprintf(notes, sizeof(notes), + "sail closes (%.0f..%.0f m2 for the 25-yr guideline, solar " + "max..min); no EDT kit needed for this class", + a25_max, a25_min); + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,recommended_kit,sail,0.0,0.0,0.0,[-],\"%s\"\n", + kSchema, B.name, B.mass_kg, B.altitude_km, B.inclination_deg, notes); + } + + // --- Catalog C: Envisat-class -- controlled-reentry mission class, + // not a sail/EDT kit recommendation (see wp13_classC.md). ----------- + { + const double a25_max = area_for_target_years(C, 25.0, cfg.kit_mass_kg, + cfg.drag_cd, stop_km, cfg.solar_max_density_factor); + const double a25_min = area_for_target_years(C, 25.0, cfg.kit_mass_kg, + cfg.drag_cd, stop_km, cfg.solar_min_density_factor); + const EdtResult edt = edt_deorbit_years(C, ecfg, stop_km); + write_common_rows(f, C, cfg, ecfg, a25_max, a25_min, edt); + + std::fprintf(f, + "%s,%s,%.1f,%.1f,%.1f,recommended_kit,controlled-reentry-mission-class," + "0.0,0.0,0.0,[-],\"massive (%.0f kg) high-inclination payload -- " + "sail/EDT kit-only deorbit is not the recommended mission class; " + "see generated/wp13_classC.md for the controlled-reentry " + "comparison\"\n", + kSchema, C.name, C.mass_kg, C.altitude_km, C.inclination_deg, + C.mass_kg); + } + + std::fclose(f); + + write_kit_trade_schema_md(out_dir); + write_classc_csv(out_dir, A, C); + write_classc_md(out_dir, A, C); + + std::printf("[WP13] wrote %s/wp13_kit_trade.csv, wp13_kit_trade_schema.md, " + "wp13_classC.csv and wp13_classC.md (3 catalogs, stop %.0f km)\n", + out_dir.c_str(), stop_km); + return 0; +} diff --git a/tests/test_decay.cpp b/tests/test_decay.cpp index 7cc5209..feb2778 100644 --- a/tests/test_decay.cpp +++ b/tests/test_decay.cpp @@ -89,6 +89,100 @@ int main() { CHECK(d2 < d1); } + // 7. EDT physics core (WP13): eta(i) = |cos i| (optimistic) / cos(i)^2 + // (conservative), eta(0) = 1, matching the derivation-note table + // (wp13-edt-derivation.md Section 8.3). + { + const DebrisCatalog equatorial{"eta-test equatorial", 9000.0, 840.0, 0.0, false}; + const EdtConfig cfg{}; + const EdtResult eq = edt_deorbit_years(equatorial, cfg, 180.0); + CHECK(std::abs(eq.eta_hi - 1.0) < 1e-12); + CHECK(std::abs(eq.eta_lo - 1.0) < 1e-12); + + const EdtResult a71 = edt_deorbit_years(catalog_A(), cfg, 180.0); + CHECK(std::abs(a71.eta_hi - 0.3255681545) < 1e-6); + CHECK(std::abs(a71.eta_lo - 0.1059946232) < 1e-6); + } + + // 8. EDT deorbit-time band: the conservative (EMF-limited, cos^2 i) edge + // always takes longer than the optimistic (power-capped, cos i) edge + // for a non-equatorial catalog -- never collapse to a point value. + { + const EdtResult a = edt_deorbit_years(catalog_A(), EdtConfig{}, 180.0); + CHECK(std::isfinite(a.years_optimistic)); + CHECK(std::isfinite(a.years_conservative)); + CHECK(a.years_conservative > a.years_optimistic); + } + + // 9. Cross-check: the public power-law EDT integrator against the + // elementary closed form t = m*sqrt(mu)*(a0^2.5 - as^2.5) / (5*C) for + // F(a) = C / a^3 (aligned-dipole B_n ~ a^-3, wp13-edt-derivation.md + // Section 8.1), and against edt_deorbit_years' own optimistic-edge + // output for the SAME physical constants (verifies the algebra AND the + // wiring, mirroring CHECK 1's integrator-vs-closed-form pattern). + { + const double kB0_T = 3.01153e-5; // SPENVIS centred dipole, IGRF 2000 + const double kDipoleRefRadius = 6371200.0; // m, SPENVIS centred-dipole radius + const double kSecPerYear = 365.25 * 86400.0; + + const DebrisCatalog A = catalog_A(); + const EdtConfig cfg{}; + const double cos_i = std::cos(A.inclination_deg * kPi / 180.0); + const double a0 = kEarthRadius + A.altitude_km * 1000.0; + const double a_stop = kEarthRadius + 180.0 * 1000.0; + const double m = A.mass_kg + cfg.kit_mass_kg; + const double C_hi = cfg.avg_current_a * cfg.tether_length_m * kB0_T * + std::pow(kDipoleRefRadius, 3.0) * cos_i * cfg.eta_libration; + + const double t_num = integrate_edt_seconds( + a0, a_stop, m, [C_hi](double a) { return C_hi / (a * a * a); }); + const double t_closed = m * std::sqrt(kEarthMu) * + (std::pow(a0, 2.5) - std::pow(a_stop, 2.5)) / (5.0 * C_hi); + CHECK(std::abs(t_num - t_closed) / t_closed < 1e-6); + + const EdtResult r = edt_deorbit_years(A, cfg, 180.0); + const double closed_years = t_closed / kSecPerYear; + CHECK(std::abs(r.years_optimistic - closed_years) / closed_years < 1e-6); + } + + // 10. Monotonicity: years increase (deorbit is slower) as inclination + // departs from equatorial, matching eta(i)'s monotonic decrease. + { + const DebrisCatalog A = catalog_A(); + DebrisCatalog A_equatorial = A; + A_equatorial.inclination_deg = 0.0; + const EdtConfig cfg{}; + const EdtResult incl = edt_deorbit_years(A, cfg, 180.0); + const EdtResult equ = edt_deorbit_years(A_equatorial, cfg, 180.0); + CHECK(incl.years_optimistic > equ.years_optimistic); + CHECK(incl.years_conservative > equ.years_conservative); + } + + // 11. Polar orbit: the aligned-dipole orbit-normal field is exactly zero + // (wp13-edt-derivation.md Section 6, limit L3) -> infinite deorbit + // time, reported honestly rather than as a divide-by-zero artifact. + { + const DebrisCatalog polar{"eta-test polar", 9000.0, 840.0, 90.0, false}; + const EdtResult p = edt_deorbit_years(polar, EdtConfig{}, 180.0); + CHECK(std::isinf(p.years_optimistic)); + CHECK(std::isinf(p.years_conservative)); + } + + // 12. Retrograde orbit (catalog_C, Envisat-class at 98.4 deg): the passive + // tether's induced current always drags (Lenz), so the force magnitude + // scales with |cos i| and the deorbit time is FINITE -- only an + // exactly-polar orbit nulls the aligned-dipole coupling. eta_hi is the + // magnitude |cos 98.4 deg|, eta_lo its square. + { + const EdtResult r = edt_deorbit_years(catalog_C(), EdtConfig{}, 180.0); + CHECK(std::abs(r.eta_hi - 0.1460830286) < 1e-6); + CHECK(std::abs(r.eta_lo - 0.0213402512) < 1e-6); + CHECK(!std::isinf(r.years_optimistic)); + CHECK(!std::isinf(r.years_conservative)); + CHECK(r.years_conservative > r.years_optimistic); + CHECK(r.emf_v > 0.0); // reported as a magnitude (polarity note in docs) + } + std::printf("decay: all tests passed\n"); return 0; } diff --git a/tools/evidence/make_evidence.py b/tools/evidence/make_evidence.py index 2d4c67c..51159a1 100644 --- a/tools/evidence/make_evidence.py +++ b/tools/evidence/make_evidence.py @@ -54,6 +54,8 @@ def __init__(self, root): self.wp6 = load_csv(os.path.join(g, "wp6_cost_summary.csv")) self.wp3 = load_csv(os.path.join(g, "wp3_decay_trade.csv")) self.wp12 = load_csv(os.path.join(g, "wp12_ladder.csv")) + self.wp13 = load_csv(os.path.join(g, "wp13_kit_trade.csv")) + self.wp13c = load_csv(os.path.join(g, "wp13_classC.csv")) with open(os.path.join(g, "t6_flux_sweep.md"), encoding="utf-8") as f: self.t6 = {} for line in f: @@ -66,6 +68,14 @@ def __init__(self, root): for r in self.wp5: if r["catalog"] not in self.catalogs: self.catalogs.append(r["catalog"]) + self.wp13_catalogs = [] + for r in self.wp13: + if r["catalog"] not in self.wp13_catalogs: + self.wp13_catalogs.append(r["catalog"]) + self.wp13c_candidates = [] + for r in self.wp13c: + if r["candidate"] not in self.wp13c_candidates: + self.wp13c_candidates.append(r["candidate"]) def wp5m(self, catalog, metric): for r in self.wp5: @@ -97,6 +107,25 @@ def wp3_area25(self, catalog): return float(r["value_solar_max"]), float(r["value_solar_min"]) raise KeyError(catalog) + def wp13v(self, catalog, record_type, kit_option=None): + """One row from generated/wp13_kit_trade.csv (WP13 kit-class trade + + EDT-v1 physics). kit_option disambiguates only where a (catalog, + record_type) pair has more than one row (e.g. kit_mass: sail vs + edt); every other record_type has exactly one row per catalog.""" + for r in self.wp13: + if (r["catalog"] == catalog and r["record_type"] == record_type + and (kit_option is None or r["kit_option"] == kit_option)): + return r + raise KeyError((catalog, record_type, kit_option)) + + def wp13cv(self, candidate, metric): + """One row from generated/wp13_classC.csv (WP13 class-C controlled- + reentry mission comparison).""" + for r in self.wp13c: + if r["candidate"] == candidate and r["metric"] == metric: + return r + raise KeyError((candidate, metric)) + def rate_str(row): """'0.556 [0.512, 0.599] (Wilson 95%)' from a WP5 rate row.""" @@ -124,6 +153,19 @@ def pctl_str(row, dp=2): fmt(row["p05"]), fmt(row["p50"]), fmt(row["p95"])) +def edt_band_str(row, fmt): + """'lo..hi' from a wp13_kit_trade.csv value_lo/value_hi band, formatted + with fmt -- or the literal text verbatim (e.g. an exactly-polar catalog's + 'n/a (polar: aligned-dipole avg force -> 0)') when the field is not a + number: that text is the honest physical null result, not missing data + (see wp13_kit_trade_schema.md's edt_years row).""" + lo, hi = row["value_lo"], row["value_hi"] + try: + return "%s..%s" % (fmt(float(lo)), fmt(float(hi))) + except ValueError: + return "%s .. %s" % (lo, hi) + + def collect_placeholders(root): """Mechanically collect every uppercase PLACEHOLDER mark from the source tree (include/, src/, tools/ minus tools/evidence which only names the @@ -530,6 +572,74 @@ def build(d): w(" with a deliberately coarse altitude-independent solar factor marked") w(" PLACEHOLDER.") w("") + w("### Kit-class trade + EDT physics (WP13)") + w("") + w("Data source: `generated/wp13_kit_trade.csv` (schema %s; column" + % d.wp13[0]["schema_version"]) + w("definitions in `generated/wp13_kit_trade_schema.md`), regenerated by") + w("`kit_trade` (src/main_kit_trade.cpp) by combining the EXISTING WP3") + w("sail-decay model with the NEW WP13 EDT-v1 aligned-dipole physics core") + w("(src/decay.cpp: edt_deorbit_years) -- no hand-written numbers.") + w("") + w("| catalog | recommended kit | sail area, 25-yr [m^2] | EDT deorbit time [yr] | EDT eta(i) band |") + w("|---|---|---:|---:|---:|") + for cat in d.wp13_catalogs: + rk = d.wp13v(cat, "recommended_kit") + sail = d.wp13v(cat, "sail_area_25yr") + edt = d.wp13v(cat, "edt_years") + eta = d.wp13v(cat, "edt_eta") + w("| %s | %s | %s | %s | %s |" % ( + cat, rk["kit_option"], edt_band_str(sail, f0), + edt_band_str(edt, f1), edt_band_str(eta, f3))) + w("") + edt_tag = d.wp13v(A, "edt_years")["notes"] + w("**EDT-v1 model scope (R14 fidelity tag), quoted verbatim from the") + w("committed CSV -- never hand-transcribed, so the generator and the data") + w("cannot drift:**") + w("") + w("> %s" % edt_tag) + w("") + deploy = d.wp13v(A, "deploy_risk") + rk_A = d.wp13v(A, "recommended_kit") + w("**Honesty check (T7, D12/D13 - never claim closed).** Libration /") + w("dynamic tether stability is explicitly UNRESOLVED (T7; Pelaez, J., et") + w("al., 2000, cited in `wp13-literature.md`); the edt_years band above") + w("folds it in only as a flat PLACEHOLDER duty-cycle knob") + w("(`EdtConfig::eta_libration`), never claimed solved. Plasma electron") + w("density is a cited PLACEHOLDER solar-min/max pair, not an IRI") + w("implementation (spec non-goal; `wp13-literature.md` Topic 3 citation") + w("gap). Every band above is an aligned-dipole idealization (SPENVIS") + w("centred dipole, IGRF epoch 2000), not a tilted-dipole or full-IGRF") + w("field. Deploy risk is reported, not applied: deploy_risk = %s [-]" + % f4(deploy["value"])) + w("(PLACEHOLDER `EdtConfig::deploy_failure_prob`) is quoted for the") + w("record and is NOT folded into the edt_years band above. Bottom line,") + w("EDT is a candidate for the %s class, not a closed recommendation --" + % Ashort) + w("quoted verbatim from the committed CSV's own recommended-kit") + w("rationale: \"%s\"" % rk_A["notes"]) + w("") + w("**Class C (controlled reentry, a separate mission class - not a") + w("sail/EDT kit choice).** Massive/high-risk derelicts where the") + w("deliverable is a controlled reentry, evaluated on casualty risk,") + w("ground footprint, delta-v, consent and cost in") + w("`generated/wp13_classC.md` (data: `generated/wp13_classC.csv`, schema") + w("%s), not on a sail/EDT kit trade. Computed controlled-deorbit delta-v" + % d.wp13c[0]["schema_version"]) + w("(single impulsive perigee-lowering burn from the catalog's circular") + w("orbit to a target perigee of 6371+40 km):") + w("") + w("| candidate | v_c [m/s] | delta-v [m/s] |") + w("|---|---:|---:|") + for cand in d.wp13c_candidates: + vc = d.wp13cv(cand, "v_circular_initial") + dv = d.wp13cv(cand, "controlled_deorbit_dv") + w("| %s | %s | %s |" % (cand, f1(vc["value"]), f1(dv["value"]))) + w("") + w("Cross-reference: the eta(i) closed-form derivation is") + w("`wp13-edt-derivation.md`; the citation pack is `wp13-literature.md`") + w("(both committed at the repo root).") + w("") w("## 5. Campaign statistics and cost/FoM") w("") w("WP5 Monte Carlo: %s dispersed missions per catalog at fixed master seed" diff --git a/tools/evidence/test_evidence.py b/tools/evidence/test_evidence.py index 83e0228..3aa3be2 100644 --- a/tools/evidence/test_evidence.py +++ b/tools/evidence/test_evidence.py @@ -107,11 +107,21 @@ def main(): (r"ds-v2", "L2 ladder re-verification (ds-v2 stream)"), (r"\[L4: L0 dynamics \+ dropout", "L4 estimate-driven guidance"), - (r"\[L5: MIB/delay/fault", "L5 actuator realization")): + (r"\[L5: MIB/delay/fault", "L5 actuator realization"), + (r"\[model: EDT-v1 aligned-dipole", + "EDT-v1 model-scope tag")): check(re.search(tag, pack) is not None, "pack lacks R14-tagged headline claim: %s" % why) check("element" in pack[pack.find("TRL 4"):pack.find("TRL 4") + 200], "TRL 4 statement is not element-scoped") + # T7 honesty check (WP13): libration/dynamic tether stability must be + # flagged as an open, unresolved risk -- never claimed solved. + check("libration" in low, "pack lacks 'libration' (T7 honesty check)") + check(re.search(r"\bT7\b", pack) is not None, + "pack lacks a 'T7' reference (T7 honesty check)") + check(re.search(r"UNRESOLVED", pack) is not None or + re.search(r"\bOPEN\b", pack) is not None, + "pack lacks 'UNRESOLVED' or 'OPEN' for the T7 libration caveat") # citation discipline: the pack either cites fully or marks the gap - # the marker must exist for the knowingly-unconfirmed external sources. check("[CITATION NEEDED - PLACEHOLDER" in pack, @@ -167,6 +177,18 @@ def must_contain(s, why): must_contain("%.0f..%.0f m^2" % (float(r["value_solar_max"]), float(r["value_solar_min"])), "25-yr sail area range for %s" % r["catalog"]) + # WP13 EDT-v1 deorbit-time band for the SL-16 / Zenit-2 catalog, formatted + # exactly as make_evidence.py's edt_band_str(row, f1) formats it. + wp13 = load_csv(os.path.join(g, "wp13_kit_trade.csv")) + for r in wp13: + if (r["catalog"] == "SL-16 / Zenit-2 second stage" + and r["record_type"] == "edt_years"): + must_contain("%.1f..%.1f" % (float(r["value_lo"]), + float(r["value_hi"])), + "SL-16 EDT-v1 deorbit-time band (WP13)") + break + else: + check(False, "SL-16 edt_years row not found in wp13_kit_trade.csv") # rates with Wilson interval formatting for metric in ("success_rate", "keep_out_violation_rate", "gate_abort_run_rate"): diff --git a/tools/regenerate_all.sh b/tools/regenerate_all.sh index 63cb3c4..357367e 100644 --- a/tools/regenerate_all.sh +++ b/tools/regenerate_all.sh @@ -13,25 +13,27 @@ set -euo pipefail cd "$(dirname "$0")/.." # repo root BUILD="${1:-build}" -echo "[1/10] WP5 campaign Monte Carlo (N=500 x 2 catalogs)" +echo "[1/11] WP5 campaign Monte Carlo (N=500 x 2 catalogs)" "$BUILD/adsc_campaign" 500 generated -echo "[2/10] WP12 fidelity ladder (L0/L1/L2 re-verification of the WP5 abort events)" +echo "[2/11] WP12 fidelity ladder (L0/L1/L2 re-verification of the WP5 abort events)" "$BUILD/adsc_ladder" 500 generated -echo "[3/10] WP6 cost model + FoM (kit sweep, consumes WP5)" +echo "[3/11] WP6 cost model + FoM (kit sweep, consumes WP5)" "$BUILD/adsc_cost" 500 generated -echo "[4/10] WP3 decay-trade CSV" +echo "[4/11] WP3 decay-trade CSV" "$BUILD/decay_trade" generated -echo "[5/10] T6 small-debris flux sweep" +echo "[5/11] WP13 kit-class trade + class-C controlled-reentry comparison" +"$BUILD/kit_trade" generated +echo "[6/11] T6 small-debris flux sweep" "$BUILD/flux_sweep" generated -echo "[6/10] WP7 reference metrics (WP1/F1/WP2/WP3/WP4 pinned numbers)" +echo "[7/11] WP7 reference metrics (WP1/F1/WP2/WP3/WP4 pinned numbers)" "$BUILD/sim_metrics" generated -echo "[7/10] WP10c keep-out violation forensics (read-only replay of WP5)" +echo "[8/11] WP10c keep-out violation forensics (read-only replay of WP5)" python3 tools/forensics/make_forensics.py -echo "[8/10] WP7a visualization pack" +echo "[9/11] WP7a visualization pack" python3 tools/viz/make_viz.py . generated/viz -echo "[9/10] WP8 compliance precheck + matrix (not legal advice)" +echo "[10/11] WP8 compliance precheck + matrix (not legal advice)" python3 tools/compliance/check_compliance.py python3 tools/compliance/generate_matrix.py -echo "[10/10] WP7 evidence pack" +echo "[11/11] WP7 evidence pack" python3 tools/evidence/make_evidence.py echo "regenerate_all: complete" diff --git a/wp13-edt-derivation.md b/wp13-edt-derivation.md new file mode 100644 index 0000000..a948344 --- /dev/null +++ b/wp13-edt-derivation.md @@ -0,0 +1,345 @@ +# WP13 Annex — Orbit-Averaged Inclination Efficiency eta(i) for a Nadir-Aligned Electrodynamic Tether + +Status: derivation for review (Fable5 reviews before coding, WP13 task-2). +Scope: closed-form derivation + deterministic quadrature cross-check of the +mandatory inclination-dependent v x B efficiency (spec:247-251, instr 0.1). +All math ASCII; every step reproducible. Numerics cross-checked by a 64-node +Gauss-Legendre quadrature (script at end; results pinned in Section 8). + +-------------------------------------------------------------------------- +## 0. Result up front (for the impatient reviewer) + +For a tether deployed along the local vertical (radial / nadir-zenith) in an +Earth-centered ALIGNED-dipole field, on a circular orbit of radius r and +inclination i, the orbit-averaged along-track (drag) efficiency is CLOSED FORM: + + Fixed-current model (I held at a power/limit-capped value): + eta_F(i) = cos(i) + + Self-consistent model (current scales with the motional EMF, I ~ EMF): + eta_SC(i) = cos(i)^2 + +both normalized so eta(0) = 1. No quadrature is actually required for the +aligned dipole; the 64-node Gauss-Legendre integration in Section 8 reproduces +these to 2.2e-16 and exists only as a cross-check and as the general-field +harness (a future tilted dipole reintroduces u-dependence). + +Why so clean: for a radial tether the ONLY field component that produces an +along-track Lorentz force -- and the only one that appears in the radial +tether's motional EMF -- is the orbit-normal component B_n, and for an aligned +dipole B_n = B0 (RE/r)^3 cos(i) is CONSTANT around the orbit. The u-varying +components B_r and B_t cancel out of the along-track projection. + +-------------------------------------------------------------------------- +## 1. Assumptions and caveats (stated, not hidden) + +A1. Circular orbit, radius r, inclination i, argument of latitude u measured + from the ascending node. Orbital speed v = sqrt(GM/r), inertial. +A2. Tether along the local vertical: t_hat = e_r (radial, zenith-nadir). + Rigid, straight; libration IGNORED here (T7 open -- separate penalty knob, + instr 0.2; never claimed solved). +A3. Geomagnetic field = Earth-centered ALIGNED dipole: moment along the Earth + rotation / geographic axis z_hat. + CAVEAT (tilt): the real dipole is tilted ~11.5 deg (and offset). The tilt + rotates B_n by a slowly varying phase and, critically, makes eta(90 deg) + small-but-nonzero instead of exactly zero (Section 6). Treat the aligned + result as the leading-order geometry; carry tilt as an uncertainty, not a + correction baked into v1. +A4. Non-rotating plasma / inertial frame for the motional EMF: the corotation + electric field E_corot = -(Omega_E x r) x B is IGNORED. + CAVEAT (corotation): at LEO v_orbit ~ 7.6 km/s while the corotation speed + ~0.4 km/s, so the neglected term is ~5% of the EMF and is largest near the + equator; it does not change the cos(i) geometry, only the scalar prefactor. + Fold into the current-efficiency band, do not model explicitly in v1. +A5. B0 = mu0 m /(4 pi RE^3) ~ 3.12e-5 T is the equatorial surface field. + Define the shorthand beta(r) = B0 (RE/r)^3. + +-------------------------------------------------------------------------- +## 2. Aligned-dipole field in the orbital frame + +### 2.1 Field vector form +A magnetic dipole of moment m_vec = m * m_hat produces, at position R, + + B = (mu0 m / 4 pi R^3) [ 3 (m_hat . r_hat) r_hat - m_hat ]. + +For Earth the external field points NORTH at the equator, which requires the +moment to point SOUTH: m_hat = -z_hat. With beta = B0 (RE/r)^3, + + B = beta [ 3 (-z_hat . r_hat) r_hat - (-z_hat) ] + = beta [ z_hat - 3 (z_hat . r_hat) r_hat ]. (2.1) + +Sanity: at the equator z_hat . r_hat = 0 => B = beta z_hat (northward). OK. + +### 2.2 Orbital frame unit vectors +Place the ascending node on the x-axis (RAAN = 0; RAAN only rotates the whole +picture about z and drops out of orbit averages). At argument of latitude u: + + r_hat = ( cos u , sin u cos i , sin u sin i ) (radial out) + e_t = d r_hat/du = ( -sin u , cos u cos i , cos u sin i ) (along-track) + e_n = r_hat x e_t = ( 0 , -sin i , cos i ) (orbit normal) + +e_n is constant (fixed orbit plane), as it must be. (e_r, e_t, e_n) is a +right-handed triad: e_r x e_t = e_n, e_t x e_n = e_r, e_n x e_r = e_t. + +The z-projections we will need: + z_hat . r_hat = sin i sin u (= cos(colatitude) = sin(mag. latitude)) + z_hat . e_t = sin i cos u + z_hat . e_n = cos i + +Note the magnetic latitude relation sin(lambda) = sin i sin u (spherical +triangle: a great circle inclined at i has latitude lambda at angle u from the +node with sin lambda = sin i sin u). + +### 2.3 Components (project 2.1 onto the triad) +Using B . a = beta [ (z_hat.a) - 3 (z_hat.r_hat)(r_hat.a) ] and r_hat.e_t = +r_hat.e_n = 0, r_hat.r_hat = 1: + + B_r = B . e_r = beta [ sin i sin u - 3 sin i sin u ] = -2 beta sin i sin u + B_t = B . e_t = beta [ sin i cos u - 0 ] = beta sin i cos u + B_n = B . e_n = beta [ cos i - 0 ] = beta cos i (2.3) + + B_r = -2 B0 (RE/r)^3 sin i sin u (radial) + B_t = B0 (RE/r)^3 sin i cos u (along-track) + B_n = B0 (RE/r)^3 cos i (orbit-normal, CONSTANT in u) + +The B_r form matches the value given in the task statement. + +### 2.4 Consistency check vs textbook spherical components +Geomagnetic spherical components (theta = colatitude): B_r = -2 beta cos theta, +B_theta = -beta sin theta, B_phi = 0. With cos theta = sin i sin u this gives +B_r = -2 beta sin i sin u (matches 2.3). The tangential-plane magnitude must be +frame-independent: + + B_theta^2 + B_phi^2 = beta^2 sin^2 theta = beta^2 (1 - sin^2 i sin^2 u) + B_t^2 + B_n^2 = beta^2 (sin^2 i cos^2 u + cos^2 i) = beta^2 (1 - sin^2 i sin^2 u) + +They agree identically. Full magnitude: |B| = beta sqrt(1 + 3 sin^2 i sin^2 u) += beta sqrt(1 + 3 sin^2 lambda). OK. + +-------------------------------------------------------------------------- +## 3. (a) Motional EMF along the radial tether + +Per unit length the motional field is (v x B); projected on the tether +direction t_hat = e_r it drives the current: + + E_m(u) = (v x B) . e_r , v = v e_t . + +Compute v x B with v = v e_t and B = B_r e_r + B_t e_t + B_n e_n: + + v x B = v [ B_r (e_t x e_r) + B_n (e_t x e_n) ] + = v [ -B_r e_n + B_n e_r ] (2.2 triad rules) + +Projection on e_r: + + E_m(u) = v B_n = v beta cos i = v B0 (RE/r)^3 cos i. (3.1) + +KEY: the radial-tether EMF sees ONLY B_n. It is constant around the orbit and +scales as cos i. The open-circuit tether voltage is V_emf = E_m * L = +v B0 (RE/r)^3 cos i * L. + +-------------------------------------------------------------------------- +## 4. (b) Lorentz drag force for radial current + +Current I flows along the tether, so the current element is I L e_r. The force + + F = I L (e_r x B) + = I L [ B_t (e_r x e_t) + B_n (e_r x e_n) ] + = I L [ B_t e_n - B_n e_t ]. (4.1) + +Two components: + - Along-track (drag / energy): F_t = F . e_t = -I L B_n = -I L beta cos i + - Orbit-normal (out-of-plane): F_n = F . e_t? no: F_n = F . e_n = I L B_t + = I L beta sin i cos u + +The sign of I is fixed by the passive EMF-driven current so that F_t opposes v +(drag). The DRAG magnitude that removes orbital energy is + + |F_t| = I L B_n = I L B0 (RE/r)^3 cos i. (4.2) + +The out-of-plane F_n = I L beta sin i cos u averages to zero over an orbit and, +being perpendicular to v, does ZERO work: it perturbs node/inclination at +second order but does NOT drive secular semimajor-axis decay. Only F_t enters +the deorbit rate (Gauss planetary eq.: da/dt = (2/n) * (F_t / m) for a +circular orbit, i.e. da/dt is proportional to the along-track force). + +-------------------------------------------------------------------------- +## 5. (c) Same geometry factor; definition of eta(i) + +From (3.1) and (4.2), BOTH the EMF projection and the along-track force +projection carry the identical field-geometry factor + + B_n = beta cos i (the orbit-normal field, constant over u). + +This is the whole content of the "inclination-dependent v x B efficiency": a +radial tether couples to the orbit-normal field only, and for an aligned dipole +that projection is cos i. + +Define eta(i) = (orbit-averaged secular drag at i) / (same at i = 0), so +eta(0) = 1. Let < . > = (1/2pi) integral_0^{2pi} ( . ) du. + +### 5.1 Model (i): FIXED current (force-averaged) +I is set by the power budget / hardware current cap and held constant vs i. +Secular drag ~ < |F_t| > = I L beta < |cos i| >. Since B_n is constant and +single-signed over the orbit (no cancellation), + + eta_F(i) = < |B_n(u,i)| > / < |B_n(u,0)| > = |cos i| / 1 = cos i. (5.1) + +### 5.2 Model (ii): SELF-CONSISTENT current (I ~ EMF) +When the current is EMF/collection-limited rather than power-capped, I(u) is +proportional to the local EMF. In the linear (resistively-limited or +short-circuit) regime I ~ E_m ~ v B_n. The secular drag is then the orbit +average of the PRODUCT (EMF factor) x (force factor): + + F_t ~ I * B_n ~ B_n^2 , so + eta_SC(i) = < B_n(u,i)^2 > / < B_n(u,0)^2 > = cos^2 i. (5.2) + +General statement (holds for a future u-dependent B_n, e.g. tilted dipole): + + eta_F(i) = < |B_n(u,i)| > / < |B_n(u,0)| > + eta_SC(i) = < EMF(u,i) |B_n(u,i)| > / < EMF(u,0) |B_n(u,0)| > + = < B_n(u,i)^2 > / < B_n(u,0)^2 > (EMF ~ B_n). + +For the aligned dipole B_n is u-independent and these collapse to cos i and +cos^2 i respectively. A nonlinear OML law I ~ EMF^(3/2) would give an +intermediate power ~ cos^(1+3/2)=cos^2.5 i locally, but bracketed by the two +models above; cos i and cos^2 i are the honest optimistic/conservative edges. + +-------------------------------------------------------------------------- +## 6. Limit checks + +L1. eta(0) = 1 for both (equatorial orbit: orbit normal = z_hat = north, the + full dipole equatorial field is orbit-normal => maximal coupling). PASS. + +L2. Monotone decreasing on [0, 90 deg]: cos i and cos^2 i are both strictly + decreasing there. PASS (verified numerically, Section 8). + +L3. eta(90 deg): EXACTLY ZERO for the aligned dipole (both models): + eta_F(90) = cos 90 = 0, eta_SC(90) = 0. + WHY (physical, not a bug): the aligned-dipole field always lies in the + meridian plane spanned by z_hat and r_hat, i.e. B . e_n = beta[ z_hat.e_n + - 3(z_hat.r_hat)(r_hat.e_n) ]. For a polar orbit the orbit plane CONTAINS + z_hat, so its normal e_n is equatorial (z_hat.e_n = cos 90 = 0) and r_hat + lies in a plane orthogonal to e_n (r_hat.e_n = 0). Hence B_n = 0 everywhere + on a polar orbit, so a radial current has zero along-track force at every + point -- not just on average. A polar radial tether does no secular drag in + an aligned dipole. The out-of-plane F_n = I L beta sin i cos u is nonzero at + 90 deg but does no work. + HONESTY NOTE: this exact zero is an ARTIFACT of the aligned-dipole + radial- + tether idealization. The ~11.5 deg dipole tilt tips e_n out of the field's + null, restoring a small nonzero drag of order (sin 11.5 deg)^2 ~ 0.04 of the + equatorial value (order-of-magnitude; a tilted-dipole quadrature would pin + it). So report eta(90) = 0 for the aligned model and flag "true value is + small-but-nonzero, dominated by dipole tilt" as the caveat. Neither catalog + is at 90 deg (A ~71, B ~74), so this does not touch the headline numbers. + +-------------------------------------------------------------------------- +## 7. Recommendation for ADSC v1 + +Expectation under test (from the task): "fixed-average-current with a power- +budget cap is the simplest defensible model" => eta = cos i. Verdict: JUSTIFIED +as the OPTIMISTIC edge, but it must NOT be shipped as a point value. + +Reasoning: + - eta_F = cos i is exactly correct WHEN the binding limit is the power supply / + hardware max current, so I really is inclination-independent. It is the + simplest, and it is the honest UPPER efficiency. + - eta_SC = cos^2 i is exactly correct WHEN the current is EMF / OML-collection + limited (I falls with the EMF, which itself falls as cos i). It is the honest + LOWER efficiency. At 71 deg it is 0.106 vs 0.326 -- a ~3x heavier penalty. + - A real bare tether at ~800 km, 71-74 deg with realistic Ne sits BETWEEN + these regimes and can switch between them over solar min/max. Committing to + one point value would either overstate EDT at high i (cos i) -- exactly the + trap the WP13 claims-audit exists to catch (instr 3) -- or understate it. + +RECOMMENDATION: implement BOTH as the inclination contribution to the MANDATORY +uncertainty band (spec: "never a point value"): + + eta_hi(i) = cos i (optimistic edge; power/limit-capped current) + eta_lo(i) = cos^2 i (conservative edge; EMF/collection-limited current) + +and report the deorbit-time band with eta in [eta_lo, eta_hi]. The band widens +at high inclination precisely where honesty matters most, and the conservative +edge structurally prevents EDT from silently "fixing" class A at 71 deg. + +Cleanest single-code realization (avoids double counting): apply the geometry +factor cos i ONCE to the EMF and ONCE to the force, and let the current model +decide the regime -- if I is power-capped the net inclination scaling is cos i; +if I is computed from the EMF the net scaling is cos^2 i automatically. Then the +band above is just the two current-limit regimes, not an ad hoc fudge. + +-------------------------------------------------------------------------- +## 8. C++-implementable formula + deterministic quadrature cross-check + +### 8.1 What v1 actually computes (O(1), closed form) +``` +// inputs: i [rad], r [m]; constants B0=3.12e-5 T, RE=6.371e6 m, GM=3.986004418e14 +double beta = B0 * pow(RE/r, 3.0); // T +double v = sqrt(GM / r); // m/s +double Bn = beta * cos(i); // orbit-normal field (constant in u) +// motional EMF per unit length and open-circuit voltage: +double Em = v * Bn; // V/m +double Vemf = Em * L; // V (L = tether length) +// inclination efficiency knobs (eta(0)=1): +double eta_hi = cos(i); // fixed / power-capped current +double eta_lo = cos(i)*cos(i); // self-consistent EMF-limited current +// along-track drag magnitude: |F_t| = I * L * Bn (I from the current model) +``` +No branch, no iteration, pure double, no external deps. cos i / cos^2 i are the +efficiency multipliers on the force. + +### 8.2 Fixed 64-node Gauss-Legendre harness (general field, deterministic) +Needed only if B_n acquires u-dependence (tilted dipole extension). Nodes are +generated by Newton-Raphson on the Legendre polynomial P_64 at init -- fully +deterministic, SEEDLESS, no RNG, no external table. Map GL nodes x_k in [-1,1] +to u_k = pi (x_k + 1) in [0, 2pi); orbit average = 0.5 * sum_k w_k f(u_k). + + eta_F(i) = avg_u |Bn(u,i)| / avg_u |Bn(u,0)| + eta_SC(i) = avg_u Bn(u,i)^2 / avg_u Bn(u,0)^2 + +with Bn(u,i) = beta cos i for the aligned dipole (u-independent => the average +is exact for any node count; N=64 chosen for the general case). Pseudocode for +node generation: +``` +for k in 0..N-1: + z = cos(pi*(k+0.75)/(N+0.5)); // initial guess + repeat: + p0=1; p1=0; + for j in 0..N-1: p2=p1; p1=p0; p0=((2j+1)*z*p1 - j*p2)/(j+1); // P_N(z) + dp = N*(z*p0 - p1)/(z*z - 1); // P_N'(z) + dz = p0/dp; z -= dz; + until |dz| < 1e-15; + x[k]=z; w[k]=2/((1-z*z)*dp*dp); +``` + +### 8.3 Verified numeric table (GL-64 quadrature == closed form to 2.2e-16) +Catalog inclinations: A 71 deg, B 78 deg (the code presets; note the real +SL-8 population clusters at 74/83 deg -- wp13-literature.md Topic 5b -- so +78 is a PLACEHOLDER compromise between the clusters). + + i (deg) | eta_F = cos i | eta_SC = cos^2 i + --------+---------------+----------------- + 0.0 | 1.0000000000 | 1.0000000000 + 30.0 | 0.8660254038 | 0.7500000000 + 51.6 | 0.6211477803 | 0.3858245649 + 71.0 | 0.3255681545 | 0.1059946232 + 74.0 | 0.2756373558 | 0.0759759519 + 90.0 | 0.0000000000 | 0.0000000000 + +Limit checks (numerically confirmed): + - eta_F(0)=1, eta_SC(0)=1 PASS (L1) + - strictly monotone decreasing 0->90 deg PASS (L2) + - eta_F(90)=6.1e-17, eta_SC(90)=3.7e-33 (== 0) PASS (L3, exact-zero limit) + - max | GL-64 - closed form | over 10 sample i = 2.2e-16 + +### 8.4 Reproduce +Python cross-check script (`/tmp/wp13_eta.py` at authoring time; portable): +implements the GL-64 harness above and the closed forms, printing the table and +all four limit checks. Deterministic, no seeds, no external deps. + +-------------------------------------------------------------------------- +## 9. One-line summary for the evidence pack +Radial EDT drag couples only to the orbit-normal field B_n = B0 (RE/r)^3 cos i; +orbit-averaged inclination efficiency is eta = cos i (fixed/power-capped current) +to cos^2 i (EMF/collection-limited current), = 0.33..0.11 at 71 deg -- ship the +[cos^2 i, cos i] band, never a point value. eta(90 deg)=0 in the aligned dipole +(tilt makes the true value small-but-nonzero). Libration (T7) remains open. diff --git a/wp13-literature.md b/wp13-literature.md new file mode 100644 index 0000000..6dd224a --- /dev/null +++ b/wp13-literature.md @@ -0,0 +1,392 @@ +# WP13 Literature / Citation Pack + +Task: WP13 Task 1 (Opus, effort high) — literature/citation candidates for the +kit-class trade study + EDT physics model. Spec: `adsc-specification-v5.md` +§WP13 (lines 240-258), T7, D12/D13. + +Rules honored: EVERY citation below was checked by live WebSearch/WebFetch on the +retrieval date. Each carries a verified-status label. **Nothing is fabricated.** +Where a specific number could not be pulled from a freely accessible source, that +is stated explicitly and the item is flagged for PLACEHOLDER treatment +(spec:167-174 pattern) rather than invented. + +- Retrieval date (all items): **2026-07-11** +- Verified-status legend: + - `web-verified` — citation and the key number(s) were confirmed against a + fetched page or an explicit search-result snippet on 2026-07-11. + - `web-verified (citation); number-paywalled` — bibliographic record confirmed + live; the specific numeric value sits behind a paywall/binary PDF and is + reported from a secondary snippet or left for PLACEHOLDER. + - `training-data / derived` — value computed or recalled, not directly fetched; + use only as cross-check, not as the primary cited number. + - `not-found` — could not be verified; do NOT insert into the repo. + +--- + +## Topic 1 — Bare electrodynamic tether deorbit physics (citable baseline) + +### 1a. Sanmartín OML bare-tether foundation paper — PRIMARY BASELINE + +**Sanmartín, J. R., Martínez-Sánchez, M., & Ahedo, E. (1993). "Bare Wire Anodes +for Electrodynamic Tethers." *Journal of Propulsion and Power*, 9(3), 353-360.** +- DOI: 10.2514/3.23629 +- URL: https://arc.aiaa.org/doi/10.2514/3.23629 +- Verified-status: **web-verified** (title, all three authors, journal, vol 9, + no 3, pp 353-360, year 1993, and DOI confirmed via search 2026-07-11). +- Key content extracted: introduces the *bare tether* concept — leave a fraction + of the tether near the anodic end uninsulated so it collects ambient electrons + in the **orbital-motion-limited (OML)** regime once locally biased positive + relative to the plasma. Validity condition: tether transverse radius small + compared with both the electron Debye length and thermal gyroradius; large + currents at moderate voltage drop. This is the citable OML current-collection + law the EdtConfig force/current model must reference (task §0.1, decay model). + +Supporting review (for the OML law written out + system context): +- **Sanmartín, J. R. "A Review of Electrodynamic Tethers for Space Applications."** + UPM open archive PDF: https://oa.upm.es/30953/1/UA2.pdf + - Verified-status: **web-found** (URL live). PDF is image/scan-based — machine + text extraction failed, so specific formula lines were not pulled. Use the + 1993 paper above as the citable OML source; this review is a secondary pointer. + +### 1b. EU FP7 BETs project (Bare Electrodynamic Tethers) — deorbit performance + +**BETs — "Propellantless deorbiting of space debris by bare electrodynamic +tethers", FP7-SPACE, Grant Agreement No. 262972.** Coordinator: Prof. Juan R. +Sanmartín, Universidad Politécnica de Madrid (UPM). Duration: 2010-11-01 to +2014-01-31. +- CORDIS record: https://cordis.europa.eu/project/id/262972 +- CORDIS reporting: https://cordis.europa.eu/project/id/262972/reporting +- Final Report PDF (UPM archive): https://oa.upm.es/39287/1/Final%20BETs%20Report%202.pdf +- Project site: http://www.thebetsproject.com/ (resources page: + http://www.thebetsproject.com/resources) +- Verified-status: **web-verified** (GA number, coordinator, institution, dates, + and project scope confirmed via CORDIS/search 2026-07-11). Final Report PDF URL + is live but FlateDecode-compressed — headline numbers below come from the ESA + outreach summary and BETs design papers, not the raw report binary. + +**Deorbit performance numbers (citable, web-verified via search 2026-07-11):** +- Order-of-magnitude result: *a 1000-kg spacecraft can be deorbited from a + 1000-km-altitude orbit by a **10-kg** tether in about **one month**; a **1-kg** + tether deorbits the same 1000-kg spacecraft in **less than a year**.* (ESA / + phys.org BETs outreach, 2014: https://phys.org/news/2014-05-tether-solution-satellite-de-orbiting-reentry.html) +- Comparative claim: for state-of-the-art values with a few-weeks deorbit time, + bare tethers are ~1-2 orders of magnitude lighter than active + (propulsive) technologies and than drag-augmentation (sail) devices respectively. +- BETs design reference scenarios (used across BETs papers): + (i) Earth-observation satellites, **700-1000 kg, initial orbit ~800 km, 98° + inclination**; (ii) mega-constellation spacecraft, ~200 kg, 1200 km, 90°. + Example tape-tether geometry: L = 3 km, width w = 2.5 cm, thickness h = 40 µm, + with 2 km bare + 1 km inert segments. +- Software: BETsMA v1.0 mission-analysis tool developed under the project. +- Inclination note (directly supports spec's mandatory η(i), 240-243): the BETs + performance scenarios are all high-inclination (98°, 90°), and the along-track + component of v×B — the drag-producing component — degrades away from the + equatorial/low-inclination case. See Topic 2/derivation task for the physics. + +> Retrieval caveat: the exact BETs deorbit-time-vs-altitude-vs-inclination +> tables live in the compressed Final Report PDF and the paywalled Acta +> Astronautica BETs papers (e.g. "Comparison of technologies for deorbiting +> spacecraft from LEO at end of mission", S0094576516306555; "A code for the +> analysis of missions with electrodynamic tethers", S0094576522003113). The +> qualitative + order-of-magnitude numbers above ARE web-verified; any finer +> table pulled into the repo must cite the specific paper and be re-fetched. + +--- + +## Topic 2 — EDT libration / dynamic instability (T7 open-risk pointer) + +### PRIMARY libration-instability citation + +**Peláez, J., Lorenzini, E. C., López-Rebollal, O., & Ruiz, M. (2000). "A New +Kind of Dynamic Instability in Electrodynamic Tethers." *The Journal of the +Astronautical Sciences*, 48(4), 449-476.** +- DOI: 10.1007/BF03546266 +- URL: https://link.springer.com/article/10.1007/BF03546266 +- Verified-status: **web-verified** (authors, title, journal, vol 48, no 4, pp + 449-476, year 2000, DOI confirmed via search 2026-07-11; Springer page itself + redirects to an auth gate, so the citation is confirmed from the search + record, not a full-text fetch). +- Key finding (for the T7 pointer): a conductive tether on a circular *inclined* + orbit exhibits a genuine **dynamic instability** — non-periodic trajectories + carry a positive net energy flux per orbit, pumping in-plane libration until it + grows into rotation. The instability is present for flexible OR rigid tethers, + in generator (deorbit) OR thruster mode, and its strength **depends on orbital + inclination**. This is exactly the "libration/dynamic stability is explicitly + unresolved" risk WP13 must cite and must NOT claim solved (spec:244-245, + task §0.2). + +### Supporting / corroborating libration references (all web-verified citations) + +- **Zhong, R., & Zhu, Z. H. (2013). "Libration dynamics and stability of + electrodynamic tethers in satellite deorbit." *Celestial Mechanics and + Dynamical Astronomy*, 116(3), 279-298.** DOI: 10.1007/s10569-013-9489-4 — + long-term libration/stability analysis in deorbit. + URL: https://link.springer.com/article/10.1007/s10569-013-9489-4 +- **"The dynamic instability analysis of electrodynamic tether system." *Nonlinear + Dynamics* (2024).** DOI: 10.1007/s11071-024-09771-w — + URL: https://link.springer.com/article/10.1007/s11071-024-09771-w — recent + confirmation the instability remains an active, unresolved research topic. +- Verified-status: **web-verified** (both surfaced with full journal/vol/DOI in + search 2026-07-11). Use Peláez 2000 as the primary T7 pointer; these two as + "still open / actively studied" corroboration. + +--- + +## Topic 3 — Ionospheric electron density Ne at ~700-900 km (cited parameter, no IRI impl.) + +Spec:246 forbids an IRI implementation; WP13 only needs cited Ne numbers with a +source. Best web-verified anchors found: + +### 3a. Representative engineering value at 800 km (PRIMARY citable number) + +- **Ne ≈ 1.0 × 10¹¹ m⁻³ at 800 km altitude (daytime, representative value)** — + assumed in a plasma-deorbit particle-simulation study. + Source: "Particle Simulation of Plasma Drag Force Generation in the Magnetic + Plasma Deorbit", arXiv:1805.06123. + URL: https://arxiv.org/abs/1805.06123 + - Verified-status: **web-verified** (the "ion number density of 1.0×10¹¹ m⁻³ … + typical daytime value at 800 km" statement confirmed via search snippet + 2026-07-11; the PDF binary itself did not text-extract cleanly). + +### 3b. Altitude-appropriate in-situ measurement basis (DMSP at ~840-860 km) + +- **Cai, X., et al. (2019). "Long-Term Trend of Topside Ionospheric Electron + Density Derived From DMSP Data During 1995-2017." *Journal of Geophysical + Research: Space Physics*, 124(12), 10708-10727.** DOI: 10.1029/2019JA027522 + URL: https://agupubs.onlinelibrary.wiley.com/doi/full/10.1029/2019JA027522 + - DMSP SSIES makes **in-situ** thermal ion/electron density measurements at + **~840-860 km** — i.e. directly in the ~800 km regime of both ADSC catalogs. + - Verified-status: **web-verified (citation); number-paywalled** (journal/vol/ + pages/DOI confirmed; the article body returned HTTP 402, so discrete solar- + min/max Ne values were not fetched). +- DMSP altitude confirmed independently: SSIES instruments measure at ~840 km; + at solar maximum the 848-km ionosphere is >50% O⁺ at all local times, while at + solar minimum O⁺ falls to ≲ H⁺/He⁺ (DMSP/SSIES mid-latitude page, + https://dmsp.bc.edu/html2/ssiesmidlatitude.html — page live but TLS-cert + mismatch blocked full fetch; content from search snippet). + +### 3c. Solar min vs solar max range (order-of-magnitude, to be pinned) + +Well-established topside behavior at ~800 km: Ne rises ~an order of magnitude from +solar minimum to solar maximum. Working range for the EdtConfig uncertainty band: +- **Solar min (~night/low F10.7): Ne ~ 10¹⁰ m⁻³ (order 10⁹-10¹⁰ m⁻³)** +- **Solar max (~day/high F10.7): Ne ~ 10¹¹ m⁻³ (up to ~10¹² m⁻³ in daytime EIA)** + +> Honest gap: a single freely-accessible table giving a clean solar-min **and** +> solar-max Ne pair at exactly 800 km was NOT extracted. The two best sources that +> tabulate it are paywalled: Cai et al. 2019 (above) and "Validation of the +> IRI-2020 topside ionosphere options through in-situ electron density +> observations by LEO satellites", *Adv. Space Res.* (2024), +> https://www.sciencedirect.com/science/article/pii/S0273117724005222 . For the +> repo: cite 3a (1.0×10¹¹ m⁻³ @ 800 km) as the mid/solar-active anchor, use the +> 3c range for the solar-min/max band, and keep the exact pair PLACEHOLDER until +> one of the paywalled tables is obtained. Do NOT invent a solar-min number. + +Model-paper cross-refs (web-verified citations, for context, NOT to implement): +- Hoque, M. M., Jakowski, N., & Prol, F. S. (2022). "A new climatological electron + density model…" *J. Space Weather Space Clim.*, 12, 1. DOI: 10.1051/swsc/2021044 + (validated against DMSP at ~800 km). +- Prol, F. S., Smirnov, A. G., Hoque, M. M., & Shprits, Y. Y. (2022). "Combined + model of topside ionosphere and plasmasphere…" *Scientific Reports*, 12, 9732. + DOI: 10.1038/s41598-022-13302-1 (topside model good to ~800 km). + +--- + +## Topic 4 — Geomagnetic dipole moment (for the dipole B-field in the EDT model) + +### PRIMARY citable dipole values + +- **Centred-dipole magnetic moment M = 7.788 × 10²² A·m²; equatorial surface + field B₀ = 3.01153 × 10⁴ nT (IGRF epoch 2000).** + Source: SPENVIS (ESA/BIRA-IASB Space Environment Information System), + "Dipole approximations of the geomagnetic field." + URL: https://www.spenvis.oma.be/help/background/magfield/cd.html + - Verified-status: **web-verified** (both M and B₀ pulled directly from the + fetched page 2026-07-11). B₀ = √(g₁₀² + g₁₁² + h₁₁²); M = (4π R³/µ₀)·B₀. + +### Current (IGRF-13/14 era) model reference + present value + +- **Alken, P., Thébault, E., Beggan, C. D., et al. (2021). "International + Geomagnetic Reference Field: the thirteenth generation." *Earth, Planets and + Space*, 73, 49.** DOI: 10.1186/s40623-020-01288-x + URL: https://link.springer.com/article/10.1186/s40623-020-01288-x + - Verified-status: **web-verified (citation)** (authors, journal, article, + year, DOI, epoch-2020.0 main-field model confirmed via search 2026-07-11). +- **Present value (epoch 2020.0): dipole moment ≈ 7.71 × 10²² A·m²** (slowly + declining, ~5-6% over 1980→2020). Axial coefficient g₁₀(2020.0) = -29404.8 nT; + with g₁₁ = -1450.9 nT, h₁₁ = 4652.5 nT ⇒ B₀ = √(·) ≈ 2.98 × 10⁴ nT ⇒ + M = (4π R³/µ₀)B₀ ≈ 7.71 × 10²² A·m² (R = 6371.2 km). + - Verified-status: **training-data / derived** (IGRF-13 2020.0 g/h coefficients + are standard; the 7.71×10²² value is computed from them, not fetched as a + printed figure — the NOAA/IGRF-13 PDFs returned 403). Consistent with the + web-verified SPENVIS 2000 value of 7.788×10²² and the documented declining + trend. **For the repo, cite the SPENVIS value (7.788×10²²) as the primary + web-verified dipole moment and note IGRF-13 (Alken 2021) as the current + model; the 7.71×10²² is a derived cross-check.** + +Design note: for an *aligned* dipole the model overstates high-latitude field +structure; spec/task allow "aligned dipole + stated tilt caveat" — the ~11° tilt +(geomagnetic vs geographic axis) and the eccentric-dipole offset are the caveats +to state (SPENVIS same page documents both). + +--- + +## Topic 5 — Target parameters (catalog stage bodies) + +### 5a. Zenit-2 second stage (SL-16 class) — Catalog A anchor / also massive + +- **Empty (dry) mass ≈ 8,300 kg** (one source lists ~9,000 kg empty); + **length 11.50 m, diameter 3.90 m**; engine 1× RD-120 (+ RD-8 vernier). + Sources: braeunig space specs (http://www.braeunig.us/space/specs/zenit.htm), + Astronautix Zenit-2 (http://www.astronautix.com/z/zenit-2.html), + Wikipedia Zenit-2 (https://en.wikipedia.org/wiki/Zenit-2). +- **Typical orbit (Tselina-2 payloads): ~845 × 857 km, inclination 71.01°.** + (Also a 65° family; SL-16 R/B clusters span ~750-850 km, incl. 71° and 81°.) +- Verified-status: **web-verified** (mass 8,300 kg, dims, RD-120, and the + 845×857 km / 71.01° Tselina-2 orbit all confirmed via search 2026-07-11; the + 8,300-vs-9,000 kg spread is a real source discrepancy — carry both, flag the + 8,300 kg as the more common dry-mass figure). +- Cross-check: McKnight 2021 (Topic 6) — the top 20 most-concerning LEO objects + are all SL-16 (Zenit-2) second stages, ~9 t class, ~850 km. + +### 5b. Kosmos-3M second stage (SL-8 class, 11K65M) + +- **Empty (dry) mass ≈ 1,435 kg**; gross mass ≈ 20,135 kg; engine 1× 11D49 + (RD-219 family verniers); length ~6 m, diameter 2.4 m. + Sources: Astronautix Kosmos 11K65M (http://www.astronautix.com/k/kosmos11k65m.html), + Gunter's Space Page (https://space.skyrocket.de/doc_lau_det/kosmos-3m.htm). +- **Typical orbits — SL-8 R/B clusters (as of 2014 survey): Box 3 ≈ 700-800 km + (50 R/Bs), Box 2 ≈ 900-1000 km (143 R/Bs), Box 1 ≈ 1500-1600 km (44 R/Bs); + inclinations ~74° and ~83°.** The ~740-780 km / ~74° preset the task cites + matches the 700-800 km, 74° sub-population. +- Verified-status: **web-verified** (1,435 kg empty mass, 20,135 kg gross, 11D49, + and the three-box cluster altitudes/inclinations confirmed via search + 2026-07-11; per-object page fetches were blocked by host cert/connection + issues, so mass is snippet-level, not a fetched spec sheet — solid but tag it + as such). + +--- + +## Topic 6 — Class-C candidates (massive / high-risk, controlled-reentry class) + +### PRIMARY ranking citation (the criticality basis, spec §4 amendment) + +- **McKnight, D., Witner, R., Letizia, F., Lemmens, S., Anselmo, L., Pardini, C., + Rossi, A., Kunstadter, C., et al. (2021). "Identifying the 50 + statistically-most-concerning derelict objects in LEO." *Acta Astronautica*, + 181, 282-291.** DOI: 10.1016/j.actaastro.2021.01.021 + URL: https://www.sciencedirect.com/science/article/abs/pii/S0094576521000217 + ADS: https://ui.adsabs.harvard.edu/abs/2021AcAau.181..282M/abstract + - Verified-status: **web-verified (citation + headline findings)** (journal, + vol 181, pp 282-291, 2021 confirmed; the abstract page and open PDF fetches + partially blocked, key findings from search 2026-07-11). + - Key findings: 11 expert teams produced ranked lists → consensus top-50. + **Rocket bodies dominate; the top ~20 are all SL-16 (Zenit-2) second stages.** + **Average mass of the top-50 objects ≈ 5,295 kg.** Removing the top-50 would + roughly halve LEO collision-risk; top-10 ≈ 30% reduction. + +### Class-C candidate targets (2-3, with cited mass/orbit/inclination) + +1. **Envisat (ESA) — the canonical controlled-reentry-class derelict.** + - **Mass 8,211 kg** (Service Module 2,673 + PEB 1,021 + Payload Carrier 2,078 + + Fuel 319 + Instruments 2,118 kg); **sun-synchronous orbit ~765-800 km, + inclination ~98.4-98.55°**; defunct since April 2012. + - Sources: eoPortal (https://www.eoportal.org/satellite-missions/envisat), + ESA Earth Online (https://earth.esa.int/eogateway/missions/envisat/description), + Wikipedia (https://en.wikipedia.org/wiki/Envisat). + - Verified-status: **web-verified** (8,211 kg mass breakdown, ~800 km sun-sync, + ~98.5° inclination, 2012 failure all confirmed 2026-07-11). Envisat is the + most-cited single "too massive to safely leave / high casualty-risk on + uncontrolled reentry" object — the archetypal Class-C target. + +2. **SL-16 (Zenit-2) second stage — top of the McKnight ranking, massive.** + - **~8,300-9,000 kg, ~845-857 km, 71.0° (also an 81° cluster).** (Topic 5a.) + - Verified-status: **web-verified.** Note: the ADSC task uses Zenit-2 as the + Class-A *anchor*; by mass it is equally a Class-C controlled-reentry + candidate. Whether an 8.9-t stage needs controlled reentry (Ec>1e-4) is the + Topic-7 casualty-area question — flag for the Class-C vs Class-A split. + +3. **SL-8 (Kosmos-3M) second stage — high-count, lighter concern object.** + - **~1,435 kg, 700-1000 km, 74°/83°.** (Topic 5b.) Appears prominently in + SL-8-specific ADR mission studies (e.g. ADReS-A, Springer + 10.1007/978-3-319-15982-9_3). Lighter than Envisat/SL-16 → likely NOT + controlled-reentry class, useful as the mass contrast in the Class-C table. + - Verified-status: **web-verified.** + +> For a third *distinct massive* Class-C payload (not a rocket body), Envisat is +> the strongest and only fully-verified large defunct **satellite** in the +> concern literature at ~800 km. If a second large payload is wanted, candidates +> to verify later: ADEOS-II/Midori-II (~3.7 t, ~800 km, 98.6°) and ERS-2 (~2.5 t +> — but ERS-2 already reentered Feb 2024, so it is historical). These were NOT +> re-verified in this pass — mark PLACEHOLDER until fetched. + +--- + +## Topic 7 — Casualty-risk framework (controlled vs uncontrolled reentry) + +### PRIMARY standard + +- **NASA-STD-8719.14A (with Change 1), "Process for Limiting Orbital Debris."** + URL: https://soma.larc.nasa.gov/SIMPLEx/pdf_files/871914.pdf (also + https://explorers.larc.nasa.gov/HPMIDEX/pdf_files/10_nasa-std-8719.14a_with_change_1.pdf) + - **Requirement 4.7-1 — Limit the risk of human casualty: the expected + worldwide human-casualty risk from reentering debris shall not exceed + 0.0001 (1 in 10,000), i.e. Ec < 1 × 10⁻⁴.** Casualty is assumed for any + surviving fragment impacting with kinetic energy **> 15 J**. + - **Controlled reentry:** the reentry must still satisfy 4.7-1, AND the targeted + trajectory shall ensure no surviving debris of >15 J lands closer than + **370 km from foreign landmasses** (with defined stand-off from U.S. + territories); the deorbit-burn success probability must be high enough not to + violate 4.7-1. + - Compliance tools: **DAS (Debris Assessment Software)** and **ORSAT (Object + Reentry Survival Analysis Tool)**. + - Verified-status: **web-verified** (the 1×10⁻⁴ / 1-in-10,000 limit, the 15 J + casualty threshold, the 370 km controlled-reentry rule, and DAS/ORSAT all + confirmed via fetch + search 2026-07-11). Note: the requirement is numbered + **4.7-1** in the standard; a first WebFetch guessed "4.7.2" — disregard that, + 4.7-1 is correct per the follow-up search of the standard's own text. + +### ESA / European equivalent (same 1e-4 threshold, DRAMA/SARA tool) + +- **ESA re-entry safety: the 1 × 10⁻⁴ casualty-risk limit is likewise applied + (harmonised with IADC).** Uncontrolled reentries exceeding it require design- + for-demise or controlled/targeted reentry. + URL: https://technology.esa.int/page/re-entry-safety +- **DRAMA / SARA** — ESA's Debris Risk Assessment and Mitigation Analysis suite; + **SARA (Survival And Risk Analysis)** is its reentry-survival + on-ground- + casualty module (the ESA analogue of NASA DAS/ORSAT). + ESA DRAMA: https://sdup.esoc.esa.int/drama/ (SARA is the reentry module). +- Verified-status: **web-verified (framework + threshold)** (ESA's use of the + same 1e-4 casualty limit and the DRAMA/SARA tooling confirmed via search + 2026-07-11; SARA's exact algorithm details not fetched — cite the ESA + re-entry-safety page + DRAMA suite, not a specific SARA number). + +Cross-refs (web-verified citations): NASA ORSAT page +(https://orbitaldebris.jsc.nasa.gov/reentry/orsat.html); "Debris Assessment +Software (DAS) Reentry Risk Analysis" (ResearchGate 320808841). + +--- + +## Summary table — verification status by topic + +| # | Item | Citation status | Key number status | +|---|------|-----------------|-------------------| +| 1a | Sanmartín/Martínez-Sánchez/Ahedo 1993 OML bare-tether | web-verified | OML law (qualitative) verified | +| 1b | BETs FP7 GA 262972 + deorbit performance | web-verified | 10 kg→1000 kg from 1000 km in ~1 mo: verified; fine tables paywalled | +| 2 | Peláez et al. 2000 libration instability (T7) | web-verified | inclination-dependent instability: verified | +| 3 | Ne @ ~800 km | 1.0e11 m⁻³ @800 km verified; solar-min/max pair paywalled | mid anchor verified; solar-min PLACEHOLDER | +| 4 | Geomagnetic dipole moment | SPENVIS M=7.788e22 web-verified; IGRF-13 model verified | 2020 value 7.71e22 derived | +| 5a | Zenit-2/SL-16 2nd stage | web-verified | 8,300(-9,000) kg, 11.5×3.9 m, 845×857 km/71.0° | +| 5b | Kosmos-3M/SL-8 2nd stage | web-verified | 1,435 kg dry, 700-1000 km, 74°/83° | +| 6 | McKnight 2021 top-50 + Envisat | web-verified | Envisat 8,211 kg/~800 km/98.5°; top-50 avg 5,295 kg | +| 7 | NASA-STD-8719.14A + ESA/DRAMA-SARA | web-verified | Ec<1e-4, 15 J, 370 km controlled rule | + +## Items to keep PLACEHOLDER (spec:167-174) — NOT fabricated + +- Exact solar-min **and** solar-max Ne pair at exactly 800 km (only paywalled + tables found; mid/solar-active anchor 1.0×10¹¹ m⁻³ is usable now). +- Precise BETs deorbit-time-vs-(altitude, inclination) table (compressed Final + Report PDF + paywalled Acta Astronautica papers). +- A second large non-rocket-body Class-C payload beyond Envisat (ADEOS-II / ERS-2 + candidates not re-verified this pass). +- IGRF-13 2020.0 printed dipole-moment figure (value derived, not fetched).