Skip to main content

xlog_prob/
epistemic.rs

1//! Bounded epistemic/probabilistic integration helpers.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use xlog_core::{symbol, Result, ScalarType, XlogError};
6use xlog_cuda::{CompareOp, CudaBuffer, CudaKernelProvider};
7use xlog_ir::{
8    EirEpistemicMode, EirEpistemicOp, EirTerm, EpistemicExecutionBackend, EpistemicFallbackPolicy,
9    EpistemicTupleMembershipBinding,
10};
11use xlog_logic::{
12    ast::{Atom, EpistemicLiteral, EpistemicOp, Term},
13    epistemic::{EpistemicWorldView, TruthValue},
14};
15use xlog_runtime::{
16    EpistemicGpuExecutionResult, EpistemicGpuKernelTimingTrace, EpistemicGpuProviderIdentity,
17};
18
19/// Default tolerance for deterministic probability fixtures.
20pub const EPISTEMIC_PROBABILITY_TOLERANCE: f64 = 1.0e-12;
21
22/// Role epistemic choices play in probabilistic compilation.
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum EpistemicProbabilisticRole {
25    /// Epistemic choices are compiled as evidence conditions over the probabilistic query.
26    EvidenceConditioning,
27}
28
29/// Semantic contract between epistemic and probabilistic layers.
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct EpistemicProbabilisticContract {
32    /// How epistemic choices affect probabilistic inference.
33    pub epistemic_role: EpistemicProbabilisticRole,
34}
35
36impl Default for EpistemicProbabilisticContract {
37    fn default() -> Self {
38        Self {
39            epistemic_role: EpistemicProbabilisticRole::EvidenceConditioning,
40        }
41    }
42}
43
44/// Epistemic assumption operator used as probabilistic evidence.
45#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
46pub enum EpistemicAssumptionKind {
47    /// `know atom` assumption.
48    Know,
49    /// `possible atom` assumption.
50    Possible,
51}
52
53impl EpistemicAssumptionKind {
54    fn evidence_prefix(self) -> &'static str {
55        match self {
56            Self::Know => "know",
57            Self::Possible => "possible",
58        }
59    }
60}
61
62/// Concrete tuple key term for nonzero-arity epistemic evidence conditioning.
63#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
64pub enum EpistemicEvidenceTerm {
65    /// Integer tuple key term.
66    Integer(i64),
67    /// String tuple key term.
68    String(String),
69    /// Interned symbol tuple key term.
70    Symbol(u32),
71}
72
73impl EpistemicEvidenceTerm {
74    /// Construct an integer tuple key term.
75    pub fn integer(value: i64) -> Self {
76        Self::Integer(value)
77    }
78
79    /// Construct a string tuple key term.
80    pub fn string(value: impl Into<String>) -> Self {
81        Self::String(value.into())
82    }
83
84    /// Construct an interned symbol tuple key term.
85    pub fn symbol(value: u32) -> Self {
86        Self::Symbol(value)
87    }
88
89    fn evidence_literal(&self) -> String {
90        match self {
91            Self::Integer(value) => value.to_string(),
92            Self::String(value) => format!("{value:?}"),
93            Self::Symbol(value) => format!("#{value}"),
94        }
95    }
96}
97
98/// One bounded epistemic assumption compiled as evidence.
99#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
100pub struct EpistemicAssumption {
101    /// Assumption kind.
102    pub kind: EpistemicAssumptionKind,
103    /// Predicate name.
104    pub predicate: String,
105    /// Predicate arity.
106    pub arity: usize,
107    /// Concrete tuple terms for nonzero-arity evidence conditioning.
108    pub terms: Vec<EpistemicEvidenceTerm>,
109    /// Assumed evidence truth value.
110    pub value: bool,
111}
112
113impl EpistemicAssumption {
114    /// Construct a `know predicate/arity = value` assumption.
115    pub fn known(predicate: impl Into<String>, arity: usize, value: bool) -> Self {
116        Self {
117            kind: EpistemicAssumptionKind::Know,
118            predicate: predicate.into(),
119            arity,
120            terms: Vec::new(),
121            value,
122        }
123    }
124
125    /// Construct a `know predicate(terms...) = value` assumption.
126    pub fn known_tuple(
127        predicate: impl Into<String>,
128        terms: Vec<EpistemicEvidenceTerm>,
129        value: bool,
130    ) -> Self {
131        Self {
132            kind: EpistemicAssumptionKind::Know,
133            predicate: predicate.into(),
134            arity: terms.len(),
135            terms,
136            value,
137        }
138    }
139
140    /// Construct a `possible predicate/arity = value` assumption.
141    pub fn possible(predicate: impl Into<String>, arity: usize, value: bool) -> Self {
142        Self {
143            kind: EpistemicAssumptionKind::Possible,
144            predicate: predicate.into(),
145            arity,
146            terms: Vec::new(),
147            value,
148        }
149    }
150
151    /// Construct a `possible predicate(terms...) = value` assumption.
152    pub fn possible_tuple(
153        predicate: impl Into<String>,
154        terms: Vec<EpistemicEvidenceTerm>,
155        value: bool,
156    ) -> Self {
157        Self {
158            kind: EpistemicAssumptionKind::Possible,
159            predicate: predicate.into(),
160            arity: terms.len(),
161            terms,
162            value,
163        }
164    }
165
166    /// Return the compiler-facing evidence literal for this assumption.
167    pub fn evidence_literal(&self) -> String {
168        if self.terms.is_empty() {
169            format!(
170                "{}:{}/{}={}",
171                self.kind.evidence_prefix(),
172                self.predicate,
173                self.arity,
174                self.value
175            )
176        } else {
177            let terms = self
178                .terms
179                .iter()
180                .map(EpistemicEvidenceTerm::evidence_literal)
181                .collect::<Vec<_>>()
182                .join(",");
183            format!(
184                "{}:{}/{}({})={}",
185                self.kind.evidence_prefix(),
186                self.predicate,
187                self.arity,
188                terms,
189                self.value
190            )
191        }
192    }
193
194    fn same_evidence_key(&self, other: &Self) -> bool {
195        self.kind == other.kind
196            && self.predicate == other.predicate
197            && self.arity == other.arity
198            && self.terms == other.terms
199    }
200}
201
202/// Knowledge compiler adapter kind.
203#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum CompilerAdapterKind {
205    /// Existing GPU-native Decision-DNNF to XGCF compiler.
206    GpuD4,
207    /// Alternative external Decision-DNNF text adapter.
208    ExternalDdnnfText,
209    /// Alternative external c2d Decision-DNNF compiler adapter.
210    ExternalC2d,
211    /// Alternative external miniC2D Decision-DNNF compiler adapter.
212    ExternalMiniC2d,
213}
214
215/// Implementation status for an adapter.
216#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum CompilerAdapterSupport {
218    /// Adapter is implemented in this crate.
219    Implemented,
220    /// Adapter is recorded as a design contract for a future implementation.
221    DesignOnly,
222}
223
224/// Compiler input format.
225#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum CompilerInputFormat {
227    /// Device-resident GPU CNF.
228    GpuCnf,
229    /// DIMACS CNF text.
230    DimacsCnf,
231}
232
233/// Compiler output format.
234#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum CompilerOutputFormat {
236    /// Device-resident XGCF circuit.
237    Xgcf,
238    /// Decision-DNNF text.
239    DecisionDnnfText,
240}
241
242/// Knowledge compiler adapter metadata used by bounded fixtures.
243#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct KnowledgeCompilerAdapter {
245    /// Human-readable adapter name.
246    pub name: String,
247    /// Adapter kind.
248    pub kind: CompilerAdapterKind,
249    /// Implementation support status.
250    pub support: CompilerAdapterSupport,
251    /// Input format consumed by the adapter.
252    pub input_format: CompilerInputFormat,
253    /// Output format emitted by the adapter.
254    pub output_format: CompilerOutputFormat,
255    incremental_evidence: bool,
256}
257
258impl KnowledgeCompilerAdapter {
259    /// Return the existing GPU-native Decision-DNNF adapter.
260    pub fn gpu_d4() -> Self {
261        Self {
262            name: "gpu-d4".to_string(),
263            kind: CompilerAdapterKind::GpuD4,
264            support: CompilerAdapterSupport::Implemented,
265            input_format: CompilerInputFormat::GpuCnf,
266            output_format: CompilerOutputFormat::Xgcf,
267            incremental_evidence: true,
268        }
269    }
270
271    /// Return an alternative external Decision-DNNF text adapter design.
272    pub fn external_ddnnf_text(name: impl Into<String>) -> Self {
273        Self {
274            name: name.into(),
275            kind: CompilerAdapterKind::ExternalDdnnfText,
276            support: CompilerAdapterSupport::DesignOnly,
277            input_format: CompilerInputFormat::DimacsCnf,
278            output_format: CompilerOutputFormat::DecisionDnnfText,
279            incremental_evidence: false,
280        }
281    }
282
283    /// Return the explicit c2d Decision-DNNF text adapter design.
284    pub fn external_c2d() -> Self {
285        Self {
286            name: "c2d".to_string(),
287            kind: CompilerAdapterKind::ExternalC2d,
288            support: CompilerAdapterSupport::DesignOnly,
289            input_format: CompilerInputFormat::DimacsCnf,
290            output_format: CompilerOutputFormat::DecisionDnnfText,
291            incremental_evidence: false,
292        }
293    }
294
295    /// Return the explicit miniC2D Decision-DNNF text adapter design.
296    pub fn external_mini_c2d() -> Self {
297        Self {
298            name: "miniC2D".to_string(),
299            kind: CompilerAdapterKind::ExternalMiniC2d,
300            support: CompilerAdapterSupport::DesignOnly,
301            input_format: CompilerInputFormat::DimacsCnf,
302            output_format: CompilerOutputFormat::DecisionDnnfText,
303            incremental_evidence: false,
304        }
305    }
306
307    /// Whether the adapter can update evidence without rebuilding the circuit.
308    pub fn supports_incremental_evidence(&self) -> bool {
309        self.incremental_evidence
310    }
311}
312
313/// Circuit update mode for assumption changes.
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub enum CircuitUpdateMode {
316    /// Assumption was already active, so no circuit state changed.
317    Unchanged,
318    /// Evidence was updated without rebuilding the compiled circuit.
319    IncrementalEvidence,
320    /// The adapter does not support incremental evidence and rebuilt the circuit.
321    FullRebuild,
322}
323
324/// Result of applying an epistemic assumption to a circuit.
325#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub struct CircuitUpdate {
327    /// Update mode used by the adapter.
328    pub mode: CircuitUpdateMode,
329    /// Number of compile operations performed by this circuit state.
330    pub compile_count: usize,
331    /// Stable circuit fingerprint after the update.
332    pub circuit_fingerprint: u64,
333}
334
335/// Evidence derived from an accepted epistemic world view.
336#[derive(Debug, Clone, PartialEq, Eq)]
337pub struct AcceptedWorldViewEvidence {
338    assumptions: Vec<EpistemicAssumption>,
339    world_count: usize,
340    gpu_epistemic_mode: Option<EirEpistemicMode>,
341    gpu_tuple_key_column_reads: usize,
342    gpu_final_tuple_row_filters: usize,
343    gpu_final_tuple_negated_row_filters: usize,
344    gpu_row_specific_membership_row_capacity: usize,
345    gpu_row_filter_fallback_row_capacity: usize,
346    gpu_checked_constraint_relations: usize,
347    gpu_constraint_row_count_device_reads: usize,
348}
349
350impl AcceptedWorldViewEvidence {
351    /// Construct evidence from a non-empty accepted world view.
352    pub fn new(
353        world_view: &EpistemicWorldView,
354        assumptions: Vec<EpistemicAssumption>,
355    ) -> Result<Self> {
356        if assumptions.is_empty() {
357            return Err(XlogError::UnsupportedEpistemicConstruct {
358                construct: "accepted world-view evidence".to_string(),
359                context:
360                    "probabilistic evidence requires at least one accepted epistemic assumption"
361                        .to_string(),
362            });
363        }
364        validate_world_view_assumptions(world_view, &assumptions)?;
365        Ok(Self {
366            assumptions,
367            world_count: world_view.world_count(),
368            gpu_epistemic_mode: None,
369            gpu_tuple_key_column_reads: 0,
370            gpu_final_tuple_row_filters: 0,
371            gpu_final_tuple_negated_row_filters: 0,
372            gpu_row_specific_membership_row_capacity: 0,
373            gpu_row_filter_fallback_row_capacity: 0,
374            gpu_checked_constraint_relations: 0,
375            gpu_constraint_row_count_device_reads: 0,
376        })
377    }
378
379    /// Construct evidence from an accepted GPU epistemic execution result.
380    ///
381    /// This is the production boundary used by probabilistic adapters: it
382    /// accepts only results that used timed GPU candidate-generation,
383    /// propagation, validation, stable-model tuple membership, world-view,
384    /// accepted-candidate, final-result, and final-tuple kernels, zero
385    /// hot-path host transfers, and a non-empty device final output.
386    pub fn from_gpu_execution_result(
387        provider: &CudaKernelProvider,
388        result: &EpistemicGpuExecutionResult,
389        assumptions: Vec<EpistemicAssumption>,
390    ) -> Result<Self> {
391        let provider_identity = EpistemicGpuProviderIdentity::from_provider(provider);
392        if result.provider_identity != provider_identity {
393            return Err(XlogError::UnsupportedEpistemicConstruct {
394                construct: "accepted GPU world-view evidence".to_string(),
395                context: format!(
396                    "probabilistic evidence provider mismatch: result device={} provider device={} \
397                     result_device_ptr={} provider_device_ptr={} result_memory_ptr={} \
398                     provider_memory_ptr={}",
399                    result.provider_identity.device_ordinal,
400                    provider_identity.device_ordinal,
401                    result.provider_identity.device_ptr,
402                    provider_identity.device_ptr,
403                    result.provider_identity.memory_ptr,
404                    provider_identity.memory_ptr
405                ),
406            });
407        }
408        match result.prepared.preflight.execution_backend {
409            EpistemicExecutionBackend::Gpu => {}
410        }
411        match result.prepared.preflight.fallback_policy {
412            EpistemicFallbackPolicy::RejectUnsupported => {}
413        }
414        result.require_runtime_dispatch_certification()?;
415        result
416            .model_membership
417            .require_stable_model_tuple_source()?;
418        if result.constraint_validation.violated_constraint_relations != 0 {
419            return Err(XlogError::UnsupportedEpistemicConstruct {
420                construct: "accepted GPU world-view evidence".to_string(),
421                context: format!(
422                    "probabilistic evidence requires zero reduced constraint violations, got {} \
423                     across {} checked constraint relations",
424                    result.constraint_validation.violated_constraint_relations,
425                    result.constraint_validation.checked_constraint_relations
426                ),
427            });
428        }
429        if result.constraint_validation.row_count_device_reads as usize
430            > result.constraint_validation.checked_constraint_relations
431        {
432            return Err(XlogError::UnsupportedEpistemicConstruct {
433                construct: "accepted GPU world-view evidence".to_string(),
434                context: format!(
435                    "probabilistic evidence constraint metadata reads cannot exceed checked \
436                     reduced constraint relations, got reads={} checked={}",
437                    result.constraint_validation.row_count_device_reads,
438                    result.constraint_validation.checked_constraint_relations
439                ),
440            });
441        }
442        require_gpu_kernel_trace(
443            "candidate generation",
444            result.candidate_generation.kernel_launches,
445            result.candidate_generation.host_write_ops,
446            result.candidate_generation.kernel_timing,
447        )?;
448        require_gpu_kernel_trace(
449            "candidate propagation",
450            result.propagation.kernel_launches,
451            result.propagation.host_write_ops,
452            result.propagation.kernel_timing,
453        )?;
454        require_gpu_kernel_trace(
455            "candidate validation",
456            result.candidate_validation.kernel_launches,
457            result.candidate_validation.host_write_ops,
458            result.candidate_validation.kernel_timing,
459        )?;
460        require_gpu_kernel_trace(
461            "model membership",
462            result.model_membership.kernel_launches,
463            result.model_membership.host_write_ops,
464            result.model_membership.kernel_timing,
465        )?;
466        require_gpu_kernel_trace(
467            "world-view validation",
468            result.world_view_validation.kernel_launches,
469            result.world_view_validation.host_write_ops,
470            result.world_view_validation.kernel_timing,
471        )?;
472        require_gpu_kernel_trace(
473            "accepted-candidate materialization",
474            result.materialization.kernel_launches,
475            result.materialization.host_write_ops,
476            result.materialization.kernel_timing,
477        )?;
478        require_gpu_kernel_trace(
479            "final-result materialization",
480            result.final_result_materialization.kernel_launches,
481            result.final_result_materialization.host_write_ops,
482            result.final_result_materialization.kernel_timing,
483        )?;
484        require_gpu_kernel_trace(
485            "final tuple materialization",
486            result.final_tuple_materialization.kernel_launches,
487            result.final_tuple_materialization.host_write_ops,
488            result.final_tuple_materialization.kernel_timing,
489        )?;
490        // The runtime has already captured this via read_device_row_count during
491        // the bounded final-result transfer; do not re-read it in the prob gate.
492        let accepted_rows = result.final_result_transfer.final_output_rows;
493        result
494            .final_tuple_materialization
495            .require_row_filter_materialization_evidence(
496                "accepted GPU world-view evidence",
497                accepted_rows,
498            )?;
499        if result.transfer_budget.tracked_dtoh_calls != 0
500            || result.transfer_budget.tracked_htod_calls != 0
501            || result.transfer_budget.tracked_data_plane_htod_calls != 0
502            || result.transfer_budget.per_candidate_host_round_trips != 0
503        {
504            return Err(XlogError::UnsupportedEpistemicConstruct {
505                construct: "accepted GPU world-view evidence".to_string(),
506                context: format!(
507                    "probabilistic evidence requires zero hot-path transfers outside bounded \
508                     launch metadata, got dtoh_calls={}, htod_calls={}, \
509                     data_plane_htod_calls={}, launch_metadata_htod_calls={}, \
510                     per_candidate_round_trips={}",
511                    result.transfer_budget.tracked_dtoh_calls,
512                    result.transfer_budget.tracked_htod_calls,
513                    result.transfer_budget.tracked_data_plane_htod_calls,
514                    result.transfer_budget.tracked_launch_metadata_htod_calls,
515                    result.transfer_budget.per_candidate_host_round_trips
516                ),
517            });
518        }
519        require_accepted_gpu_semantic_trace(result)?;
520
521        if accepted_rows == 0 {
522            return Err(XlogError::UnsupportedEpistemicConstruct {
523                construct: "accepted GPU world-view evidence".to_string(),
524                context: "probabilistic evidence requires non-empty accepted GPU final output"
525                    .to_string(),
526            });
527        }
528        if result.semantic_trace.accepted_candidates == 0 {
529            return Err(XlogError::UnsupportedEpistemicConstruct {
530                construct: "accepted GPU world-view evidence".to_string(),
531                context: "probabilistic evidence requires at least one GPU-accepted candidate"
532                    .to_string(),
533            });
534        }
535        let accepted_assumptions =
536            accepted_gpu_evidence_assumptions(provider, result, &assumptions)?;
537
538        Ok(Self {
539            assumptions: accepted_assumptions,
540            world_count: result.semantic_trace.accepted_world_views,
541            gpu_epistemic_mode: Some(result.prepared.preflight.epistemic_mode),
542            gpu_tuple_key_column_reads: result.model_membership.tuple_source_key_column_device_reads
543                as usize,
544            gpu_final_tuple_row_filters: result.final_tuple_materialization.row_filter_count,
545            gpu_final_tuple_negated_row_filters: result
546                .final_tuple_materialization
547                .negated_row_filter_count,
548            gpu_row_specific_membership_row_capacity: result
549                .final_tuple_materialization
550                .row_specific_membership_row_capacity,
551            gpu_row_filter_fallback_row_capacity: result
552                .final_tuple_materialization
553                .row_filter_row_capacity_outside_model_slot_window,
554            gpu_checked_constraint_relations: result
555                .constraint_validation
556                .checked_constraint_relations,
557            gpu_constraint_row_count_device_reads: result
558                .constraint_validation
559                .row_count_device_reads as usize,
560        })
561    }
562
563    /// Number of worlds used to validate this evidence.
564    pub fn world_count(&self) -> usize {
565        self.world_count
566    }
567
568    /// Accepted epistemic assumptions represented by this evidence.
569    pub fn assumptions(&self) -> &[EpistemicAssumption] {
570        &self.assumptions
571    }
572
573    pub(crate) fn with_assumptions(&self, assumptions: Vec<EpistemicAssumption>) -> Self {
574        let mut evidence = self.clone();
575        evidence.assumptions = assumptions;
576        evidence
577    }
578
579    /// Epistemic mode reported by the accepted GPU runtime evidence, when present.
580    pub fn gpu_epistemic_mode(&self) -> Option<EirEpistemicMode> {
581        self.gpu_epistemic_mode
582    }
583
584    /// Number of accepted epistemic assumptions represented by this evidence.
585    pub fn assumption_count(&self) -> usize {
586        self.assumptions.len()
587    }
588
589    /// Accepted nonzero-arity epistemic assumptions represented by this evidence.
590    pub fn nonzero_arity_assumption_count(&self) -> usize {
591        self.assumptions
592            .iter()
593            .filter(|assumption| assumption.arity > 0)
594            .count()
595    }
596
597    /// Maximum accepted epistemic assumption arity represented by this evidence.
598    pub fn max_assumption_arity(&self) -> usize {
599        self.assumptions
600            .iter()
601            .map(|assumption| assumption.arity)
602            .max()
603            .unwrap_or(0)
604    }
605
606    /// Tuple-key device column reads used while staging accepted GPU tuple evidence.
607    pub fn gpu_tuple_key_column_reads(&self) -> usize {
608        self.gpu_tuple_key_column_reads
609    }
610
611    /// GPU final-tuple row filters used to materialize variable-bound evidence.
612    pub fn gpu_final_tuple_row_filters(&self) -> usize {
613        self.gpu_final_tuple_row_filters
614    }
615
616    /// Negated GPU final-tuple row filters used to materialize variable-bound evidence.
617    pub fn gpu_final_tuple_negated_row_filters(&self) -> usize {
618        self.gpu_final_tuple_negated_row_filters
619    }
620
621    /// Final-output row capacity checked against row-specific GPU model slots.
622    pub fn gpu_row_specific_membership_row_capacity(&self) -> usize {
623        self.gpu_row_specific_membership_row_capacity
624    }
625
626    /// Final-output row capacity checked by fallback GPU row filters outside model slots.
627    pub fn gpu_row_filter_fallback_row_capacity(&self) -> usize {
628        self.gpu_row_filter_fallback_row_capacity
629    }
630
631    /// Reduced integrity-constraint relations checked by accepted GPU execution.
632    pub fn gpu_checked_constraint_relations(&self) -> usize {
633        self.gpu_checked_constraint_relations
634    }
635
636    /// Constraint row-count metadata reads used by accepted GPU execution.
637    pub fn gpu_constraint_row_count_device_reads(&self) -> usize {
638        self.gpu_constraint_row_count_device_reads
639    }
640}
641
642fn validate_world_view_assumptions(
643    world_view: &EpistemicWorldView,
644    assumptions: &[EpistemicAssumption],
645) -> Result<()> {
646    for assumption in assumptions {
647        let actual_value =
648            world_view.evaluate(&epistemic_literal_for_assumption(assumption)?) == TruthValue::True;
649        if actual_value != assumption.value {
650            return Err(XlogError::UnsupportedEpistemicConstruct {
651                construct: "accepted world-view evidence".to_string(),
652                context: format!(
653                    "probabilistic evidence assumption {} was not accepted by world view",
654                    assumption.evidence_literal()
655                ),
656            });
657        }
658    }
659    Ok(())
660}
661
662fn epistemic_literal_for_assumption(assumption: &EpistemicAssumption) -> Result<EpistemicLiteral> {
663    if assumption.arity > 0 && assumption.terms.is_empty() {
664        return Err(XlogError::UnsupportedEpistemicConstruct {
665            construct: "accepted world-view evidence".to_string(),
666            context: format!(
667                "nonzero probabilistic evidence assumption {} requires concrete tuple terms",
668                assumption.evidence_literal()
669            ),
670        });
671    }
672    if !assumption.terms.is_empty() && assumption.terms.len() != assumption.arity {
673        return Err(XlogError::UnsupportedEpistemicConstruct {
674            construct: "accepted world-view evidence".to_string(),
675            context: format!(
676                "probabilistic evidence assumption {} has arity {}, but {} concrete terms",
677                assumption.evidence_literal(),
678                assumption.arity,
679                assumption.terms.len()
680            ),
681        });
682    }
683
684    let terms = assumption
685        .terms
686        .iter()
687        .map(epistemic_evidence_term_to_logic_term)
688        .collect();
689    let op = match assumption.kind {
690        EpistemicAssumptionKind::Know => EpistemicOp::Know,
691        EpistemicAssumptionKind::Possible => EpistemicOp::Possible,
692    };
693
694    Ok(EpistemicLiteral {
695        op,
696        negated: false,
697        atom: Atom {
698            predicate: assumption.predicate.clone(),
699            terms,
700        },
701    })
702}
703
704fn epistemic_evidence_term_to_logic_term(term: &EpistemicEvidenceTerm) -> Term {
705    match term {
706        EpistemicEvidenceTerm::Integer(value) => Term::Integer(*value),
707        EpistemicEvidenceTerm::String(value) => Term::String(value.clone()),
708        EpistemicEvidenceTerm::Symbol(value) => Term::Symbol(*value),
709    }
710}
711
712fn require_accepted_gpu_semantic_trace(result: &EpistemicGpuExecutionResult) -> Result<()> {
713    let trace = &result.semantic_trace;
714    let accounted_candidates = trace
715        .accepted_candidates
716        .checked_add(trace.rejected_candidates)
717        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
718            construct: "accepted GPU world-view evidence".to_string(),
719            context: format!(
720                "probabilistic evidence semantic trace candidate accounting overflowed: \
721                 accepted={} rejected={}",
722                trace.accepted_candidates, trace.rejected_candidates
723            ),
724        })?;
725    if trace.generated_candidates != result.candidate_generation.generated_candidates
726        || trace.tested_candidates != result.world_view_validation.candidates_checked
727        || trace.accepted_candidates != trace.accepted_candidate_indices.len()
728        || trace.rejected_candidates != trace.rejected_candidate_indices.len()
729        || trace.accepted_world_views != trace.accepted_candidates
730        || accounted_candidates != trace.generated_candidates
731    {
732        return Err(XlogError::UnsupportedEpistemicConstruct {
733            construct: "accepted GPU world-view evidence".to_string(),
734            context: format!(
735                "probabilistic evidence requires a consistent GPU semantic trace, got \
736                 generated={}, tested={}, expected_generated={}, \
737                 expected_tested={}, accepted={} accepted_indices={}, accepted_world_views={}, \
738                 rejected={} rejected_indices={}",
739                trace.generated_candidates,
740                trace.tested_candidates,
741                result.candidate_generation.generated_candidates,
742                result.world_view_validation.candidates_checked,
743                trace.accepted_candidates,
744                trace.accepted_candidate_indices.len(),
745                trace.accepted_world_views,
746                trace.rejected_candidates,
747                trace.rejected_candidate_indices.len()
748            ),
749        });
750    }
751    Ok(())
752}
753
754fn accepted_gpu_evidence_assumptions(
755    provider: &CudaKernelProvider,
756    result: &EpistemicGpuExecutionResult,
757    assumptions: &[EpistemicAssumption],
758) -> Result<Vec<EpistemicAssumption>> {
759    let preflight = &result.prepared.preflight;
760    if result.tuple_membership_bindings.len() != preflight.tuple_membership_binding_count {
761        return Err(XlogError::UnsupportedEpistemicConstruct {
762            construct: "accepted GPU world-view evidence".to_string(),
763            context: format!(
764                "probabilistic evidence requires executed tuple-membership bindings, got {} \
765                 bindings for preflight count {}",
766                result.tuple_membership_bindings.len(),
767                preflight.tuple_membership_binding_count
768            ),
769        });
770    }
771    if !assumptions.is_empty() && assumptions.len() != preflight.tuple_membership_binding_count {
772        return Err(XlogError::UnsupportedEpistemicConstruct {
773            construct: "accepted GPU world-view evidence".to_string(),
774            context: format!(
775                "probabilistic evidence must cover every GPU-validated tuple-membership binding, \
776                 or supply no assumption facts for gate-only production reuse; got {} assumptions \
777                 for {} bindings",
778                assumptions.len(),
779                preflight.tuple_membership_binding_count
780            ),
781        });
782    }
783    let assumptions =
784        resolve_gpu_evidence_assumptions(provider, result, assumptions, preflight.epistemic_mode)?;
785    let mut know_bindings = BTreeSet::new();
786    let mut possible_bindings = BTreeSet::new();
787    let mut not_know_bindings = BTreeSet::new();
788    let mut not_possible_bindings = BTreeSet::new();
789    let mut bound_tuple_bindings = BTreeSet::new();
790    let mut negated_bound_tuple_bindings = BTreeSet::new();
791    let mut accepted_assumptions = Vec::with_capacity(assumptions.len());
792    for assumption in &assumptions {
793        if assumption.arity > 0 && assumption.terms.is_empty() {
794            return Err(XlogError::UnsupportedEpistemicConstruct {
795                construct: "accepted GPU world-view evidence".to_string(),
796                context: format!(
797                    "nonzero probabilistic evidence assumption {} requires concrete tuple terms",
798                    assumption.evidence_literal()
799                ),
800            });
801        }
802        if !assumption.terms.is_empty() && assumption.terms.len() != assumption.arity {
803            return Err(XlogError::UnsupportedEpistemicConstruct {
804                construct: "accepted GPU world-view evidence".to_string(),
805                context: format!(
806                    "probabilistic evidence assumption {} has arity {}, but {} concrete terms",
807                    assumption.evidence_literal(),
808                    assumption.arity,
809                    assumption.terms.len()
810                ),
811            });
812        }
813        let Some(binding_match) =
814            find_gpu_evidence_binding(provider, result, assumption, preflight.epistemic_mode)?
815        else {
816            return Err(XlogError::UnsupportedEpistemicConstruct {
817                construct: "accepted GPU world-view evidence".to_string(),
818                context: format!(
819                    "probabilistic evidence assumption {} was not validated by the accepted GPU \
820                     tuple-membership bindings",
821                    assumption.evidence_literal()
822                ),
823            });
824        };
825        if accepted_assumptions
826            .iter()
827            .any(|previous: &EpistemicAssumption| {
828                binding_match
829                    .accepted_assumption
830                    .same_evidence_key(previous)
831            })
832        {
833            return Err(XlogError::UnsupportedEpistemicConstruct {
834                construct: "accepted GPU world-view evidence".to_string(),
835                context: format!(
836                    "probabilistic evidence duplicates epistemic assumption key {}",
837                    binding_match.accepted_assumption.evidence_literal()
838                ),
839            });
840        }
841        let binding = binding_match.binding;
842        let binding_key = (binding.literal_index, binding.reduction_index);
843        match (binding.op, binding.negated) {
844            (EirEpistemicOp::Know, false) => {
845                know_bindings.insert(binding_key);
846            }
847            (EirEpistemicOp::Possible, false) => {
848                possible_bindings.insert(binding_key);
849            }
850            (EirEpistemicOp::Know, true) => {
851                not_know_bindings.insert(binding_key);
852            }
853            (EirEpistemicOp::Possible, true) => {
854                not_possible_bindings.insert(binding_key);
855            }
856        }
857        if binding_match.matched_concrete_tuple_key
858            && binding.bound_output_columns.iter().any(Option::is_some)
859        {
860            bound_tuple_bindings.insert(binding_key);
861            if binding.negated {
862                negated_bound_tuple_bindings.insert(binding_key);
863            }
864        }
865        accepted_assumptions.push(binding_match.accepted_assumption);
866    }
867    if know_bindings.len() > preflight.know_operator_count
868        || possible_bindings.len() > preflight.possible_operator_count
869        || not_know_bindings.len() > preflight.not_know_operator_count
870        || not_possible_bindings.len() > preflight.not_possible_operator_count
871    {
872        return Err(XlogError::UnsupportedEpistemicConstruct {
873            construct: "accepted GPU world-view evidence".to_string(),
874            context: format!(
875                "probabilistic evidence assumptions exceed GPU-validated operator counts: \
876                 know={}/{} possible={}/{} not_know={}/{} not_possible={}/{}",
877                know_bindings.len(),
878                preflight.know_operator_count,
879                possible_bindings.len(),
880                preflight.possible_operator_count,
881                not_know_bindings.len(),
882                preflight.not_know_operator_count,
883                not_possible_bindings.len(),
884                preflight.not_possible_operator_count
885            ),
886        });
887    }
888    let final_tuple_trace = result.final_tuple_materialization;
889    if bound_tuple_bindings.len() > final_tuple_trace.row_filter_count
890        || negated_bound_tuple_bindings.len() > final_tuple_trace.negated_row_filter_count
891    {
892        return Err(XlogError::UnsupportedEpistemicConstruct {
893            construct: "accepted GPU world-view evidence".to_string(),
894            context: format!(
895                "probabilistic evidence supplied variable-bound tuple assumptions without \
896                 matching GPU final-tuple row-filter materialization: bound={}/{} \
897                 negated_bound={}/{}",
898                bound_tuple_bindings.len(),
899                final_tuple_trace.row_filter_count,
900                negated_bound_tuple_bindings.len(),
901                final_tuple_trace.negated_row_filter_count
902            ),
903        });
904    }
905    Ok(accepted_assumptions)
906}
907
908fn resolve_gpu_evidence_assumptions(
909    provider: &CudaKernelProvider,
910    result: &EpistemicGpuExecutionResult,
911    assumptions: &[EpistemicAssumption],
912    mode: EirEpistemicMode,
913) -> Result<Vec<EpistemicAssumption>> {
914    if assumptions.is_empty() {
915        return concrete_gpu_evidence_assumptions_for_all_bindings(provider, result);
916    }
917
918    let mut resolved = BTreeSet::new();
919    for assumption in assumptions {
920        if assumption.arity > 0 && assumption.terms.is_empty() {
921            for concrete in concrete_gpu_evidence_assumptions(provider, result, assumption, mode)? {
922                resolved.insert(concrete);
923            }
924        } else {
925            resolved.insert(assumption.clone());
926        }
927    }
928    Ok(resolved.into_iter().collect())
929}
930
931fn concrete_gpu_evidence_assumptions_for_all_bindings(
932    provider: &CudaKernelProvider,
933    result: &EpistemicGpuExecutionResult,
934) -> Result<Vec<EpistemicAssumption>> {
935    if result.tuple_membership_bindings.is_empty() {
936        return Err(XlogError::UnsupportedEpistemicConstruct {
937            construct: "accepted GPU world-view evidence".to_string(),
938            context: "probabilistic evidence requires at least one accepted GPU tuple-membership \
939                      binding"
940                .to_string(),
941        });
942    }
943
944    let mut resolved = BTreeSet::new();
945    for binding in &result.tuple_membership_bindings {
946        let assumption = EpistemicAssumption {
947            kind: match binding.op {
948                EirEpistemicOp::Know => EpistemicAssumptionKind::Know,
949                EirEpistemicOp::Possible => EpistemicAssumptionKind::Possible,
950            },
951            predicate: binding.predicate.clone(),
952            arity: binding.arity,
953            terms: Vec::new(),
954            value: !binding.negated,
955        };
956        if binding.arity == 0 {
957            resolved.insert(assumption);
958        } else {
959            for concrete in concrete_gpu_evidence_assumptions_for_binding(
960                provider,
961                result.tuple_evidence_output(),
962                &assumption,
963                binding,
964            )? {
965                resolved.insert(concrete);
966            }
967        }
968    }
969
970    if resolved.is_empty() {
971        return Err(XlogError::UnsupportedEpistemicConstruct {
972            construct: "accepted GPU world-view evidence".to_string(),
973            context: "accepted GPU tuple-membership bindings did not materialize any \
974                      probabilistic evidence assumptions"
975                .to_string(),
976        });
977    }
978    Ok(resolved.into_iter().collect())
979}
980
981fn concrete_gpu_evidence_assumptions(
982    provider: &CudaKernelProvider,
983    result: &EpistemicGpuExecutionResult,
984    assumption: &EpistemicAssumption,
985    mode: EirEpistemicMode,
986) -> Result<Vec<EpistemicAssumption>> {
987    let candidate_bindings = result
988        .tuple_membership_bindings
989        .iter()
990        .filter(|binding| {
991            assumption_kind_matches_binding(assumption, binding, mode)
992                && assumption.predicate == binding.predicate
993                && assumption.value != binding.negated
994        })
995        .collect::<Vec<_>>();
996
997    if candidate_bindings.is_empty() {
998        return Err(XlogError::UnsupportedEpistemicConstruct {
999            construct: "accepted GPU world-view evidence".to_string(),
1000            context: format!(
1001                "nonzero probabilistic evidence assumption {} was not validated by any accepted \
1002                 GPU tuple-membership binding",
1003                assumption.evidence_literal()
1004            ),
1005        });
1006    }
1007
1008    let arity_matched = candidate_bindings
1009        .iter()
1010        .copied()
1011        .filter(|binding| binding.arity == assumption.arity)
1012        .collect::<Vec<_>>();
1013    if arity_matched.is_empty() {
1014        let available_arities = candidate_bindings
1015            .iter()
1016            .map(|binding| binding.arity)
1017            .collect::<BTreeSet<_>>();
1018        return Err(XlogError::UnsupportedEpistemicConstruct {
1019            construct: "accepted GPU world-view evidence".to_string(),
1020            context: format!(
1021                "nonzero probabilistic evidence assumption {} requires arity {}, but accepted \
1022                 GPU tuple-membership bindings for the predicate have arities {:?}",
1023                assumption.evidence_literal(),
1024                assumption.arity,
1025                available_arities
1026            ),
1027        });
1028    }
1029
1030    let mut concrete = BTreeSet::new();
1031    for binding in arity_matched {
1032        for assumption in concrete_gpu_evidence_assumptions_for_binding(
1033            provider,
1034            result.tuple_evidence_output(),
1035            assumption,
1036            binding,
1037        )? {
1038            concrete.insert(assumption);
1039        }
1040    }
1041
1042    if concrete.is_empty() {
1043        return Err(XlogError::UnsupportedEpistemicConstruct {
1044            construct: "accepted GPU world-view evidence".to_string(),
1045            context: format!(
1046                "nonzero probabilistic evidence assumption {} did not materialize any concrete \
1047                 GPU tuple evidence",
1048                assumption.evidence_literal()
1049            ),
1050        });
1051    }
1052    Ok(concrete.into_iter().collect())
1053}
1054
1055fn concrete_gpu_evidence_assumptions_for_binding(
1056    provider: &CudaKernelProvider,
1057    final_output: &CudaBuffer,
1058    assumption: &EpistemicAssumption,
1059    binding: &EpistemicTupleMembershipBinding,
1060) -> Result<Vec<EpistemicAssumption>> {
1061    if binding.arity == 0 {
1062        return Ok(vec![assumption.clone()]);
1063    }
1064    if binding.key_terms.len() != binding.arity
1065        || binding.bound_output_columns.len() != binding.key_terms.len()
1066    {
1067        return Err(XlogError::UnsupportedEpistemicConstruct {
1068            construct: "accepted GPU world-view evidence".to_string(),
1069            context: format!(
1070                "GPU tuple-membership binding for {}/{} has inconsistent key metadata: \
1071                 key_terms={} bound_output_columns={}",
1072                binding.predicate,
1073                binding.arity,
1074                binding.key_terms.len(),
1075                binding.bound_output_columns.len()
1076            ),
1077        });
1078    }
1079
1080    let output_rows = provider.device_row_count(final_output)?;
1081    if output_rows == 0 {
1082        return Err(XlogError::UnsupportedEpistemicConstruct {
1083            construct: "accepted GPU world-view evidence".to_string(),
1084            context: format!(
1085                "nonzero probabilistic evidence assumption {} requires non-empty GPU final output",
1086                assumption.evidence_literal()
1087            ),
1088        });
1089    }
1090
1091    let mut output_columns = BTreeMap::new();
1092    for output_col in binding.bound_output_columns.iter().flatten() {
1093        if !output_columns.contains_key(output_col) {
1094            let terms =
1095                download_gpu_final_output_evidence_terms(provider, final_output, *output_col)?;
1096            if terms.len() != output_rows {
1097                return Err(XlogError::UnsupportedEpistemicConstruct {
1098                    construct: "accepted GPU world-view evidence".to_string(),
1099                    context: format!(
1100                        "GPU final-output column {} produced {} evidence terms for {} rows",
1101                        output_col,
1102                        terms.len(),
1103                        output_rows
1104                    ),
1105                });
1106            }
1107            output_columns.insert(*output_col, terms);
1108        }
1109    }
1110
1111    let row_count = if output_columns.is_empty() {
1112        1
1113    } else {
1114        output_rows
1115    };
1116    let mut concrete = Vec::with_capacity(row_count);
1117    for row in 0..row_count {
1118        let mut terms = Vec::with_capacity(binding.key_terms.len());
1119        for (key_term, output_col) in binding
1120            .key_terms
1121            .iter()
1122            .zip(binding.bound_output_columns.iter())
1123        {
1124            match key_term {
1125                EirTerm::Variable(_) => {
1126                    let Some(output_col) = *output_col else {
1127                        return Err(XlogError::UnsupportedEpistemicConstruct {
1128                            construct: "accepted GPU world-view evidence".to_string(),
1129                            context: format!(
1130                                "probabilistic evidence assumption {} has an unbound variable \
1131                                 tuple key",
1132                                assumption.evidence_literal()
1133                            ),
1134                        });
1135                    };
1136                    let column_terms = output_columns.get(&output_col).ok_or_else(|| {
1137                        XlogError::UnsupportedEpistemicConstruct {
1138                            construct: "accepted GPU world-view evidence".to_string(),
1139                            context: format!(
1140                                "GPU final-output column {} was not staged for {}",
1141                                output_col,
1142                                assumption.evidence_literal()
1143                            ),
1144                        }
1145                    })?;
1146                    terms.push(column_terms[row].clone());
1147                }
1148                _ => terms.push(evidence_term_from_ground_eir_term(key_term, assumption)?),
1149            }
1150        }
1151        concrete.push(match assumption.kind {
1152            EpistemicAssumptionKind::Know => {
1153                EpistemicAssumption::known_tuple(&assumption.predicate, terms, assumption.value)
1154            }
1155            EpistemicAssumptionKind::Possible => {
1156                EpistemicAssumption::possible_tuple(&assumption.predicate, terms, assumption.value)
1157            }
1158        });
1159    }
1160    Ok(concrete)
1161}
1162
1163fn download_gpu_final_output_evidence_terms(
1164    provider: &CudaKernelProvider,
1165    final_output: &CudaBuffer,
1166    output_col: usize,
1167) -> Result<Vec<EpistemicEvidenceTerm>> {
1168    let col_type = final_output
1169        .schema()
1170        .column_type(output_col)
1171        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1172            construct: "accepted GPU world-view evidence".to_string(),
1173            context: format!(
1174                "probabilistic evidence references missing GPU final-output column {}",
1175                output_col
1176            ),
1177        })?;
1178    match col_type {
1179        ScalarType::U32 => Ok(provider
1180            .download_column::<u32>(final_output, output_col)?
1181            .into_iter()
1182            .map(|value| EpistemicEvidenceTerm::Integer(i64::from(value)))
1183            .collect()),
1184        ScalarType::U64 => provider
1185            .download_column::<u64>(final_output, output_col)?
1186            .into_iter()
1187            .map(|value| {
1188                i64::try_from(value)
1189                    .map(EpistemicEvidenceTerm::Integer)
1190                    .map_err(|_| XlogError::UnsupportedEpistemicConstruct {
1191                        construct: "accepted GPU world-view evidence".to_string(),
1192                        context: format!(
1193                            "GPU final-output column {} value {} exceeds exact evidence i64 \
1194                                 range",
1195                            output_col, value
1196                        ),
1197                    })
1198            })
1199            .collect(),
1200        ScalarType::I32 => Ok(provider
1201            .download_column::<i32>(final_output, output_col)?
1202            .into_iter()
1203            .map(|value| EpistemicEvidenceTerm::Integer(i64::from(value)))
1204            .collect()),
1205        ScalarType::I64 => Ok(provider
1206            .download_column::<i64>(final_output, output_col)?
1207            .into_iter()
1208            .map(EpistemicEvidenceTerm::Integer)
1209            .collect()),
1210        ScalarType::Symbol => Ok(provider
1211            .download_column::<u32>(final_output, output_col)?
1212            .into_iter()
1213            .map(EpistemicEvidenceTerm::Symbol)
1214            .collect()),
1215        ScalarType::Bool | ScalarType::F32 | ScalarType::F64 => {
1216            Err(XlogError::UnsupportedEpistemicConstruct {
1217                construct: "accepted GPU world-view evidence".to_string(),
1218                context: format!(
1219                    "GPU final-output column {} type {:?} cannot be used as exact epistemic \
1220                     evidence",
1221                    output_col, col_type
1222                ),
1223            })
1224        }
1225    }
1226}
1227
1228fn evidence_term_from_ground_eir_term(
1229    term: &EirTerm,
1230    assumption: &EpistemicAssumption,
1231) -> Result<EpistemicEvidenceTerm> {
1232    match term {
1233        EirTerm::Integer(value) => Ok(EpistemicEvidenceTerm::Integer(*value)),
1234        EirTerm::String(value) => Ok(EpistemicEvidenceTerm::String(value.clone())),
1235        EirTerm::Symbol(value) => Ok(EpistemicEvidenceTerm::Symbol(*value)),
1236        EirTerm::Variable(_)
1237        | EirTerm::Anonymous
1238        | EirTerm::FloatBits(_)
1239        | EirTerm::List(_)
1240        | EirTerm::Cons { .. }
1241        | EirTerm::Compound { .. }
1242        | EirTerm::PredRef(_)
1243        | EirTerm::Aggregate { .. } => Err(XlogError::UnsupportedEpistemicConstruct {
1244            construct: "accepted GPU world-view evidence".to_string(),
1245            context: format!(
1246                "probabilistic evidence assumption {} uses unsupported tuple key term {:?}",
1247                assumption.evidence_literal(),
1248                term
1249            ),
1250        }),
1251    }
1252}
1253
1254struct GpuEvidenceBindingMatch<'a> {
1255    binding: &'a EpistemicTupleMembershipBinding,
1256    accepted_assumption: EpistemicAssumption,
1257    matched_concrete_tuple_key: bool,
1258}
1259
1260fn find_gpu_evidence_binding<'a>(
1261    provider: &CudaKernelProvider,
1262    result: &'a EpistemicGpuExecutionResult,
1263    assumption: &EpistemicAssumption,
1264    mode: EirEpistemicMode,
1265) -> Result<Option<GpuEvidenceBindingMatch<'a>>> {
1266    let mut saw_final_tuple_miss = false;
1267    for binding in result
1268        .tuple_membership_bindings
1269        .iter()
1270        .filter(|binding| assumption_matches_gpu_binding(assumption, binding, mode))
1271    {
1272        if assumption.arity > 0
1273            && !assumption.terms.is_empty()
1274            && binding.bound_output_columns.iter().any(Option::is_some)
1275        {
1276            let matched_rows = gpu_final_output_rows_matching_assumption(
1277                provider,
1278                result.tuple_evidence_output(),
1279                assumption,
1280                binding,
1281            )?;
1282            if matched_rows == 0 {
1283                saw_final_tuple_miss = true;
1284                continue;
1285            }
1286        }
1287        return Ok(Some(GpuEvidenceBindingMatch {
1288            binding,
1289            accepted_assumption: assumption.clone(),
1290            matched_concrete_tuple_key: !assumption.terms.is_empty(),
1291        }));
1292    }
1293    if saw_final_tuple_miss {
1294        return Err(XlogError::UnsupportedEpistemicConstruct {
1295            construct: "accepted GPU world-view evidence".to_string(),
1296            context: format!(
1297                "probabilistic evidence assumption {} did not match any GPU-materialized final \
1298                 tuple row",
1299                assumption.evidence_literal()
1300            ),
1301        });
1302    }
1303    Ok(None)
1304}
1305
1306fn gpu_final_output_rows_matching_assumption(
1307    provider: &CudaKernelProvider,
1308    final_output: &CudaBuffer,
1309    assumption: &EpistemicAssumption,
1310    binding: &EpistemicTupleMembershipBinding,
1311) -> Result<usize> {
1312    let mut filtered: Option<CudaBuffer> = None;
1313    let mut checked_variable_terms = 0usize;
1314
1315    for ((assumption_term, binding_term), bound_output_column) in assumption
1316        .terms
1317        .iter()
1318        .zip(binding.key_terms.iter())
1319        .zip(binding.bound_output_columns.iter())
1320    {
1321        if !matches!(binding_term, EirTerm::Variable(_)) {
1322            continue;
1323        }
1324        let Some(output_col) = *bound_output_column else {
1325            return Err(XlogError::UnsupportedEpistemicConstruct {
1326                construct: "accepted GPU world-view evidence".to_string(),
1327                context: format!(
1328                    "probabilistic evidence assumption {} has an unbound variable tuple key",
1329                    assumption.evidence_literal()
1330                ),
1331            });
1332        };
1333        let input = filtered.as_ref().unwrap_or(final_output);
1334        filtered = Some(filter_gpu_final_output_by_evidence_term(
1335            provider,
1336            input,
1337            output_col,
1338            assumption_term,
1339            assumption,
1340        )?);
1341        checked_variable_terms += 1;
1342    }
1343
1344    if checked_variable_terms == 0 {
1345        return provider.device_row_count(final_output);
1346    }
1347    let filtered = filtered
1348        .as_ref()
1349        .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1350            construct: "accepted GPU world-view evidence".to_string(),
1351            context: format!(
1352                "probabilistic evidence assumption {} did not produce a GPU final-output filter",
1353                assumption.evidence_literal()
1354            ),
1355        })?;
1356    provider.device_row_count(filtered)
1357}
1358
1359fn filter_gpu_final_output_by_evidence_term(
1360    provider: &CudaKernelProvider,
1361    input: &CudaBuffer,
1362    output_col: usize,
1363    term: &EpistemicEvidenceTerm,
1364    assumption: &EpistemicAssumption,
1365) -> Result<CudaBuffer> {
1366    let col_type = input.schema().column_type(output_col).ok_or_else(|| {
1367        XlogError::UnsupportedEpistemicConstruct {
1368            construct: "accepted GPU world-view evidence".to_string(),
1369            context: format!(
1370                "probabilistic evidence assumption {} references missing final-output column {}",
1371                assumption.evidence_literal(),
1372                output_col
1373            ),
1374        }
1375    })?;
1376
1377    match (col_type, term) {
1378        (ScalarType::U32, EpistemicEvidenceTerm::Integer(value)) => {
1379            let value = u32::try_from(*value)
1380                .map_err(|_| evidence_term_type_error(assumption, output_col, col_type, term))?;
1381            provider.filter::<u32>(input, output_col, value, CompareOp::Eq)
1382        }
1383        (ScalarType::U64, EpistemicEvidenceTerm::Integer(value)) => {
1384            let value = u64::try_from(*value)
1385                .map_err(|_| evidence_term_type_error(assumption, output_col, col_type, term))?;
1386            provider.filter::<u64>(input, output_col, value, CompareOp::Eq)
1387        }
1388        (ScalarType::I32, EpistemicEvidenceTerm::Integer(value)) => {
1389            let value = i32::try_from(*value)
1390                .map_err(|_| evidence_term_type_error(assumption, output_col, col_type, term))?;
1391            provider.filter::<i32>(input, output_col, value, CompareOp::Eq)
1392        }
1393        (ScalarType::I64, EpistemicEvidenceTerm::Integer(value)) => {
1394            provider.filter::<i64>(input, output_col, *value, CompareOp::Eq)
1395        }
1396        (ScalarType::Symbol, EpistemicEvidenceTerm::Symbol(value)) => {
1397            provider.filter::<u32>(input, output_col, *value, CompareOp::Eq)
1398        }
1399        (ScalarType::Symbol, EpistemicEvidenceTerm::String(value)) => {
1400            let value = symbol::intern(value);
1401            provider.filter::<u32>(input, output_col, value, CompareOp::Eq)
1402        }
1403        _ => Err(evidence_term_type_error(
1404            assumption, output_col, col_type, term,
1405        )),
1406    }
1407}
1408
1409fn evidence_term_type_error(
1410    assumption: &EpistemicAssumption,
1411    output_col: usize,
1412    col_type: ScalarType,
1413    term: &EpistemicEvidenceTerm,
1414) -> XlogError {
1415    XlogError::UnsupportedEpistemicConstruct {
1416        construct: "accepted GPU world-view evidence".to_string(),
1417        context: format!(
1418            "probabilistic evidence assumption {} term {:?} is incompatible with \
1419             GPU final-output column {} type {:?}",
1420            assumption.evidence_literal(),
1421            term,
1422            output_col,
1423            col_type
1424        ),
1425    }
1426}
1427
1428fn assumption_matches_gpu_binding(
1429    assumption: &EpistemicAssumption,
1430    binding: &EpistemicTupleMembershipBinding,
1431    mode: EirEpistemicMode,
1432) -> bool {
1433    if assumption_kind_matches_binding(assumption, binding, mode)
1434        && assumption.predicate == binding.predicate
1435        && assumption.arity == binding.arity
1436        && assumption.value != binding.negated
1437    {
1438        assumption_terms_match_binding(assumption, binding)
1439    } else {
1440        false
1441    }
1442}
1443
1444fn assumption_kind_matches_op(kind: EpistemicAssumptionKind, op: EirEpistemicOp) -> bool {
1445    matches!(
1446        (kind, op),
1447        (EpistemicAssumptionKind::Know, EirEpistemicOp::Know)
1448            | (EpistemicAssumptionKind::Possible, EirEpistemicOp::Possible)
1449    )
1450}
1451
1452fn assumption_kind_matches_binding(
1453    assumption: &EpistemicAssumption,
1454    binding: &EpistemicTupleMembershipBinding,
1455    mode: EirEpistemicMode,
1456) -> bool {
1457    assumption_kind_matches_op(assumption.kind, binding.op)
1458        || (matches!(mode, EirEpistemicMode::Faeel)
1459            && assumption.kind == EpistemicAssumptionKind::Know
1460            && assumption.value
1461            && !binding.negated
1462            && matches!(binding.op, EirEpistemicOp::Possible))
1463}
1464
1465fn assumption_terms_match_binding(
1466    assumption: &EpistemicAssumption,
1467    binding: &EpistemicTupleMembershipBinding,
1468) -> bool {
1469    if assumption.arity == 0 {
1470        return assumption.terms.is_empty() && binding.key_terms.is_empty();
1471    }
1472    if assumption.terms.is_empty() {
1473        return false;
1474    }
1475    if assumption.terms.len() != binding.key_terms.len()
1476        || binding.bound_output_columns.len() != binding.key_terms.len()
1477    {
1478        return false;
1479    }
1480
1481    assumption
1482        .terms
1483        .iter()
1484        .zip(binding.key_terms.iter())
1485        .zip(binding.bound_output_columns.iter())
1486        .all(
1487            |((assumption_term, binding_term), bound_output_column)| match binding_term {
1488                EirTerm::Variable(_) => bound_output_column.is_some(),
1489                EirTerm::Integer(value) => {
1490                    matches!(assumption_term, EpistemicEvidenceTerm::Integer(v) if v == value)
1491                }
1492                EirTerm::String(value) => {
1493                    matches!(assumption_term, EpistemicEvidenceTerm::String(v) if v == value)
1494                }
1495                EirTerm::Symbol(value) => {
1496                    matches!(assumption_term, EpistemicEvidenceTerm::Symbol(v) if v == value)
1497                }
1498                EirTerm::Anonymous
1499                | EirTerm::FloatBits(_)
1500                | EirTerm::List(_)
1501                | EirTerm::Cons { .. }
1502                | EirTerm::Compound { .. }
1503                | EirTerm::PredRef(_)
1504                | EirTerm::Aggregate { .. } => false,
1505            },
1506        )
1507}
1508
1509fn require_gpu_kernel_trace(
1510    phase: &'static str,
1511    kernel_launches: u32,
1512    host_write_ops: u32,
1513    kernel_timing: EpistemicGpuKernelTimingTrace,
1514) -> Result<()> {
1515    if kernel_launches == 0 || host_write_ops != 0 || !kernel_timing.is_recorded() {
1516        return Err(XlogError::UnsupportedEpistemicConstruct {
1517            construct: "accepted GPU world-view evidence".to_string(),
1518            context: format!(
1519                "probabilistic evidence requires GPU {phase} trace with nonzero launches and \
1520                 zero host writes plus CUDA-event timing, got launches={kernel_launches}, \
1521                 host_writes={host_write_ops}, timing_recorded={}",
1522                kernel_timing.is_recorded()
1523            ),
1524        });
1525    }
1526    Ok(())
1527}
1528
1529/// Deterministic probability value with a comparison tolerance.
1530#[derive(Debug, Clone, Copy, PartialEq)]
1531pub struct ProbabilityValue {
1532    /// Probability value after normalization.
1533    pub probability: f64,
1534    /// Absolute tolerance for comparisons.
1535    pub tolerance: f64,
1536}
1537
1538impl ProbabilityValue {
1539    /// Return true when this probability is within tolerance of `expected`.
1540    pub fn within_tolerance(&self, expected: f64) -> bool {
1541        (self.probability - expected).abs() <= self.tolerance
1542    }
1543}
1544
1545/// Bounded circuit state for epistemic/probabilistic fixtures.
1546#[derive(Debug, Clone)]
1547pub struct EpistemicCircuit {
1548    adapter: KnowledgeCompilerAdapter,
1549    base_probability: f64,
1550    conditioned_probabilities: BTreeMap<EpistemicAssumption, f64>,
1551    active_assumptions: BTreeSet<EpistemicAssumption>,
1552    compile_count: usize,
1553    incremental_update_count: usize,
1554    circuit_fingerprint: u64,
1555    tolerance: f64,
1556}
1557
1558impl EpistemicCircuit {
1559    /// Compile a bounded circuit fixture with optional assumption-conditioned probabilities.
1560    pub fn compile(
1561        base_probability: f64,
1562        conditioned_probabilities: Vec<(EpistemicAssumption, f64)>,
1563        adapter: KnowledgeCompilerAdapter,
1564    ) -> Result<Self> {
1565        let base_probability = normalize_probability(
1566            base_probability,
1567            EPISTEMIC_PROBABILITY_TOLERANCE,
1568            "epistemic base probability",
1569        )?;
1570        let mut conditioned = BTreeMap::new();
1571        for (assumption, probability) in conditioned_probabilities {
1572            let probability = normalize_probability(
1573                probability,
1574                EPISTEMIC_PROBABILITY_TOLERANCE,
1575                "epistemic conditioned probability",
1576            )?;
1577            conditioned.insert(assumption, probability);
1578        }
1579
1580        let active_assumptions = BTreeSet::new();
1581        let circuit_fingerprint = circuit_fingerprint(
1582            &adapter,
1583            base_probability,
1584            &conditioned,
1585            &active_assumptions,
1586        );
1587
1588        Ok(Self {
1589            adapter,
1590            base_probability,
1591            conditioned_probabilities: conditioned,
1592            active_assumptions,
1593            compile_count: 1,
1594            incremental_update_count: 0,
1595            circuit_fingerprint,
1596            tolerance: EPISTEMIC_PROBABILITY_TOLERANCE,
1597        })
1598    }
1599
1600    /// Return the semantic contract for this circuit.
1601    pub fn semantic_contract(&self) -> EpistemicProbabilisticContract {
1602        EpistemicProbabilisticContract::default()
1603    }
1604
1605    /// Return active compiler evidence literals in deterministic order.
1606    pub fn compiler_evidence_literals(&self) -> Vec<String> {
1607        self.active_assumptions
1608            .iter()
1609            .map(EpistemicAssumption::evidence_literal)
1610            .collect()
1611    }
1612
1613    /// Return the current query probability.
1614    pub fn query_probability(&self) -> ProbabilityValue {
1615        let probability = self
1616            .active_assumptions
1617            .iter()
1618            .find_map(|assumption| self.conditioned_probabilities.get(assumption))
1619            .copied()
1620            .unwrap_or(self.base_probability);
1621
1622        ProbabilityValue {
1623            probability,
1624            tolerance: self.tolerance,
1625        }
1626    }
1627
1628    /// Apply an epistemic assumption as probabilistic evidence.
1629    pub fn apply_assumption(&mut self, assumption: EpistemicAssumption) -> Result<CircuitUpdate> {
1630        if self.active_assumptions.contains(&assumption) {
1631            return Ok(self.update_result(CircuitUpdateMode::Unchanged));
1632        }
1633
1634        let stale_assumptions = self
1635            .active_assumptions
1636            .iter()
1637            .filter(|active| active.same_evidence_key(&assumption))
1638            .cloned()
1639            .collect::<Vec<_>>();
1640        for stale in stale_assumptions {
1641            self.active_assumptions.remove(&stale);
1642        }
1643        self.active_assumptions.insert(assumption);
1644
1645        if self.adapter.supports_incremental_evidence() {
1646            self.incremental_update_count += 1;
1647            return Ok(self.update_result(CircuitUpdateMode::IncrementalEvidence));
1648        }
1649
1650        self.compile_count += 1;
1651        self.circuit_fingerprint = circuit_fingerprint(
1652            &self.adapter,
1653            self.base_probability,
1654            &self.conditioned_probabilities,
1655            &self.active_assumptions,
1656        );
1657        Ok(self.update_result(CircuitUpdateMode::FullRebuild))
1658    }
1659
1660    /// Apply epistemic evidence that has already passed world-view validation.
1661    pub fn apply_accepted_world_view(
1662        &mut self,
1663        evidence: AcceptedWorldViewEvidence,
1664    ) -> Result<CircuitUpdate> {
1665        let mut mode = CircuitUpdateMode::Unchanged;
1666        for assumption in evidence.assumptions {
1667            let update = self.apply_assumption(assumption)?;
1668            mode = combine_update_modes(mode, update.mode);
1669        }
1670        Ok(CircuitUpdate {
1671            mode,
1672            compile_count: self.compile_count,
1673            circuit_fingerprint: self.circuit_fingerprint,
1674        })
1675    }
1676
1677    /// Return the stable circuit fingerprint.
1678    pub fn circuit_fingerprint(&self) -> u64 {
1679        self.circuit_fingerprint
1680    }
1681
1682    /// Return the number of incremental evidence updates applied.
1683    pub fn incremental_update_count(&self) -> usize {
1684        self.incremental_update_count
1685    }
1686
1687    fn update_result(&self, mode: CircuitUpdateMode) -> CircuitUpdate {
1688        CircuitUpdate {
1689            mode,
1690            compile_count: self.compile_count,
1691            circuit_fingerprint: self.circuit_fingerprint,
1692        }
1693    }
1694}
1695
1696/// Convert log-space `P(query and evidence)` and `P(evidence)` into `P(query | evidence)`.
1697pub fn conditional_probability_from_logs(
1698    log_joint: f64,
1699    log_evidence: f64,
1700    tolerance: f64,
1701) -> Result<ProbabilityValue> {
1702    validate_tolerance(tolerance)?;
1703    if !log_joint.is_finite() || !log_evidence.is_finite() {
1704        return Err(XlogError::Compilation(
1705            "epistemic probability logs must be finite".to_string(),
1706        ));
1707    }
1708
1709    let raw = (log_joint - log_evidence).exp();
1710    Ok(ProbabilityValue {
1711        probability: normalize_probability(raw, tolerance, "epistemic conditional probability")?,
1712        tolerance,
1713    })
1714}
1715
1716fn normalize_probability(value: f64, tolerance: f64, context: &str) -> Result<f64> {
1717    validate_tolerance(tolerance)?;
1718    if !value.is_finite() {
1719        return Err(XlogError::Compilation(format!(
1720            "{context} must be finite, got {value}"
1721        )));
1722    }
1723    if value < 0.0 {
1724        if value >= -tolerance {
1725            return Ok(0.0);
1726        }
1727        return Err(XlogError::Compilation(format!(
1728            "{context} below 0 by more than tolerance: {value}"
1729        )));
1730    }
1731    if value > 1.0 {
1732        if value <= 1.0 + tolerance {
1733            return Ok(1.0);
1734        }
1735        return Err(XlogError::Compilation(format!(
1736            "{context} above 1 by more than tolerance: {value}"
1737        )));
1738    }
1739    Ok(value)
1740}
1741
1742fn validate_tolerance(tolerance: f64) -> Result<()> {
1743    if tolerance.is_finite() && tolerance >= 0.0 {
1744        Ok(())
1745    } else {
1746        Err(XlogError::Compilation(format!(
1747            "epistemic probability tolerance must be finite and non-negative, got {tolerance}"
1748        )))
1749    }
1750}
1751
1752fn combine_update_modes(left: CircuitUpdateMode, right: CircuitUpdateMode) -> CircuitUpdateMode {
1753    match (left, right) {
1754        (CircuitUpdateMode::FullRebuild, _) | (_, CircuitUpdateMode::FullRebuild) => {
1755            CircuitUpdateMode::FullRebuild
1756        }
1757        (CircuitUpdateMode::IncrementalEvidence, _)
1758        | (_, CircuitUpdateMode::IncrementalEvidence) => CircuitUpdateMode::IncrementalEvidence,
1759        (CircuitUpdateMode::Unchanged, CircuitUpdateMode::Unchanged) => {
1760            CircuitUpdateMode::Unchanged
1761        }
1762    }
1763}
1764
1765fn circuit_fingerprint(
1766    adapter: &KnowledgeCompilerAdapter,
1767    base_probability: f64,
1768    conditioned_probabilities: &BTreeMap<EpistemicAssumption, f64>,
1769    active_assumptions: &BTreeSet<EpistemicAssumption>,
1770) -> u64 {
1771    let mut hash = 0xcbf2_9ce4_8422_2325;
1772    mix_u64(&mut hash, adapter.kind as u64);
1773    mix_u64(&mut hash, adapter.support as u64);
1774    mix_str(&mut hash, &adapter.name);
1775    mix_u64(&mut hash, base_probability.to_bits());
1776    for (assumption, probability) in conditioned_probabilities {
1777        mix_assumption(&mut hash, assumption);
1778        mix_u64(&mut hash, probability.to_bits());
1779    }
1780    for assumption in active_assumptions {
1781        mix_assumption(&mut hash, assumption);
1782    }
1783    hash
1784}
1785
1786fn mix_assumption(hash: &mut u64, assumption: &EpistemicAssumption) {
1787    mix_u64(hash, assumption.kind as u64);
1788    mix_str(hash, &assumption.predicate);
1789    mix_u64(hash, assumption.arity as u64);
1790    for term in &assumption.terms {
1791        mix_evidence_term(hash, term);
1792    }
1793    mix_u64(hash, u64::from(assumption.value));
1794}
1795
1796fn mix_evidence_term(hash: &mut u64, term: &EpistemicEvidenceTerm) {
1797    match term {
1798        EpistemicEvidenceTerm::Integer(value) => {
1799            mix_u64(hash, 0);
1800            mix_u64(hash, *value as u64);
1801        }
1802        EpistemicEvidenceTerm::String(value) => {
1803            mix_u64(hash, 1);
1804            mix_str(hash, value);
1805        }
1806        EpistemicEvidenceTerm::Symbol(value) => {
1807            mix_u64(hash, 2);
1808            mix_u64(hash, u64::from(*value));
1809        }
1810    }
1811}
1812
1813fn mix_str(hash: &mut u64, value: &str) {
1814    for byte in value.as_bytes() {
1815        mix_u64(hash, u64::from(*byte));
1816    }
1817}
1818
1819fn mix_u64(hash: &mut u64, value: u64) {
1820    *hash ^= value;
1821    *hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
1822}