1use 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
19pub const EPISTEMIC_PROBABILITY_TOLERANCE: f64 = 1.0e-12;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum EpistemicProbabilisticRole {
25 EvidenceConditioning,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub struct EpistemicProbabilisticContract {
32 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#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
46pub enum EpistemicAssumptionKind {
47 Know,
49 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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
64pub enum EpistemicEvidenceTerm {
65 Integer(i64),
67 String(String),
69 Symbol(u32),
71}
72
73impl EpistemicEvidenceTerm {
74 pub fn integer(value: i64) -> Self {
76 Self::Integer(value)
77 }
78
79 pub fn string(value: impl Into<String>) -> Self {
81 Self::String(value.into())
82 }
83
84 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#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
100pub struct EpistemicAssumption {
101 pub kind: EpistemicAssumptionKind,
103 pub predicate: String,
105 pub arity: usize,
107 pub terms: Vec<EpistemicEvidenceTerm>,
109 pub value: bool,
111}
112
113impl EpistemicAssumption {
114 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 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 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
204pub enum CompilerAdapterKind {
205 GpuD4,
207 ExternalDdnnfText,
209 ExternalC2d,
211 ExternalMiniC2d,
213}
214
215#[derive(Debug, Clone, Copy, PartialEq, Eq)]
217pub enum CompilerAdapterSupport {
218 Implemented,
220 DesignOnly,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq)]
226pub enum CompilerInputFormat {
227 GpuCnf,
229 DimacsCnf,
231}
232
233#[derive(Debug, Clone, Copy, PartialEq, Eq)]
235pub enum CompilerOutputFormat {
236 Xgcf,
238 DecisionDnnfText,
240}
241
242#[derive(Debug, Clone, PartialEq, Eq)]
244pub struct KnowledgeCompilerAdapter {
245 pub name: String,
247 pub kind: CompilerAdapterKind,
249 pub support: CompilerAdapterSupport,
251 pub input_format: CompilerInputFormat,
253 pub output_format: CompilerOutputFormat,
255 incremental_evidence: bool,
256}
257
258impl KnowledgeCompilerAdapter {
259 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 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 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 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 pub fn supports_incremental_evidence(&self) -> bool {
309 self.incremental_evidence
310 }
311}
312
313#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub enum CircuitUpdateMode {
316 Unchanged,
318 IncrementalEvidence,
320 FullRebuild,
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
326pub struct CircuitUpdate {
327 pub mode: CircuitUpdateMode,
329 pub compile_count: usize,
331 pub circuit_fingerprint: u64,
333}
334
335#[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 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 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 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 pub fn world_count(&self) -> usize {
573 self.world_count
574 }
575
576 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 #[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 pub fn gpu_epistemic_mode(&self) -> Option<EirEpistemicMode> {
595 self.gpu_epistemic_mode
596 }
597
598 pub fn assumption_count(&self) -> usize {
600 self.assumptions.len()
601 }
602
603 pub fn nonzero_arity_assumption_count(&self) -> usize {
605 self.assumptions
606 .iter()
607 .filter(|assumption| assumption.arity > 0)
608 .count()
609 }
610
611 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 pub fn gpu_tuple_key_column_reads(&self) -> usize {
622 self.gpu_tuple_key_column_reads
623 }
624
625 pub fn gpu_final_tuple_row_filters(&self) -> usize {
627 self.gpu_final_tuple_row_filters
628 }
629
630 pub fn gpu_final_tuple_negated_row_filters(&self) -> usize {
632 self.gpu_final_tuple_negated_row_filters
633 }
634
635 pub fn gpu_row_specific_membership_row_capacity(&self) -> usize {
637 self.gpu_row_specific_membership_row_capacity
638 }
639
640 pub fn gpu_row_filter_fallback_row_capacity(&self) -> usize {
642 self.gpu_row_filter_fallback_row_capacity
643 }
644
645 pub fn gpu_checked_constraint_relations(&self) -> usize {
647 self.gpu_checked_constraint_relations
648 }
649
650 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#[derive(Debug, Clone, Copy, PartialEq)]
1545pub struct ProbabilityValue {
1546 pub probability: f64,
1548 pub tolerance: f64,
1550}
1551
1552impl ProbabilityValue {
1553 pub fn within_tolerance(&self, expected: f64) -> bool {
1555 (self.probability - expected).abs() <= self.tolerance
1556 }
1557}
1558
1559#[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 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 pub fn semantic_contract(&self) -> EpistemicProbabilisticContract {
1616 EpistemicProbabilisticContract::default()
1617 }
1618
1619 pub fn compiler_evidence_literals(&self) -> Vec<String> {
1621 self.active_assumptions
1622 .iter()
1623 .map(EpistemicAssumption::evidence_literal)
1624 .collect()
1625 }
1626
1627 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 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 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 pub fn circuit_fingerprint(&self) -> u64 {
1693 self.circuit_fingerprint
1694 }
1695
1696 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
1710pub 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}