use std::{collections::BTreeMap, fmt};
use crate::{
BinaryRelation, NaryRelation, NaryRelationError, UnaryRelation, traits::FiniteRelation,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum IntervalError<T> {
InvalidBounds {
start: T,
end: T,
},
}
impl<T: fmt::Debug> fmt::Display for IntervalError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidBounds { start, end } => {
write!(
f,
"invalid half-open interval: expected start < end, got start={start:?}, end={end:?}"
)
}
}
}
}
impl<T: fmt::Debug> std::error::Error for IntervalError<T> {}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Interval<T: Ord> {
start: T,
end: T,
}
impl<T: Ord> Interval<T> {
pub fn new(start: T, end: T) -> Result<Self, IntervalError<T>> {
if start < end {
Ok(Self { start, end })
} else {
Err(IntervalError::InvalidBounds { start, end })
}
}
#[must_use]
pub fn start(&self) -> &T {
&self.start
}
#[must_use]
pub fn end(&self) -> &T {
&self.end
}
#[must_use]
pub fn contains(&self, point: &T) -> bool {
&self.start <= point && point < &self.end
}
#[must_use]
pub fn overlaps(&self, other: &Self) -> bool {
self.start < other.end && other.start < self.end
}
#[must_use]
pub fn is_adjacent_to(&self, other: &Self) -> bool {
self.end == other.start || other.end == self.start
}
#[must_use]
pub fn can_coalesce_with(&self, other: &Self) -> bool {
self.overlaps(other) || self.is_adjacent_to(other)
}
}
impl<T: Ord + Clone> Interval<T> {
#[must_use]
pub fn intersection(&self, other: &Self) -> Option<Self> {
let start = if self.start >= other.start {
self.start.clone()
} else {
other.start.clone()
};
let end = if self.end <= other.end {
self.end.clone()
} else {
other.end.clone()
};
Self::new(start, end).ok()
}
#[must_use]
pub fn restrict_to(&self, constraint: &Self) -> Option<Self> {
self.intersection(constraint)
}
#[must_use]
pub fn coalesce(&self, other: &Self) -> Option<Self> {
if !self.can_coalesce_with(other) {
return None;
}
let start = if self.start <= other.start {
self.start.clone()
} else {
other.start.clone()
};
let end = if self.end >= other.end {
self.end.clone()
} else {
other.end.clone()
};
Some(Self { start, end })
}
}
fn canonicalize_intervals<T: Ord + Clone>(mut intervals: Vec<Interval<T>>) -> Vec<Interval<T>> {
intervals.sort();
let mut canonical: Vec<Interval<T>> = Vec::with_capacity(intervals.len());
for interval in intervals {
if let Some(last) = canonical.last_mut() {
if let Some(coalesced) = last.coalesce(&interval) {
*last = coalesced;
continue;
}
}
canonical.push(interval);
}
canonical
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidTimeSupport<T: Ord + Clone> {
intervals: Vec<Interval<T>>,
}
impl<T: Ord + Clone> ValidTimeSupport<T> {
#[must_use]
pub fn new() -> Self {
Self {
intervals: Vec::new(),
}
}
#[must_use]
pub fn from_intervals<I>(intervals: I) -> Self
where
I: IntoIterator<Item = Interval<T>>,
{
intervals.into_iter().collect()
}
#[must_use]
pub fn len(&self) -> usize {
self.intervals.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.intervals.is_empty()
}
#[must_use]
pub fn contains(&self, point: &T) -> bool {
self.intervals
.iter()
.any(|interval| interval.contains(point))
}
#[must_use]
pub fn overlaps(&self, constraint: &Interval<T>) -> bool {
self.intervals
.iter()
.any(|interval| interval.overlaps(constraint))
}
#[must_use]
pub fn restrict_to(&self, constraint: &Interval<T>) -> Self {
self.intervals
.iter()
.filter_map(|interval| interval.restrict_to(constraint))
.collect()
}
pub fn iter(&self) -> impl Iterator<Item = &Interval<T>> {
self.intervals.iter()
}
#[must_use]
pub fn to_vec(&self) -> Vec<Interval<T>> {
self.intervals.clone()
}
fn insert_interval(&mut self, interval: Interval<T>) -> bool {
let mut updated = self.intervals.clone();
updated.push(interval);
updated = canonicalize_intervals(updated);
if updated == self.intervals {
false
} else {
self.intervals = updated;
true
}
}
}
impl<T: Ord + Clone> Default for ValidTimeSupport<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Ord + Clone> FromIterator<Interval<T>> for ValidTimeSupport<T> {
fn from_iter<I: IntoIterator<Item = Interval<T>>>(iter: I) -> Self {
let intervals = canonicalize_intervals(iter.into_iter().collect());
Self { intervals }
}
}
impl<T: Ord + Clone> Extend<Interval<T>> for ValidTimeSupport<T> {
fn extend<I: IntoIterator<Item = Interval<T>>>(&mut self, iter: I) {
let mut updated = self.intervals.clone();
updated.extend(iter);
self.intervals = canonicalize_intervals(updated);
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidTimeRelation<F: Ord, T: Ord + Clone> {
facts: BTreeMap<F, ValidTimeSupport<T>>,
}
impl<F: Ord, T: Ord + Clone> ValidTimeRelation<F, T> {
#[must_use]
pub fn new() -> Self {
Self {
facts: BTreeMap::new(),
}
}
#[must_use]
pub fn from_facts<I>(facts: I) -> Self
where
I: IntoIterator<Item = (F, Interval<T>)>,
{
facts.into_iter().collect()
}
pub fn insert(&mut self, fact: F, interval: Interval<T>) -> bool {
match self.facts.entry(fact) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(ValidTimeSupport::from_intervals([interval]));
true
}
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().insert_interval(interval)
}
}
}
pub fn insert_bounds(&mut self, fact: F, start: T, end: T) -> Result<bool, IntervalError<T>> {
Ok(self.insert(fact, Interval::new(start, end)?))
}
#[must_use]
pub fn contains_fact(&self, fact: &F) -> bool {
self.facts.contains_key(fact)
}
#[must_use]
pub fn valid_time_of(&self, fact: &F) -> Option<&ValidTimeSupport<T>> {
self.facts.get(fact)
}
#[must_use]
pub fn is_active_at(&self, fact: &F, point: &T) -> bool {
self.facts
.get(fact)
.is_some_and(|support| support.contains(point))
}
pub fn iter(&self) -> impl Iterator<Item = (&F, &ValidTimeSupport<T>)> {
self.facts.iter()
}
#[must_use]
pub fn support(&self) -> UnaryRelation<F>
where
F: Clone,
{
self.facts.keys().cloned().collect()
}
#[must_use]
pub fn snapshot_at(&self, point: &T) -> UnaryRelation<F>
where
F: Clone,
{
self.facts
.iter()
.filter(|(_, support)| support.contains(point))
.map(|(fact, _)| fact.clone())
.collect()
}
#[must_use]
pub fn restrict_to(&self, constraint: &Interval<T>) -> Self
where
F: Clone,
{
let facts = self
.facts
.iter()
.filter_map(|(fact, support)| {
let restricted = support.restrict_to(constraint);
if restricted.is_empty() {
None
} else {
Some((fact.clone(), restricted))
}
})
.collect();
Self { facts }
}
}
impl<F: Ord, T: Ord + Clone> Default for ValidTimeRelation<F, T> {
fn default() -> Self {
Self::new()
}
}
impl<F: Ord, T: Ord + Clone> FiniteRelation for ValidTimeRelation<F, T> {
fn len(&self) -> usize {
self.facts.len()
}
}
impl<F: Ord, T: Ord + Clone> FromIterator<(F, Interval<T>)> for ValidTimeRelation<F, T> {
fn from_iter<I: IntoIterator<Item = (F, Interval<T>)>>(iter: I) -> Self {
let mut relation = Self::new();
relation.extend(iter);
relation
}
}
impl<F: Ord, T: Ord + Clone> Extend<(F, Interval<T>)> for ValidTimeRelation<F, T> {
fn extend<I: IntoIterator<Item = (F, Interval<T>)>>(&mut self, iter: I) {
for (fact, interval) in iter {
self.insert(fact, interval);
}
}
}
impl<Value: Ord + Clone, Time: Ord + Clone> ValidTimeRelation<Value, Time> {
#[must_use]
pub fn to_unary_relation(&self) -> UnaryRelation<Value> {
self.support()
}
}
impl<A: Ord + Clone, B: Ord + Clone, Time: Ord + Clone> ValidTimeRelation<(A, B), Time> {
#[must_use]
pub fn to_binary_relation(&self) -> BinaryRelation<A, B> {
self.facts.keys().cloned().collect()
}
}
impl<Value: Ord + Clone, Time: Ord + Clone> ValidTimeRelation<Vec<Value>, Time> {
pub fn to_nary_relation<I, S>(
&self,
schema: I,
) -> Result<NaryRelation<Value>, NaryRelationError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
NaryRelation::from_rows(schema, self.facts.keys().cloned())
}
}