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