use std::collections::BTreeSet;
use std::marker::PhantomData;
use crate::id::Id;
use crate::inline::encodings::genid::GenId;
use crate::inline::{Inline, InlineEncoding, IntoInline, TryFromInline};
use crate::query::intersectionconstraint::and;
use crate::query::rangeconstraint::value_range;
use crate::query::{
exists, find, temp, Binding, Candidates, Constraint, Frontier, ProposalBuffer, TriblePattern,
Variable, VariableId, VariableSet,
};
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum End {
Last,
First,
}
impl End {
pub fn flip(self) -> Self {
match self {
Self::Last => Self::First,
Self::First => Self::Last,
}
}
}
pub trait RegisterOrder {
fn dominated(&self, state: Id) -> bool;
}
impl<O: RegisterOrder + ?Sized> RegisterOrder for &O {
fn dominated(&self, state: Id) -> bool {
(**self).dominated(state)
}
}
pub fn resolve<O>(order: &O, candidates: impl IntoIterator<Item = Id>) -> BTreeSet<Id>
where
O: RegisterOrder + ?Sized,
{
candidates
.into_iter()
.filter(|candidate| !order.dominated(*candidate))
.collect()
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Resolution {
Empty,
Sole(Id),
Fork(BTreeSet<Id>),
}
impl Resolution {
pub fn sole(&self) -> Option<Id> {
match self {
Resolution::Sole(id) => Some(*id),
_ => None,
}
}
pub fn states(&self) -> BTreeSet<Id> {
match self {
Resolution::Empty => BTreeSet::new(),
Resolution::Sole(id) => BTreeSet::from([*id]),
Resolution::Fork(set) => set.clone(),
}
}
}
pub fn sole<O>(order: &O, candidates: impl IntoIterator<Item = Id>) -> Resolution
where
O: RegisterOrder + ?Sized,
{
let resolved = resolve(order, candidates);
match resolved.len() {
0 => Resolution::Empty,
1 => Resolution::Sole(*resolved.iter().next().expect("length checked")),
_ => Resolution::Fork(resolved),
}
}
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Unordered;
impl RegisterOrder for Unordered {
fn dominated(&self, _state: Id) -> bool {
false
}
}
#[derive(Clone, Copy, Debug)]
pub struct ObservationOrder<'a, P> {
facts: &'a P,
observes: Inline<GenId>,
end: End,
}
impl<'a, P> ObservationOrder<'a, P>
where
P: TriblePattern,
{
pub fn new(facts: &'a P, observes: Id) -> Self {
Self {
facts,
observes: observes.to_inline(),
end: End::Last,
}
}
pub fn first(mut self) -> Self {
self.end = End::First;
self
}
pub fn end(&self) -> End {
self.end
}
}
impl<P> RegisterOrder for ObservationOrder<'_, P>
where
P: TriblePattern,
{
fn dominated(&self, state: Id) -> bool {
let inline_state: Inline<GenId> = state.to_inline();
match self.end {
End::Last => exists!(temp!(
(observer),
self.facts.pattern(observer, self.observes, inline_state)
)),
End::First => exists!(temp!(
(observed),
self.facts
.pattern::<GenId>(inline_state, self.observes, observed)
)),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct StatedOrder<'a, P, K: InlineEncoding> {
facts: &'a P,
identity: Inline<GenId>,
order: Inline<GenId>,
end: End,
tiebreak: bool,
_order: PhantomData<K>,
}
impl<'a, P, K> StatedOrder<'a, P, K>
where
P: TriblePattern,
K: InlineEncoding,
{
pub fn new(facts: &'a P, identity: Id, order: Id) -> Self {
Self {
facts,
identity: identity.to_inline(),
order: order.to_inline(),
end: End::Last,
tiebreak: false,
_order: PhantomData,
}
}
pub fn first(mut self) -> Self {
self.end = End::First;
self
}
pub fn tiebreak_by_id(mut self) -> Self {
self.tiebreak = true;
self
}
pub fn end(&self) -> End {
self.end
}
}
crate::macros::attributes! {
"AAC4B8AD847759A6910662DB2F0321BA" as pub register_identity: GenId;
"435F580DA18908BAEB4EB675557E0BFD" as pub register_orders: GenId;
}
impl<P, K> StatedOrder<'_, P, K>
where
P: TriblePattern,
K: InlineEncoding,
{
fn coordinate(&self, state: Inline<GenId>) -> Option<(Id, Inline<K>)> {
find!(
(register: Id, order: Inline<K>),
and!(
self.facts.pattern(state, self.identity, register),
self.facts.pattern(state, self.order, order),
)
)
.next()
}
}
impl<P, K> RegisterOrder for StatedOrder<'_, P, K>
where
P: TriblePattern,
K: InlineEncoding,
{
fn dominated(&self, state: Id) -> bool {
let inline_state: Inline<GenId> = state.to_inline();
let Some((register, key)) = self.coordinate(inline_state) else {
return false;
};
let register: Inline<GenId> = register.to_inline();
let (low, high) = match self.end {
End::Last => (key, Inline::<K>::new([0xff; 32])),
End::First => (Inline::<K>::new([0x00; 32]), key),
};
find!(
(other: Id, candidate: Inline<K>),
and!(
self.facts.pattern(other, self.identity, register),
self.facts.pattern(other, self.order, candidate),
value_range(candidate, low, high),
)
)
.any(|(other, candidate)| self.beats(candidate.raw, other, key.raw, state))
}
}
impl<P, K> StatedOrder<'_, P, K>
where
P: TriblePattern,
K: InlineEncoding,
{
fn beats(&self, challenger_key: [u8; 32], challenger: Id, key: [u8; 32], state: Id) -> bool {
if challenger_key == key {
return self.tiebreak
&& match self.end {
End::Last => challenger > state,
End::First => challenger < state,
};
}
match self.end {
End::Last => challenger_key > key,
End::First => challenger_key < key,
}
}
}
pub struct Maximal<'a, O> {
variable: VariableId,
order: &'a O,
}
impl<'a, O> Maximal<'a, O>
where
O: RegisterOrder,
{
pub fn new(variable: Variable<GenId>, order: &'a O) -> Self {
Self {
variable: variable.index,
order,
}
}
fn survives(&self, raw: &[u8; 32]) -> bool {
match Id::try_from_inline(Inline::<GenId>::as_transmute_raw(raw)) {
Ok(state) => !self.order.dominated(state),
Err(_) => false,
}
}
}
pub fn maximal<'a, O>(variable: Variable<GenId>, order: &'a O) -> Maximal<'a, O>
where
O: RegisterOrder,
{
Maximal::new(variable, order)
}
impl<'a, O> Constraint<'a> for Maximal<'a, O>
where
O: RegisterOrder,
{
fn variables(&self) -> VariableSet {
VariableSet::new_singleton(self.variable)
}
fn estimate(&self, variable: VariableId, _binding: &Binding) -> Option<usize> {
if self.variable == variable {
Some(usize::MAX)
} else {
None
}
}
fn propose(
&self,
_variable: VariableId,
_frontier: &Frontier<'_>,
_proposals: &mut ProposalBuffer,
) {
}
fn confirm(&self, variable: VariableId, _frontier: &Frontier<'_>, cands: &mut Candidates<'_>) {
if self.variable != variable {
return;
}
for i in 0..cands.len() {
if cands.is_live(i) && !self.survives(&cands.values()[i]) {
cands.kill(i);
}
}
}
fn satisfied(&self, binding: &Binding) -> bool {
match binding.get(self.variable) {
Some(raw) => self.survives(raw),
None => true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::id::ExclusiveId;
use crate::inline::encodings::time::{i128_to_ordered_be, NsTAIInterval};
use crate::macros::entity;
use crate::metadata;
use crate::prelude::*;
attributes! {
"46D95EBAB8D5D0E9103148B35731065C" as note_of: crate::inline::encodings::genid::GenId;
"3B7AD155842AE30B164A311858D4D9A6" as note_at: NsTAIInterval;
"3DE10799440A0C3D4B40261E7089DCAD" as status_of: crate::inline::encodings::genid::GenId;
"641C46439161BDA3F7EE8ED412AD5DEF" as other_clock: NsTAIInterval;
}
fn at(nanos: i128) -> Inline<NsTAIInterval> {
let bound = i128_to_ordered_be(nanos);
let mut raw = [0u8; 32];
raw[0..16].copy_from_slice(&bound);
raw[16..32].copy_from_slice(&bound);
Inline::new(raw)
}
fn edge(successor: &ExclusiveId, predecessor: &ExclusiveId) -> TribleSet {
entity! { successor @ metadata::supersedes: predecessor }.into()
}
fn observation<P: TriblePattern>(facts: &P) -> ObservationOrder<'_, P> {
ObservationOrder::new(facts, metadata::supersedes.id())
}
fn note(subject: &ExclusiveId, state: &ExclusiveId, nanos: i128) -> TribleSet {
let when = at(nanos);
entity! { state @ note_of: subject, note_at: when }.into()
}
fn stated<P: TriblePattern>(facts: &P) -> StatedOrder<'_, P, NsTAIInterval> {
StatedOrder::new(facts, note_of.id(), note_at.id())
}
#[test]
fn a_lone_state_is_its_own_frontier() {
let only = ufoid();
let facts = TribleSet::new();
assert_eq!(
resolve(&observation(&facts), [*only]),
[*only].into_iter().collect::<BTreeSet<_>>()
);
}
#[test]
fn concurrent_states_both_survive() {
let base = ufoid();
let left = ufoid();
let right = ufoid();
let mut facts = TribleSet::new();
facts += edge(&left, &base);
facts += edge(&right, &base);
assert_eq!(
resolve(&observation(&facts), [*base, *left, *right]),
[*left, *right].into_iter().collect::<BTreeSet<_>>()
);
assert_eq!(
sole(&observation(&facts), [*base, *left, *right]).sole(),
None
);
}
#[test]
fn first_resolves_to_the_roots() {
let base = ufoid();
let left = ufoid();
let right = ufoid();
let mut facts = TribleSet::new();
facts += edge(&left, &base);
facts += edge(&right, &base);
let candidates = [*base, *left, *right];
assert_eq!(
resolve(&observation(&facts).first(), candidates),
[*base].into_iter().collect::<BTreeSet<_>>()
);
assert_eq!(
sole(&observation(&facts).first(), candidates),
Resolution::Sole(*base)
);
}
#[test]
fn an_edge_dominates_whatever_the_reader_thinks_of_its_source() {
let track = ufoid();
let other_track = ufoid();
let head = ufoid();
let interloper = ufoid();
let mut facts = TribleSet::new();
facts += entity! { &head @ note_of: &track };
facts += entity! { &interloper @ note_of: &other_track };
facts += edge(&interloper, &head);
assert_eq!(
resolve(&observation(&facts), [*head]),
BTreeSet::new(),
"an observation edge is a claim of same-register membership, \
and resolution must not silently disbelieve it"
);
}
#[test]
fn states_resolve_only_within_their_own_register() {
let goal = ufoid();
let other_goal = ufoid();
let early = ufoid();
let late = ufoid();
let elsewhere = ufoid();
let mut facts = TribleSet::new();
facts += note(&goal, &early, 10);
facts += note(&goal, &late, 20);
facts += note(&other_goal, &elsewhere, 99);
let candidates = [*early, *late, *elsewhere];
assert_eq!(
resolve(&stated(&facts), candidates),
[*late, *elsewhere].into_iter().collect::<BTreeSet<_>>()
);
assert_eq!(
resolve(&stated(&facts).first(), candidates),
[*early, *elsewhere].into_iter().collect::<BTreeSet<_>>()
);
}
#[test]
fn equal_keys_are_a_fork_until_the_reader_asks_for_a_total_order() {
let goal = ufoid();
let one = ufoid();
let two = ufoid();
let mut facts = TribleSet::new();
facts += note(&goal, &one, 42);
facts += note(&goal, &two, 42);
let candidates = [*one, *two];
assert_eq!(
resolve(&stated(&facts), candidates),
[*one, *two].into_iter().collect::<BTreeSet<_>>()
);
assert_eq!(sole(&stated(&facts), candidates).sole(), None);
assert_eq!(
sole(&stated(&facts).tiebreak_by_id(), candidates),
Resolution::Sole((*one).max(*two))
);
assert_eq!(
sole(&stated(&facts).tiebreak_by_id().first(), candidates),
Resolution::Sole((*one).min(*two))
);
}
#[test]
fn empty_and_forked_are_distinguishable_and_the_fork_is_returned() {
let goal = ufoid();
let one = ufoid();
let two = ufoid();
let mut facts = TribleSet::new();
facts += note(&goal, &one, 10);
facts += note(&goal, &two, 10);
assert_eq!(sole(&stated(&facts), []), Resolution::Empty);
let forked = sole(&stated(&facts), [*one, *two]);
assert_eq!(
forked,
Resolution::Fork([*one, *two].into_iter().collect::<BTreeSet<_>>())
);
assert_eq!(forked.states().len(), 2, "the fork must not be empty");
assert_eq!(forked.sole(), None);
assert_eq!(Resolution::Empty.sole(), None);
assert_ne!(forked, Resolution::Empty);
}
#[test]
fn a_weak_grouping_read_as_identity_lets_a_foreign_kind_dominate() {
let goal = ufoid();
let status = ufoid();
let commentary = ufoid();
let mut facts = TribleSet::new();
facts += note(&goal, &status, 10);
facts += note(&goal, &commentary, 20);
facts += entity! { &status @ status_of: &goal };
assert_eq!(resolve(&stated(&facts), [*status]), BTreeSet::new());
assert_eq!(
sole(&stated(&facts).tiebreak_by_id(), [*status]).sole(),
None
);
let order = StatedOrder::<_, NsTAIInterval>::new(&facts, status_of.id(), note_at.id())
.tiebreak_by_id();
assert_eq!(sole(&order, [*status]), Resolution::Sole(*status));
}
#[test]
fn a_state_without_a_key_is_incomparable_rather_than_lost() {
let goal = ufoid();
let keyed = ufoid();
let bare = ufoid();
let mut facts = TribleSet::new();
facts += note(&goal, &keyed, 7);
facts += entity! { &bare @ metadata::name: "no coordinate" };
assert_eq!(
resolve(&stated(&facts), [*keyed, *bare]),
[*keyed, *bare].into_iter().collect::<BTreeSet<_>>()
);
}
#[test]
fn the_empty_order_leaves_every_state_current() {
let a = ufoid();
let b = ufoid();
let mut facts = TribleSet::new();
facts += edge(&b, &a);
assert_eq!(
resolve(&Unordered, [*a, *b]),
[*a, *b].into_iter().collect::<BTreeSet<_>>()
);
let _ = facts;
}
#[test]
fn arrival_order_does_not_change_the_result() {
let goal = ufoid();
let early = ufoid();
let late = ufoid();
let candidates = [*early, *late];
let expected: BTreeSet<Id> = [*late].into_iter().collect();
let mut forwards = TribleSet::new();
forwards += note(&goal, &early, 1);
forwards += note(&goal, &late, 2);
let mut backwards = TribleSet::new();
backwards += note(&goal, &late, 2);
backwards += note(&goal, &early, 1);
assert_eq!(resolve(&stated(&forwards), candidates), expected);
assert_eq!(resolve(&stated(&backwards), candidates), expected);
let mut edge_forwards = TribleSet::new();
edge_forwards += entity! { &early @ metadata::name: "e" };
edge_forwards += edge(&late, &early);
let mut edge_backwards = TribleSet::new();
edge_backwards += edge(&late, &early);
edge_backwards += entity! { &early @ metadata::name: "e" };
assert_eq!(resolve(&observation(&edge_forwards), candidates), expected);
assert_eq!(resolve(&observation(&edge_backwards), candidates), expected);
}
#[test]
fn frames_disagree_and_both_are_right() {
let goal = ufoid();
let first = ufoid();
let second = ufoid();
let candidates = [*first, *second];
let mut early = TribleSet::new();
early += note(&goal, &first, 1);
let mut late = early.clone();
late += note(&goal, &second, 2);
assert_eq!(
resolve(&stated(&early), candidates),
[*first, *second].into_iter().collect::<BTreeSet<_>>()
);
assert_eq!(
resolve(&stated(&late), candidates),
[*second].into_iter().collect::<BTreeSet<_>>()
);
}
macro_rules! assert_union_frame_resolution {
($build:expr, $c1:expr, $c2:expr, $candidates:expr $(,)?) => {{
let c1 = $c1;
let c2 = $c2;
let candidates = $candidates;
let build = $build;
let mut union = c1.clone();
union += c2.clone();
let l1 = resolve(&build(&c1), candidates);
let l2 = resolve(&build(&c2), candidates);
let joined = resolve(&build(&union), l1.iter().chain(l2.iter()).copied());
assert_eq!(resolve(&build(&union), candidates), joined);
joined
}};
}
#[test]
fn plain_survivor_union_can_overshoot_the_full_frame_oracle() {
let base = ufoid();
let left = ufoid();
let candidates = [*base, *left];
let c1 = TribleSet::new();
let mut c2 = TribleSet::new();
c2 += edge(&left, &base);
let mut union = c1.clone();
union += c2.clone();
let l1 = resolve(&observation(&c1), candidates);
let l2 = resolve(&observation(&c2), candidates);
let truth = resolve(&observation(&union), candidates);
let naive: BTreeSet<Id> = l1.union(&l2).copied().collect();
assert_eq!(naive, [*base, *left].into_iter().collect::<BTreeSet<_>>());
assert_ne!(naive, truth);
let joined = resolve(&observation(&union), l1.iter().chain(l2.iter()).copied());
assert_eq!(joined, truth);
assert_eq!(truth, [*left].into_iter().collect::<BTreeSet<_>>());
}
#[test]
fn observation_survivors_re_resolve_in_the_union_frame() {
let base = ufoid();
let left = ufoid();
let right = ufoid();
let merge = ufoid();
let mut c1 = TribleSet::new();
c1 += edge(&left, &base);
let mut c2 = TribleSet::new();
c2 += edge(&right, &base);
c2 += edge(&merge, &right);
let joined = assert_union_frame_resolution!(
|facts| observation(facts),
c1,
c2,
[*base, *left, *right, *merge],
);
assert_eq!(joined, [*left, *merge].into_iter().collect::<BTreeSet<_>>());
}
#[test]
fn last_write_wins_survivors_re_resolve_in_the_union_frame() {
let goal = ufoid();
let a = ufoid();
let b = ufoid();
let c = ufoid();
let d = ufoid();
let mut c1 = TribleSet::new();
c1 += note(&goal, &a, 1);
c1 += note(&goal, &c, 3);
let mut c2 = TribleSet::new();
c2 += note(&goal, &b, 2);
c2 += note(&goal, &d, 4);
let joined = assert_union_frame_resolution!(
|facts| stated(facts).tiebreak_by_id(),
c1,
c2,
[*a, *b, *c, *d],
);
assert_eq!(joined, [*d].into_iter().collect::<BTreeSet<_>>());
}
#[test]
fn first_write_wins_survivors_re_resolve_in_the_union_frame() {
let goal = ufoid();
let a = ufoid();
let b = ufoid();
let c = ufoid();
let d = ufoid();
let mut c1 = TribleSet::new();
c1 += note(&goal, &a, 10);
c1 += note(&goal, &c, 30);
let mut c2 = TribleSet::new();
c2 += note(&goal, &b, 20);
c2 += note(&goal, &d, 40);
let joined = assert_union_frame_resolution!(
|facts| stated(facts).tiebreak_by_id().first(),
c1,
c2,
[*a, *b, *c, *d],
);
assert_eq!(joined, [*a].into_iter().collect::<BTreeSet<_>>());
}
#[test]
fn multi_value_stated_survivors_re_resolve_in_the_union_frame() {
let goal = ufoid();
let a = ufoid();
let b = ufoid();
let c = ufoid();
let d = ufoid();
let mut c1 = TribleSet::new();
c1 += note(&goal, &a, 5);
c1 += note(&goal, &c, 5);
let mut c2 = TribleSet::new();
c2 += note(&goal, &b, 5);
c2 += note(&goal, &d, 2);
let joined =
assert_union_frame_resolution!(|facts| stated(facts), c1, c2, [*a, *b, *c, *d]);
assert_eq!(joined, [*a, *b, *c].into_iter().collect::<BTreeSet<_>>());
}
#[test]
fn maximal_composes_inside_a_query() {
let goal = ufoid();
let early = ufoid();
let late = ufoid();
let mut facts = TribleSet::new();
facts += note(&goal, &early, 1);
facts += note(&goal, &late, 2);
let all: BTreeSet<Id> = find!(
state: Id,
pattern!(&facts, [{ ?state @ note_of: &goal }])
)
.collect();
assert_eq!(all, [*early, *late].into_iter().collect::<BTreeSet<_>>());
let order = stated(&facts).tiebreak_by_id();
let current: BTreeSet<Id> = find!(
state: Id,
and!(
pattern!(&facts, [{ ?state @ note_of: &goal }]),
maximal(state, &order),
)
)
.collect();
assert_eq!(current, [*late].into_iter().collect::<BTreeSet<_>>());
assert_eq!(current, resolve(&order, all));
}
#[test]
fn maximal_agrees_with_resolve_over_the_observation_order() {
let track = ufoid();
let base = ufoid();
let left = ufoid();
let right = ufoid();
let mut facts = TribleSet::new();
facts += edge(&left, &base);
facts += edge(&right, &base);
for state in [&base, &left, &right] {
facts += entity! { state @ note_of: &track };
}
let order = observation(&facts);
let queried: BTreeSet<Id> = find!(
state: Id,
and!(
pattern!(&facts, [{ ?state @ note_of: &track }]),
maximal(state, &order),
)
)
.collect();
assert_eq!(queried, resolve(&order, [*base, *left, *right]));
assert_eq!(
queried,
[*left, *right].into_iter().collect::<BTreeSet<_>>()
);
}
}