diff --git a/README.md b/README.md index 882028a..7dca071 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,11 @@ trochia against a real measured flight: its predicted apogee lands within ~1 % o the altimeter reading (39.1 m vs 39 m). The [psas-launch12](examples/psas-launch12/) example scales that up to a real ~5 km high-power flight (validated apogee) and computes the contingency landing zones — -nominal recovery vs parachute failure vs CATO. +nominal recovery vs parachute failure vs CATO. The +[astra](examples/astra/) example validates a real ~3.25 km flight on **both** the +ascent and the descent (its recovery failed, so the measured fall is near +ballistic — exactly what trochia can check) and lays out the hazard zone +(警戒区域) and abort (途中破談) footprints. ## Python tooling (uv) diff --git a/examples/README.md b/examples/README.md index 996d6de..039c87b 100644 --- a/examples/README.md +++ b/examples/README.md @@ -11,8 +11,8 @@ $ ./fetch-engine.sh # LARKSPUR-XP.300 (20191020_01.eng), used by # single-trajectory, landing-dispersion and parachute ``` -`validation-estes-viking` fetches the Estes A8 from ThrustCurve.org with its own -`fetch-engine.sh`. +`validation-estes-viking` (Estes A8) and `astra` (Cesaroni 4263L1350-P) fetch +their own motors with their own `fetch-engine.sh`. You also need the `trochia` binary — build it from the repo root (see the top [README](../README.md)); it ends up at `build/bin/trochia`. trochia always reads @@ -28,5 +28,6 @@ run each example from its own directory. | [parachute](parachute/) | parachute vs ballistic descent — how recovery changes the landing point | | [validation-estes-viking](validation-estes-viking/) | accuracy check: predicted vs **measured** apogee of a real flight | | [psas-launch12](psas-launch12/) | km-scale real flight: apogee validation + contingency landing zones | +| [astra](astra/) | km-scale real flight: ascent **+ descent** validation, hazard zone (警戒区域) & abort (途中破談) | See each directory's `README.md` for how to run it and the resulting plot. diff --git a/examples/astra/README.md b/examples/astra/README.md new file mode 100644 index 0000000..8d45dd8 --- /dev/null +++ b/examples/astra/README.md @@ -0,0 +1,114 @@ +# Astra — km-scale real-flight validation (ascent **and** descent) + hazard zone & abort + +**Use case:** a real, instrumented flight (~3.25 km apogee) used to (a) validate +trochia against measured data on **both** the ascent and the descent, and +(b) compute the hazard area / landing dispersion (警戒区域 / 落下分散) and the +abort (途中破談) footprints — a full launch-safety analysis on a single rocket. + +## The flight + +**Astra**, built by **Faraday Rocketry UPV**, flown at **EuRoC 2022** (European +Rocketry Challenge, Ponte de Sor, Portugal) on a commercial **Cesaroni +4263L1350-P** solid motor. Measured apogee **3249 m at t ≈ 24 s**; the recovery +**did not deploy cleanly**, so the rocket came down close to ballistic +(~100 m/s) — which, conveniently, lets us validate trochia's *descent* leg +against real data, not just the apogee. + +The rocket parameters, the measured trajectory (`measured.dat`, +`measured-track.dat`) and the drag curve all come from the RocketPy "Astra" +example: +- rocket model + measured flight data: +- worked example notebook: `docs/examples/astra_flight_sim.ipynb` in that repo +- motor: + +## Run + +```sh +./run.sh # fetch motor, run validation + hazard/abort sims, write the plots +``` + +`run.sh` needs the `trochia` binary at `build/bin/trochia` (build from the repo +root) and, for the map, network access + `uv`. + +## Validation vs the measured flight + +`config-validation.toml` reconstructs the actual flight in still air and is +plotted against the rocket's own measured altitude history: + +![validation](validation.png) + +| quantity | measured | trochia | +|---|---|---| +| apogee | 3249 m | 3260 m (**+0.3 %**) | +| time to apogee | ~24 s | 25.0 s | +| descent speed (near ground) | ~100 m/s | ~108 m/s | + +- **Ascent (left)** is the validation: with a single representative drag + coefficient the whole climb and the apogee match within a fraction of a + percent. +- **Descent (right)** is the part that's usually *not* checkable. Because Astra's + recovery only partially deployed, the measured descent is close to ballistic + and trochia reproduces it to within ~70 m down to ~145 m AGL, where the + measured record ends (see *recovery modelling* below). + +### Why a constant Cd works here + +trochia uses a single constant drag coefficient, but Astra's real drag varies +with Mach (subsonic ~0.58, a transonic peak of 0.857 near Mach 0.89, which the +rocket reaches around burnout). Sweeping the constant Cd in still air shows the +measured apogee selects an **effective constant Cd ≈ 0.55** — the constant that +reproduces the measured apogee, i.e. the transonic drag rise folded into one +number (not the arithmetic mean of the Cd–Mach curve): + +![cd-sensitivity](cd-sensitivity.png) + +## Hazard zone (落下分散) + abort (途中破談) + +`config.toml` flies the validated ascent model under a wind-direction sweep +(5 m/s, 0–360°) for four contingency branches, giving one landing footprint +each: + +| scenario | meaning | recovery | +|---|---|---| +| `nominal` | intended flight | dual deploy (drogue at apogee + main at 450 m AGL) | +| `recovery-failure` | total recovery failure | none → ballistic (worst-case impact) | +| `motor-cutoff` | thrust quits mid-burn (t = 1.5 s) | airframe intact, recovers | +| `cato` | structural failure (t = 1.0 s) | thrust stops + no recovery | + +![footprints](footprints.png) + +On the real launch site (the measured GPS track, up to where its record ends at +~145 m AGL, is drawn for reference): + +![footprints map](footprints-map.png) + +The launch is tilted (84° elevation, 133° heading), so every footprint is biased +**downrange toward 133°**. Reading the safety story off the plot: + +- **dual deploy** drifts the *most* — it spends minutes under canopy, so wind + exposure dominates and the zone is the widest, though the landing is gentle; +- **recovery failure** (no recovery at all) is a tight, downrange *lawn-dart* — + it falls too fast to drift much, but hits hard. The real flight's partial + recovery (~100 m/s) was a milder version of this; a clean total failure would + be faster and tighter still; +- **motor cutoff** stays closer in (lower apogee, still recovers); +- **CATO** debris stays right by the pad. + +## Modelling notes / caveats + +- **Representative Cd.** 0.55 is the effective constant that reproduces the + measured apogee; trochia cannot reproduce the *timing* of the transonic drag + rise, only its net effect on apogee (which is what `cd-sensitivity.png` + calibrates). +- **CATO is single-body.** trochia models the `cato` branch as one intact body + with thrust cut and no recovery, not a fragmenting debris cloud; read its + footprint as "where the (single) vehicle comes down", not a debris dispersion. +- **Recovery modelling.** Astra's recovery hardware drag is not published. The + validation run models the as-flown partial recovery as an *effective* drag + `cd·area ≈ 0.015 m²` calibrated to the measured descent; `recovery-failure` + in the hazard analysis is a stricter, fully ballistic fall (faster still). +- **Mass / motor.** trochia's `mass` is the airframe **without** the loaded + motor (9.10 kg); the motor (loaded 3.57 kg, 2.02 kg propellant) comes from the + `.eng`, giving a 12.67 kg liftoff mass that matches the RocketPy model. +- **CG / CP / inertia / Cna** are estimated from the RocketPy geometry and are + second-order for these (apogee- and descent-dominated) results. diff --git a/examples/astra/cd-apogee.dat b/examples/astra/cd-apogee.dat new file mode 100644 index 0000000..d5e84e4 --- /dev/null +++ b/examples/astra/cd-apogee.dat @@ -0,0 +1,8 @@ +# Cd apogee[m] t_apogee[s] +0.45 3529.8 26.2 +0.50 3388.3 25.5 +0.55 3260.1 25.0 +0.58 3188.8 24.5 +0.65 3036.5 23.8 +0.75 2847.6 23.0 +0.85 2685.5 22.0 diff --git a/examples/astra/cd-sensitivity.gp b/examples/astra/cd-sensitivity.gp new file mode 100644 index 0000000..b6f0633 --- /dev/null +++ b/examples/astra/cd-sensitivity.gp @@ -0,0 +1,21 @@ +# Apogee sensitivity to the constant drag coefficient. +# cd-apogee.dat ("Cd apogee[m] t_apogee[s]") is produced by run.sh, which sweeps +# Cd in still air. The measured apogee picks out the representative Cd. +set datafile separator whitespace +set terminal pngcairo size 760,560 enhanced font "sans,11" +set output "cd-sensitivity.png" + +set title "Astra apogee vs constant Cd\nthe measured apogee picks the effective constant Cd = 0.55" +set xlabel "constant drag coefficient Cd [-]" +set ylabel "apogee [m]" +set grid +set key top right + +MEAS = 3249.0 +set arrow from graph 0, first MEAS to graph 1, first MEAS nohead lw 2 dt 2 lc rgb "#e08214" +set label "measured 3249 m" at graph 0.03, first MEAS+90 tc rgb "#b35806" +set arrow from 0.55, graph 0 to 0.55, graph 1 nohead dt 3 lc rgb "#888888" +set label "Cd=0.55" at 0.56, graph 0.10 tc rgb "#555555" + +plot "cd-apogee.dat" using 1:2 with linespoints pt 7 ps 1.2 lw 2 lc rgb "#31688e" \ + title "trochia (still air)" diff --git a/examples/astra/cd-sensitivity.png b/examples/astra/cd-sensitivity.png new file mode 100644 index 0000000..bc07aa4 Binary files /dev/null and b/examples/astra/cd-sensitivity.png differ diff --git a/examples/astra/config-validation.toml b/examples/astra/config-validation.toml new file mode 100644 index 0000000..e3cbff3 --- /dev/null +++ b/examples/astra/config-validation.toml @@ -0,0 +1,48 @@ +# Astra validation: reconstruct the actual EuRoC'22 flight and compare to the +# rocket's own measured trajectory (measured.dat, from RocketPy's flight_data). +# +# Still air, single flight. The descent uses an *effective* recovery drag +# calibrated to the measured fall: Astra's recovery did not deploy cleanly, so +# the rocket came down at ~100 m/s (tumbling / partial deployment) -- far slower +# than a clean nose-first ballistic fall (~185 m/s) but far faster than a working +# main. cd*area ~ 0.015 m^2 reproduces the measured altitude history. +# +# ./fetch-engine.sh +# ../../build/bin/trochia config-validation.toml +# gnuplot validation.gp # -> validation.png + +[simulation] +dt = 0.005 +output.dt = 0.25 +output.dir = "output-validation" +timeout = 120 + +[launcher] +length = 12 # 12 m rail (RocketPy Flight rail_length) +elevation = 84 # inclination 84 deg from horizontal +azimuth = 133 # heading 133 deg + +[wind] +model = "power" +ground.dir = [ 0.0 ] +ground.speed= 0.001 # still air (validate the vertical profile) + +[rocket] +name = "Astra (Faraday Rocketry UPV)" +type = "single" + +[[rocket.stage]] +engine = "Cesaroni_4263L1350-P.eng" # Cesaroni 4263L1350-P, fetched above +length = 2.52 # nose tip (2.5214) to tail (~0), RocketPy geometry +diameter= 0.094 # radius 0.047 m +mass = 9.10 # dry 10.6462 kg (incl. casing) minus casing 1.546 kg +I0 = 7.15 # loaded pitch inertia (dry 6.70 + propellant, est.) +If = 6.70 # empty pitch inertia (RocketPy dry inertia 6.6986) +lcg0 = 1.21 # loaded CG from nose (est. from RocketPy masses) +lcgf = 1.126 # empty CG from nose (CoM-without-motor 1.3957 from tail) +lcgp = 1.66 # propellant CG from nose (motor at aft) +lcp = 1.60 # CP from nose (near fins, est.) +Cd = 0.55 # representative constant Cd (see README; sweep in run.sh) +Cna = 14.0 # estimated (4 fins + nose) +# as-flown effective recovery: ~100 m/s descent (cd*area ~ 0.015 m^2) +parachute= { condition = "top", delay = 0.0, cd = 1.0, diameter = 0.14 } diff --git a/examples/astra/config.toml b/examples/astra/config.toml new file mode 100644 index 0000000..e8fcc76 --- /dev/null +++ b/examples/astra/config.toml @@ -0,0 +1,62 @@ +# Astra hazard zone (警戒区域 / 落下分散) + abort (途中破談) analysis. +# +# The validated ascent model (see config-validation.toml) is flown under a wind +# direction sweep for several contingency branches, giving one landing footprint +# per scenario -- the core of a launch-safety / hazard-area analysis. +# +# ./fetch-engine.sh +# ../../build/bin/trochia config.toml # all scenarios x wind dirs +# gnuplot footprints.gp # -> footprints.png +# uv run footprints-map.py # -> footprints-map.png + +[simulation] +dt = 0.005 +output.dt = 0.5 +output.dir = "output" +timeout = 400 # a dual-deploy descent from ~3.2 km takes minutes + +[launcher] +length = 12 +elevation = 84 +azimuth = 133 # downrange toward heading 133 deg + +[wind] +model = "power" +ground.dir = [ 0.0, 30.0, 60.0, 90.0, 120.0, 150.0, 180.0, 210.0, 240.0, 270.0, 300.0, 330.0, 360.0 ] +ground.speed= 5.0 + +[rocket] +name = "Astra (Faraday Rocketry UPV)" +type = "single" + +[[rocket.stage]] +engine = "Cesaroni_4263L1350-P.eng" +length = 2.52 +diameter= 0.094 +mass = 9.10 +I0 = 7.15 +If = 6.70 +lcg0 = 1.21 +lcgf = 1.126 +lcgp = 1.66 +lcp = 1.60 +Cd = 0.55 +Cna = 14.0 +# intended recovery: dual deploy -- drogue at apogee, main at 450 m AGL. +parachute= [ + { condition = "top", delay = 0.0, cd = 1.5, diameter = 0.5 }, # drogue (~24 m/s) + { altitude = 450.0, cd = 1.5, diameter = 1.5 }, # main (~8 m/s) +] + +# contingency branches -> one landing footprint each +[[scenario]] +name = "nominal" # dual deploy works +[[scenario]] +name = "recovery-failure" # no recovery -> ballistic (worst case impact) +recovery = "none" +[[scenario]] +name = "motor-cutoff" # thrust quits mid-burn, airframe intact (recovers) +motor_cutoff = 1.5 +[[scenario]] +name = "cato" # structural failure: thrust stops + no recovery +cato = 1.0 diff --git a/examples/astra/fetch-engine.sh b/examples/astra/fetch-engine.sh new file mode 100755 index 0000000..a7a7f50 --- /dev/null +++ b/examples/astra/fetch-engine.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +# Download the Cesaroni 4263L1350-P motor thrust curve (RASP .eng). +# This is the exact file used by RocketPy's "Astra" model, so the motor matches +# the rocket parameters in config.toml. +set -euo pipefail +cd "$(dirname "$0")" +URL="https://raw.githubusercontent.com/RocketPy-Team/RocketPy/master/data/motors/cesaroni/Cesaroni_4263L1350-P.eng" +echo "downloading Cesaroni_4263L1350-P.eng ..." +curl -fsSL "$URL" -o Cesaroni_4263L1350-P.eng +echo "saved to $(pwd)/Cesaroni_4263L1350-P.eng" diff --git a/examples/astra/footprints-map.png b/examples/astra/footprints-map.png new file mode 100644 index 0000000..a6e44c5 Binary files /dev/null and b/examples/astra/footprints-map.png differ diff --git a/examples/astra/footprints-map.py b/examples/astra/footprints-map.py new file mode 100644 index 0000000..c223eee --- /dev/null +++ b/examples/astra/footprints-map.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +"""Overlay the contingency landing footprints + the real GPS track on the map. + +Reads each scenario's ground-hit points (output//84/ghp.csv), converts +the local ENU coordinates to lat/lon (pymap3d), and draws one footprint per +scenario on OpenStreetMap tiles (staticmap). The rocket's measured GPS track +(measured-track.dat, up to ~145 m AGL where the record ends) is drawn too. + +Run via uv after trochia: uv run footprints-map.py # -> footprints-map.png +""" +import csv + +import pymap3d as pm +from PIL import ImageDraw, ImageFont +from staticmap import StaticMap, Line, CircleMarker + +# Launch site: Ponte de Sor, Portugal (EuRoC). First GPS fix in flight_data. +LAUNCH_LAT, LAUNCH_LON, LAUNCH_ALT = 39.3898, -8.290, 160.0 + +SCENARIOS = [ + ("nominal", "nominal (dual deploy)", "#31688e"), + ("recovery-failure", "recovery failure (ballistic)", "#cc0000"), + ("motor-cutoff", "motor cutoff @ 1.5 s", "#dd8800"), + ("cato", "CATO @ 1.0 s", "#4ac16d"), +] + + +def enu_to_lonlat(e, n): + x, y, z = pm.enu2ecef(e, n, 0.0, LAUNCH_LAT, LAUNCH_LON, LAUNCH_ALT) + lat, lon, _ = pm.ecef2geodetic(x, y, z) + return lon, lat + + +def loop(scenario): + pts = [] + with open(f"output/{scenario}/84/ghp.csv") as f: + for row in csv.DictReader(f, skipinitialspace=True): + pts.append(enu_to_lonlat(float(row["ghp_e"]), float(row["ghp_n"]))) + return pts + + +def measured_track(): + pts = [] + with open("measured-track.dat") as f: + for line in f: + if line.startswith("#"): + continue + _, lat, lon, _ = line.split() + lat, lon = float(lat), float(lon) + if abs(lat) < 1.0 and abs(lon) < 1.0: + continue # GPS dropout fix (lat=lon~0); skip + pts.append((lon, lat)) + return pts + + +def find_font(size): + for p in ("/usr/share/fonts/TTF/Roboto-Regular.ttf", + "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"): + try: + return ImageFont.truetype(p, size) + except OSError: + continue + return ImageFont.load_default() + + +def main(): + m = StaticMap(950, 950, url_template="https://tile.openstreetmap.org/{z}/{x}/{y}.png") + for name, _, color in SCENARIOS: + m.add_line(Line(loop(name), color, 3)) + # measured GPS track (white casing + dark line), ends at ~145 m AGL + track = measured_track() + m.add_line(Line(track, "white", 5)) + m.add_line(Line(track, "#222222", 2)) + m.add_marker(CircleMarker(enu_to_lonlat(0.0, 0.0), "black", 13)) + m.add_marker(CircleMarker(enu_to_lonlat(0.0, 0.0), "white", 7)) + + img = m.render().convert("RGB") + d = ImageDraw.Draw(img, "RGBA") + font, title = find_font(15), find_font(16) + rows = SCENARIOS + [(None, "measured GPS track", "#222222")] + w, h = 250, 24 * (len(rows) + 1) + 34 + d.rectangle([10, 10, 10 + w, 10 + h], fill=(255, 255, 255, 225), outline=(0, 0, 0, 255)) + d.text((20, 18), "contingency footprints", font=title, fill=(0, 0, 0)) + for i, (_, label, color) in enumerate(rows): + y = 44 + i * 24 + d.line([(20, y + 8), (50, y + 8)], fill=color, width=4) + d.text((58, y), label, font=font, fill=(0, 0, 0)) + y = 44 + len(rows) * 24 + d.ellipse([30, y + 2, 40, y + 12], fill="black") + d.text((58, y), "launch (Ponte de Sor)", font=font, fill=(0, 0, 0)) + + img.save("footprints-map.png") + print("saved footprints-map.png") + + +if __name__ == "__main__": + main() diff --git a/examples/astra/footprints.gp b/examples/astra/footprints.gp new file mode 100644 index 0000000..e2cd4d1 --- /dev/null +++ b/examples/astra/footprints.gp @@ -0,0 +1,31 @@ +# Contingency landing footprints (one loop per scenario), East-North from the pad. +# ghp.csv columns: wspeed, wdir, ghp_e, ghp_n, max_altitude. +set datafile separator "," +set terminal pngcairo size 820,800 enhanced font "sans,11" +set output "footprints.png" +set title "Astra contingency landing footprints (落下分散)\nelev 84 deg, heading 133 deg, 5 m/s wind swept 0-360 deg" +set xlabel "East [m]"; set ylabel "North [m]" +set size ratio -1; set grid +set key box opaque top left +set xzeroaxis lt -1 lw 0.5; set yzeroaxis lt -1 lw 0.5 + +N="output/nominal/84/ghp.csv" +F="output/recovery-failure/84/ghp.csv" +M="output/motor-cutoff/84/ghp.csv" +C="output/cato/84/ghp.csv" + +$LAUNCH << EOD +0,0 +EOD + +# downrange direction (heading 133 deg): E=sin, N=cos +set arrow from 0,0 to 900*sin(133*pi/180),900*cos(133*pi/180) \ + head filled lw 2 lc rgb "#666666" +set label "downrange\n(133 deg)" at 760*sin(133*pi/180),760*cos(133*pi/180) \ + left tc rgb "#666666" + +plot N using 3:4 skip 1 with linespoints pt 7 ps 0.5 lc rgb "#31688e" title "nominal (dual deploy)", \ + F using 3:4 skip 1 with linespoints pt 7 ps 0.5 lc rgb "#cc0000" title "recovery failure (ballistic)", \ + M using 3:4 skip 1 with linespoints pt 7 ps 0.5 lc rgb "#dd8800" title "motor cutoff @ 1.5 s", \ + C using 3:4 skip 1 with linespoints pt 7 ps 0.5 lc rgb "#4ac16d" title "CATO @ 1.0 s", \ + $LAUNCH using 1:2 with points pt 3 ps 2.5 lw 2 lc rgb "black" title "launch" diff --git a/examples/astra/footprints.png b/examples/astra/footprints.png new file mode 100644 index 0000000..d163b4d Binary files /dev/null and b/examples/astra/footprints.png differ diff --git a/examples/astra/measured-track.dat b/examples/astra/measured-track.dat new file mode 100644 index 0000000..5df751f --- /dev/null +++ b/examples/astra/measured-track.dat @@ -0,0 +1,62 @@ +# time[s] lat lon alt[m] (measured GPS, dropout fixes removed, ends ~145 m AGL) +0.00 39.389800 -8.290000 0.4 +0.00 39.389800 -8.290000 0.4 +0.00 39.389800 -8.290000 47.4 +1.00 39.389800 -8.290000 202.2 +2.00 39.389800 -8.290000 501.4 +3.00 39.389800 -8.290000 786.0 +5.00 39.389800 -8.290000 1290.8 +6.00 39.389800 -8.290000 1498.8 +7.00 39.389800 -8.290000 1714.1 +8.00 39.389800 -8.290000 1915.9 +9.00 39.390300 -8.290400 2077.2 +10.00 39.390300 -8.290500 2245.9 +11.00 39.390400 -8.290600 2381.5 +12.00 39.390500 -8.290700 2524.0 +13.00 39.390500 -8.290700 2650.4 +14.00 39.390500 -8.290700 2751.7 +15.00 39.390500 -8.290700 2854.7 +16.00 39.390500 -8.290700 2946.1 +17.00 39.390500 -8.290700 3008.5 +18.00 39.390500 -8.290700 3087.7 +19.00 39.390500 -8.290700 3141.4 +20.00 39.390500 -8.290700 3178.4 +21.00 39.381900 -8.287400 3210.8 +22.00 39.381900 -8.287300 3231.5 +23.00 39.381900 -8.287300 3246.4 +24.00 39.381900 -8.287300 3249.5 +26.00 39.385200 -8.277000 3228.4 +27.00 39.386200 -8.278800 3204.8 +28.00 39.386000 -8.278300 3173.7 +29.00 39.385900 -8.278300 3135.8 +30.00 39.385200 -8.278300 3087.5 +31.00 39.385300 -8.278300 3032.6 +32.00 39.385000 -8.278000 2971.7 +33.00 39.385500 -8.277100 2907.1 +34.00 39.385500 -8.277100 2827.4 +35.00 39.386400 -8.281000 2746.3 +36.00 39.386800 -8.281300 2664.0 +37.00 39.386900 -8.281700 2569.9 +38.00 39.387300 -8.281800 2472.5 +39.00 39.387200 -8.282200 2387.1 +40.00 39.387000 -8.282500 2291.9 +41.00 39.386900 -8.282800 2200.9 +42.00 39.387000 -8.283100 2101.7 +43.00 39.387000 -8.283300 1988.1 +44.00 39.387000 -8.283600 1893.7 +46.00 39.387100 -8.283900 1680.0 +47.00 39.387100 -8.284100 1566.0 +48.00 39.387000 -8.284300 1460.1 +49.00 39.387000 -8.284400 1343.6 +50.00 39.387000 -8.284600 1250.3 +51.00 39.387000 -8.284700 1148.2 +52.00 39.387000 -8.284900 1044.3 +53.00 39.387000 -8.285100 929.7 +54.00 39.387000 -8.285200 834.2 +55.00 39.387000 -8.285400 729.5 +56.00 39.387000 -8.285700 628.1 +57.00 39.386900 -8.286100 520.5 +58.00 39.386800 -8.286500 445.8 +59.00 39.386800 -8.286800 344.2 +60.00 39.386800 -8.287200 246.3 +61.00 39.386900 -8.287600 145.1 diff --git a/examples/astra/measured.dat b/examples/astra/measured.dat new file mode 100644 index 0000000..6b7a32b --- /dev/null +++ b/examples/astra/measured.dat @@ -0,0 +1,66 @@ +# Astra measured flight (RocketPy data/rockets/astra/flight_data.csv) +# time[s] altitude_AGL[m] +0.00 0.36 +0.00 0.42 +0.00 47.42 +1.00 202.23 +2.00 501.42 +3.00 786.00 +4.00 1029.11 +5.00 1290.78 +6.00 1498.85 +7.00 1714.14 +8.00 1915.90 +9.00 2077.20 +10.00 2245.90 +11.00 2381.50 +12.00 2523.98 +13.00 2650.45 +14.00 2751.66 +15.00 2854.70 +16.00 2946.10 +17.00 3008.46 +18.00 3087.70 +19.00 3141.45 +20.00 3178.45 +21.00 3210.78 +22.00 3231.47 +23.00 3246.39 +24.00 3249.46 +25.00 3244.75 +26.00 3228.37 +27.00 3204.81 +28.00 3173.67 +29.00 3135.84 +30.00 3087.52 +31.00 3032.63 +32.00 2971.69 +33.00 2907.08 +34.00 2827.41 +35.00 2746.26 +36.00 2664.03 +37.00 2569.95 +38.00 2472.49 +39.00 2387.13 +40.00 2291.90 +41.00 2200.93 +42.00 2101.73 +43.00 1988.07 +44.00 1893.71 +45.00 1780.79 +46.00 1680.01 +47.00 1565.96 +48.00 1460.09 +49.00 1343.59 +50.00 1250.34 +51.00 1148.23 +52.00 1044.26 +53.00 929.70 +54.00 834.17 +55.00 729.46 +56.00 628.13 +57.00 520.50 +58.00 445.78 +59.00 344.16 +60.00 246.34 +61.00 145.07 diff --git a/examples/astra/run.sh b/examples/astra/run.sh new file mode 100755 index 0000000..c0ce7c8 --- /dev/null +++ b/examples/astra/run.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Fetch the motor, run the validation + hazard/abort sims, sweep Cd, make plots. +set -euo pipefail +cd "$(dirname "$0")" + +BIN=../../build/bin/trochia +[ -f Cesaroni_4263L1350-P.eng ] || ./fetch-engine.sh + +rm -rf output output-validation + +# 1) validation: reconstruct the actual flight (still air, as-flown descent) +"$BIN" config-validation.toml >/dev/null + +# 2) hazard zone + abort: wind sweep x contingency scenarios +"$BIN" config.toml >/dev/null + +# 3) Cd sensitivity: sweep the constant Cd in still air, record apogee +echo "# Cd apogee[m] t_apogee[s]" > cd-apogee.dat +for cd in 0.45 0.50 0.55 0.58 0.65 0.75 0.85; do + # POSIX sed (portable across GNU/BSD): set Cd, redirect output dir + sed -e "s/^[[:space:]]*Cd[[:space:]]*=.*/Cd = $cd/" \ + -e 's#output-validation#.cd-out#' config-validation.toml > .cd-sweep.toml + rm -rf .cd-out + "$BIN" .cd-sweep.toml >/dev/null + f=$(find .cd-out -name pos.dat | head -1) + awk -v cd="$cd" 'NR>1{a=$3; if(a>mx){mx=a;mt=$1}} END{printf "%s %.1f %.1f\n",cd,mx,mt}' "$f" >> cd-apogee.dat +done +rm -rf .cd-out .cd-sweep.toml + +# 4) plots +gnuplot validation.gp # -> validation.png (vs measured) +gnuplot cd-sensitivity.gp # -> cd-sensitivity.png (representative Cd) +gnuplot footprints.gp # -> footprints.png (hazard zone, East-North) +uv run footprints-map.py # -> footprints-map.png (hazard zone on OSM map; needs network) + +echo "wrote validation.png, cd-sensitivity.png, footprints.png and footprints-map.png" diff --git a/examples/astra/validation.gp b/examples/astra/validation.gp new file mode 100644 index 0000000..5015cc9 --- /dev/null +++ b/examples/astra/validation.gp @@ -0,0 +1,32 @@ +# Altitude history: trochia vs Astra's measured flight (RocketPy flight_data). +# measured.dat is "time[s] altitude_AGL[m]"; trochia pos.dat is +# "time east up north" -> altitude is column 3. +set terminal pngcairo size 1180,560 enhanced font "sans,10" +set output "validation.png" + +MEAS = "measured.dat" +TROC = "output-validation/84/0.001/0/pos.dat" + +set multiplot layout 1,2 title \ + "Astra (Faraday Rocketry UPV, EuRoC'22): trochia vs measured flight" font "sans,13" + +# --- ascent (validation) --- +set title "Ascent (validation): apogee 3260 m vs measured 3249 m" +set xlabel "time [s]"; set ylabel "altitude AGL [m]" +set xrange [0:28]; set yrange [0:3500]; set grid +set key bottom right +set arrow from 24,0 to 24,3249 nohead dt 3 lc rgb "#aaaaaa" +set label "measured apogee\n3249 m, t = 24 s" at 3,3300 left tc rgb "#555555" +plot MEAS using 1:2 with points pt 7 ps 0.7 lc rgb "#e08214" title "measured", \ + TROC using 1:3 with lines lw 2.5 lc rgb "#31688e" title "trochia (Cd=0.55)" + +# --- full flight incl. the ballistic / partial-recovery descent --- +unset arrow; unset label +set title "Full flight: as-flown descent (recovery did not fully deploy, approx 100 m/s)" +set xlabel "time [s]"; set ylabel "altitude AGL [m]" +set xrange [0:65]; set yrange [0:3500]; set grid +set key top right +plot MEAS using 1:2 with points pt 7 ps 0.7 lc rgb "#e08214" title "measured", \ + TROC using 1:3 with lines lw 2.5 lc rgb "#31688e" title "trochia (effective CdA)" + +unset multiplot diff --git a/examples/astra/validation.png b/examples/astra/validation.png new file mode 100644 index 0000000..bc3e588 Binary files /dev/null and b/examples/astra/validation.png differ