use crate::{Axis2, Real, RealKernelExt, ZeroStatus};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Displacement2Facts {
pub component_zero: [ZeroStatus; 2],
pub known_axis: Option<Axis2>,
pub known_zero: bool,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProductTerm2Facts {
pub factor_zero: [ZeroStatus; 2],
pub term_zero: ZeroStatus,
}
impl ProductTerm2Facts {
pub const fn known_zero(self) -> bool {
matches!(self.term_zero, ZeroStatus::Zero)
}
pub const fn known_nonzero(self) -> bool {
matches!(self.term_zero, ZeroStatus::NonZero)
}
pub const fn unknown_zero(self) -> bool {
matches!(self.term_zero, ZeroStatus::Unknown)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ProductSum2Facts<const TERMS: usize> {
pub term_zero: [ZeroStatus; TERMS],
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Orient2Facts {
pub ab: Displacement2Facts,
pub ac: Displacement2Facts,
pub determinant_terms: ProductSum2Facts<2>,
pub known_zero: Option<bool>,
}
impl<const TERMS: usize> ProductSum2Facts<TERMS> {
pub const fn new(term_zero: [ZeroStatus; TERMS]) -> Self {
Self { term_zero }
}
pub const fn term_zero(self, index: usize) -> ZeroStatus {
self.term_zero[index]
}
pub fn known_zero_mask(self) -> u64 {
term_mask(self.term_zero, ZeroStatus::Zero)
}
pub fn known_nonzero_mask(self) -> u64 {
term_mask(self.term_zero, ZeroStatus::NonZero)
}
pub fn unknown_zero_mask(self) -> u64 {
term_mask(self.term_zero, ZeroStatus::Unknown)
}
pub fn known_zero_count(self) -> u32 {
self.term_zero
.into_iter()
.filter(|status| matches!(status, ZeroStatus::Zero))
.count() as u32
}
pub fn known_nonzero_count(self) -> u32 {
self.term_zero
.into_iter()
.filter(|status| matches!(status, ZeroStatus::NonZero))
.count() as u32
}
pub fn unknown_zero_count(self) -> u32 {
self.term_zero
.into_iter()
.filter(|status| matches!(status, ZeroStatus::Unknown))
.count() as u32
}
pub fn all_terms_known_zero(self) -> bool {
self.known_zero_count() as usize == TERMS
}
}
impl Orient2Facts {
pub fn from_displacements(ab: [&Real; 2], ac: [&Real; 2]) -> Self {
let ab_facts = Displacement2Facts::from_components(ab);
let ac_facts = Displacement2Facts::from_components(ac);
let determinant_terms = product_sum2_facts([[ab[0], ac[1]], [ab[1], ac[0]]]);
let known_zero = determinant_zero_from_terms(determinant_terms);
Self {
ab: ab_facts,
ac: ac_facts,
determinant_terms,
known_zero,
}
}
pub const fn known_zero(self) -> Option<bool> {
self.known_zero
}
pub const fn known_nonzero(self) -> Option<bool> {
match self.known_zero {
Some(true) => Some(false),
Some(false) => Some(true),
None => None,
}
}
pub const fn known_axis_pair(self) -> Option<(Axis2, Axis2)> {
match (self.ab.known_axis, self.ac.known_axis) {
(Some(ab), Some(ac)) => Some((ab, ac)),
_ => None,
}
}
}
impl Displacement2Facts {
pub fn from_components(components: [&Real; 2]) -> Self {
let component_zero = [components[0].zero_status(), components[1].zero_status()];
let known_zero = matches!(component_zero, [ZeroStatus::Zero, ZeroStatus::Zero]);
let known_axis = match component_zero {
[ZeroStatus::NonZero, ZeroStatus::Zero] => Some(Axis2::X),
[ZeroStatus::Zero, ZeroStatus::NonZero] => Some(Axis2::Y),
_ => None,
};
Self {
component_zero,
known_axis,
known_zero,
}
}
pub fn component_zero(self, axis: Axis2) -> ZeroStatus {
self.component_zero[axis.index()]
}
pub fn known_zero_mask(self) -> u8 {
let mut mask = 0;
if matches!(self.component_zero[0], ZeroStatus::Zero) {
mask |= Axis2::X.bit();
}
if matches!(self.component_zero[1], ZeroStatus::Zero) {
mask |= Axis2::Y.bit();
}
mask
}
pub fn known_nonzero_mask(self) -> u8 {
let mut mask = 0;
if matches!(self.component_zero[0], ZeroStatus::NonZero) {
mask |= Axis2::X.bit();
}
if matches!(self.component_zero[1], ZeroStatus::NonZero) {
mask |= Axis2::Y.bit();
}
mask
}
pub fn unknown_zero_mask(self) -> u8 {
let mut mask = 0;
if matches!(self.component_zero[0], ZeroStatus::Unknown) {
mask |= Axis2::X.bit();
}
if matches!(self.component_zero[1], ZeroStatus::Unknown) {
mask |= Axis2::Y.bit();
}
mask
}
pub fn known_zero_count(self) -> u32 {
self.known_zero_mask().count_ones()
}
pub fn known_nonzero_count(self) -> u32 {
self.known_nonzero_mask().count_ones()
}
pub fn unknown_zero_count(self) -> u32 {
self.unknown_zero_mask().count_ones()
}
}
fn term_mask<const TERMS: usize>(statuses: [ZeroStatus; TERMS], needle: ZeroStatus) -> u64 {
let mut mask = 0;
let limit = TERMS.min(64);
for (index, status) in statuses.into_iter().enumerate().take(limit) {
if status == needle {
mask |= 1_u64 << index;
}
}
mask
}
fn determinant_zero_from_terms(terms: ProductSum2Facts<2>) -> Option<bool> {
if terms.all_terms_known_zero() {
Some(true)
} else if terms.known_zero_count() == 1 && terms.known_nonzero_count() == 1 {
Some(false)
} else {
None
}
}
pub fn displacement2(from: [&Real; 2], to: [&Real; 2]) -> [Real; 2] {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "displacement2");
[to[0] - from[0], to[1] - from[1]]
}
pub fn displacement2_facts(from: [&Real; 2], to: [&Real; 2]) -> Displacement2Facts {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "displacement2-facts");
let delta = displacement2(from, to);
Displacement2Facts::from_components([&delta[0], &delta[1]])
}
pub fn product_term2_facts(term: [&Real; 2]) -> ProductTerm2Facts {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "product-term2-facts");
let factor_zero = [term[0].zero_status(), term[1].zero_status()];
let term_zero = if matches!(factor_zero[0], ZeroStatus::Zero)
|| matches!(factor_zero[1], ZeroStatus::Zero)
{
ZeroStatus::Zero
} else if matches!(factor_zero, [ZeroStatus::NonZero, ZeroStatus::NonZero]) {
ZeroStatus::NonZero
} else {
ZeroStatus::Unknown
};
ProductTerm2Facts {
factor_zero,
term_zero,
}
}
pub fn product_sum2_facts<const TERMS: usize>(
terms: [[&Real; 2]; TERMS],
) -> ProductSum2Facts<TERMS> {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "product-sum2-facts");
ProductSum2Facts::new(terms.map(|term| product_term2_facts(term).term_zero))
}
pub fn orient2_expr_facts(a: [&Real; 2], b: [&Real; 2], c: [&Real; 2]) -> Orient2Facts {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "orient2-expr-facts");
let [abx, aby] = displacement2(a, b);
let [acx, acy] = displacement2(a, c);
Orient2Facts::from_displacements([&abx, &aby], [&acx, &acy])
}
pub fn signed_product_sum2<const TERMS: usize>(
positive_terms: [bool; TERMS],
terms: [[&Real; 2]; TERMS],
) -> Real {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "signed-product-sum2");
Real::signed_product_sum2(positive_terms, terms)
}
pub fn positive_product_sum2<const TERMS: usize>(terms: [[&Real; 2]; TERMS]) -> Real {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "positive-product-sum2");
signed_product_sum2([true; TERMS], terms)
}
pub fn wedge2(left: [&Real; 2], right: [&Real; 2]) -> Real {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "wedge2");
signed_product_sum2([true, false], [[left[0], right[1]], [left[1], right[0]]])
}
pub fn dot2(left: [&Real; 2], right: [&Real; 2]) -> Real {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "dot2");
positive_product_sum2([[left[0], right[0]], [left[1], right[1]]])
}
pub fn squared_norm2(vector: [&Real; 2]) -> Real {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "squared-norm2");
positive_product_sum2([[vector[0], vector[0]], [vector[1], vector[1]]])
}
pub fn squared_distance2(a: [&Real; 2], b: [&Real; 2]) -> Real {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "squared-distance2");
let [dx, dy] = displacement2(a, b);
squared_norm2([&dx, &dy])
}
pub fn orient2_expr(a: [&Real; 2], b: [&Real; 2], c: [&Real; 2]) -> Real {
crate::trace_dispatch!("hyperlattice_algebra2", "helper", "orient2-expr");
let [abx, aby] = displacement2(a, b);
let [acx, acy] = displacement2(a, c);
wedge2([&abx, &aby], [&acx, &acy])
}
#[cfg(test)]
mod tests {
use super::*;
type S = Real;
fn s(value: i32) -> S {
value.into()
}
#[test]
fn wedge2_builds_signed_area_expression() {
let left = [s(3), s(4)];
let right = [s(5), s(6)];
assert_eq!(wedge2([&left[0], &left[1]], [&right[0], &right[1]]), s(-2));
}
#[test]
fn displacement2_builds_coordinate_difference() {
let from = [s(1), s(2)];
let to = [s(4), s(6)];
assert_eq!(
displacement2([&from[0], &from[1]], [&to[0], &to[1]]),
[s(3), s(4)]
);
}
#[test]
fn displacement2_facts_expose_axis_and_zero_masks() {
let from = [s(1), s(2)];
let horizontal = [s(4), s(2)];
let same = [s(1), s(2)];
let horizontal_facts =
displacement2_facts([&from[0], &from[1]], [&horizontal[0], &horizontal[1]]);
assert_eq!(horizontal_facts.known_axis, Some(Axis2::X));
assert_eq!(horizontal_facts.known_zero_mask(), Axis2::Y.bit());
assert_eq!(horizontal_facts.known_nonzero_mask(), Axis2::X.bit());
let same_facts = displacement2_facts([&from[0], &from[1]], [&same[0], &same[1]]);
assert!(same_facts.known_zero);
assert_eq!(same_facts.known_zero_count(), 2);
}
#[test]
fn product_sum_helpers_preserve_short_exact_expressions() {
let a = s(2);
let b = s(3);
let c = s(5);
let d = s(7);
assert_eq!(positive_product_sum2([[&a, &b], [&c, &d]]), s(41));
assert_eq!(
signed_product_sum2([true, false], [[&a, &d], [&b, &c]]),
s(-1)
);
}
#[test]
fn product_sum_facts_track_structurally_zero_terms() {
let zero = s(0);
let one = s(1);
let two = s(2);
let three = s(3);
let facts = product_sum2_facts([[&zero, &two], [&one, &three]]);
assert_eq!(facts.term_zero(0), ZeroStatus::Zero);
assert_eq!(facts.term_zero(1), ZeroStatus::NonZero);
assert_eq!(facts.known_zero_mask(), 0b01);
assert_eq!(facts.known_nonzero_mask(), 0b10);
assert_eq!(facts.unknown_zero_count(), 0);
assert!(!facts.all_terms_known_zero());
}
#[test]
fn orient2_expr_facts_track_axis_collinear_and_perpendicular_cases() {
let a = [s(0), s(0)];
let x_axis = [s(4), s(0)];
let farther_x_axis = [s(9), s(0)];
let y_axis = [s(0), s(3)];
let dense = [s(2), s(5)];
let other_dense = [s(9), s(7)];
let collinear = orient2_expr_facts(
[&a[0], &a[1]],
[&x_axis[0], &x_axis[1]],
[&farther_x_axis[0], &farther_x_axis[1]],
);
assert_eq!(collinear.known_axis_pair(), Some((Axis2::X, Axis2::X)));
assert_eq!(collinear.known_zero(), Some(true));
assert_eq!(collinear.determinant_terms.known_zero_count(), 2);
let perpendicular = orient2_expr_facts(
[&a[0], &a[1]],
[&x_axis[0], &x_axis[1]],
[&y_axis[0], &y_axis[1]],
);
assert_eq!(perpendicular.known_axis_pair(), Some((Axis2::X, Axis2::Y)));
assert_eq!(perpendicular.known_nonzero(), Some(true));
assert_eq!(perpendicular.determinant_terms.known_zero_count(), 1);
assert_eq!(perpendicular.determinant_terms.known_nonzero_count(), 1);
let dense_case = orient2_expr_facts(
[&a[0], &a[1]],
[&dense[0], &dense[1]],
[&other_dense[0], &other_dense[1]],
);
assert_eq!(dense_case.known_axis_pair(), None);
assert_eq!(dense_case.known_zero(), None);
}
#[test]
fn squared_norm2_reuses_cached_displacement() {
let dx = s(3);
let dy = s(4);
assert_eq!(squared_norm2([&dx, &dy]), s(25));
}
#[test]
fn orient2_expr_matches_counter_clockwise_triangle() {
let a = [s(0), s(0)];
let b = [s(4), s(0)];
let c = [s(0), s(3)];
assert_eq!(
orient2_expr([&a[0], &a[1]], [&b[0], &b[1]], [&c[0], &c[1]]),
s(12)
);
}
#[test]
fn squared_distance2_avoids_square_root() {
let a = [s(1), s(2)];
let b = [s(4), s(6)];
assert_eq!(squared_distance2([&a[0], &a[1]], [&b[0], &b[1]]), s(25));
}
}