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