triblespace-core 0.47.1

The triblespace core implementation.
Documentation
use crate::id::id_from_value;
use crate::id::id_into_value;
use crate::id::ID_LEN;
use crate::inline::InlineEncoding;
use crate::inline::INLINE_LEN;
use crate::patch::IdentitySchema;
use crate::patch::PATCH;

use super::Binding;
use super::Constraint;
use super::ContainsConstraint;
use super::Frontier;
use super::Variable;
use super::VariableId;
use super::VariableSet;
use crate::query::Candidates;
use crate::query::ProposalBuffer;

/// Constrains a variable to full-width values present in a [`PATCH`].
///
/// Proposals enumerate every entry; confirmations check prefix membership.
pub struct PatchValueConstraint<'a, T: InlineEncoding> {
    variable: Variable<T>,
    patch: &'a PATCH<INLINE_LEN, IdentitySchema, ()>,
}

impl<'a, T: InlineEncoding> PatchValueConstraint<'a, T> {
    /// Creates a constraint that restricts `variable` to values in `patch`.
    pub fn new(variable: Variable<T>, patch: &'a PATCH<INLINE_LEN, IdentitySchema, ()>) -> Self {
        PatchValueConstraint { variable, patch }
    }
}

impl<'a, S: InlineEncoding> Constraint<'a> for PatchValueConstraint<'a, S> {
    fn variables(&self) -> VariableSet {
        VariableSet::new_singleton(self.variable.index)
    }

    fn estimate(&self, variable: VariableId, _binding: &Binding) -> Option<usize> {
        if self.variable.index == variable {
            Some(self.patch.len() as usize)
        } else {
            None
        }
    }

    fn propose(
        &self,
        variable: VariableId,
        frontier: &Frontier<'_>,
        proposals: &mut ProposalBuffer,
    ) {
        if self.variable.index == variable {
            for row in 0..frontier.len() {
                proposals.open(row as u32);
                self.patch
                    .infixes(&[0; 0], &mut |&k: &[u8; 32]| proposals.push(k));
            }
        }
    }

    /// Prefix membership does not depend on the parent binding.
    fn confirm(&self, variable: VariableId, _frontier: &Frontier<'_>, cands: &mut Candidates<'_>) {
        if self.variable.index == variable {
            for i in 0..cands.len() {
                let v = &cands.values()[i];
                if cands.is_live(i) && !self.patch.has_prefix(v) {
                    cands.kill(i);
                }
            }
        }
    }
}

impl<'a, S: InlineEncoding> ContainsConstraint<'a, S>
    for &'a PATCH<INLINE_LEN, IdentitySchema, ()>
{
    type Constraint = PatchValueConstraint<'a, S>;

    fn has(self, v: Variable<S>) -> Self::Constraint {
        PatchValueConstraint::new(v, self)
    }
}

/// Constrains a variable to ID-width values present in a [`PATCH`].
///
/// Like [`PatchValueConstraint`] but for 16-byte identifiers. Values are
/// converted between the ID representation and the 32-byte value
/// representation automatically.
pub struct PatchIdConstraint<S>
where
    S: InlineEncoding,
{
    variable: Variable<S>,
    patch: PATCH<ID_LEN, IdentitySchema, ()>,
}

impl<S> PatchIdConstraint<S>
where
    S: InlineEncoding,
{
    /// Creates a constraint that restricts `variable` to IDs in `patch`.
    pub fn new(variable: Variable<S>, patch: PATCH<ID_LEN, IdentitySchema, ()>) -> Self {
        PatchIdConstraint { variable, patch }
    }
}

impl<'a, S> Constraint<'a> for PatchIdConstraint<S>
where
    S: InlineEncoding,
{
    fn variables(&self) -> VariableSet {
        VariableSet::new_singleton(self.variable.index)
    }

    fn estimate(&self, variable: VariableId, _binding: &Binding) -> Option<usize> {
        if self.variable.index == variable {
            Some(self.patch.len() as usize)
        } else {
            None
        }
    }

    fn propose(
        &self,
        variable: VariableId,
        frontier: &Frontier<'_>,
        proposals: &mut ProposalBuffer,
    ) {
        if self.variable.index == variable {
            for row in 0..frontier.len() {
                proposals.open(row as u32);
                self.patch.infixes(&[0; 0], &mut |id: &[u8; 16]| {
                    proposals.push(id_into_value(id))
                });
            }
        }
    }

    /// Prefix membership does not depend on the parent binding — but it does
    /// depend on the variable, so guard on it as `estimate` and `propose`
    /// already do. Composites only reach `confirm` for constraints whose
    /// `estimate` returned `Some`, which made the missing check harmless;
    /// relying on a caller's gating for correctness is the kind of coupling
    /// that survives right up until someone changes the caller.
    fn confirm(&self, variable: VariableId, _frontier: &Frontier<'_>, cands: &mut Candidates<'_>) {
        if self.variable.index != variable {
            return;
        }
        for i in 0..cands.len() {
            if !cands.is_live(i) {
                continue;
            }
            let v = &cands.values()[i];
            let keep = if let Some(id) = id_from_value(v) {
                self.patch.has_prefix(&id)
            } else {
                false
            };
            if !keep {
                cands.kill(i);
            }
        }
    }
}

impl<'a, S: InlineEncoding> ContainsConstraint<'a, S> for PATCH<ID_LEN, IdentitySchema, ()> {
    type Constraint = PatchIdConstraint<S>;

    fn has(self, v: Variable<S>) -> Self::Constraint {
        PatchIdConstraint::new(v, self)
    }
}