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.