epiphany/crates/epiphany-determinism/src/coord.rs

200 lines
8.0 KiB
Rust

//! The canonical spatial coordinate grid.
//!
//! Appendix D §"Quantized Layout Coordinates": canonical layout coordinates
//! are quantized to a fixed grid of `1/1024` staff space per unit. Internal
//! solvers may use floating point during computation; the act of quantization
//! at serialization time absorbs all floating-point variation from canonical
//! output. Two implementations whose internal computations agree to better
//! than `1/2048` staff space at every coordinate produce identical canonical
//! output after quantization.
//!
//! Chapter 7 §7.2 fixes the surrounding context: IR coordinates are expressed
//! in staff spaces (as `f32`); the conversion to this canonical integer grid
//! happens only when emitting canonical `ResolvedLayoutIR`.
/// Canonical-grid resolution: units of `1/1024` staff space per unit. Fixed
/// for this format version; changing it is a non-backward-compatible major
/// change (Appendix D).
pub const STAFF_SPACE_GRID: i64 = 1024;
/// First `f64` strictly above `i64::MAX` (`2^63`). A finite coordinate is
/// representable as `QuantizedCoord` iff its scaled unit value lands in
/// `[-2^63, 2^63)`; outside that, `as i64` would silently saturate.
const I64_SPAN: f64 = 9_223_372_036_854_775_808.0;
/// A canonical spatial coordinate, quantized to `1/1024` of a staff space.
///
/// This is the exact integer type that appears in canonical serialized
/// `ResolvedLayoutIR`. Solvers compute in floating point and round to this
/// grid via [`QuantizedCoord::from_staff_spaces`] at serialization time.
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
pub struct QuantizedCoord {
/// Coordinate value in units of `1/1024` staff space.
pub units: i64,
}
impl QuantizedCoord {
/// The origin (`0` staff spaces).
pub const ORIGIN: QuantizedCoord = QuantizedCoord { units: 0 };
/// Constructs directly from a grid-unit count.
#[inline]
pub const fn from_units(units: i64) -> Self {
QuantizedCoord { units }
}
/// Quantizes a finite, representable staff-space measurement onto the
/// canonical grid using round-to-nearest, ties-to-even on the integer unit
/// value (Appendix D).
///
/// Returns `None` for input that cannot be faithfully placed on the grid:
/// NaN, infinity, or a value whose scaled unit count falls outside the
/// `i64` range. The spec's contract is to *quantize finite internal
/// coordinates*, not to normalize invalid geometry into canonical state —
/// so an `as i64` saturation that would turn `+inf` into `i64::MAX` or NaN
/// into `0` is reported as a failure instead of silently accepted. Callers
/// with a value they know is on-grid can use [`QuantizedCoord::from_units`].
#[inline]
pub fn from_staff_spaces(staff_spaces: f64) -> Option<Self> {
if !staff_spaces.is_finite() {
return None;
}
let scaled = (staff_spaces * STAFF_SPACE_GRID as f64).round_ties_even();
if !(-I64_SPAN..I64_SPAN).contains(&scaled) {
return None;
}
// `scaled` is an integer-valued f64 within `[-2^63, 2^63)`, so the cast
// is exact (no saturation).
Some(QuantizedCoord {
units: scaled as i64,
})
}
/// Quantizes an `f32` staff-space measurement (the IR's native precision,
/// Chapter 7 §7.2) by widening to `f64` first so the multiply-by-1024 and
/// the tie-break happen in the wider format. Same rejection rules as
/// [`QuantizedCoord::from_staff_spaces`].
#[inline]
pub fn from_staff_space_f32(staff_spaces: f32) -> Option<Self> {
Self::from_staff_spaces(staff_spaces as f64)
}
/// The coordinate as a staff-space measurement. Exact for
/// `|units| < 2^53`; `1024` is a power of two, so the division introduces
/// no rounding in that range.
#[inline]
pub fn to_staff_spaces(self) -> f64 {
self.units as f64 / STAFF_SPACE_GRID as f64
}
/// Canonical little-endian serialization (8 bytes, `i64`).
#[inline]
pub fn to_le_bytes(self) -> [u8; 8] {
self.units.to_le_bytes()
}
/// Decodes canonical little-endian bytes. Total: every 8-byte string is a
/// valid `i64` grid coordinate.
#[inline]
pub fn from_le_bytes(bytes: [u8; 8]) -> Self {
QuantizedCoord {
units: i64::from_le_bytes(bytes),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn whole_staff_space_is_1024_units() {
assert_eq!(QuantizedCoord::from_staff_spaces(1.0).unwrap().units, 1024);
assert_eq!(
QuantizedCoord::from_staff_spaces(-2.0).unwrap().units,
-2048
);
assert_eq!(
QuantizedCoord::from_staff_spaces(1.0)
.unwrap()
.to_staff_spaces(),
1.0
);
}
#[test]
fn quantization_is_round_half_to_even() {
// Build inputs that land exactly on `*.5` units after *1024.
let q = |ss: f64| QuantizedCoord::from_staff_spaces(ss).unwrap().units;
let half = 0.5 / STAFF_SPACE_GRID as f64; // 0.5 units -> ties to 0
let one_half = 1.5 / STAFF_SPACE_GRID as f64; // 1.5 units -> ties to 2
let two_half = 2.5 / STAFF_SPACE_GRID as f64; // 2.5 units -> ties to 2
let three_half = 3.5 / STAFF_SPACE_GRID as f64; // 3.5 units -> ties to 4
assert_eq!(q(half), 0);
assert_eq!(q(one_half), 2);
assert_eq!(q(two_half), 2);
assert_eq!(q(three_half), 4);
// Negative ties also go to even.
assert_eq!(q(-one_half), -2);
assert_eq!(q(-two_half), -2);
}
#[test]
fn sub_half_unit_variation_quantizes_away() {
// Appendix D rationale: implementations agreeing to better than
// 1/2048 staff space produce identical canonical output.
let base = 12.0 / STAFF_SPACE_GRID as f64; // exactly 12 units
let jitter = 0.49 / STAFF_SPACE_GRID as f64; // < half a unit
assert_eq!(
QuantizedCoord::from_staff_spaces(base + jitter),
QuantizedCoord::from_staff_spaces(base - jitter)
);
}
#[test]
fn coordinate_round_trips_through_staff_spaces_in_musical_range() {
for units in [-1_000_000, -1024, -1, 0, 1, 1024, 999_999] {
let q = QuantizedCoord::from_units(units);
assert_eq!(
QuantizedCoord::from_staff_spaces(q.to_staff_spaces()),
Some(q)
);
}
}
#[test]
fn bytes_round_trip_full_i64_range() {
for units in [i64::MIN, -1, 0, 1, 42, i64::MAX] {
let q = QuantizedCoord::from_units(units);
assert_eq!(QuantizedCoord::from_le_bytes(q.to_le_bytes()), q);
}
}
#[test]
fn invalid_geometry_is_rejected_not_normalized() {
// NaN, infinities, and out-of-range magnitudes must NOT become valid
// canonical coordinates via `as i64` saturation.
assert_eq!(QuantizedCoord::from_staff_spaces(f64::NAN), None);
assert_eq!(QuantizedCoord::from_staff_spaces(f64::INFINITY), None);
assert_eq!(QuantizedCoord::from_staff_spaces(f64::NEG_INFINITY), None);
assert_eq!(QuantizedCoord::from_staff_spaces(1e300), None);
assert_eq!(QuantizedCoord::from_staff_spaces(-1e300), None);
assert_eq!(QuantizedCoord::from_staff_space_f32(f32::INFINITY), None);
}
#[test]
fn representable_range_boundary() {
// Largest representable: just under 2^63 units, i.e. (2^63 / 1024)
// staff spaces minus a hair. The exact i64::MIN unit count is
// reachable; one staff space beyond the top is not.
let min_ss = i64::MIN as f64 / STAFF_SPACE_GRID as f64;
assert_eq!(
QuantizedCoord::from_staff_spaces(min_ss),
Some(QuantizedCoord::from_units(i64::MIN))
);
// 2^63 staff-space-units worth is out of range (would saturate).
let over = I64_SPAN / STAFF_SPACE_GRID as f64;
assert_eq!(QuantizedCoord::from_staff_spaces(over), None);
}
}