pub struct Simulation { /* private fields */ }Expand description
A rocket, its surroundings, its rail and its settings, ready to fly.
Implementations§
Source§impl Simulation
impl Simulation
Sourcepub fn new(
rocket: &Rocket,
configuration_id: &str,
environment: Environment,
rail: Rail,
settings: FlightSettings,
) -> Result<Self, SimError>
pub fn new( rocket: &Rocket, configuration_id: &str, environment: Environment, rail: Rail, settings: FlightSettings, ) -> Result<Self, SimError>
Assembles rocket in configuration configuration_id, runs its checks and builds its
aerodynamic model.
§Errors
SimError::DesignChecks if the checks report errors and the settings don’t accept them;
errors assembling the design or building its aerodynamics; a bad rail, rail geometry or
time cap.
Sourcepub fn from_laid_out(
laid_out: LaidOut,
configuration_id: &str,
environment: Environment,
rail: Rail,
settings: FlightSettings,
) -> Result<Self, SimError>
pub fn from_laid_out( laid_out: LaidOut, configuration_id: &str, environment: Environment, rail: Rail, settings: FlightSettings, ) -> Result<Self, SimError>
As Simulation::new, on a design laid out already (hpr_design::LaidOut). The checks
and the assembly share its layout, which is most of the cost of building a simulation; a
Monte Carlo run lays out each flight from the nominal one’s
(hpr_design::LaidOut::relay).
§Errors
As Simulation::new.
Sourcepub fn with_drag_table(self, table: DragTable) -> Self
pub fn with_drag_table(self, table: DragTable) -> Self
Flies another tool’s C_D0(M) table instead of the drag buildup, and instead of any drag
model (Simulation::with_drag_model). A negative coefficient where the flight meets one
stops it with hpr_aero::AeroError::Domain.
Sourcepub fn with_drag_model(self, model: impl DragModel + 'static) -> Self
pub fn with_drag_model(self, model: impl DragModel + 'static) -> Self
Flies a drag model of your own instead of the drag buildup, and instead of any drag table
(hpr_aero::custom). The model gives the zero-lift drag coefficient on the rocket’s
reference area, not rescaled; the flight scales it for the angle of attack, and the
normal force, center of pressure, roll and damping stay hpr’s. A model’s own errors reach
the caller as SimError::Aero around hpr_aero::AeroError::DragModel. Like a table,
the model is the whole stack’s: a flight with a powered separation refuses it at the
separation, since the sustainer would fly on without it.
§Examples
Valetudo with a drag coefficient of 0.45 at every speed:
use hpr_aero::{AeroError, DragModel, DragQuery};
use hpr_core::geodesy::Geodetic;
use hpr_design::Rocket;
use hpr_sim::{Environment, EventKind, FlightSettings, Rail, Simulation};
#[derive(Debug)]
struct Constant(f64);
impl DragModel for Constant {
fn zero_lift_drag(&self, _query: &DragQuery<'_>) -> Result<f64, AeroError> {
Ok(self.0)
}
}
let rocket: Rocket = serde_json::from_str(include_str!(
"../../../validation/designs/rocketpy-valetudo.json"
))?;
let site = Geodetic::from_degrees(32.99, -106.97, 1400.0)?;
// The apogee's height with a constant drag coefficient `cd`.
let apogee_m = |cd: f64| -> Result<f64, Box<dyn std::error::Error>> {
let flight = Simulation::new(
&rocket,
"example",
Environment::standard(site)?,
Rail::vertical(3.0),
FlightSettings::default(),
)?
.with_drag_model(Constant(cd))
.run(&mut ())?;
let apogee = flight.event(EventKind::Apogee).ok_or("no apogee")?;
Ok(apogee.sample.cg_enu_m.z)
};
assert!(apogee_m(0.9)? < apogee_m(0.45)?);As Simulation::with_drag_model, with a model already shared, as when one model flies
many simulations.
Sourcepub fn with_drag_scale(self, scale: f64) -> Result<Self, SimError>
pub fn with_drag_scale(self, scale: f64) -> Result<Self, SimError>
Multiplies the rocket’s zero-lift drag coefficient by scale, whatever gives it: hpr’s
buildup, a drag table or a drag model (hpr_aero::AeroModel::with_drag_scale). Unlike
a table or a model it is not the whole stack’s: a sustainer lit at a powered separation
keeps the same scale. Recovery devices’ drag is their own and is not scaled. A Monte Carlo
run disperses drag this way (hpr_analysis::montecarlo).
§Errors
SimError::Aero for a scale that is negative or not finite.
Sourcepub fn with_full_base_drag_under_power(self) -> Self
pub fn with_full_base_drag_under_power(self) -> Self
Keeps the aft base’s whole drag while a motor burns, as OpenRocket 24.12 does, instead of
taking the burning motor’s cross-section off it
(hpr_aero::AeroModel::with_full_base_drag_under_power). A sustainer lit at a powered
separation keeps the same rule. For sizing a difference from OpenRocket, not a better model
(ADR-097).
Sourcepub fn with_normal_force_table(
self,
table: NormalForceTable,
) -> Result<Self, SimError>
pub fn with_normal_force_table( self, table: NormalForceTable, ) -> Result<Self, SimError>
Flies another tool’s normal force and center of pressure, against Mach number and angle of
attack, instead of hpr’s own (hpr_aero::NormalForceTable, read from a RASAero II
export). The table sets the static normal force at the center of mass’s airflow; the pitch
and yaw damping stay hpr’s, from the airspeed the rotation adds at each component, since a
table has none (the decision record on normal-force overrides, ADR-032). The
flight still refuses Mach 5 and faster, where hpr’s components, which give that damping,
end.
§Errors
SimError::Aero around hpr_aero::AeroError::Domain for a center of pressure in the
table outside the rocket (hpr_aero::AeroModel::with_normal_force_table).
§Examples
Valetudo from a 3 m rail on a small export with invented numbers: 9 per radian, and the center of pressure 55 inches from the nose tip.
use hpr_aero::NormalForceTable;
use hpr_core::geodesy::Geodetic;
use hpr_design::Rocket;
use hpr_sim::{Environment, EventKind, FlightSettings, Rail, Simulation};
let rocket: Rocket = serde_json::from_str(include_str!(
"../../../validation/designs/rocketpy-valetudo.json"
))?;
// The text of a RASAero II export; a program would read it from the file.
let export = "Mach,Alpha,CN,CN Potential,CP\n\
0,0,0,0,55\n\
1,0,0,0,55\n\
0,2,0.314159,0.314159,55\n\
1,2,0.314159,0.314159,55\n";
// On RASAero II's reference, the body's largest section, which hpr rescales to the
// rocket's reference area.
let table = NormalForceTable::from_rasaero_csv(export)?;
let site = Geodetic::from_degrees(32.99, -106.97, 1400.0)?;
let simulation = Simulation::new(
&rocket,
"example",
Environment::standard(site)?,
Rail::vertical(3.0),
FlightSettings::default(),
)?
.with_normal_force_table(table)?;
let flight = simulation.run(&mut ())?;
assert!(flight.event(EventKind::Apogee).is_some());Sourcepub fn with_event(self, event: UserEvent) -> Self
pub fn with_event(self, event: UserEvent) -> Self
Adds a user event, checked during free flight and the descent.
Sourcepub fn with_recovery(self, devices: Vec<Device>) -> Result<Self, SimError>
pub fn with_recovery(self, devices: Vec<Device>) -> Result<Self, SimError>
Flies with these recovery devices, in the order given: a device’s index in this list names
it in EventKind and in crate::recovery::Device::released_by.
§Errors
SimError::Domain for a device whose drag area, lag, inflation, trigger or release index
is outside its domain, or whose trigger names a motor that isn’t there or has no ejection
delay in seconds.
Sourcepub fn with_separation(self, separation: Separation) -> Result<Self, SimError>
pub fn with_separation(self, separation: Separation) -> Result<Self, SimError>
Flies with a separation: at its trigger the stack comes apart at the stage boundary, and
each body descends under its own devices (crate::recovery::Separation); or, when the
nose’s body still has a motor to burn, it flies on as a sustainer and the aft body
descends. The same as Self::with_separations with this one alone.
Call this after Self::with_recovery: it checks the devices against the bodies.
§Errors
SimError::Domain if the design has no stage aft of the split, if a body carries no
device (the descent has no airframe drag, so it would fall as if in a vacuum), if the
trigger is out of its domain, or if its time is known and an aft body’s motor burns past
it (but for a lit motor a powered separation drops burning when it says so,
Separation::drops_burning), or if it is timed from a motor with no ignition known
before the flight, so that it could never fire; SimError::Parting if an ejection
already given parts at its stage boundary. The same checks run again if Self::with_recovery is called afterwards, so the
builders can be given in any order. A device on a body that nothing makes is refused when
the flight starts, since an ejection given later can make it.
Sourcepub fn with_separations(
self,
separations: Vec<Separation>,
) -> Result<Self, SimError>
pub fn with_separations( self, separations: Vec<Separation>, ) -> Result<Self, SimError>
Flies with several separations, in the order they fire, each at a stage boundary further
forward than the one before: a stack that drops its stages one at a time under power, as
a three-stage rocket does. Separation k makes body k + 1, the stages between its
boundary and the one before it (or the tail); body 0 keeps the nose. With more than one,
each must leave the nose’s body a motor to burn, so that it flies on as a sustainer and the
part behind descends (crate::recovery::Separation); a lone separation may also end the
ascent, as Self::with_separation says. An empty list flies none.
Call this after Self::with_recovery: it checks the devices against the bodies.
§Errors
As Self::with_separation, for each separation; SimError::Domain as well if the
boundaries don’t move forward in the order given, or if two of the times known before the
flight come in the other order; SimError::Unsupported for more than one separation in
a flight with ejections, whose pieces a sustainer’s cut design doesn’t track. In flight, a
separation that leaves nothing ahead of it to burn, in a flight with more than one, is an
error, as is one that fires before the separation ahead of it in the list.
Sourcepub fn with_ejections(self, ejections: Vec<Ejection>) -> Result<Self, SimError>
pub fn with_ejections(self, ejections: Vec<Ejection>) -> Result<Self, SimError>
Flies with ejections: at each one’s trigger a piece leaves the airframe, at the joint aft of
a body component or as a payload from inside it, and each body flies on to its own landing
under its own devices (crate::Ejection). With a separation as well, the separation’s aft
body is body 1 and ejection k makes body k + 2; without one, ejection k makes body
k + 1. A flight with more than one separation takes none.
Call this after Self::with_recovery: it checks the devices against the bodies. The
builders can be given in any order, and the checks run again when the flight starts.
§Errors
SimError::Parting for a parting the design can’t make; SimError::Domain if a body
carries no device, if a trigger or impulse is out of its domain, if its time is known and a
motor burns past it, or if it is timed from a motor with no ignition known before the
flight; SimError::Unsupported with more than one separation. A device on a body that
nothing makes, and a pushed payload in the nose’s piece, are
refused when the flight starts. In flight, an ejection that fires while a motor burns, or
ahead of a separation that would light one, is an error, and so are a powered separation in
a flight with ejections and a pushed payload whose section’s forward joint hasn’t parted.
Sourcepub fn with_shifts(self, shifts: Vec<MassShift>) -> Result<Self, SimError>
pub fn with_shifts(self, shifts: Vec<MassShift>) -> Result<Self, SimError>
Flies with parts that move along the airframe (MassShift), in the order given: a
shift’s index in this list names it in EventKind::Shift. A shift with a trigger known
before the flight (a time, or a motor’s burnout or delay) starts then; the flight watches
for the apogee and for a height, descending, as it does for a recovery device’s.
§Errors
SimError::Shift for a part that can’t move (MassShift says which);
SimError::Domain for a travel, duration or trigger outside its domain, or a trigger on
a motor with no ignition known before the flight, which could never fire (a shift that
starts before the rocket leaves the rail is refused by Self::run when it comes);
SimError::Unsupported with a separation or ejections, whose pieces are fixed before the
flight with every part where the design puts it.
Sourcepub fn with_releases(self, releases: Vec<MassRelease>) -> Result<Self, SimError>
pub fn with_releases(self, releases: Vec<MassRelease>) -> Result<Self, SimError>
Flies with parts released in flight (MassRelease), in the order given: a release’s
index in this list names it in EventKind::MassRelease and in
FlightResult::released. A release with a trigger known before the flight (a time, or a
motor’s burnout or delay) comes then; the flight watches for the apogee and for a height,
descending, as it does for a recovery device’s.
§Errors
SimError::MassRelease for a part that can’t be released (MassRelease says which);
SimError::Domain for a drag area or trigger outside its domain, or a trigger on a
motor with no ignition known before the flight, which could never fire (a release that
comes before the rocket leaves the rail is refused by Self::run when it comes);
SimError::Unsupported with a separation, ejections or mass shifts, whose parts are
fixed before the flight with every part where the design puts it.
Sourcepub fn releases(&self) -> &[MassRelease]
pub fn releases(&self) -> &[MassRelease]
The releases, in the order given.
Sourcepub fn mass_properties(&self, flight: &FlightResult, t_s: f64) -> MassProperties
pub fn mass_properties(&self, flight: &FlightResult, t_s: f64) -> MassProperties
The stack’s mass properties at t_s as flight flew it: the design’s, its motors burned
to t_s, with each part that moves where it was then. flight must be a flight of this
simulation; nothing checks that it is. A shift whose trigger is known before the flight (a
time, or a motor’s burnout or delay) starts then, whether or not flight got that far; one
the flight watched for starts where flight records it (EventKind::Shift), and hasn’t
started if it doesn’t. A part released at or before t_s is gone: its release came when
its trigger’s time is known before the flight, and otherwise where flight records it
(EventKind::MassRelease). They are the whole stack’s, in body axes about its center of
mass, before any separation (and a flight with a separation has no shifts or releases).
Sourcepub fn tumbling_piece(&self, piece: usize) -> Result<DeviceDrag, SimError>
pub fn tumbling_piece(&self, piece: usize) -> Result<DeviceDrag, SimError>
The drag area of piece piece tumbling on its own, for a device on the body it leads:
crate::recovery::DeviceDrag::tumbling’s model over the piece’s own body components and
fin sets. Piece 0 is the nose’s, the separation makes piece 1, and each ejection the next
(crate::Ejection), so piece k leads body k. Call it after Self::with_ejections
and Self::with_separation, which fix the pieces.
It is the piece’s own area only: a body that still carries another section, until that section’s own parting, tumbles with its lead piece’s area. The model was fitted to whole model rockets tumbling, so a lone nose cone is outside its fit: see the recovery page’s tumble section.
§Errors
SimError::Domain for a piece the airframe doesn’t part into, a payload (it has no
body tube or fin of its own), and as crate::recovery::DeviceDrag::tumbling.
Sourcepub fn separation(&self) -> Option<Separation>
pub fn separation(&self) -> Option<Separation>
The first separation, if the flight has one.
Sourcepub fn separations(&self) -> &[Separation]
pub fn separations(&self) -> &[Separation]
The separations, in the order they fire.
Takes other’s supersonic table for this flight’s aerodynamic model where the two would
build the same table (AeroModel::share_supersonic_table), so that it is built once for
both: for many flights of one airframe, as a Monte Carlo run’s. other is usually
another simulation’s Simulation::aero. The flight is unchanged, bit for bit. A
sustainer’s model, built at a powered separation, still builds its own table every
flight. Returns true if the table is now shared.
Sourcepub fn environment(&self) -> &Environment
pub fn environment(&self) -> &Environment
The environment.
Sourcepub fn settings(&self) -> FlightSettings
pub fn settings(&self) -> FlightSettings
The settings.
Sourcepub fn initial_state(&self) -> State
pub fn initial_state(&self) -> State
The state at ignition: on the rail, aft end at its foot, at rest.
Sourcepub fn run(&self, observer: &mut dyn Observer) -> Result<FlightResult, SimError>
pub fn run(&self, observer: &mut dyn Observer) -> Result<FlightResult, SimError>
Flies from ignition on the pad until the flight ends.
§Errors
SimError from the models or the integrator (other than the step limit, which is a
Termination), or from the observer. The checks that wait for every builder run here
too: a device on a body nothing makes, and a pushed payload in the nose’s piece
(Self::with_ejections). A mass shift that starts, or a mass release that comes, before
the rocket leaves the rail is SimError::Domain (Self::with_shifts,
Self::with_releases), as is a release that steps the rest’s center of mass below the
ground while it climbs, and a separation or ejection whose time is known before the flight
and comes before the rocket leaves the rail (Self::with_separations,
Self::with_ejections).