Skip to main content

Simulation

Struct Simulation 

Source
pub struct Simulation { /* private fields */ }
Expand description

A rocket, its surroundings, its rail and its settings, ready to fly.

Implementations§

Source§

impl Simulation

Source

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.

Source

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.

Source

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.

Source

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)?);
Source

pub fn with_shared_drag_model(self, model: Arc<dyn DragModel>) -> Self

As Simulation::with_drag_model, with a model already shared, as when one model flies many simulations.

Source

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.

Source

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).

Source

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());
Source

pub fn with_event(self, event: UserEvent) -> Self

Adds a user event, checked during free flight and the descent.

Source

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.

Source

pub fn recovery(&self) -> &[Device]

The recovery devices.

Source

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.

Source

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.

Source

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.

Source

pub fn ejections(&self) -> &[Ejection]

The ejections, in the order given.

Source

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.

Source

pub fn shifts(&self) -> &[MassShift]

The mass shifts, in the order given.

Source

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.

Source

pub fn releases(&self) -> &[MassRelease]

The releases, in the order given.

Source

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).

Source

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.

Source

pub fn separation(&self) -> Option<Separation>

The first separation, if the flight has one.

Source

pub fn separations(&self) -> &[Separation]

The separations, in the order they fire.

Source

pub fn assembly(&self) -> &Assembly

The assembled design.

Source

pub fn aero(&self) -> &AeroModel

The aerodynamic model.

Source

pub fn share_supersonic_table(&mut self, other: &AeroModel) -> bool

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.

Source

pub fn guides(&self) -> Guides

The rail guides.

Source

pub fn rail(&self) -> Rail

The rail.

Source

pub fn environment(&self) -> &Environment

The environment.

Source

pub fn settings(&self) -> FlightSettings

The settings.

Source

pub fn initial_state(&self) -> State

The state at ignition: on the rail, aft end at its foot, at rest.

Source

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).

Source

pub fn run_free( &self, t0_s: f64, state: State, observer: &mut dyn Observer, ) -> Result<FlightResult, SimError>

Flies freely from state at t0_s seconds after launch, as after a rail exit or from a restart: the motors burn as their curves say at that time.

§Errors

As Self::run.

Trait Implementations§

Source§

impl Debug for Simulation

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.