Skip to main content

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}