hpr/guide.rs
1//! A guide to the `hpr` crate, in the API reference: what to read first, and how the pieces fit.
2//!
3//! This module has no code, only chapters. Each is short, and the code in it is compiled and run
4//! by CI, so it stays true. The documentation site says the same things at more length, with the
5//! numbers the example programs print; each of the first four chapters links its page.
6//!
7//! 1. [`building`]: a rocket, part by part from the nose back.
8//! 2. [`flying`]: where it flies, from what rail, and what a flight tells you.
9//! 3. [`custom_models`]: a drag model, a wind or an atmosphere of your own in hpr's place.
10//! 4. [`beneath`]: the crates under the builder, for what it doesn't offer.
11//! 5. [`examples`]: the example programs, and what each shows.
12//!
13//! **How far to trust it.** The builder adds no physics; its numbers are those of the models
14//! beneath it. The site's [Accuracy][accuracy] page says how well each has been checked, with
15//! every comparison made. Read it before trusting a number.
16//!
17//! [accuracy]: https://nrdptel.github.io/hpr-sim/accuracy.html
18
19pub mod building {
20 //! A rocket, part by part from the nose back.
21 //!
22 //! [`Rocket::new`](crate::Rocket::new) takes a name and the airframe's outside diameter. The
23 //! `add_` methods then stack its parts in the order they sit, from the nose tip aft:
24 //!
25 //! - [`add_nose`](crate::Rocket::add_nose): the nose cone, first or not at all.
26 //! - [`add_tube`](crate::Rocket::add_tube) and
27 //! [`add_transition`](crate::Rocket::add_transition): body tubes, and a change in diameter
28 //! between them.
29 //! - [`add_fins`](crate::Rocket::add_fins), [`add_motor_tube`](crate::Rocket::add_motor_tube)
30 //! and [`add_mass`](crate::Rocket::add_mass): parts on or in the last tube added.
31 //! - [`set_motor`](crate::Rocket::set_motor): a [`Motor`](crate::Motor) in the motor tube,
32 //! from the built-in catalog or a RASP `.eng` file, the thrust-curve format ThrustCurve.org
33 //! serves.
34 //! - [`add_parachute`](crate::Rocket::add_parachute): a recovery device and what opens it.
35 //!
36 //! Every part names its material ([`material`](crate::rocket::material) finds a built-in one
37 //! by id), and a hollow part its wall. There are no defaults for either, since each would be
38 //! a guess at the rocket's mass. Each number is checked as the part is added, and a part out
39 //! of order is refused with [`Error::Order`](crate::Error::Order).
40 //!
41 //! Once built, a rocket weighs itself ([`mass_properties`](crate::Rocket::mass_properties))
42 //! and finds its stability margin ([`margin`](crate::Rocket::margin)):
43 //!
44 //! ```
45 //! use hpr::rocket::{Fins, Mass, MotorTube, Nose, Tube, material};
46 //! use hpr::{FinPlanform, Motor, NoseShape, Position, Rocket};
47 //!
48 //! let mut rocket = Rocket::new("Small", 0.0563)?;
49 //! rocket
50 //! .add_nose(Nose::hollow(
51 //! NoseShape::Ogive { radius_ratio: 1.0 },
52 //! 0.22,
53 //! 0.0015,
54 //! material("abs")?,
55 //! ))?
56 //! .add_tube(Tube::new(0.9, 0.00115, material("kraft_phenolic")?))?
57 //! .add_fins(Fins::new(
58 //! 3,
59 //! FinPlanform::Trapezoidal {
60 //! root_chord_m: 0.1,
61 //! tip_chord_m: 0.04,
62 //! span_m: 0.045,
63 //! sweep_m: 0.05,
64 //! },
65 //! 0.003175,
66 //! material("birch_plywood")?,
67 //! ))?
68 //! .add_motor_tube(MotorTube::new(0.2, 0.029, 0.001, material("kraft_phenolic")?))?
69 //! // The parachute and its bay as one 200 g mass, 7 cm below the top of the tube.
70 //! .add_mass(Mass::new(0.2, Position::Top { aft_offset_m: 0.07 }))?
71 //! .set_motor(Motor::from_catalog("H54")?)?;
72 //!
73 //! // At liftoff, 0 s after ignition, with the air along the axis at Mach 0.3.
74 //! let liftoff = rocket.mass_properties(0.0)?;
75 //! let margin = rocket.margin(0.0, 0.3)?;
76 //! assert!(liftoff.mass_kg > 0.4);
77 //! assert!(margin.margin_cal.is_some_and(|calibres| calibres > 1.0));
78 //! # Ok::<(), hpr::Error>(())
79 //! ```
80 //!
81 //! The site's page [The builder][builder] walks through a whole rocket, with a recovery bay
82 //! and a parachute.
83 //!
84 //! [builder]: https://nrdptel.github.io/hpr-sim/the-builder.html
85}
86
87pub mod flying {
88 //! Where a rocket flies, from what rail, and what a flight tells you.
89 //!
90 //! An [`Environment`](crate::Environment) is the launch site (latitude, longitude,
91 //! elevation), the atmosphere and the wind: the 1976 US Standard Atmosphere and calm air until
92 //! you change them.
93 //! [`Flight::builder`](crate::Flight::builder) takes the rocket, the environment and the
94 //! rail's length; its methods lean the rail
95 //! ([`inclination_deg`](crate::FlightBuilder::inclination_deg),
96 //! [`heading_deg`](crate::FlightBuilder::heading_deg)) and set the integrator
97 //! ([`settings`](crate::FlightBuilder::settings)). [`fly`](crate::FlightBuilder::fly) flies it
98 //! to the ground.
99 //!
100 //! A [`Flight`](crate::Flight) has the numbers most asked for (apogee, top speed, rail exit
101 //! speed, landing) as methods, and every metric in its
102 //! [`summary`](crate::Flight::summary). Heights are the center of gravity's, above the
103 //! launch site; the rocket starts on the rail, so the first height isn't zero.
104 //!
105 //! To keep the path, pass an observer to [`fly_with`](crate::FlightBuilder::fly_with): a
106 //! [`Recorder`](crate::hpr_sim::Recorder) keeps a row of the channels you ask for at a steady
107 //! interval and at every event.
108 //!
109 //! ```
110 //! # use hpr::rocket::{Fins, Mass, MotorTube, Nose, Tube, material};
111 //! # use hpr::{FinPlanform, Motor, NoseShape, Position, Rocket};
112 //! # let mut rocket = Rocket::new("Small", 0.0563)?;
113 //! # rocket
114 //! # .add_nose(Nose::hollow(NoseShape::Ogive { radius_ratio: 1.0 }, 0.22, 0.0015, material("abs")?))?
115 //! # .add_tube(Tube::new(0.9, 0.00115, material("kraft_phenolic")?))?
116 //! # .add_fins(Fins::new(
117 //! # 3,
118 //! # FinPlanform::Trapezoidal { root_chord_m: 0.1, tip_chord_m: 0.04, span_m: 0.045, sweep_m: 0.05 },
119 //! # 0.003175,
120 //! # material("birch_plywood")?,
121 //! # ))?
122 //! # .add_motor_tube(MotorTube::new(0.2, 0.029, 0.001, material("kraft_phenolic")?))?
123 //! # .add_mass(Mass::new(0.2, Position::Top { aft_offset_m: 0.07 }))?
124 //! # .set_motor(Motor::from_catalog("H54")?)?;
125 //! use hpr::hpr_sim::{Channel, Recorder};
126 //! use hpr::{Environment, Flight};
127 //!
128 //! // Spaceport America, 1,400 m up, with 5 m/s of wind from the west.
129 //! let environment = Environment::new(32.99, -106.97, 1400.0)?.with_constant_wind(5.0, 270.0)?;
130 //! // Height every half second, and at each event.
131 //! let mut recorder = Recorder::new(vec![Channel::Time, Channel::HeightAboveGround], Some(0.5))
132 //! .map_err(hpr::Error::Sim)?;
133 //! let flight = Flight::builder(&rocket, &environment, 1.8)
134 //! .inclination_deg(85.0)
135 //! .heading_deg(270.0)
136 //! .fly_with(&mut recorder)?;
137 //! let highest = recorder.rows().iter().map(|row| row[1]).fold(0.0, f64::max);
138 //! assert_eq!(Some(highest), flight.apogee_m());
139 //! # Ok::<(), hpr::Error>(())
140 //! ```
141 //!
142 //! The site's [Flight metrics][metrics] page defines each metric, and
143 //! [Recording a trajectory][trajectory] each channel.
144 //!
145 //! [metrics]: https://nrdptel.github.io/hpr-sim/physics/metrics.html
146 //! [trajectory]: https://nrdptel.github.io/hpr-sim/recording-a-trajectory.html
147}
148
149pub mod custom_models {
150 //! A drag model, a wind or an atmosphere of your own, flown in hpr's place.
151 //!
152 //! Three of hpr's models are traits a program can implement:
153 //!
154 //! | Trait | What it gives | Where it goes |
155 //! | --- | --- | --- |
156 //! | [`DragModel`](crate::hpr_aero::DragModel) | the rocket's zero-lift drag coefficient at a flow | [`FlightBuilder::drag_model`](crate::FlightBuilder::drag_model) |
157 //! | [`Wind`](crate::hpr_atmos::Wind) | the wind's velocity at a height | [`Environment::with_wind`](crate::Environment::with_wind) |
158 //! | [`Atmosphere`](crate::hpr_atmos::Atmosphere) | the air's pressure, temperature, density, speed of sound and viscosity at a height | [`Environment::with_atmosphere`](crate::Environment::with_atmosphere) |
159 //!
160 //! A drag model replaces the zero-lift drag only, as a drag table from another tool does. The
161 //! flight still scales it for the angle of attack, and the normal force, center of pressure,
162 //! roll and damping stay hpr's, so the margin a rocket reports doesn't change. A model is
163 //! asked a [`DragQuery`](crate::hpr_aero::DragQuery): the Mach number and angles, the
164 //! Reynolds number, whether a motor burns, and hpr's own drag at that flow
165 //! ([`DragQuery::buildup`](crate::hpr_aero::DragQuery::buildup)), so a model can adjust hpr's
166 //! number instead of replacing it. The coefficient is on the rocket's reference area, by
167 //! default a circle of its largest body diameter, and unlike a drag table's it isn't
168 //! rescaled: a curve measured on another area is converted before it is returned
169 //! ([`DragQuery::reference_area_m2`](crate::hpr_aero::DragQuery::reference_area_m2)).
170 //!
171 //! ```
172 //! # use hpr::rocket::{Fins, Mass, MotorTube, Nose, Tube, material};
173 //! # use hpr::{FinPlanform, Motor, NoseShape, Position, Rocket};
174 //! # let mut rocket = Rocket::new("Small", 0.0563)?;
175 //! # rocket
176 //! # .add_nose(Nose::hollow(NoseShape::Ogive { radius_ratio: 1.0 }, 0.22, 0.0015, material("abs")?))?
177 //! # .add_tube(Tube::new(0.9, 0.00115, material("kraft_phenolic")?))?
178 //! # .add_fins(Fins::new(
179 //! # 3,
180 //! # FinPlanform::Trapezoidal { root_chord_m: 0.1, tip_chord_m: 0.04, span_m: 0.045, sweep_m: 0.05 },
181 //! # 0.003175,
182 //! # material("birch_plywood")?,
183 //! # ))?
184 //! # .add_motor_tube(MotorTube::new(0.2, 0.029, 0.001, material("kraft_phenolic")?))?
185 //! # .add_mass(Mass::new(0.2, Position::Top { aft_offset_m: 0.07 }))?
186 //! # .set_motor(Motor::from_catalog("H54")?)?;
187 //! use hpr::hpr_aero::{AeroError, DragModel, DragQuery};
188 //! use hpr::{Environment, Flight};
189 //!
190 //! /// hpr's own drag below Mach 0.5, and 0.6 from there: a made-up rule, to show the idea. It
191 //! /// jumps at Mach 0.5, which a real model would smooth.
192 //! #[derive(Debug)]
193 //! struct Mine;
194 //!
195 //! impl DragModel for Mine {
196 //! fn zero_lift_drag(&self, query: &DragQuery<'_>) -> Result<f64, AeroError> {
197 //! if query.mach() < 0.5 {
198 //! Ok(query.buildup()?.zero_lift_coefficient)
199 //! } else {
200 //! Ok(0.6)
201 //! }
202 //! }
203 //! }
204 //!
205 //! let environment = Environment::new(32.99, -106.97, 1400.0)?;
206 //! let flight = Flight::builder(&rocket, &environment, 1.8)
207 //! .drag_model(Mine)
208 //! .fly()?;
209 //! assert!(flight.max_mach().is_some_and(|mach| mach > 0.5));
210 //! # Ok::<(), hpr::Error>(())
211 //! ```
212 //!
213 //! **How far to trust it:** as far as the model, and no further than hpr's other models,
214 //! which still fly the rest of the rocket and are not yet validated against real flights.
215 //! hpr refuses a drag coefficient that is negative or not finite; it can't know whether a
216 //! model is right. A model is asked many times a step, so keep it quick, and give the same
217 //! answer to the same question: a flight is only as repeatable as its models.
218 //!
219 //! hpr refuses a wind velocity that isn't finite wherever it reads the wind, climbing or
220 //! under a canopy, with a [`SimError::Domain`](hpr_sim::SimError::Domain) that names the wind
221 //! and gives the height above sea level, m. It refuses air it can't use the same way, naming
222 //! the field: a density or pressure that is negative or not finite, or a temperature, speed
223 //! of sound or viscosity that is zero, negative or not finite
224 //! ([issue #301](https://github.com/nrdptel/hpr-sim/issues/301), the report that asked for
225 //! it). A zero density or pressure is taken, as in a vacuum: the standard atmosphere's own
226 //! pressure and density shrink to zero far above its 86 km top.
227 //!
228 //! The site's page [Models of your own][custom] runs the `custom_drag` and `custom_wind`
229 //! examples.
230 //!
231 //! [custom]: https://nrdptel.github.io/hpr-sim/custom-models.html
232}
233
234pub mod beneath {
235 //! The crates under the builder, for what it doesn't offer.
236 //!
237 //! The builder makes the same things the crates below it use, and hands them over:
238 //!
239 //! - [`Rocket::design`](crate::Rocket::design) is the design tree, an [`hpr_design::Rocket`],
240 //! as a design file holds it. Add what the builder can't (clusters, pods, launch lugs, rail
241 //! buttons, stages) with [`hpr_design`], and make a rocket of it again with
242 //! [`Rocket::from_design`](crate::Rocket::from_design).
243 //! - [`FlightBuilder::simulation`](crate::FlightBuilder::simulation) is the
244 //! [`hpr_sim::Simulation`] a flight runs. Its methods add a stage separation, events of your
245 //! own, moving or released masses, or another tool's drag table;
246 //! [`run`](crate::hpr_sim::Simulation::run) flies it.
247 //! - [`Environment::sim`](crate::Environment::sim) is the [`hpr_sim::Environment`], and
248 //! [`Environment::from_sim`](crate::Environment::from_sim) wraps one built directly, with a
249 //! geoid undulation, say.
250 //!
251 //! Every crate of the workspace is re-exported by name: `hpr::hpr_aero` is the aerodynamics,
252 //! `hpr::hpr_motor` the motors, `hpr::hpr_io` the file formats, and so on. The site's
253 //! [API reference][api] page lists them all.
254 //!
255 //! [api]: https://nrdptel.github.io/hpr-sim/api.html
256}
257
258pub mod examples {
259 //! The example programs, and what each shows.
260 //!
261 //! Each runs with `cargo run --example <name> -p hpr` from anywhere in the repository. What
262 //! each prints is committed beside it as `<name>.output.txt`, and CI checks, on macOS,
263 //! Windows and Linux, that it still prints exactly that.
264 //!
265 //! | Example | What it shows |
266 //! | --- | --- |
267 //! | [`build_and_fly`][build_and_fly] | a rocket built part by part, weighed, and flown in a wind under a parachute |
268 //! | [`motor_choice`][motor_choice] | one rocket on each 29 mm motor in the catalog, with the best ejection delay |
269 //! | [`fin_sizing`][fin_sizing] | fins of five spans: the margin, the apogee and the drift of each |
270 //! | [`catalog_rocket`][catalog_rocket] | a rocket built from a maker's catalog parts, weighed part by part and flown |
271 //! | [`custom_drag`][custom_drag] | drag models of your own in place of hpr's |
272 //! | [`custom_wind`][custom_wind] | a wind model of your own, which turns with height |
273 //! | [`ork_two_stage`][ork_two_stage] | a two-stage OpenRocket file flown through the crates beneath |
274 //! | [`fin_flutter`][fin_flutter] | the fin flutter speed along a flight |
275 //! | [`era5_weather`][era5_weather] | a flight in a day's weather from an ERA5 file |
276 //!
277 //! [build_and_fly]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/build_and_fly.rs
278 //! [motor_choice]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/motor_choice.rs
279 //! [fin_sizing]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/fin_sizing.rs
280 //! [catalog_rocket]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/catalog_rocket.rs
281 //! [custom_drag]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/custom_drag.rs
282 //! [custom_wind]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/custom_wind.rs
283 //! [ork_two_stage]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/ork_two_stage.rs
284 //! [fin_flutter]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/fin_flutter.rs
285 //! [era5_weather]: https://github.com/nrdptel/hpr-sim/blob/main/crates/hpr/examples/era5_weather.rs
286}