Skip to main content

Loss

Struct Loss 

Source
pub struct Loss {
    pub volume_error: f64,
    pub boundary_fraction: f64,
    pub thin_runs: usize,
    pub small_triangles: usize,
    pub retried_rows: usize,
    pub ambiguous_rows: usize,
}
Expand description

What a rasterisation lost, reported rather than left to be discovered.

A missing feature has no symptom. It does not make a solver fail, produce a NaN or trip the conservation audit — it produces a smooth, plausible answer about a different object, which is the failure this workspace is organised around not having. So every Voxels carries one of these and Loss::is_clean says in one call whether anything here needs reading.

Fields§

§volume_error: f64

voxel volume / mesh volume − 1.

The aggregate of what the grid kept and did not, and it is signed, because the cells the surface bulges out of and the ones it cuts into partly cancel.

That cancellation is why this is not a discretisation error with an order, and it is worth saying plainly because the expectation is so natural. Rasterising a sphere of radius 10 mm — a 32×64 tessellation, and the numbers move a few tenths of a point with that — at 2.5, 2.0 and 1.5 mm gives +4.9%, +5.8%, −2.3%: the first refinement makes it worse and the second changes its sign. Sliding the mesh relative to the grid changes none of it. What is left after the cancellation is a lattice-point count, and its error is erratic by nature.

So use it as a result and not as a bound on the next resolution.

§boundary_fraction: f64

The share of the voxel volume in cells that have a face on the outside.

The rasterisation’s uncertainty rather than its error: the volume that sat close enough to the surface for the answer to have gone either way.

Unlike the volume error this is clean, because it is a surface area rather than a cancellation: the layer is one cell thick over an area A, so the fraction is A·dx/Vfirst order, with a coefficient. For a sphere that is c·3dx/R, and the tests measure c ≈ 0.82.

It is the number to put in front of someone choosing a cell size. Forty-three percent of your object’s volume is in cells that could have gone either way says what 2 mm on a 20 mm ball means in a way that a step count does not.

§It does not bound the volume error, and an earlier version of this sentence said it did

The argument is seductive and wrong: “only cells the surface passes through can be misclassified, and those are these”. They are not. This counts exposed cells that came out filled; a cell the surface passes through whose centre landed outside is misclassified too and appears in neither the numerator nor the denominator. The two are also fractions of different volumes — this of the voxel volume, the error of the mesh’s.

The 0.4 mm plate in a_designed_shape.rs is the counterexample and it was in the suite the whole time: at a 2 mm cell it reports a volume error of +4.0 against a boundary fraction of 1.0.

§thin_runs: usize

Solid runs one or two cells thick, counted along all three axes.

A feature two cells across is not resolved by any scheme here — a seven-point stencil has no interior in it, a trilinear element has one element — and a feature one cell across exists only by luck of where the surface fell relative to the cell centres. Move the mesh half a cell and it may not be there at all.

§small_triangles: usize

Triangles smaller than one face of a cell. See Mesh::triangles_below.

§retried_rows: usize

Scanlines whose first ray was degenerate and which a perturbed one decided.

Not a loss — these rows came out right — but the count says the mesh has geometry lined up with the grid, which is the common case rather than the exotic one: a cube on cell boundaries sends every row through a face diagonal, and this reads in the hundreds.

It is here because the alternative is a mechanism nothing can see working. Loss::ambiguous_rows counts only the rows where all the perturbations failed, and no mesh in the test suite has ever produced one — so without this field the whole retry path would be exercised only by inference from a cell count, and a broken retry would look exactly like a mesh that never needed it.

§ambiguous_rows: usize

Scanlines no ray could decide, after every perturbation was tried.

A ray through a closed surface must cross it an even number of times, and must not pass through an edge. A row that fails both tests on all four rays cannot be filled by parity. These rows are left empty, which is visible as a slot missing from the shape rather than as a subtly wrong fill — and the count is here so it is not only visible in a picture.

No mesh in the test suite has produced one, which is stated rather than left to be assumed: the branch is written and reasoned about and is not covered by a measurement, so a caller who sees a nonzero value here is in territory this crate has not walked. Loss::retried_rows is the nearby thing that is exercised.

Implementations§

Source§

impl Loss

Source

pub fn is_clean(&self) -> bool

Whether anything was lost that a caller should look at.

The thresholds are deliberately loose and deliberately stated: 2% of volume, and any thin run or ambiguous row at all. Volume error is a smooth thing that a caller trades against cost, so it gets a number; a feature one cell thick and a row that could not be filled are not trade-offs, they are things that either happened or did not.

Three fields are deliberately not here, for two different reasons.

Loss::boundary_fraction and Loss::small_triangles are left out because no threshold on either is right for more than one physics: a diffusion problem run to steady state barely notices a boundary layer that would move a stress concentration by a factor of two, and a large flat face tessellated into a thousand slivers loses nothing at all. Putting a number on those would be this library guessing, which is the one thing it does not do — so they are reported and the judgement is the caller’s.

Loss::retried_rows is left out because it is not a loss. Those rows came out right.

Trait Implementations§

Source§

impl Clone for Loss

Source§

fn clone(&self) -> Loss

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 Copy for Loss

Source§

impl Debug for Loss

Source§

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

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

impl PartialEq for Loss

Source§

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

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

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

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Loss

Auto Trait Implementations§

§

impl Freeze for Loss

§

impl RefUnwindSafe for Loss

§

impl Send for Loss

§

impl Sync for Loss

§

impl Unpin for Loss

§

impl UnsafeUnpin for Loss

§

impl UnwindSafe for Loss

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