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