Skip to main content

ShockExpansionBody

Struct ShockExpansionBody 

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

A body of revolution laid out for the second-order shock-expansion method: its segments and the straight elements of its tangent body. A pointed nose’s elements are laid out once; a blunt tip’s start at a handover that moves with the Mach number (crate::blunt_tip), so they are laid out at each.

Implementations§

Source§

impl ShockExpansionBody

Source

pub fn new( segments: &[BodySegment], elements_per_curve: usize, ) -> Result<Self, AeroError>

Lays out a body from its segments, nose first: a curved segment gets elements_per_curve equal steps in x (tangent at both ends and between), a straight one a single element. Behind a blunt tip the steps are counted from the handover aft, laid out at each Mach number (crate::blunt_tip); the segments its cap covers get none.

§Errors
  • AeroError::Unsupported if the first segment doesn’t close to a point at its front (pointed, or blunt with a vertical tip), a spherical cap isn’t the first segment, the radius steps between segments (by more than a millionth of it), the radius falls to zero anywhere but the tip, or, for a pointed nose, the tangent lines of consecutive elements don’t meet in order along the body (a profile the tangent body can’t follow).
  • AeroError::Domain for no segments, elements per curve outside 1..=MAX_ELEMENTS_PER_CURVE, a negative or non-finite cylinder dimension, or a spherical cap whose radius isn’t finite and positive or whose length isn’t in (0, radius].
Source

pub fn with_handover_start(self, start: HandoverStart) -> Self

This body with its march behind a blunt tip’s cap starting from start; a pointed body is unchanged.

Source

pub fn with_handover_cap_rad(self, cap_rad: f64) -> Self

This body with its blunt tip handing over no steeper than cap_rad instead of the flown crate::blunt_tip::MAX_HANDOVER_RAD; a pointed body is unchanged. A steeper cap follows TN D-4865’s own rule to a higher Mach number and starts the march from a steeper cone; what each is worth is measured in ADR-043.

The cap is checked when the handover is taken (Self::handover_m), which refuses one outside (0, crate::blunt_tip::CONE_TABLE_CAP_RAD]. A pointed body has no handover, so it ignores the cap and never reports a bad one.

use hpr_aero::blunt_tip::CONE_TABLE_CAP_RAD;
use hpr_aero::shock_expansion::{BodySegment, DEFAULT_ELEMENTS_PER_CURVE, ShockExpansionBody};
use hpr_design::{NoseShape, Profile};

let nose = BodySegment::Profile {
    profile: Profile::nose(NoseShape::PowerSeries { exponent: 0.5 }, 0.5, 0.05)?,
};
let body = ShockExpansionBody::new(&[nose], DEFAULT_ELEMENTS_PER_CURVE)?;
// At Mach 3 the wedge detaches past either cap, so each hands over at its own slope, and
// the steeper one leaves the shorter cap. This nose is r = R√(x/L), whose slope is
// R/(2√(xL)), so the handover sits at x = (R/(2 tan δ))²/L: 6.31 mm of nose at 24°,
// 3.75 mm at 30°.
let station = |degrees: f64| (0.05 / (2.0 * degrees.to_radians().tan())).powi(2) / 0.5;
let flown = body.handover_m(3.0)?.expect("a vertical tip hands over");
let steeper = body
    .clone()
    .with_handover_cap_rad(CONE_TABLE_CAP_RAD)
    .handover_m(3.0)?
    .expect("a vertical tip hands over");
assert!((flown - station(24.0)).abs() < 1e-9 && (flown - 0.006_31).abs() < 5e-6);
assert!((steeper - station(30.0)).abs() < 1e-9 && (steeper - 0.003_75).abs() < 5e-6);
Source

pub fn length_m(&self) -> f64

The body’s length, m.

Source

pub fn vertex_angle_rad(&self) -> f64

The tip’s half-angle, rad: π/2 for a blunt or vertical tip.

Source

pub fn has_blunt_tip(&self) -> bool

Whether the tip is blunt or vertical, so the body flies TN D-4865’s Newtonian cap ahead of the method (crate::blunt_tip).

Source

pub fn handover_m(&self, mach: f64) -> Result<Option<f64>, AeroError>

Where a blunt tip’s cap hands over to the method at Mach mach, m aft of the vertex: where the body’s slope first falls to crate::blunt_tip::handover_angle_rad, or to the body’s own cap (Self::with_handover_cap_rad). None for a pointed tip.

§Errors
Source

pub fn slope( &self, mach: f64, reference_area_m2: f64, ) -> Result<ShockExpansionSlope, AeroError>

C_Nα (per radian, on reference_area_m2) and the center of pressure at Mach mach, by TN 3527’s multi-step method.

§Errors
  • AeroError::Domain for a Mach number that isn’t above 1, or a reference area that isn’t positive.
  • AeroError::Unsupported where the method doesn’t hold: a tip cone whose shock detaches, a tangent cone steeper than the cone tables’ 30°, a corner the flow can’t turn supersonically, a tip cone whose surface flow is subsonic, a cylinder’s or a boattail’s element whose pressure moves away from the one it relaxes toward (the free stream’s and footnote 8’s; neither is a tangent cone of that element’s own flow, so the reduction of flare_reduction_turns_rad is not read there), or a lift that doesn’t sum to a positive force; and for a blunt tip, whose elements are laid out at each Mach number, a nose steeper than the handover’s slope all the way to its end, or a tangent body whose elements don’t meet in order behind the handover (as Self::new says for a pointed one).

The report states the method for Mach number over nose fineness from 0.4 to 2 (Summary, p. 1); slope doesn’t enforce that range, and its own Mach 6.28 rows are at 2.09.

Source

pub fn segment_slopes( &self, mach: f64, reference_area_m2: f64, ) -> Result<Vec<SegmentSlope>, AeroError>

Each segment’s share of Self::slope, in the order of the segments: its C_Nα (per radian, on reference_area_m2) and that slope’s moment about the vertex. The shares sum to the whole body’s slope and moment up to rounding: every segment’s start is a break of the integral, so each piece of it lies inside one segment.

§Errors

As Self::slope.

Source

pub fn element_flows( &self, mach: f64, ) -> Result<Vec<ElementFlowReport>, AeroError>

The flow the method computes on each element of the tangent body at Mach mach, in order from the vertex or a blunt tip’s handover: what a hand calculation of eq. 19, C_Nα = (2π/A_ref) ∫ Λ r dx, needs. Behind a blunt tip the list starts at the handover (Self::handover_m), and the lift of the Newtonian cap ahead of it is not in the list.

§Errors

As Self::slope, less the check that the lift sums to a positive force.

Source

pub fn aft_flow(&self, mach: f64) -> Result<AftFlow, AeroError>

The surface flow the march delivers to the body’s aft end at Mach mach: everything a corner behind the body needs: the surface Mach number and angle there, the pressure, the gradient the last element carries to it, the radius, and the free stream’s Mach number.

The angle is the last element’s, so on a body that ends in a curve it is that element’s chord rather than the tangent at the very end, and it moves a little with elements_per_curve. On a body that ends in a cylinder or a cone, which is every body a flare joins in a flight today, the two are the same.

This is the flow a corner behind the body turns. The march is downstream-only: TN 3527 eq. 3 fixes each element from the one ahead of it and nothing behind, so a flare added at the aft end cannot change it, and the limit on that flare’s corner (flare_corner_limit_rad) can be read from this body before the flare is drawn.

§Errors

As Self::slope, less the check that the lift sums to a positive force, and AeroError::Unsupported where the surface flow at the aft end is not supersonic.

Source

pub fn reduced_elements(&self, mach: f64) -> Result<usize, AeroError>

How many of the body’s elements the march reduces to the generalized method at Mach mach: those where the gradient behind the corner points away from the tangent cone’s pressure (η < 0, TN 3527 p. 13), which carry no gradient on (see issue #81). Zero means the result doesn’t depend on that reading.

Which turns a corner reduces is a property of its own state, and flare_reduction_turns_rad solves for the two that bound them.

§Errors
  • AeroError::Domain for a Mach number that isn’t finite and above 1.
  • AeroError::Unsupported where the march fails, as for Self::slope: a detached tip shock, a tangent cone past the cone tables’ 30°, a corner the flow can’t turn, subsonic surface flow, or a cylinder’s or boattail’s element that would be reduced. An Ok count doesn’t promise that Self::slope succeeds: it also needs a positive total lift.
Source

pub fn tangent_cone_crossings(&self, mach: f64) -> Result<usize, AeroError>

How many times the marched surface pressure crosses its own tangent cone’s at Mach mach: the number of elements whose gap p_c − p₂ has the opposite sign to the last element that had one, counting only pairs within one segment of the body. A pair that straddles a segment’s start does not count: there p_c itself steps (from a cone’s pressure to the free stream’s where a nose meets a cylinder, to footnote 8’s where a boattail begins, or to a steeper cone’s where a flare does), so the gap changes sign without ever passing through zero, and there is no pole. Within a segment the profile is continuous, so p_c is too, and a sign change means the gap really closed.

A crossing is what marks an answer that moves with the element count. Along an element the method relaxes the pressure and the loading toward the tangent cone’s as e^(−η) with η = k (x − x₂) (eqs. 8, 9 and 19), where the rate per unit length is k = (∂p/∂s)₂ / ((p_c − p₂) cos δ₂). Where the pressure crosses its tangent cone’s the gap passes through zero while the gradient does not, so k has a pole. The pressure itself rides through it (k (p_c − p) cos δ₂ is just the gradient, which stays finite), but the loading borrows the pressure’s k (eq. 19) while its own gap Λ_c − Λ does not close with it, so the loading is driven onto the tangent cone’s arbitrarily fast. A march applies k from the corner over a whole element, so how much of that lands depends on where the crossing falls between corners, and the answer follows the element count instead of settling.

It is a flag, not a verdict, at either end. A count of zero does not promise an answer settled: whether a crossing is seen depends on the mesh, and the count is not even monotone in it: readings that cross at 40 and 160 elements per curve can show none at 10. Nor does a count above zero promise the answer never settles: one reading of hpr’s own sweep crosses at every mesh and still holds to 0.003 per radian from 60 elements on. What is measured is that over 10, 40 and 160 elements the crossings, and only the crossings, mark the readings that move (ADR-044). Nothing in a flight calls this: it is a tool for studying a body, not a guard.

The fineness-3 ogive TN 3527 prints values for never crosses at the Mach numbers hpr can check it at; there η < 0 comes from the gradient changing sign with the gap all one way, which is bounded and settles. So this count, not Self::reduced_elements, is the one to read when an answer moves with the element count; see issue #108.

An element whose gap is exactly zero is skipped rather than given a sign: behind a blunt tip the march starts on its own tangent cone under the default HandoverStart::TangentCone, and that element has no side to be on. Under HandoverStart::Newtonian it does, because its pressure and its tangent cone’s come from different models, and the count then includes that mismatch.

§Errors

As Self::reduced_elements.

Trait Implementations§

Source§

impl Clone for ShockExpansionBody

Source§

fn clone(&self) -> ShockExpansionBody

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for ShockExpansionBody

Source§

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

Formats the value using the given formatter. Read more
Source§

impl PartialEq for ShockExpansionBody

Source§

fn eq(&self, other: &ShockExpansionBody) -> bool

Tests for self and other values to be equal, and is used by ==.
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Tests for !=. The default implementation is almost always sufficient, and should not be overridden without very good reason.
Source§

impl StructuralPartialEq for ShockExpansionBody

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.