1use std::collections::{BTreeMap, BTreeSet};
4
5use xlog_core::RelId;
6
7use crate::eir::{EirEpistemicLiteral, EirEpistemicMode, EirEpistemicOp, EirTerm};
8use crate::plan::ExecutionPlan;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum EpistemicGpuHotPathPhase {
13 CandidateGeneration,
15 Propagation,
17 CandidateValidation,
19 ModelMembership,
21 WorldViewValidation,
23 ResultMaterialization,
25 FinalResultMaterialization,
27 FinalTupleMaterialization,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
33pub enum EpistemicGpuBufferKind {
34 CandidateAssumptions,
36 WorldViews,
38 ModelMembership,
40 RejectionReasons,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum EpistemicWcojReductionStatus {
47 NotWcojCandidate,
49 RequiresPlannerEligibility,
51}
52
53#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
55pub struct EpistemicCpuFallbackCounters {
56 pub candidate_enumeration: u64,
58 pub world_view_validation: u64,
60 pub solver_search: u64,
62 pub probabilistic_recompute: u64,
64}
65
66impl EpistemicCpuFallbackCounters {
67 pub fn is_zero(&self) -> bool {
69 self.candidate_enumeration == 0
70 && self.world_view_validation == 0
71 && self.solver_search == 0
72 && self.probabilistic_recompute == 0
73 }
74}
75
76#[derive(Debug, Clone, PartialEq, Eq)]
78pub struct EpistemicReductionPlan {
79 pub rule_index: usize,
81 pub head_predicate: String,
83 pub public_head_arity: usize,
89 pub relational_body_atoms: usize,
91 pub wcoj_status: EpistemicWcojReductionStatus,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct EpistemicTupleMembershipBinding {
98 pub literal_index: usize,
100 pub reduction_index: usize,
102 pub predicate: String,
104 pub arity: usize,
106 pub key_columns: Vec<usize>,
108 pub key_terms: Vec<EirTerm>,
110 pub bound_output_columns: Vec<Option<usize>>,
114 pub op: EirEpistemicOp,
116 pub negated: bool,
118}
119
120#[derive(Debug, Clone, PartialEq, Eq)]
130pub struct EpistemicConstraintPlan {
131 pub constraint_index: usize,
133 pub literal_indices: Vec<usize>,
139}
140
141#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
143pub enum EpistemicSolverCapability {
144 IncrementalSat,
146 AssumptionLifecycle,
148 LearnedClauseTransfer,
150 WeightedMaxSat,
152 PortfolioSatMaxSat,
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
158pub enum EpistemicSolverStatusKind {
159 Sat,
161 Unsat,
163 Unknown,
165 Timeout,
167}
168
169#[derive(Debug, Clone, PartialEq, Eq)]
171pub struct EpistemicSolverAssumptionBinding {
172 pub literal_index: usize,
174 pub reduction_index: usize,
176 pub predicate: String,
178 pub arity: usize,
180 pub terms: Vec<EirTerm>,
182 pub op: EirEpistemicOp,
184 pub negated: bool,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq)]
190pub struct EpistemicSolverServiceContract {
191 pub assumption_bindings: Vec<EpistemicSolverAssumptionBinding>,
193 pub required_capabilities: Vec<EpistemicSolverCapability>,
195 pub required_statuses: Vec<EpistemicSolverStatusKind>,
197}
198
199impl EpistemicSolverServiceContract {
200 pub fn production_default(assumption_bindings: Vec<EpistemicSolverAssumptionBinding>) -> Self {
202 Self {
203 assumption_bindings,
204 required_capabilities: vec![
205 EpistemicSolverCapability::IncrementalSat,
206 EpistemicSolverCapability::AssumptionLifecycle,
207 EpistemicSolverCapability::LearnedClauseTransfer,
208 EpistemicSolverCapability::WeightedMaxSat,
209 EpistemicSolverCapability::PortfolioSatMaxSat,
210 ],
211 required_statuses: vec![
212 EpistemicSolverStatusKind::Sat,
213 EpistemicSolverStatusKind::Unsat,
214 EpistemicSolverStatusKind::Unknown,
215 EpistemicSolverStatusKind::Timeout,
216 ],
217 }
218 }
219
220 pub fn distinct_required_capability_count(&self) -> usize {
222 self.required_capabilities
223 .iter()
224 .copied()
225 .collect::<BTreeSet<_>>()
226 .len()
227 }
228
229 pub fn distinct_required_status_count(&self) -> usize {
231 self.required_statuses
232 .iter()
233 .copied()
234 .collect::<BTreeSet<_>>()
235 .len()
236 }
237}
238
239#[derive(Debug, Clone, PartialEq, Eq)]
241pub struct EpistemicGpuPlan {
242 pub mode: EirEpistemicMode,
244 pub epistemic_literals: Vec<EirEpistemicLiteral>,
246 pub required_phases: Vec<EpistemicGpuHotPathPhase>,
248 pub required_kernel_phases: Vec<EpistemicGpuHotPathPhase>,
250 pub required_buffers: Vec<EpistemicGpuBufferKind>,
252 pub reductions: Vec<EpistemicReductionPlan>,
254 pub tuple_membership_bindings: Vec<EpistemicTupleMembershipBinding>,
256 pub constraints: Vec<EpistemicConstraintPlan>,
258 pub final_output_columns: Option<Vec<usize>>,
261 pub solver_contract: EpistemicSolverServiceContract,
263 pub cpu_fallbacks: EpistemicCpuFallbackCounters,
265}
266
267impl EpistemicGpuPlan {
268 pub fn new(
270 mode: EirEpistemicMode,
271 epistemic_literals: Vec<EirEpistemicLiteral>,
272 reductions: Vec<EpistemicReductionPlan>,
273 ) -> Self {
274 let tuple_membership_bindings = epistemic_literals
275 .iter()
276 .enumerate()
277 .map(|(literal_index, literal)| EpistemicTupleMembershipBinding {
278 literal_index,
279 reduction_index: literal_index.min(reductions.len().saturating_sub(1)),
280 predicate: literal.atom.predicate.clone(),
281 arity: literal.atom.arity,
282 key_columns: (0..literal.atom.arity).collect(),
283 key_terms: literal.atom.terms.clone(),
284 bound_output_columns: vec![None; literal.atom.arity],
285 op: literal.op,
286 negated: literal.negated,
287 })
288 .collect();
289 let solver_assumption_bindings = epistemic_literals
290 .iter()
291 .enumerate()
292 .map(
293 |(literal_index, literal)| EpistemicSolverAssumptionBinding {
294 literal_index,
295 reduction_index: literal_index.min(reductions.len().saturating_sub(1)),
296 predicate: literal.atom.predicate.clone(),
297 arity: literal.atom.arity,
298 terms: literal.atom.terms.clone(),
299 op: literal.op,
300 negated: literal.negated,
301 },
302 )
303 .collect();
304
305 Self {
306 mode,
307 epistemic_literals,
308 required_phases: vec![
309 EpistemicGpuHotPathPhase::CandidateGeneration,
310 EpistemicGpuHotPathPhase::Propagation,
311 EpistemicGpuHotPathPhase::WorldViewValidation,
312 EpistemicGpuHotPathPhase::ResultMaterialization,
313 ],
314 required_kernel_phases: vec![
315 EpistemicGpuHotPathPhase::CandidateGeneration,
316 EpistemicGpuHotPathPhase::Propagation,
317 EpistemicGpuHotPathPhase::CandidateValidation,
318 EpistemicGpuHotPathPhase::ModelMembership,
319 EpistemicGpuHotPathPhase::WorldViewValidation,
320 EpistemicGpuHotPathPhase::ResultMaterialization,
321 EpistemicGpuHotPathPhase::FinalResultMaterialization,
322 EpistemicGpuHotPathPhase::FinalTupleMaterialization,
323 ],
324 required_buffers: vec![
325 EpistemicGpuBufferKind::CandidateAssumptions,
326 EpistemicGpuBufferKind::WorldViews,
327 EpistemicGpuBufferKind::ModelMembership,
328 EpistemicGpuBufferKind::RejectionReasons,
329 ],
330 reductions,
331 tuple_membership_bindings,
332 constraints: Vec::new(),
333 final_output_columns: None,
334 solver_contract: EpistemicSolverServiceContract::production_default(
335 solver_assumption_bindings,
336 ),
337 cpu_fallbacks: EpistemicCpuFallbackCounters::default(),
338 }
339 }
340
341 pub fn with_tuple_membership_bindings(
343 mut self,
344 tuple_membership_bindings: Vec<EpistemicTupleMembershipBinding>,
345 ) -> Self {
346 self.tuple_membership_bindings = tuple_membership_bindings;
347 self
348 }
349
350 pub fn with_constraints(mut self, constraints: Vec<EpistemicConstraintPlan>) -> Self {
352 self.constraints = constraints;
353 self
354 }
355
356 pub fn with_final_output_columns(mut self, final_output_columns: Option<Vec<usize>>) -> Self {
358 self.final_output_columns = final_output_columns;
359 self
360 }
361
362 pub fn validate_constraints(&self) -> xlog_core::Result<()> {
364 for constraint in &self.constraints {
365 if constraint.literal_indices.is_empty() {
366 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
367 construct: "epistemic GPU world-view constraint".to_string(),
368 context: format!(
369 "constraint[{}] has no epistemic body literals; epistemic integrity \
370 constraints must constrain accepted world views through at least one \
371 know/possible literal",
372 constraint.constraint_index
373 ),
374 });
375 }
376 let mut seen = vec![false; self.epistemic_literals.len()];
377 for &literal_index in &constraint.literal_indices {
378 if literal_index >= self.epistemic_literals.len() {
379 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
380 construct: "epistemic GPU world-view constraint".to_string(),
381 context: format!(
382 "constraint[{}] references literal_index {} exceeding literal count {}",
383 constraint.constraint_index,
384 literal_index,
385 self.epistemic_literals.len()
386 ),
387 });
388 }
389 if seen[literal_index] {
390 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
391 construct: "epistemic GPU world-view constraint".to_string(),
392 context: format!(
393 "constraint[{}] references literal_index {} more than once",
394 constraint.constraint_index, literal_index
395 ),
396 });
397 }
398 seen[literal_index] = true;
399 }
400 }
401 Ok(())
402 }
403
404 pub fn with_solver_contract(mut self, solver_contract: EpistemicSolverServiceContract) -> Self {
406 self.solver_contract = solver_contract;
407 self
408 }
409
410 pub fn validate_solver_contract(&self) -> xlog_core::Result<()> {
412 let contract = &self.solver_contract;
413 if contract.assumption_bindings.len() != self.epistemic_literals.len() {
414 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
415 construct: "epistemic solver service contract".to_string(),
416 context: format!(
417 "expected {} solver assumption bindings for epistemic literals, found {}",
418 self.epistemic_literals.len(),
419 contract.assumption_bindings.len()
420 ),
421 });
422 }
423
424 let distinct_capability_count = contract.distinct_required_capability_count();
425 if distinct_capability_count != contract.required_capabilities.len() {
426 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
427 construct: "epistemic solver service contract".to_string(),
428 context: format!(
429 "solver capability requirements must be distinct, got {} entries but {} distinct",
430 contract.required_capabilities.len(),
431 distinct_capability_count
432 ),
433 });
434 }
435
436 let distinct_status_count = contract.distinct_required_status_count();
437 if distinct_status_count != contract.required_statuses.len() {
438 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
439 construct: "epistemic solver service contract".to_string(),
440 context: format!(
441 "solver status requirements must be distinct, got {} entries but {} distinct",
442 contract.required_statuses.len(),
443 distinct_status_count
444 ),
445 });
446 }
447
448 for required in [
449 EpistemicSolverCapability::IncrementalSat,
450 EpistemicSolverCapability::AssumptionLifecycle,
451 EpistemicSolverCapability::LearnedClauseTransfer,
452 EpistemicSolverCapability::WeightedMaxSat,
453 EpistemicSolverCapability::PortfolioSatMaxSat,
454 ] {
455 if !contract.required_capabilities.contains(&required) {
456 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
457 construct: "epistemic solver service contract".to_string(),
458 context: format!("missing required solver capability {required:?}"),
459 });
460 }
461 }
462
463 for required in [
464 EpistemicSolverStatusKind::Sat,
465 EpistemicSolverStatusKind::Unsat,
466 EpistemicSolverStatusKind::Unknown,
467 EpistemicSolverStatusKind::Timeout,
468 ] {
469 if !contract.required_statuses.contains(&required) {
470 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
471 construct: "epistemic solver service contract".to_string(),
472 context: format!("missing required solver status {required:?}"),
473 });
474 }
475 }
476
477 let mut seen_literals = vec![false; self.epistemic_literals.len()];
478 for binding in &contract.assumption_bindings {
479 if binding.literal_index >= self.epistemic_literals.len() {
480 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
481 construct: "epistemic solver service contract".to_string(),
482 context: format!(
483 "literal_index {} exceeds literal count {}",
484 binding.literal_index,
485 self.epistemic_literals.len()
486 ),
487 });
488 }
489 if seen_literals[binding.literal_index] {
490 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
491 construct: "epistemic solver service contract".to_string(),
492 context: format!(
493 "duplicate solver assumption for literal_index {}",
494 binding.literal_index
495 ),
496 });
497 }
498 seen_literals[binding.literal_index] = true;
499
500 if binding.reduction_index >= self.reductions.len() {
501 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
502 construct: "epistemic solver service contract".to_string(),
503 context: format!(
504 "reduction_index {} exceeds reduction count {}",
505 binding.reduction_index,
506 self.reductions.len()
507 ),
508 });
509 }
510
511 let literal = &self.epistemic_literals[binding.literal_index];
512 let tuple_binding =
513 self.tuple_membership_bindings
514 .iter()
515 .find(|tuple_binding| tuple_binding.literal_index == binding.literal_index)
516 .ok_or_else(|| {
517 xlog_core::XlogError::UnsupportedEpistemicConstruct {
518 construct: "epistemic solver service contract".to_string(),
519 context: format!(
520 "solver assumption for literal_index {} has no matching tuple-membership binding",
521 binding.literal_index
522 ),
523 }
524 })?;
525 if binding.reduction_index != tuple_binding.reduction_index {
526 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
527 construct: "epistemic solver service contract".to_string(),
528 context: format!(
529 "solver assumption for literal_index {} uses reduction_index {}, but tuple membership uses {}",
530 binding.literal_index,
531 binding.reduction_index,
532 tuple_binding.reduction_index
533 ),
534 });
535 }
536 if binding.predicate != literal.atom.predicate
537 || binding.arity != literal.atom.arity
538 || binding.terms != literal.atom.terms
539 || binding.op != literal.op
540 || binding.negated != literal.negated
541 {
542 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
543 construct: "epistemic solver service contract".to_string(),
544 context: format!(
545 "solver assumption for literal_index {} does not match epistemic literal",
546 binding.literal_index
547 ),
548 });
549 }
550 }
551
552 Ok(())
553 }
554
555 pub fn validate_tuple_membership_bindings(&self) -> xlog_core::Result<()> {
557 if self.tuple_membership_bindings.len() != self.epistemic_literals.len() {
558 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
559 construct: "epistemic GPU tuple membership binding".to_string(),
560 context: format!(
561 "expected {} bindings for epistemic literals, found {}",
562 self.epistemic_literals.len(),
563 self.tuple_membership_bindings.len()
564 ),
565 });
566 }
567
568 let mut seen_literals = vec![false; self.epistemic_literals.len()];
569
570 for binding in &self.tuple_membership_bindings {
571 if binding.literal_index >= self.epistemic_literals.len() {
572 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
573 construct: "epistemic GPU tuple membership binding".to_string(),
574 context: format!(
575 "literal_index {} exceeds literal count {}",
576 binding.literal_index,
577 self.epistemic_literals.len()
578 ),
579 });
580 }
581 if seen_literals[binding.literal_index] {
582 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
583 construct: "epistemic GPU tuple membership binding".to_string(),
584 context: format!(
585 "duplicate literal_index {} in tuple-membership bindings",
586 binding.literal_index
587 ),
588 });
589 }
590 seen_literals[binding.literal_index] = true;
591
592 if binding.reduction_index >= self.reductions.len() {
593 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
594 construct: "epistemic GPU tuple membership binding".to_string(),
595 context: format!(
596 "reduction_index {} exceeds reduction count {}",
597 binding.reduction_index,
598 self.reductions.len()
599 ),
600 });
601 }
602
603 let literal = &self.epistemic_literals[binding.literal_index];
604 if binding.predicate != literal.atom.predicate
605 || binding.arity != literal.atom.arity
606 || binding.op != literal.op
607 || binding.negated != literal.negated
608 {
609 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
610 construct: "epistemic GPU tuple membership binding".to_string(),
611 context: format!(
612 "binding for literal_index {} does not match epistemic literal",
613 binding.literal_index
614 ),
615 });
616 }
617
618 if binding.key_columns.len() != binding.arity {
619 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
620 construct: "epistemic GPU tuple membership binding".to_string(),
621 context: format!(
622 "binding for literal_index {} has {} key columns for arity {}",
623 binding.literal_index,
624 binding.key_columns.len(),
625 binding.arity
626 ),
627 });
628 }
629
630 if binding.key_terms.len() != binding.arity {
631 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
632 construct: "epistemic GPU tuple membership binding".to_string(),
633 context: format!(
634 "binding for literal_index {} has {} key terms for arity {}",
635 binding.literal_index,
636 binding.key_terms.len(),
637 binding.arity
638 ),
639 });
640 }
641
642 if binding.key_terms != literal.atom.terms {
643 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
644 construct: "epistemic GPU tuple membership binding".to_string(),
645 context: format!(
646 "key terms for literal_index {} do not match epistemic literal",
647 binding.literal_index
648 ),
649 });
650 }
651
652 if binding.bound_output_columns.len() != binding.arity {
653 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
654 construct: "epistemic GPU tuple membership binding".to_string(),
655 context: format!(
656 "binding for literal_index {} has {} bound output columns for arity {}",
657 binding.literal_index,
658 binding.bound_output_columns.len(),
659 binding.arity
660 ),
661 });
662 }
663
664 for (term, bound_col) in binding
665 .key_terms
666 .iter()
667 .zip(binding.bound_output_columns.iter())
668 {
669 match (term, bound_col) {
670 (EirTerm::Variable(_), Some(_)) => {}
671 (EirTerm::Variable(variable), None) => {
672 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
673 construct: "epistemic GPU tuple membership binding".to_string(),
674 context: format!(
675 "variable tuple key {variable} for literal_index {} is missing a \
676 reduced output column",
677 binding.literal_index
678 ),
679 });
680 }
681 (_, None) => {}
682 (_, Some(bound_col)) => {
683 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
684 construct: "epistemic GPU tuple membership binding".to_string(),
685 context: format!(
686 "ground tuple key for literal_index {} unexpectedly binds \
687 reduced output column {}",
688 binding.literal_index, bound_col
689 ),
690 });
691 }
692 }
693 }
694
695 let mut seen_key_columns = vec![false; binding.arity];
696 for &key_col in &binding.key_columns {
697 if key_col >= binding.arity {
698 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
699 construct: "epistemic GPU tuple membership binding".to_string(),
700 context: format!(
701 "key column {} exceeds arity {} for literal_index {}",
702 key_col, binding.arity, binding.literal_index
703 ),
704 });
705 }
706 if seen_key_columns[key_col] {
707 return Err(xlog_core::XlogError::UnsupportedEpistemicConstruct {
708 construct: "epistemic GPU tuple membership binding".to_string(),
709 context: format!(
710 "duplicate key column {} for literal_index {}",
711 key_col, binding.literal_index
712 ),
713 });
714 }
715 seen_key_columns[key_col] = true;
716 }
717 }
718
719 Ok(())
720 }
721}
722
723#[derive(Debug, Clone)]
725pub struct EpistemicExecutablePlan {
726 pub gpu_plan: EpistemicGpuPlan,
728 pub relation_ids: BTreeMap<String, RelId>,
730 pub reduced_runtime_plan: ExecutionPlan,
732}