use crate::{ContourPiece, IntegrationOutput, core::Segment};
use num_traits::Float;
use ordered_float::NotNan;
use std::collections::BinaryHeap;
#[derive(Debug, thiserror::Error)]
pub(crate) enum SegmentHeapError {
#[error("non finite segment error estimate")]
NonFiniteErrorEstimate,
}
#[derive(Clone, Default, Debug)]
pub struct SegmentHeap<P, O, F>
where
F: PartialEq + PartialOrd,
P: ContourPiece<Float = F>,
{
inner: BinaryHeap<HeapEntry<P, O, F>>,
next_order: usize,
}
impl<P, O, F> SegmentHeap<P, O, F>
where
F: Float,
P: ContourPiece<Float = F>,
{
pub fn new() -> Self {
Self {
inner: BinaryHeap::new(),
next_order: 0,
}
}
pub fn empty() -> Self {
Self::new()
}
pub fn len(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = &Segment<P, O, F>> {
self.inner.iter().map(|entry| &entry.segment)
}
pub fn push(&mut self, segment: Segment<P, O, F>) -> Result<(), SegmentHeapError> {
let error =
NotNan::new(segment.error).map_err(|_| SegmentHeapError::NonFiniteErrorEstimate)?;
let entry = HeapEntry {
error,
order: self.next_order,
segment,
};
self.next_order += 1;
self.inner.push(entry);
Ok(())
}
pub fn pop_worst(&mut self) -> Option<Segment<P, O, F>> {
self.inner.pop().map(|entry| entry.segment)
}
pub fn into_insertion_ordered(self) -> Vec<Segment<P, O, F>> {
let mut entries = self.inner.into_vec();
entries.sort_by_key(|entry| entry.order);
entries.into_iter().map(|entry| entry.segment).collect()
}
}
impl<P, O, F> SegmentHeap<P, O, F>
where
F: Float,
O: IntegrationOutput<P::Input, Float = F>,
P: ContourPiece<Float = F>,
{
pub fn error(&self) -> F {
self.iter()
.fold(F::zero(), |total, segment| total + segment.error)
}
pub fn result(&self) -> Option<O> {
let mut iter = self.iter();
let first = iter.next()?.result.clone();
Some(iter.fold(first, |total, segment| total.add(&segment.result)))
}
pub(crate) fn samples(&self) -> Option<crate::core::QuadratureSamples<P::Input, O>> {
let mut segments = self.iter().collect::<Vec<_>>();
segments.sort_by(|a, b| a.key.cmp(&b.key));
let total_len = segments
.iter()
.map(|segment| {
segment
.samples
.as_ref()
.map(|samples| samples.samples.len())
})
.sum::<Option<usize>>()?;
let mut samples = Vec::with_capacity(total_len);
for segment in segments {
samples.extend(segment.samples.as_ref()?.samples.iter().cloned());
}
Some(crate::core::QuadratureSamples { samples })
}
}
#[derive(Clone, Debug)]
struct HeapEntry<P, O, F>
where
P: ContourPiece<Float = F>,
{
error: NotNan<F>,
order: usize,
segment: Segment<P, O, F>,
}
impl<P, O, F> Ord for HeapEntry<P, O, F>
where
F: Float,
P: ContourPiece<Float = F>,
{
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.error
.cmp(&other.error)
.then_with(|| other.order.cmp(&self.order))
}
}
impl<P, O, F> PartialOrd for HeapEntry<P, O, F>
where
F: Float,
P: ContourPiece<Float = F>,
{
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<P, O, F> PartialEq for HeapEntry<P, O, F>
where
F: Float,
P: ContourPiece<Float = F>,
{
fn eq(&self, other: &Self) -> bool {
(self.error == other.error) && (self.order == other.order)
}
}
impl<P, O, F> Eq for HeapEntry<P, O, F>
where
F: Float,
P: ContourPiece<Float = F>,
{
}
#[cfg(test)]
mod tests {
use super::*;
use crate::LineSegment;
fn segment(error: f64, result: f64) -> Segment<LineSegment<f64>, f64, f64> {
Segment {
piece: LineSegment::from(0.0..1.0),
result,
error,
samples: None,
key: crate::core::PathKey::new(0),
}
}
#[test]
fn new_heap_is_empty() {
let heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
assert!(heap.is_empty());
assert_eq!(heap.len(), 0);
assert_eq!(heap.error(), 0.0);
assert_eq!(heap.result(), None);
}
#[test]
fn pop_worst_returns_largest_error_first() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
heap.push(segment(1.0, 10.0)).unwrap();
heap.push(segment(5.0, 50.0)).unwrap();
heap.push(segment(2.0, 20.0)).unwrap();
assert_eq!(heap.pop_worst().unwrap().error, 5.0);
assert_eq!(heap.pop_worst().unwrap().error, 2.0);
assert_eq!(heap.pop_worst().unwrap().error, 1.0);
assert!(heap.pop_worst().is_none());
}
#[test]
fn equal_errors_pop_in_insertion_order() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
heap.push(segment(1.0, 10.0)).unwrap();
heap.push(segment(1.0, 20.0)).unwrap();
heap.push(segment(1.0, 30.0)).unwrap();
assert_eq!(heap.pop_worst().unwrap().result, 10.0);
assert_eq!(heap.pop_worst().unwrap().result, 20.0);
assert_eq!(heap.pop_worst().unwrap().result, 30.0);
}
#[test]
fn push_rejects_nan_error() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
let result = heap.push(segment(f64::NAN, 0.0));
assert!(matches!(
result,
Err(SegmentHeapError::NonFiniteErrorEstimate)
));
}
#[test]
fn error_sums_segment_errors() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
heap.push(segment(0.1, 1.0)).unwrap();
heap.push(segment(0.2, 2.0)).unwrap();
heap.push(segment(0.3, 3.0)).unwrap();
assert!((heap.error() - 0.6).abs() < 1e-12);
}
#[test]
fn result_sums_segment_results() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
heap.push(segment(0.1, 1.0)).unwrap();
heap.push(segment(0.2, 2.0)).unwrap();
heap.push(segment(0.3, 3.0)).unwrap();
assert_eq!(heap.result(), Some(6.0));
}
#[test]
fn into_insertion_ordered_returns_original_push_order() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
heap.push(segment(3.0, 10.0)).unwrap();
heap.push(segment(1.0, 20.0)).unwrap();
heap.push(segment(2.0, 30.0)).unwrap();
let segments = heap.into_insertion_ordered();
let results = segments
.into_iter()
.map(|segment| segment.result)
.collect::<Vec<_>>();
assert_eq!(results, vec![10.0, 20.0, 30.0]);
}
use crate::core::{PathKey, QuadratureSample, QuadratureSamples};
fn segment_with_sample(
key: PathKey,
error: f64,
value: f64,
) -> Segment<LineSegment<f64>, f64, f64> {
Segment {
piece: LineSegment::new(0.0, 1.0),
result: value,
error,
key,
samples: Some(QuadratureSamples {
samples: vec![QuadratureSample {
point: value,
weight: 1.0,
value,
}],
}),
}
}
#[test]
fn heap_samples_are_returned_in_path_order_not_error_order() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
let root = PathKey::new(0);
let left_key = root.left_child();
let right_key = root.right_child();
heap.push(segment_with_sample(right_key, 10.0, 2.0))
.unwrap();
heap.push(segment_with_sample(left_key, 1.0, 1.0)).unwrap();
let samples = heap.samples().unwrap();
let values = samples
.samples
.iter()
.map(|sample| sample.value)
.collect::<Vec<_>>();
assert_eq!(values, vec![1.0, 2.0]);
}
#[test]
fn heap_samples_returns_none_if_any_segment_lacks_samples() {
let mut heap = SegmentHeap::<LineSegment<f64>, f64, f64>::new();
heap.push(segment_with_sample(PathKey::new(0).left_child(), 1.0, 1.0))
.unwrap();
heap.push(Segment {
piece: LineSegment::new(0.0, 1.0),
result: 2.0,
error: 2.0,
key: PathKey::new(0).right_child(),
samples: None,
})
.unwrap();
assert!(heap.samples().is_none());
}
}