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