xlog-logic 0.10.0

Parser, compiler, and optimizer for XLOG logic programs
Documentation
//! Compile-time configuration for the WCOJ variable-ordering cost model.
//!
//! `CompilerConfig` is a per-call argument to
//! [`crate::compile::Compiler::compile_with_config_and_stats_snapshot`].
//! `CompilerConfig::default()` disables variable-ordering rewrites, preserving
//! existing triangle, 4-cycle, recursive, and selectivity-aware dispatch
//! behavior when the default config is in effect.
//!
//! Activation requires explicitly constructing a `CompilerConfig`
//! with [`WcojVarOrderingKind::LeaderCardinality`]. There is no
//! environment override on this path; env-driven activation is outside this
//! compile-time config surface.
//!
//! # Threshold contract
//!
//! `wcoj_var_ordering_threshold` is `pub` to allow struct-literal
//! construction, but the promoter MUST go through
//! [`CompilerConfig::effective_wcoj_var_ordering_threshold`] so
//! out-of-range struct-literal values fall back to
//! [`CompilerConfig::DEFAULT_THRESHOLD`] rather than silently
//! widening the gate.

/// Selector for the WCOJ variable-ordering cost model.
///
/// `Disabled` is the load-bearing default: when set, the promoter
/// never emits `RirNode::MultiWayJoin::var_order`, preserving existing
/// triangle, 4-cycle, recursive, and selectivity-aware dispatch row-set
/// semantics.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WcojVarOrderingKind {
    /// Variable-ordering disabled: promoter never sets `var_order`.
    Disabled,
    /// Use the default `LeaderCardinalityModel` to pick a
    /// stats-driven leader for triangle / 4-cycle WCOJ inputs.
    LeaderCardinality,
    /// Use `HeatAwareLeaderModel`: combines cardinality, access heat, and
    /// observed join selectivity into a composite score. Hot relations and
    /// relations in tight (low selectivity) edges get demoted from the leader slot;
    /// cold extensional rels are preferred as leader. Same
    /// threshold gate as `LeaderCardinality` via
    /// `effective_wcoj_var_ordering_threshold()`.
    HeatAware,
}

/// Compile-time configuration for the WCOJ variable-ordering cost model.
///
/// See module docs for activation semantics + threshold contract.
#[derive(Debug, Clone, PartialEq)]
pub struct CompilerConfig {
    /// Variable-ordering cost-model selector. Default `Disabled`.
    pub wcoj_variable_ordering: WcojVarOrderingKind,

    /// Raw threshold field. Public to keep struct-literal
    /// construction available, but the promoter MUST NOT read this
    /// field directly. Use
    /// [`CompilerConfig::effective_wcoj_var_ordering_threshold`] so
    /// out-of-range values are clamped at use, not silently honored.
    pub wcoj_var_ordering_threshold: f64,
}

impl Default for CompilerConfig {
    fn default() -> Self {
        Self {
            wcoj_variable_ordering: WcojVarOrderingKind::Disabled,
            wcoj_var_ordering_threshold: Self::DEFAULT_THRESHOLD,
        }
    }
}

impl CompilerConfig {
    /// Default ratio at or below which a leader candidate triggers
    /// `var_order = Some(...)`. The gate fires on
    /// `min_card / default_leader_card ≤ threshold`. A smaller
    /// threshold demands a clearer win.
    pub const DEFAULT_THRESHOLD: f64 = 0.5;

    /// Resolve the threshold the promoter actually uses.
    ///
    /// Out-of-range values fall back to [`Self::DEFAULT_THRESHOLD`]:
    /// * `NaN`
    /// * non-finite (`±INFINITY`)
    /// * `≤ 0.0` (would never fire — clamps to default to keep the
    ///   gate honest)
    /// * `> 1.0` (would always fire — clamps to default to prevent
    ///   silent gate-disable via struct-literal)
    pub fn effective_wcoj_var_ordering_threshold(&self) -> f64 {
        let t = self.wcoj_var_ordering_threshold;
        if !t.is_finite() || t <= 0.0 || t > 1.0 {
            Self::DEFAULT_THRESHOLD
        } else {
            t
        }
    }
}

#[cfg(test)]
mod tests {
    //! Resolver unit tests pinning the out-of-range fallback contract.
    use super::*;

    #[test]
    fn default_threshold_is_half() {
        let c = CompilerConfig::default();
        assert_eq!(c.wcoj_var_ordering_threshold, 0.5);
        assert_eq!(c.effective_wcoj_var_ordering_threshold(), 0.5);
        assert_eq!(c.wcoj_variable_ordering, WcojVarOrderingKind::Disabled);
    }

    #[test]
    fn resolver_passes_through_valid_in_range() {
        let c = CompilerConfig {
            wcoj_var_ordering_threshold: 0.3,
            ..CompilerConfig::default()
        };
        assert_eq!(c.effective_wcoj_var_ordering_threshold(), 0.3);
    }

    #[test]
    fn resolver_clamps_zero_and_negative_to_default() {
        // `0.0` boundary: the gate would never fire — clamp.
        let zero = CompilerConfig {
            wcoj_var_ordering_threshold: 0.0,
            ..CompilerConfig::default()
        };
        assert_eq!(
            zero.effective_wcoj_var_ordering_threshold(),
            CompilerConfig::DEFAULT_THRESHOLD
        );
        let neg = CompilerConfig {
            wcoj_var_ordering_threshold: -0.5,
            ..CompilerConfig::default()
        };
        assert_eq!(
            neg.effective_wcoj_var_ordering_threshold(),
            CompilerConfig::DEFAULT_THRESHOLD
        );
    }

    #[test]
    fn resolver_clamps_above_one_and_nonfinite_to_default() {
        let above = CompilerConfig {
            wcoj_var_ordering_threshold: 1.5,
            ..CompilerConfig::default()
        };
        assert_eq!(
            above.effective_wcoj_var_ordering_threshold(),
            CompilerConfig::DEFAULT_THRESHOLD
        );
        let nan = CompilerConfig {
            wcoj_var_ordering_threshold: f64::NAN,
            ..CompilerConfig::default()
        };
        assert_eq!(
            nan.effective_wcoj_var_ordering_threshold(),
            CompilerConfig::DEFAULT_THRESHOLD
        );
        let inf = CompilerConfig {
            wcoj_var_ordering_threshold: f64::INFINITY,
            ..CompilerConfig::default()
        };
        assert_eq!(
            inf.effective_wcoj_var_ordering_threshold(),
            CompilerConfig::DEFAULT_THRESHOLD
        );
    }
}