{ } JustinGeoSci

Core & utilities

The result and error types every predicate shares, and the optional counters that record which stage answered each call.

Core types

The result and error types every predicate uses, at the crate root.

Sign

pub enum Sign { Positive, Negative, Zero }

The result of every predicate: the sign of the exact determinant. All three variants are real answers and must be handled; Zero is an exact degeneracy, not an error. Sign implements negation (-s), which matches reversing the order of two input points.

use arithmetica::{Sign, orient2d};

fn turn(s: Sign) -> &'static str {
    match s {
        Sign::Positive => "left turn",
        Sign::Negative => "right turn",
        Sign::Zero => "straight",
    }
}

let s = orient2d([0.0, 0.0], [1.0, 0.0], [1.0, 1.0]).unwrap();
assert_eq!(turn(s), "left turn");

// Swapping two points negates the orientation.
assert_eq!(orient2d([1.0, 0.0], [0.0, 0.0], [1.0, 1.0]), Ok(-s));
assert_eq!(-Sign::Zero, Sign::Zero);

NonFinite

pub struct NonFinite { /* private fields */ }

The error returned when an input coordinate is NaN or infinite. It implements Display and core::error::Error, so it works with ? and with error-handling crates. It has no public fields, so more detail can be added later without a breaking change.

use arithmetica::orient2d;

let err = orient2d([0.0, 0.0], [f64::INFINITY, 1.0], [2.0, 2.0]).unwrap_err();
assert_eq!(
    err.to_string(),
    "geometric predicate received a non-finite coordinate (NaN or infinity)"
);

counters — Stage counters

Per-predicate counts of which stage answered each call, for measuring how often input reaches the exact path. snapshot, reset and Counts require the stage-counters feature; with it off there are no counters, and the predicates compile to the same code as without them. The counts are for observation only: nothing in the crate reads them, and a high exact rate means the input is degenerate, not that anything is wrong.

snapshot

pub fn snapshot(predicate: Predicate) -> Counts

The current counts for one predicate. The counters are global and updated with relaxed atomics, so under concurrency they report totals, never which call used which stage.

use arithmetica::counters::{self, Predicate};
use arithmetica::orient2d;

counters::reset();
let _ = orient2d([0.0, 0.0], [1.0, 0.0], [0.0, 1.0]); // decided by the fast filter
let _ = orient2d([0.0, 0.0], [1.0, 3.0], [2.0, 6.0]); // collinear: confirmed exactly

let c = counters::snapshot(Predicate::Orient2d);
assert_eq!(c.total(), 2);
assert_eq!((c.fast, c.exact, c.exact_zero), (1, 1, 1));
assert_eq!(c.exact_rate(), Some(0.5));

Counts

pub struct Counts { pub fast: u64, pub exact: u64, pub non_finite: u64, pub exact_zero: u64 }

Counts for one predicate at one instant.

Field Meaning
fast Calls answered by Stages 1–3: the filter or the expansion stages.
exact Calls that reached the Stage 4 exact-integer path.
exact_zero Of the exact calls, those whose result was Zero. A subset of exact, not a separate category.
non_finite Calls rejected for a non-finite coordinate before any stage ran.

total() is fast + exact + non_finite. exact_rate() is the share of answered calls that needed the exact path, exact / (fast + exact), or None if no call has been answered.

reset

pub fn reset()

Sets every counter for every predicate back to zero, for a measurement that wants a clean baseline. It is global, so counts from other threads are discarded too.

Predicate

pub enum Predicate { Orient2d, Orient3d, Incircle, Insphere }

Which predicate a count belongs to. Predicate::all() lists the four, and name() gives the lowercase function name for reports.

use arithmetica::counters::{self, Predicate};

for p in Predicate::all() {
    let c = counters::snapshot(p);
    println!("{:<9} {} calls, {} exact", p.name(), c.total(), c.exact);
}

Outcome

pub enum Outcome { Fast, Exact, ExactZero, NonFinite }

How a predicate call ended, matching the fields of Counts. Used with record.

record

pub fn record(predicate: Predicate, outcome: Outcome)

Increments one counter. The predicates call this themselves; it is public so that the counters form one complete interface, and user code does not normally need it. Unlike the rest of the module, record exists with the feature off, as an empty inline function that compiles away.