1use std::collections::{BTreeMap, BTreeSet};
8use std::sync::Arc;
9
10use xlog_core::{Result, XlogError};
11use xlog_cuda::memory::TrackedCudaSlice;
12use xlog_cuda::CudaKernelProvider;
13use xlog_runtime::{
14 EpistemicGpuBatchExecutionResult, EpistemicGpuExecutionResult, EpistemicGpuKernelTimingTrace,
15 EpistemicGpuProviderIdentity,
16};
17
18use crate::{GpuCdclConfig, GpuCdclSolver, GpuCdclWorkspace, GpuCnf, Objective, SolveInstance};
19
20const PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT: u64 = 5;
21const PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT: u64 = 4;
22const MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES: u64 = 64;
23
24macro_rules! checked_solver_trace_counter_inc {
25 ($adapter:ident, $field:ident) => {{
26 $adapter.trace.$field = GpuSolverProductionAdapter::checked_trace_counter_add(
27 $adapter.trace.$field,
28 1,
29 stringify!($field),
30 )?;
31 }};
32}
33
34macro_rules! checked_solver_report_counter_inc {
35 ($report:ident, $field:ident) => {{
36 $report.$field = GpuSolverProductionAdapter::checked_report_counter_add(
37 $report.$field,
38 1,
39 stringify!($field),
40 )?;
41 }};
42}
43
44macro_rules! checked_solver_report_counter_add {
45 ($report:ident, $field:ident, $delta:expr) => {{
46 $report.$field = GpuSolverProductionAdapter::checked_report_counter_add(
47 $report.$field,
48 $delta,
49 stringify!($field),
50 )?;
51 }};
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq)]
56pub enum GpuSolverProductionCapabilityStatus {
57 Available,
59 Blocked,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum GpuSolverProductionMetricBackend {
66 GpuOnly,
68}
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub struct GpuSolverProductionCapabilities {
73 pub gpu_cdcl_sat_unsat: GpuSolverProductionCapabilityStatus,
75 pub gpu_maxsat: GpuSolverProductionCapabilityStatus,
77 pub gpu_portfolio_sat_maxsat: GpuSolverProductionCapabilityStatus,
79 pub production_metric_backend: GpuSolverProductionMetricBackend,
81 pub gpu_maxsat_blocker: &'static str,
83 pub gpu_portfolio_blocker: &'static str,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq)]
93pub struct GpuSolverAcceptedCandidateState {
94 pub evidence_records: u64,
96 pub accepted_candidate_indices: Vec<usize>,
98 pub accepted_candidates: u64,
100 pub accepted_world_views: u64,
102 pub final_output_rows: u64,
104 pub epistemic_literals: u64,
106 pub tuple_membership_bindings: u64,
108 pub solver_assumption_bindings: u64,
110 pub solver_required_capabilities: u64,
112 pub solver_required_statuses: u64,
114 pub g91_mode: bool,
116 pub faeel_mode: bool,
118 pub has_know_operator: bool,
120 pub has_possible_operator: bool,
122 pub has_not_possible_operator: bool,
124 pub has_not_know_operator: bool,
126 pub tuple_key_column_reads: u64,
128 pub has_nonzero_arity_tuple_keys: bool,
130 pub final_tuple_row_filters: u64,
132 pub final_tuple_negated_row_filters: u64,
134 pub row_specific_membership_row_capacity: u64,
136 pub row_filter_fallback_row_capacity: u64,
138 pub checked_constraint_relations: u64,
140 pub constraint_row_count_device_reads: u64,
142}
143
144impl GpuSolverAcceptedCandidateState {
145 fn from_validated_result(
146 result: &EpistemicGpuExecutionResult,
147 final_output_rows: usize,
148 ) -> Self {
149 let preflight = &result.prepared.preflight;
150 let tuple_key_column_reads = result.model_membership.tuple_source_key_column_device_reads;
151 Self {
152 evidence_records: 1,
153 accepted_candidate_indices: result.semantic_trace.accepted_candidate_indices.clone(),
154 accepted_candidates: result.semantic_trace.accepted_candidates as u64,
155 accepted_world_views: result.semantic_trace.accepted_world_views as u64,
156 final_output_rows: final_output_rows as u64,
157 epistemic_literals: result.candidate_generation.literal_count as u64,
158 tuple_membership_bindings: preflight.tuple_membership_binding_count as u64,
159 solver_assumption_bindings: preflight.solver_assumption_binding_count as u64,
160 solver_required_capabilities: preflight.solver_required_capability_count as u64,
161 solver_required_statuses: preflight.solver_required_status_count as u64,
162 g91_mode: preflight.is_g91_mode(),
163 faeel_mode: preflight.is_faeel_mode(),
164 has_know_operator: preflight.know_operator_count > 0,
165 has_possible_operator: preflight.possible_operator_count > 0,
166 has_not_possible_operator: preflight.not_possible_operator_count > 0,
167 has_not_know_operator: preflight.not_know_operator_count > 0,
168 tuple_key_column_reads: tuple_key_column_reads as u64,
169 has_nonzero_arity_tuple_keys: tuple_key_column_reads > 0,
170 final_tuple_row_filters: result.final_tuple_materialization.row_filter_count as u64,
171 final_tuple_negated_row_filters: result
172 .final_tuple_materialization
173 .negated_row_filter_count as u64,
174 row_specific_membership_row_capacity: result
175 .final_tuple_materialization
176 .row_specific_membership_row_capacity
177 as u64,
178 row_filter_fallback_row_capacity: result
179 .final_tuple_materialization
180 .row_filter_row_capacity_outside_model_slot_window
181 as u64,
182 checked_constraint_relations: result.constraint_validation.checked_constraint_relations
183 as u64,
184 constraint_row_count_device_reads: result.constraint_validation.row_count_device_reads
185 as u64,
186 }
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq, Eq)]
192pub enum GpuSolverProductionExpectation {
193 Sat,
195 Unsat,
197 Unknown {
199 reason: &'static str,
201 },
202 Timeout {
204 budget_micros: u64,
206 },
207}
208
209#[derive(Clone, Copy)]
211pub struct GpuSolverProductionLifecycleStep<'a> {
212 pub cnf: &'a GpuCnf,
214 pub branch_var_limit: &'a TrackedCudaSlice<u32>,
216 pub expectation: GpuSolverProductionExpectation,
218}
219
220#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
222pub struct GpuSolverProductionLifecycleReport {
223 pub candidate_evidence_records: u64,
225 pub steps: u64,
227 pub sat_steps: u64,
229 pub unsat_steps: u64,
231 pub assumption_pushes: u64,
233 pub assumption_retractions: u64,
235 pub workspace_reuses: u64,
237 pub unknown_steps: u64,
239 pub timeout_steps: u64,
241}
242
243#[derive(Clone, Copy)]
245pub struct GpuSolverProductionBatchExecutionEvidence<'a> {
246 pub batch: &'a EpistemicGpuBatchExecutionResult,
248}
249
250#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
252pub struct GpuSolverProductionLearnedClauseArenaReport {
253 pub unsat_solves: u64,
255 pub gpu_learned_clause_arena_publications: u64,
257 pub gpu_learned_count_buffer_publications: u64,
259}
260
261#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
263pub struct GpuSolverProductionLearnedClauseReuseReport {
264 pub candidate_evidence_records: u64,
266 pub candidates: u64,
268 pub unsat_solves: u64,
270 pub gpu_learned_clause_arena_publications: u64,
272 pub gpu_learned_clause_imports: u64,
274 pub gpu_learned_clause_reused_solves: u64,
276}
277
278#[derive(Clone, Copy)]
284pub struct GpuSolverProductionMaxSatCandidate<'a> {
285 pub score: u64,
287 pub cnf: &'a GpuCnf,
289 pub branch_var_limit: &'a TrackedCudaSlice<u32>,
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub enum GpuSolverProductionMaxSatSearchStatus {
296 Satisfiable,
298 Unsatisfiable,
300}
301
302#[derive(Clone, Copy)]
304pub struct GpuSolverProductionMaxSatSearchCandidate<'a> {
305 pub score: u64,
307 pub cnf: &'a GpuCnf,
309 pub branch_var_limit: &'a TrackedCudaSlice<u32>,
311 pub status: GpuSolverProductionMaxSatSearchStatus,
313}
314
315#[derive(Clone, Copy)]
322pub struct GpuSolverProductionWeightedMaxSatSelection<'a> {
323 pub soft_clause_indices: &'a [usize],
325 pub status: GpuSolverProductionMaxSatSearchStatus,
327}
328
329struct GpuSolverProductionEncodedMaxSatSearchCandidate {
330 score: u64,
331 cnf: GpuCnf,
332 status: GpuSolverProductionMaxSatSearchStatus,
333}
334
335struct GpuSolverProductionCompletedWeightedMaxSatFrontier {
336 selections: Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
337 completion_candidate_count: u64,
338}
339
340#[derive(Clone)]
341struct GpuSolverProductionOwnedWeightedMaxSatSelection {
342 soft_clause_indices: Vec<usize>,
343 status: GpuSolverProductionMaxSatSearchStatus,
344}
345
346struct GpuSolverProductionUnsatFrontierCertificate {
347 indices: Vec<usize>,
348 min_weight: u64,
349 min_weight_indices: Vec<usize>,
350}
351
352#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
354pub struct GpuSolverProductionMaxSatReport {
355 pub candidate_evidence_records: u64,
357 pub optimum_score: u64,
359 pub candidates_checked: u64,
361 pub satisfiable_candidates: u64,
363 pub unsat_candidates_pruned: u64,
365 pub gpu_cdcl_candidate_encodes: u64,
367 pub gpu_cdcl_candidate_solves: u64,
369 pub frontier_upper_bound_certificates: u64,
371}
372
373#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
375pub struct GpuSolverProductionMaxSatLifecycleReport {
376 pub candidate_evidence_records: u64,
378 pub lifecycle: GpuSolverProductionLifecycleReport,
380 pub maxsat: GpuSolverProductionMaxSatReport,
382}
383
384#[derive(Clone, Copy)]
386pub enum GpuSolverProductionMaxSatScheduleJob<'a> {
387 CandidateSet {
389 candidates: &'a [GpuSolverProductionMaxSatCandidate<'a>],
391 },
392 Search {
394 candidates: &'a [GpuSolverProductionMaxSatSearchCandidate<'a>],
396 },
397 EncodedSearch {
399 weighted: &'a SolveInstance,
401 branch_var_limit: &'a TrackedCudaSlice<u32>,
403 selections: &'a [GpuSolverProductionWeightedMaxSatSelection<'a>],
405 },
406 Unknown {
408 reason: &'static str,
410 },
411 Timeout {
413 budget_micros: u64,
415 },
416}
417
418#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
420pub struct GpuSolverProductionMaxSatScheduleReport {
421 pub candidate_evidence_records: u64,
423 pub jobs: u64,
425 pub candidate_set_jobs: u64,
427 pub search_jobs: u64,
429 pub encoded_search_jobs: u64,
431 pub unknown_jobs: u64,
433 pub timeout_jobs: u64,
435 pub optimum_score: u64,
437 pub candidates_checked: u64,
439 pub satisfiable_candidates: u64,
441 pub unsat_candidates_pruned: u64,
443 pub gpu_cdcl_candidate_encodes: u64,
445 pub gpu_cdcl_candidate_solves: u64,
447 pub frontier_upper_bound_certificates: u64,
449}
450
451#[derive(Clone, Copy)]
453pub enum GpuSolverProductionPortfolioJob<'a> {
454 Sat {
456 cnf: &'a GpuCnf,
458 branch_var_limit: &'a TrackedCudaSlice<u32>,
460 },
461 MaxSat {
463 candidates: &'a [GpuSolverProductionMaxSatCandidate<'a>],
465 },
466 EncodedMaxSat {
469 weighted: &'a SolveInstance,
471 branch_var_limit: &'a TrackedCudaSlice<u32>,
473 selections: &'a [GpuSolverProductionWeightedMaxSatSelection<'a>],
475 },
476 Unknown {
478 reason: &'static str,
480 },
481 Timeout {
483 budget_micros: u64,
485 },
486}
487
488#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
490pub struct GpuSolverProductionPortfolioReport {
491 pub candidate_evidence_records: u64,
493 pub jobs: u64,
495 pub sat_jobs: u64,
497 pub maxsat_jobs: u64,
499 pub unknown_jobs: u64,
501 pub timeout_jobs: u64,
503 pub maxsat_optimum_scores: u64,
505 pub maxsat_candidates_checked: u64,
507 pub maxsat_satisfiable_candidates: u64,
509 pub maxsat_unsat_candidates_pruned: u64,
511 pub maxsat_gpu_cdcl_candidate_encodes: u64,
514 pub maxsat_gpu_cdcl_candidate_solves: u64,
516 pub maxsat_frontier_upper_bound_certificates: u64,
518}
519
520pub fn production_capabilities() -> GpuSolverProductionCapabilities {
522 GpuSolverProductionCapabilities {
523 gpu_cdcl_sat_unsat: GpuSolverProductionCapabilityStatus::Available,
524 gpu_maxsat: GpuSolverProductionCapabilityStatus::Available,
525 gpu_portfolio_sat_maxsat: GpuSolverProductionCapabilityStatus::Available,
526 production_metric_backend: GpuSolverProductionMetricBackend::GpuOnly,
527 gpu_maxsat_blocker: "",
528 gpu_portfolio_blocker: "",
529 }
530}
531
532#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
534pub struct GpuSolverProductionTrace {
535 pub accepted_gpu_candidate_evidence_consumed: u64,
537 pub accepted_gpu_candidate_state_transitions: u64,
539 pub accepted_gpu_world_view_state_transitions: u64,
541 pub accepted_gpu_candidate_final_output_rows_consumed: u64,
543 pub accepted_gpu_batch_candidate_evidence_consumed: u64,
545 pub accepted_gpu_batch_candidate_component_evidence_consumed: u64,
547 pub accepted_g91_gpu_candidate_evidence_consumed: u64,
549 pub accepted_faeel_gpu_candidate_evidence_consumed: u64,
551 pub accepted_know_gpu_candidate_evidence_consumed: u64,
553 pub accepted_possible_gpu_candidate_evidence_consumed: u64,
555 pub accepted_not_possible_gpu_candidate_evidence_consumed: u64,
557 pub accepted_not_know_gpu_candidate_evidence_consumed: u64,
559 pub accepted_nonzero_arity_gpu_candidate_evidence_consumed: u64,
561 pub accepted_gpu_candidate_tuple_key_column_reads_consumed: u64,
563 pub accepted_solver_assumption_bindings_consumed: u64,
565 pub accepted_solver_required_capabilities_consumed: u64,
567 pub accepted_solver_required_statuses_consumed: u64,
569 pub accepted_gpu_final_tuple_row_filters_consumed: u64,
571 pub accepted_gpu_final_tuple_negated_row_filters_consumed: u64,
573 pub accepted_gpu_row_specific_membership_row_capacity_consumed: u64,
575 pub accepted_gpu_row_filter_fallback_row_capacity_consumed: u64,
577 pub accepted_gpu_constraint_relations_checked_consumed: u64,
579 pub accepted_gpu_constraint_row_count_device_reads_consumed: u64,
581 pub accepted_gpu_solver_production_path_events: u64,
583 pub gpu_cdcl_sat_solves: u64,
585 pub gpu_cdcl_unsat_solves: u64,
587 pub gpu_cdcl_workspace_unsat_solves: u64,
589 pub gpu_assumption_pushes: u64,
591 pub gpu_assumption_retractions: u64,
593 pub gpu_lifecycle_workspace_reuses: u64,
595 pub gpu_lifecycle_unknown_status_steps: u64,
597 pub gpu_lifecycle_timeout_status_steps: u64,
599 pub gpu_learned_clause_arena_publications: u64,
601 pub gpu_learned_count_buffer_publications: u64,
603 pub gpu_learned_clause_imports: u64,
605 pub gpu_learned_clause_reused_solves: u64,
607 pub gpu_learned_clause_reuse_rejections: u64,
609 pub gpu_maxsat_candidate_solves: u64,
611 pub gpu_maxsat_frontier_certified_candidate_solves: u64,
613 pub gpu_maxsat_candidate_encodes: u64,
615 pub gpu_maxsat_candidate_cnf_data_plane_htod_calls: u64,
617 pub gpu_maxsat_candidate_cnf_data_plane_htod_bytes: u64,
619 pub gpu_maxsat_candidate_cnf_data_plane_dtoh_calls: u64,
621 pub gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes: u64,
623 pub gpu_maxsat_candidate_cnf_launch_metadata_htod_calls: u64,
625 pub gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes: u64,
627 pub gpu_maxsat_frontier_completion_candidate_encodes: u64,
629 pub gpu_maxsat_frontier_upper_bound_certificates: u64,
631 pub gpu_maxsat_scheduler_jobs: u64,
633 pub gpu_maxsat_scheduler_candidate_set_jobs: u64,
635 pub gpu_maxsat_scheduler_search_jobs: u64,
637 pub gpu_maxsat_scheduler_encoded_search_jobs: u64,
639 pub gpu_maxsat_scheduler_unknown_status_jobs: u64,
641 pub gpu_maxsat_scheduler_timeout_status_jobs: u64,
643 pub gpu_maxsat_unsat_candidate_prunes: u64,
645 pub gpu_maxsat_optima: u64,
647 pub gpu_portfolio_jobs: u64,
649 pub gpu_portfolio_sat_jobs: u64,
651 pub gpu_portfolio_maxsat_jobs: u64,
653 pub gpu_portfolio_unknown_status_jobs: u64,
655 pub gpu_portfolio_timeout_status_jobs: u64,
657}
658
659#[derive(Debug, Clone, Copy)]
660struct GpuSolverAcceptedPathEventSnapshot {
661 production: u64,
662 status: u64,
663}
664
665impl GpuSolverProductionTrace {
666 fn checked_gpu_solver_production_path_events(&self) -> Result<u64> {
667 Self::checked_production_event_sum(
668 "gpu_solver_production_path_events",
669 &[
670 self.gpu_cdcl_sat_solves,
671 self.gpu_cdcl_unsat_solves,
672 self.gpu_cdcl_workspace_unsat_solves,
673 self.gpu_learned_clause_arena_publications,
674 self.gpu_learned_count_buffer_publications,
675 self.gpu_learned_clause_imports,
676 self.gpu_learned_clause_reused_solves,
677 self.gpu_maxsat_candidate_solves,
678 self.gpu_maxsat_candidate_encodes,
679 self.gpu_maxsat_scheduler_candidate_set_jobs,
680 self.gpu_maxsat_scheduler_search_jobs,
681 self.gpu_maxsat_scheduler_encoded_search_jobs,
682 self.gpu_maxsat_unsat_candidate_prunes,
683 self.gpu_portfolio_sat_jobs,
684 self.gpu_portfolio_maxsat_jobs,
685 ],
686 )
687 }
688
689 fn checked_gpu_solver_status_path_events(&self) -> Result<u64> {
690 Self::checked_production_event_sum(
691 "gpu_solver_status_path_events",
692 &[
693 self.gpu_lifecycle_unknown_status_steps,
694 self.gpu_lifecycle_timeout_status_steps,
695 self.gpu_maxsat_scheduler_unknown_status_jobs,
696 self.gpu_maxsat_scheduler_timeout_status_jobs,
697 self.gpu_portfolio_unknown_status_jobs,
698 self.gpu_portfolio_timeout_status_jobs,
699 ],
700 )
701 }
702
703 fn accepted_path_event_snapshot(&self) -> Result<GpuSolverAcceptedPathEventSnapshot> {
704 Ok(GpuSolverAcceptedPathEventSnapshot {
705 production: self.checked_gpu_solver_production_path_events()?,
706 status: self.checked_gpu_solver_status_path_events()?,
707 })
708 }
709
710 fn checked_production_event_sum(counter: &str, values: &[u64]) -> Result<u64> {
711 values.iter().try_fold(0u64, |acc, value| {
712 acc.checked_add(*value)
713 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
714 construct: "GPU solver production trace accounting".to_string(),
715 context: format!(
716 "GPU solver production counter {counter} overflowed while adding \
717 {value} to {acc}"
718 ),
719 })
720 })
721 }
722
723 fn checked_maxsat_production_metric_events(&self) -> Result<u64> {
724 Self::checked_production_event_sum(
725 "gpu_solver_maxsat_metric_events",
726 &[
727 self.gpu_maxsat_candidate_solves,
728 self.gpu_maxsat_frontier_certified_candidate_solves,
729 self.gpu_maxsat_candidate_encodes,
730 self.gpu_maxsat_frontier_upper_bound_certificates,
731 self.gpu_maxsat_scheduler_candidate_set_jobs,
732 self.gpu_maxsat_scheduler_search_jobs,
733 self.gpu_maxsat_scheduler_encoded_search_jobs,
734 self.gpu_maxsat_unsat_candidate_prunes,
735 self.gpu_maxsat_optima,
736 self.gpu_portfolio_maxsat_jobs,
737 ],
738 )
739 }
740
741 fn checked_portfolio_job_kind_events(&self) -> Result<u64> {
742 Self::checked_production_event_sum(
743 "gpu_solver_portfolio_job_kind_events",
744 &[
745 self.gpu_portfolio_sat_jobs,
746 self.gpu_portfolio_maxsat_jobs,
747 self.gpu_portfolio_unknown_status_jobs,
748 self.gpu_portfolio_timeout_status_jobs,
749 ],
750 )
751 }
752
753 fn checked_maxsat_scheduler_job_kind_events(&self) -> Result<u64> {
754 Self::checked_production_event_sum(
755 "gpu_solver_maxsat_scheduler_job_kind_events",
756 &[
757 self.gpu_maxsat_scheduler_candidate_set_jobs,
758 self.gpu_maxsat_scheduler_search_jobs,
759 self.gpu_maxsat_scheduler_encoded_search_jobs,
760 self.gpu_maxsat_scheduler_unknown_status_jobs,
761 self.gpu_maxsat_scheduler_timeout_status_jobs,
762 ],
763 )
764 }
765
766 fn require_maxsat_scheduler_job_accounting(&self) -> Result<()> {
767 let job_kind_events = self.checked_maxsat_scheduler_job_kind_events()?;
768 if self.gpu_maxsat_scheduler_jobs != job_kind_events {
769 return Err(XlogError::UnsupportedEpistemicConstruct {
770 construct: "GPU solver production metric gate".to_string(),
771 context: format!(
772 "MaxSAT scheduler job accounting must match aggregate jobs to job-kind/status counters, \
773 got jobs={} candidate_set={} search={} encoded_search={} unknown={} timeout={}",
774 self.gpu_maxsat_scheduler_jobs,
775 self.gpu_maxsat_scheduler_candidate_set_jobs,
776 self.gpu_maxsat_scheduler_search_jobs,
777 self.gpu_maxsat_scheduler_encoded_search_jobs,
778 self.gpu_maxsat_scheduler_unknown_status_jobs,
779 self.gpu_maxsat_scheduler_timeout_status_jobs
780 ),
781 });
782 }
783 Ok(())
784 }
785
786 fn require_portfolio_job_accounting(&self) -> Result<()> {
787 let job_kind_events = self.checked_portfolio_job_kind_events()?;
788 if self.gpu_portfolio_jobs != job_kind_events {
789 return Err(XlogError::UnsupportedEpistemicConstruct {
790 construct: "GPU solver production metric gate".to_string(),
791 context: format!(
792 "portfolio job accounting must match aggregate jobs to job-kind counters, \
793 got jobs={} sat={} maxsat={} unknown={} timeout={}",
794 self.gpu_portfolio_jobs,
795 self.gpu_portfolio_sat_jobs,
796 self.gpu_portfolio_maxsat_jobs,
797 self.gpu_portfolio_unknown_status_jobs,
798 self.gpu_portfolio_timeout_status_jobs
799 ),
800 });
801 }
802 Ok(())
803 }
804
805 fn require_encoded_maxsat_upload_transfer_accounting(&self) -> Result<()> {
806 if (self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes != 0
807 && self.gpu_maxsat_candidate_cnf_data_plane_htod_calls == 0)
808 || (self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes != 0
809 && self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls == 0)
810 {
811 return Err(XlogError::UnsupportedEpistemicConstruct {
812 construct: "GPU solver production metric gate".to_string(),
813 context: format!(
814 "encoded MaxSAT candidate CNF upload bytes require matching host-to-device calls, \
815 got data_plane_calls={} data_plane_bytes={} launch_metadata_calls={} \
816 launch_metadata_bytes={}",
817 self.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
818 self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
819 self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
820 self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes
821 ),
822 });
823 }
824 if (self.gpu_maxsat_candidate_cnf_data_plane_htod_calls != 0
825 && self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes == 0)
826 || (self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls != 0
827 && self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes == 0)
828 {
829 return Err(XlogError::UnsupportedEpistemicConstruct {
830 construct: "GPU solver production metric gate".to_string(),
831 context: format!(
832 "encoded MaxSAT candidate CNF upload calls require matching host-to-device bytes, \
833 got data_plane_calls={} data_plane_bytes={} launch_metadata_calls={} \
834 launch_metadata_bytes={}",
835 self.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
836 self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
837 self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
838 self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes
839 ),
840 });
841 }
842 Ok(())
843 }
844
845 pub fn require_accepted_gpu_tuple_membership_trace(&self) -> Result<()> {
847 if self.accepted_nonzero_arity_gpu_candidate_evidence_consumed == 0
848 && self.accepted_gpu_candidate_tuple_key_column_reads_consumed != 0
849 {
850 return Err(XlogError::UnsupportedEpistemicConstruct {
851 construct: "GPU solver production metric gate".to_string(),
852 context: format!(
853 "accepted tuple-key reads require accepted nonzero-arity GPU evidence, got \
854 nonzero_evidence=0 tuple_key_reads={}",
855 self.accepted_gpu_candidate_tuple_key_column_reads_consumed
856 ),
857 });
858 }
859 if self.accepted_nonzero_arity_gpu_candidate_evidence_consumed > 0
860 && self.accepted_gpu_candidate_tuple_key_column_reads_consumed == 0
861 {
862 return Err(XlogError::UnsupportedEpistemicConstruct {
863 construct: "GPU solver production metric gate".to_string(),
864 context: format!(
865 "accepted nonzero-arity GPU solver evidence requires tuple-key device column \
866 reads, got nonzero_evidence={} tuple_key_reads=0",
867 self.accepted_nonzero_arity_gpu_candidate_evidence_consumed
868 ),
869 });
870 }
871 if self.accepted_gpu_final_tuple_negated_row_filters_consumed
872 > self.accepted_gpu_final_tuple_row_filters_consumed
873 {
874 return Err(XlogError::UnsupportedEpistemicConstruct {
875 construct: "GPU solver production metric gate".to_string(),
876 context: format!(
877 "accepted negated final-tuple row filters cannot exceed total row filters: \
878 negated={} total={}",
879 self.accepted_gpu_final_tuple_negated_row_filters_consumed,
880 self.accepted_gpu_final_tuple_row_filters_consumed
881 ),
882 });
883 }
884 if self.accepted_gpu_final_tuple_row_filters_consumed == 0
885 && (self.accepted_gpu_row_specific_membership_row_capacity_consumed != 0
886 || self.accepted_gpu_row_filter_fallback_row_capacity_consumed != 0)
887 {
888 return Err(XlogError::UnsupportedEpistemicConstruct {
889 construct: "GPU solver production metric gate".to_string(),
890 context: format!(
891 "accepted row-specific/fallback tuple capacity requires accepted GPU row \
892 filters, got row_filters=0 row_specific_capacity={} fallback_capacity={}",
893 self.accepted_gpu_row_specific_membership_row_capacity_consumed,
894 self.accepted_gpu_row_filter_fallback_row_capacity_consumed
895 ),
896 });
897 }
898 if self.accepted_gpu_final_tuple_row_filters_consumed > 0
899 && self.accepted_gpu_row_specific_membership_row_capacity_consumed == 0
900 {
901 return Err(XlogError::UnsupportedEpistemicConstruct {
902 construct: "GPU solver production metric gate".to_string(),
903 context: format!(
904 "accepted GPU final-tuple row filters require row-specific model-slot \
905 capacity, got row_filters={} row_specific_capacity=0",
906 self.accepted_gpu_final_tuple_row_filters_consumed
907 ),
908 });
909 }
910 if self.accepted_gpu_constraint_row_count_device_reads_consumed
911 > self.accepted_gpu_constraint_relations_checked_consumed
912 {
913 return Err(XlogError::UnsupportedEpistemicConstruct {
914 construct: "GPU solver production metric gate".to_string(),
915 context: format!(
916 "accepted constraint row-count device reads cannot exceed checked reduced \
917 constraint relations, got reads={} checked={}",
918 self.accepted_gpu_constraint_row_count_device_reads_consumed,
919 self.accepted_gpu_constraint_relations_checked_consumed
920 ),
921 });
922 }
923 Ok(())
924 }
925
926 pub fn require_accepted_gpu_candidate_evidence_trace(&self) -> Result<()> {
928 let mode_count = self
929 .accepted_g91_gpu_candidate_evidence_consumed
930 .checked_add(self.accepted_faeel_gpu_candidate_evidence_consumed)
931 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
932 construct: "GPU solver production metric gate".to_string(),
933 context: "accepted GPU solver mode counters overflowed".to_string(),
934 })?;
935 if self.accepted_gpu_candidate_evidence_consumed != 0
936 && mode_count != self.accepted_gpu_candidate_evidence_consumed
937 {
938 return Err(XlogError::UnsupportedEpistemicConstruct {
939 construct: "GPU solver production metric gate".to_string(),
940 context: format!(
941 "accepted GPU solver evidence must be classified by epistemic mode, got \
942 evidence={} g91={} faeel={}",
943 self.accepted_gpu_candidate_evidence_consumed,
944 self.accepted_g91_gpu_candidate_evidence_consumed,
945 self.accepted_faeel_gpu_candidate_evidence_consumed
946 ),
947 });
948 }
949 if self.accepted_know_gpu_candidate_evidence_consumed
950 > self.accepted_gpu_candidate_evidence_consumed
951 || self.accepted_possible_gpu_candidate_evidence_consumed
952 > self.accepted_gpu_candidate_evidence_consumed
953 || self.accepted_not_possible_gpu_candidate_evidence_consumed
954 > self.accepted_gpu_candidate_evidence_consumed
955 || self.accepted_not_know_gpu_candidate_evidence_consumed
956 > self.accepted_gpu_candidate_evidence_consumed
957 {
958 return Err(XlogError::UnsupportedEpistemicConstruct {
959 construct: "GPU solver production metric gate".to_string(),
960 context: format!(
961 "accepted GPU solver operator evidence counters cannot exceed accepted \
962 evidence records, got evidence={} know={} possible={} not_possible={} \
963 not_know={}",
964 self.accepted_gpu_candidate_evidence_consumed,
965 self.accepted_know_gpu_candidate_evidence_consumed,
966 self.accepted_possible_gpu_candidate_evidence_consumed,
967 self.accepted_not_possible_gpu_candidate_evidence_consumed,
968 self.accepted_not_know_gpu_candidate_evidence_consumed
969 ),
970 });
971 }
972 if self.accepted_gpu_candidate_evidence_consumed != 0 {
973 if self.accepted_gpu_candidate_state_transitions
974 != self.accepted_gpu_world_view_state_transitions
975 {
976 return Err(XlogError::UnsupportedEpistemicConstruct {
977 construct: "GPU solver production metric gate".to_string(),
978 context: format!(
979 "accepted GPU candidate/world-view state transitions must match, got \
980 candidates={} world_views={}",
981 self.accepted_gpu_candidate_state_transitions,
982 self.accepted_gpu_world_view_state_transitions
983 ),
984 });
985 }
986 if self.accepted_gpu_candidate_state_transitions == 0
987 || self.accepted_gpu_world_view_state_transitions == 0
988 || self.accepted_gpu_candidate_final_output_rows_consumed == 0
989 {
990 return Err(XlogError::UnsupportedEpistemicConstruct {
991 construct: "GPU solver production metric gate".to_string(),
992 context: format!(
993 "accepted GPU solver detailed evidence requires accepted \
994 candidate/world-view states and non-empty final output rows, got \
995 evidence={} candidate_states={} world_view_states={} final_rows={}",
996 self.accepted_gpu_candidate_evidence_consumed,
997 self.accepted_gpu_candidate_state_transitions,
998 self.accepted_gpu_world_view_state_transitions,
999 self.accepted_gpu_candidate_final_output_rows_consumed
1000 ),
1001 });
1002 }
1003 if self.accepted_solver_assumption_bindings_consumed
1004 < self.accepted_gpu_candidate_evidence_consumed
1005 {
1006 return Err(XlogError::UnsupportedEpistemicConstruct {
1007 construct: "GPU solver production metric gate".to_string(),
1008 context: format!(
1009 "accepted GPU solver evidence requires planner-exported assumption \
1010 bindings, got evidence={} assumption_bindings={}",
1011 self.accepted_gpu_candidate_evidence_consumed,
1012 self.accepted_solver_assumption_bindings_consumed
1013 ),
1014 });
1015 }
1016 let required_capability_floor = self
1017 .accepted_gpu_candidate_evidence_consumed
1018 .checked_mul(PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT)
1019 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1020 construct: "GPU solver production metric gate".to_string(),
1021 context: "accepted GPU solver capability floor overflowed".to_string(),
1022 })?;
1023 let required_status_floor = self
1024 .accepted_gpu_candidate_evidence_consumed
1025 .checked_mul(PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT)
1026 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1027 construct: "GPU solver production metric gate".to_string(),
1028 context: "accepted GPU solver status floor overflowed".to_string(),
1029 })?;
1030 if self.accepted_solver_required_capabilities_consumed < required_capability_floor
1031 || self.accepted_solver_required_statuses_consumed < required_status_floor
1032 {
1033 return Err(XlogError::UnsupportedEpistemicConstruct {
1034 construct: "GPU solver production metric gate".to_string(),
1035 context: format!(
1036 "accepted GPU solver evidence requires the production \
1037 capability/status contract, got evidence={} capabilities={} statuses={}",
1038 self.accepted_gpu_candidate_evidence_consumed,
1039 self.accepted_solver_required_capabilities_consumed,
1040 self.accepted_solver_required_statuses_consumed
1041 ),
1042 });
1043 }
1044 if self.accepted_gpu_candidate_state_transitions
1045 < self.accepted_gpu_candidate_evidence_consumed
1046 || self.accepted_gpu_world_view_state_transitions
1047 < self.accepted_gpu_candidate_evidence_consumed
1048 || self.accepted_gpu_candidate_final_output_rows_consumed
1049 < self.accepted_gpu_candidate_evidence_consumed
1050 {
1051 return Err(XlogError::UnsupportedEpistemicConstruct {
1052 construct: "GPU solver production metric gate".to_string(),
1053 context: format!(
1054 "accepted GPU solver state counters must cover each accepted evidence \
1055 record, got evidence={} candidate_states={} world_view_states={} \
1056 final_rows={}",
1057 self.accepted_gpu_candidate_evidence_consumed,
1058 self.accepted_gpu_candidate_state_transitions,
1059 self.accepted_gpu_world_view_state_transitions,
1060 self.accepted_gpu_candidate_final_output_rows_consumed
1061 ),
1062 });
1063 }
1064 }
1065 if self.accepted_nonzero_arity_gpu_candidate_evidence_consumed
1066 > self.accepted_gpu_candidate_evidence_consumed
1067 {
1068 return Err(XlogError::UnsupportedEpistemicConstruct {
1069 construct: "GPU solver production metric gate".to_string(),
1070 context: format!(
1071 "accepted nonzero-arity GPU solver evidence cannot exceed accepted evidence \
1072 records, got nonzero={} evidence={}",
1073 self.accepted_nonzero_arity_gpu_candidate_evidence_consumed,
1074 self.accepted_gpu_candidate_evidence_consumed
1075 ),
1076 });
1077 }
1078 if self.accepted_gpu_batch_candidate_component_evidence_consumed
1079 < self.accepted_gpu_batch_candidate_evidence_consumed
1080 {
1081 return Err(XlogError::UnsupportedEpistemicConstruct {
1082 construct: "GPU solver production metric gate".to_string(),
1083 context: format!(
1084 "accepted GPU batch component evidence must cover accepted batch evidence, \
1085 got batches={} components={}",
1086 self.accepted_gpu_batch_candidate_evidence_consumed,
1087 self.accepted_gpu_batch_candidate_component_evidence_consumed
1088 ),
1089 });
1090 }
1091 if self.accepted_gpu_batch_candidate_component_evidence_consumed
1092 > self.accepted_gpu_candidate_evidence_consumed
1093 {
1094 return Err(XlogError::UnsupportedEpistemicConstruct {
1095 construct: "GPU solver production metric gate".to_string(),
1096 context: format!(
1097 "accepted GPU batch component evidence cannot exceed accepted candidate \
1098 evidence, got components={} evidence={}",
1099 self.accepted_gpu_batch_candidate_component_evidence_consumed,
1100 self.accepted_gpu_candidate_evidence_consumed
1101 ),
1102 });
1103 }
1104 Ok(())
1105 }
1106
1107 pub fn require_production_metric_eligibility(&self) -> Result<()> {
1113 let capabilities = production_capabilities();
1114 match capabilities.production_metric_backend {
1115 GpuSolverProductionMetricBackend::GpuOnly => {}
1116 }
1117 if capabilities.gpu_cdcl_sat_unsat != GpuSolverProductionCapabilityStatus::Available {
1118 return Err(XlogError::UnsupportedEpistemicConstruct {
1119 construct: "GPU solver production metric gate".to_string(),
1120 context: "GPU CDCL SAT/UNSAT production capability is not available".to_string(),
1121 });
1122 }
1123 if capabilities.gpu_maxsat != GpuSolverProductionCapabilityStatus::Available {
1124 return Err(XlogError::UnsupportedEpistemicConstruct {
1125 construct: "GPU solver production metric gate".to_string(),
1126 context: capabilities.gpu_maxsat_blocker.to_string(),
1127 });
1128 }
1129 if capabilities.gpu_portfolio_sat_maxsat != GpuSolverProductionCapabilityStatus::Available {
1130 return Err(XlogError::UnsupportedEpistemicConstruct {
1131 construct: "GPU solver production metric gate".to_string(),
1132 context: capabilities.gpu_portfolio_blocker.to_string(),
1133 });
1134 }
1135 if self.accepted_gpu_candidate_evidence_consumed == 0 {
1136 return Err(XlogError::UnsupportedEpistemicConstruct {
1137 construct: "GPU solver production metric gate".to_string(),
1138 context: "production solver metrics require accepted GPU candidate evidence"
1139 .to_string(),
1140 });
1141 }
1142 let gpu_solver_production_path_events = self.checked_gpu_solver_production_path_events()?;
1143 let gpu_solver_status_path_events = self.checked_gpu_solver_status_path_events()?;
1144 let gpu_solver_path_events = Self::checked_production_event_sum(
1145 "gpu_solver_path_events",
1146 &[
1147 gpu_solver_production_path_events,
1148 gpu_solver_status_path_events,
1149 ],
1150 )?;
1151 if gpu_solver_path_events == 0 {
1152 return Err(XlogError::UnsupportedEpistemicConstruct {
1153 construct: "GPU solver production metric gate".to_string(),
1154 context:
1155 "production solver metrics require an existing GPU CDCL/MaxSAT/scheduler/portfolio/status counter"
1156 .to_string(),
1157 });
1158 }
1159 if self.accepted_gpu_solver_production_path_events == 0 {
1160 return Err(XlogError::UnsupportedEpistemicConstruct {
1161 construct: "GPU solver production metric gate".to_string(),
1162 context: "production solver metrics require GPU solver production/status work inside an accepted epistemic evidence gate"
1163 .to_string(),
1164 });
1165 }
1166 if self.accepted_gpu_solver_production_path_events > gpu_solver_path_events {
1167 return Err(XlogError::UnsupportedEpistemicConstruct {
1168 construct: "GPU solver production metric gate".to_string(),
1169 context: format!(
1170 "accepted GPU solver production/status events cannot exceed total GPU solver production/status events: accepted={} total={}",
1171 self.accepted_gpu_solver_production_path_events, gpu_solver_path_events
1172 ),
1173 });
1174 }
1175 if self.accepted_gpu_solver_production_path_events
1176 < self.accepted_gpu_candidate_state_transitions
1177 {
1178 return Err(XlogError::UnsupportedEpistemicConstruct {
1179 construct: "GPU solver production metric gate".to_string(),
1180 context: format!(
1181 "accepted GPU solver production events must cover each accepted candidate \
1182 state transition, got accepted_events={} candidate_states={}",
1183 self.accepted_gpu_solver_production_path_events,
1184 self.accepted_gpu_candidate_state_transitions
1185 ),
1186 });
1187 }
1188 self.require_maxsat_scheduler_job_accounting()?;
1189 self.require_portfolio_job_accounting()?;
1190 let maxsat_metric_events = self.checked_maxsat_production_metric_events()?;
1191 if maxsat_metric_events != 0 {
1192 if self.gpu_maxsat_candidate_solves == 0 {
1193 return Err(XlogError::UnsupportedEpistemicConstruct {
1194 construct: "GPU solver production metric gate".to_string(),
1195 context: "MaxSAT production metrics require GPU CDCL candidate solves"
1196 .to_string(),
1197 });
1198 }
1199 if self.gpu_maxsat_frontier_certified_candidate_solves
1200 != self.gpu_maxsat_candidate_solves
1201 {
1202 return Err(XlogError::UnsupportedEpistemicConstruct {
1203 construct: "GPU solver production metric gate".to_string(),
1204 context: format!(
1205 "MaxSAT production metrics require every candidate solve to be covered by \
1206 an encoded weighted MaxSAT upper-bound certificate, got certified_solves={} \
1207 candidate_solves={}",
1208 self.gpu_maxsat_frontier_certified_candidate_solves,
1209 self.gpu_maxsat_candidate_solves
1210 ),
1211 });
1212 }
1213 if self.gpu_maxsat_candidate_encodes > self.gpu_maxsat_candidate_solves {
1214 return Err(XlogError::UnsupportedEpistemicConstruct {
1215 construct: "GPU solver production metric gate".to_string(),
1216 context: format!(
1217 "encoded MaxSAT candidates cannot exceed GPU CDCL candidate solves, \
1218 got encodes={} solves={}",
1219 self.gpu_maxsat_candidate_encodes, self.gpu_maxsat_candidate_solves
1220 ),
1221 });
1222 }
1223 if self.gpu_maxsat_unsat_candidate_prunes > self.gpu_maxsat_candidate_solves {
1224 return Err(XlogError::UnsupportedEpistemicConstruct {
1225 construct: "GPU solver production metric gate".to_string(),
1226 context: format!(
1227 "MaxSAT UNSAT candidate prunes cannot exceed GPU CDCL candidate solves, \
1228 got prunes={} solves={}",
1229 self.gpu_maxsat_unsat_candidate_prunes, self.gpu_maxsat_candidate_solves
1230 ),
1231 });
1232 }
1233 if self.gpu_maxsat_optima > self.gpu_maxsat_candidate_solves {
1234 return Err(XlogError::UnsupportedEpistemicConstruct {
1235 construct: "GPU solver production metric gate".to_string(),
1236 context: format!(
1237 "MaxSAT optima cannot exceed GPU CDCL candidate solves, got optima={} \
1238 solves={}",
1239 self.gpu_maxsat_optima, self.gpu_maxsat_candidate_solves
1240 ),
1241 });
1242 }
1243 let encoded_maxsat_metric_events = Self::checked_production_event_sum(
1244 "gpu_solver_encoded_maxsat_metric_events",
1245 &[
1246 self.gpu_maxsat_frontier_certified_candidate_solves,
1247 self.gpu_maxsat_candidate_encodes,
1248 self.gpu_maxsat_frontier_upper_bound_certificates,
1249 self.gpu_maxsat_frontier_completion_candidate_encodes,
1250 self.gpu_maxsat_scheduler_encoded_search_jobs,
1251 ],
1252 )?;
1253 let encoded_maxsat_upload_metrics = self.gpu_maxsat_candidate_cnf_data_plane_htod_calls
1254 != 0
1255 || self.gpu_maxsat_candidate_cnf_data_plane_htod_bytes != 0
1256 || self.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls != 0
1257 || self.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes != 0
1258 || self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls != 0
1259 || self.gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes != 0;
1260 if encoded_maxsat_metric_events != 0 || encoded_maxsat_upload_metrics {
1261 if self.gpu_maxsat_frontier_upper_bound_certificates == 0 {
1262 return Err(XlogError::UnsupportedEpistemicConstruct {
1263 construct: "GPU solver production metric gate".to_string(),
1264 context: "encoded MaxSAT production metrics require a weighted MaxSAT upper-bound certificate"
1265 .to_string(),
1266 });
1267 }
1268 if self.gpu_maxsat_candidate_encodes == 0 {
1269 return Err(XlogError::UnsupportedEpistemicConstruct {
1270 construct: "GPU solver production metric gate".to_string(),
1271 context:
1272 "MaxSAT upper-bound certificates require encoded weighted MaxSAT candidates"
1273 .to_string(),
1274 });
1275 }
1276 let max_data_plane_htod_calls = self
1277 .gpu_maxsat_candidate_encodes
1278 .checked_mul(2)
1279 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1280 construct: "GPU solver production metric gate".to_string(),
1281 context:
1282 "MaxSAT candidate data-plane host-to-device call budget overflowed"
1283 .to_string(),
1284 })?;
1285 let max_launch_metadata_htod_calls = self
1286 .gpu_maxsat_candidate_encodes
1287 .checked_mul(3)
1288 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1289 construct: "GPU solver production metric gate".to_string(),
1290 context:
1291 "MaxSAT candidate launch-metadata host-to-device call budget overflowed"
1292 .to_string(),
1293 })?;
1294 if self.gpu_maxsat_candidate_cnf_data_plane_htod_calls > max_data_plane_htod_calls
1295 || self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls
1296 > max_launch_metadata_htod_calls
1297 {
1298 return Err(XlogError::UnsupportedEpistemicConstruct {
1299 construct: "GPU solver production metric gate".to_string(),
1300 context: format!(
1301 "encoded MaxSAT candidate CNF uploads exceeded bounded host-to-device call budget, \
1302 encodes={} data_plane_calls={}/{} launch_metadata_calls={}/{}",
1303 self.gpu_maxsat_candidate_encodes,
1304 self.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
1305 max_data_plane_htod_calls,
1306 self.gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
1307 max_launch_metadata_htod_calls
1308 ),
1309 });
1310 }
1311 self.require_encoded_maxsat_upload_transfer_accounting()?;
1312 if self.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls != 0
1313 || self.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes != 0
1314 {
1315 return Err(XlogError::UnsupportedEpistemicConstruct {
1316 construct: "GPU solver production metric gate".to_string(),
1317 context: format!(
1318 "encoded MaxSAT candidate CNF uploads must not perform data-plane device-to-host \
1319 transfers, got calls={} bytes={}",
1320 self.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls,
1321 self.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes
1322 ),
1323 });
1324 }
1325 if self.gpu_maxsat_frontier_completion_candidate_encodes
1326 > self.gpu_maxsat_candidate_encodes
1327 {
1328 return Err(XlogError::UnsupportedEpistemicConstruct {
1329 construct: "GPU solver production metric gate".to_string(),
1330 context: format!(
1331 "MaxSAT frontier completion candidates cannot exceed encoded candidates, \
1332 got completion_encodes={} total_encodes={}",
1333 self.gpu_maxsat_frontier_completion_candidate_encodes,
1334 self.gpu_maxsat_candidate_encodes
1335 ),
1336 });
1337 }
1338 if self.gpu_maxsat_frontier_certified_candidate_solves
1339 != self.gpu_maxsat_candidate_encodes
1340 {
1341 return Err(XlogError::UnsupportedEpistemicConstruct {
1342 construct: "GPU solver production metric gate".to_string(),
1343 context: format!(
1344 "encoded MaxSAT production metrics require every encoded candidate solve \
1345 to be covered by an upper-bound-certified frontier, got certified_solves={} \
1346 encoded_candidates={}",
1347 self.gpu_maxsat_frontier_certified_candidate_solves,
1348 self.gpu_maxsat_candidate_encodes
1349 ),
1350 });
1351 }
1352 if self.gpu_maxsat_frontier_upper_bound_certificates
1353 < self.gpu_maxsat_scheduler_encoded_search_jobs
1354 {
1355 return Err(XlogError::UnsupportedEpistemicConstruct {
1356 construct: "GPU solver production metric gate".to_string(),
1357 context: format!(
1358 "encoded MaxSAT scheduler jobs require one upper-bound certificate per job, got certificates={} encoded_jobs={}",
1359 self.gpu_maxsat_frontier_upper_bound_certificates,
1360 self.gpu_maxsat_scheduler_encoded_search_jobs
1361 ),
1362 });
1363 }
1364 }
1365 if self.gpu_maxsat_optima == 0 {
1366 return Err(XlogError::UnsupportedEpistemicConstruct {
1367 construct: "GPU solver production metric gate".to_string(),
1368 context: "MaxSAT production metrics require a GPU-certified optimum"
1369 .to_string(),
1370 });
1371 }
1372 }
1373 self.require_accepted_gpu_candidate_evidence_trace()?;
1374 self.require_accepted_gpu_tuple_membership_trace()
1375 }
1376}
1377
1378pub struct GpuSolverProductionAdapter {
1380 provider: Arc<CudaKernelProvider>,
1381 solver: GpuCdclSolver,
1382 trace: GpuSolverProductionTrace,
1383}
1384
1385impl GpuSolverProductionAdapter {
1386 pub fn new(provider: Arc<CudaKernelProvider>, config: GpuCdclConfig) -> Self {
1388 Self {
1389 solver: GpuCdclSolver::new(Arc::clone(&provider), config),
1390 provider,
1391 trace: GpuSolverProductionTrace::default(),
1392 }
1393 }
1394
1395 pub fn trace(&self) -> GpuSolverProductionTrace {
1397 self.trace
1398 }
1399
1400 fn checked_trace_counter_add(current: u64, delta: u64, counter: &str) -> Result<u64> {
1401 current
1402 .checked_add(delta)
1403 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1404 construct: "GPU solver production trace accounting".to_string(),
1405 context: format!(
1406 "accepted GPU solver trace counter {counter} overflowed while adding {delta} \
1407 to {current}"
1408 ),
1409 })
1410 }
1411
1412 fn checked_report_counter_add(current: u64, delta: u64, counter: &str) -> Result<u64> {
1413 current
1414 .checked_add(delta)
1415 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1416 construct: "GPU solver production report accounting".to_string(),
1417 context: format!(
1418 "accepted GPU solver report counter {counter} overflowed while adding {delta} \
1419 to {current}"
1420 ),
1421 })
1422 }
1423
1424 fn checked_report_counter_delta(current: u64, before: u64, counter: &str) -> Result<u64> {
1425 current
1426 .checked_sub(before)
1427 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1428 construct: "GPU solver production report accounting".to_string(),
1429 context: format!(
1430 "accepted GPU solver report counter {counter} decreased from {before} to \
1431 {current}"
1432 ),
1433 })
1434 }
1435
1436 fn checked_workspace_clause_cap(weighted: &SolveInstance) -> Result<u32> {
1437 u32::try_from(weighted.clauses.len()).map_err(|_| {
1438 XlogError::UnsupportedEpistemicConstruct {
1439 construct: "GPU solver production MaxSAT encoding".to_string(),
1440 context: format!(
1441 "weighted MaxSAT clause count {} exceeds GPU CDCL workspace capacity range",
1442 weighted.clauses.len()
1443 ),
1444 }
1445 })
1446 }
1447
1448 fn require_adapter_provider_identity(&self, provider: &CudaKernelProvider) -> Result<()> {
1449 let adapter_identity = EpistemicGpuProviderIdentity::from_provider(&self.provider);
1450 let evidence_identity = EpistemicGpuProviderIdentity::from_provider(provider);
1451 if adapter_identity != evidence_identity {
1452 return Err(XlogError::UnsupportedEpistemicConstruct {
1453 construct: "accepted GPU solver candidate evidence".to_string(),
1454 context: format!(
1455 "solver adapter provider mismatch: adapter device={} evidence device={} \
1456 adapter_device_ptr={} evidence_device_ptr={} adapter_memory_ptr={} \
1457 evidence_memory_ptr={}",
1458 adapter_identity.device_ordinal,
1459 evidence_identity.device_ordinal,
1460 adapter_identity.device_ptr,
1461 evidence_identity.device_ptr,
1462 adapter_identity.memory_ptr,
1463 evidence_identity.memory_ptr
1464 ),
1465 });
1466 }
1467 Ok(())
1468 }
1469
1470 fn require_accepted_gpu_solver_evidence(
1471 &self,
1472 provider: &CudaKernelProvider,
1473 result: &EpistemicGpuExecutionResult,
1474 ) -> Result<GpuSolverAcceptedCandidateState> {
1475 self.require_adapter_provider_identity(provider)?;
1476 require_accepted_gpu_solver_evidence(provider, result)
1477 }
1478
1479 fn require_accepted_gpu_solver_states(
1480 &self,
1481 provider: &CudaKernelProvider,
1482 results: &[&EpistemicGpuExecutionResult],
1483 ) -> Result<Vec<GpuSolverAcceptedCandidateState>> {
1484 self.require_adapter_provider_identity(provider)?;
1485 require_accepted_gpu_solver_states(provider, results)
1486 }
1487
1488 fn require_branch_var_limit_on_adapter_provider(
1489 &self,
1490 branch_var_limit: &TrackedCudaSlice<u32>,
1491 construct: &'static str,
1492 ) -> Result<()> {
1493 if branch_var_limit.len() != 1 {
1494 return Err(XlogError::UnsupportedEpistemicConstruct {
1495 construct: construct.to_string(),
1496 context: format!(
1497 "solver lifecycle branch_var_limit must have len=1, got {}",
1498 branch_var_limit.len()
1499 ),
1500 });
1501 }
1502 let expected_memory =
1503 EpistemicGpuProviderIdentity::from_provider(&self.provider).memory_ptr;
1504 let actual_memory = branch_var_limit.memory_manager_ptr_value();
1505 if actual_memory != expected_memory {
1506 return Err(XlogError::UnsupportedEpistemicConstruct {
1507 construct: construct.to_string(),
1508 context: format!(
1509 "solver lifecycle branch_var_limit belongs to memory manager {actual_memory}, expected {expected_memory}"
1510 ),
1511 });
1512 }
1513 Ok(())
1514 }
1515
1516 fn require_solver_artifact_on_adapter_provider(
1517 &self,
1518 cnf: &GpuCnf,
1519 branch_var_limit: &TrackedCudaSlice<u32>,
1520 construct: &'static str,
1521 ) -> Result<()> {
1522 cnf.require_provider_memory(&self.provider, construct)?;
1523 self.require_branch_var_limit_on_adapter_provider(branch_var_limit, construct)
1524 }
1525
1526 fn require_cnf_on_adapter_provider(&self, cnf: &GpuCnf, construct: &'static str) -> Result<()> {
1527 cnf.require_provider_memory(&self.provider, construct)
1528 }
1529
1530 fn require_workspace_capacity_for_cnf(
1531 &self,
1532 workspace: &GpuCdclWorkspace,
1533 cnf: &GpuCnf,
1534 construct: &'static str,
1535 ) -> Result<()> {
1536 self.solver
1537 .require_workspace_capacity_for_cnf(workspace, cnf.var_cap, cnf.clause_cap)
1538 .map_err(|err| XlogError::UnsupportedEpistemicConstruct {
1539 construct: construct.to_string(),
1540 context: format!("GPU CDCL workspace capacity rejected solver artifact: {err}"),
1541 })
1542 }
1543
1544 fn require_workspace_capacity_for_weighted_maxsat_encoding(
1545 &self,
1546 workspace: &GpuCdclWorkspace,
1547 weighted: &SolveInstance,
1548 construct: &'static str,
1549 ) -> Result<()> {
1550 self.solver
1551 .require_workspace_capacity_for_cnf(
1552 workspace,
1553 weighted.num_vars,
1554 Self::checked_workspace_clause_cap(weighted)?,
1555 )
1556 .map_err(|err| XlogError::UnsupportedEpistemicConstruct {
1557 construct: construct.to_string(),
1558 context: format!("GPU CDCL workspace capacity rejected MaxSAT encoding: {err}"),
1559 })
1560 }
1561
1562 fn require_assumption_lifecycle_step_artifacts(
1563 &self,
1564 workspace: &GpuCdclWorkspace,
1565 steps: &[GpuSolverProductionLifecycleStep<'_>],
1566 ) -> Result<()> {
1567 for step in steps {
1568 self.require_solver_artifact_on_adapter_provider(
1569 step.cnf,
1570 step.branch_var_limit,
1571 "GPU solver production lifecycle",
1572 )?;
1573 if matches!(step.expectation, GpuSolverProductionExpectation::Unsat) {
1574 self.require_workspace_capacity_for_cnf(
1575 workspace,
1576 step.cnf,
1577 "GPU solver production lifecycle",
1578 )?;
1579 }
1580 }
1581 Ok(())
1582 }
1583
1584 fn require_weighted_maxsat_candidate_artifacts(
1585 &self,
1586 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
1587 ) -> Result<()> {
1588 for candidate in candidates {
1589 self.require_solver_artifact_on_adapter_provider(
1590 candidate.cnf,
1591 candidate.branch_var_limit,
1592 "GPU solver production MaxSAT",
1593 )?;
1594 }
1595 Ok(())
1596 }
1597
1598 fn require_weighted_maxsat_candidates_and_artifacts(
1599 &self,
1600 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
1601 ) -> Result<()> {
1602 Self::require_weighted_maxsat_candidates(candidates)?;
1603 self.require_weighted_maxsat_candidate_artifacts(candidates)
1604 }
1605
1606 fn require_maxsat_lifecycle_artifacts(
1607 &self,
1608 workspace: &GpuCdclWorkspace,
1609 steps: &[GpuSolverProductionLifecycleStep<'_>],
1610 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
1611 ) -> Result<()> {
1612 self.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
1613 self.require_weighted_maxsat_candidate_artifacts(candidates)
1614 }
1615
1616 fn require_weighted_maxsat_search_candidate_artifacts(
1617 &self,
1618 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
1619 ) -> Result<()> {
1620 for candidate in candidates {
1621 self.require_solver_artifact_on_adapter_provider(
1622 candidate.cnf,
1623 candidate.branch_var_limit,
1624 "GPU solver production MaxSAT search",
1625 )?;
1626 }
1627 Ok(())
1628 }
1629
1630 fn require_weighted_maxsat_search_candidates_and_artifacts(
1631 &self,
1632 workspace: &GpuCdclWorkspace,
1633 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
1634 ) -> Result<()> {
1635 Self::require_weighted_maxsat_search_candidates(candidates)?;
1636 self.require_weighted_maxsat_search_candidate_artifacts(candidates)?;
1637 for candidate in candidates {
1638 if matches!(
1639 candidate.status,
1640 GpuSolverProductionMaxSatSearchStatus::Unsatisfiable
1641 ) {
1642 self.require_workspace_capacity_for_cnf(
1643 workspace,
1644 candidate.cnf,
1645 "GPU solver production MaxSAT search",
1646 )?;
1647 }
1648 }
1649 Ok(())
1650 }
1651
1652 fn require_learned_clause_publication_artifacts(
1653 &self,
1654 workspace: &GpuCdclWorkspace,
1655 cnf: &GpuCnf,
1656 branch_var_limit: &TrackedCudaSlice<u32>,
1657 ) -> Result<()> {
1658 self.require_solver_artifact_on_adapter_provider(
1659 cnf,
1660 branch_var_limit,
1661 "GPU solver learned-clause arena",
1662 )?;
1663 self.require_workspace_capacity_for_cnf(workspace, cnf, "GPU solver learned-clause arena")
1664 }
1665
1666 fn require_learned_clause_reuse_artifacts(
1667 &self,
1668 source_cnf: &GpuCnf,
1669 source_branch_var_limit: &TrackedCudaSlice<u32>,
1670 target_cnf: &GpuCnf,
1671 target_branch_var_limit: &TrackedCudaSlice<u32>,
1672 ) -> Result<()> {
1673 self.require_solver_artifact_on_adapter_provider(
1674 source_cnf,
1675 source_branch_var_limit,
1676 "GPU solver learned-clause reuse",
1677 )?;
1678 self.require_solver_artifact_on_adapter_provider(
1679 target_cnf,
1680 target_branch_var_limit,
1681 "GPU solver learned-clause reuse",
1682 )
1683 }
1684
1685 fn require_learned_clause_reuse_inputs(
1686 &mut self,
1687 workspace: &GpuCdclWorkspace,
1688 source_cnf: &GpuCnf,
1689 source_branch_var_limit: &TrackedCudaSlice<u32>,
1690 target_cnf: &GpuCnf,
1691 target_branch_var_limit: &TrackedCudaSlice<u32>,
1692 ) -> Result<()> {
1693 self.require_learned_clause_reuse_artifacts(
1694 source_cnf,
1695 source_branch_var_limit,
1696 target_cnf,
1697 target_branch_var_limit,
1698 )?;
1699 if let Err(err) = require_same_gpu_cnf_for_learned_clause_reuse(source_cnf, target_cnf) {
1700 checked_solver_trace_counter_inc!(self, gpu_learned_clause_reuse_rejections);
1701 return Err(err);
1702 }
1703 self.require_workspace_capacity_for_cnf(
1704 workspace,
1705 source_cnf,
1706 "GPU solver learned-clause reuse",
1707 )?;
1708 self.require_workspace_capacity_for_cnf(
1709 workspace,
1710 target_cnf,
1711 "GPU solver learned-clause reuse",
1712 )?;
1713 Ok(())
1714 }
1715
1716 fn require_weighted_maxsat_encoded_search_inputs_and_artifacts(
1717 &self,
1718 workspace: &GpuCdclWorkspace,
1719 weighted: &SolveInstance,
1720 branch_var_limit: &TrackedCudaSlice<u32>,
1721 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
1722 ) -> Result<()> {
1723 Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
1724 self.require_workspace_capacity_for_weighted_maxsat_encoding(
1725 workspace,
1726 weighted,
1727 "GPU solver production MaxSAT encoding",
1728 )?;
1729 self.require_branch_var_limit_on_adapter_provider(
1730 branch_var_limit,
1731 "GPU solver production MaxSAT encoding",
1732 )
1733 }
1734
1735 pub fn accepted_candidate_state(
1737 &self,
1738 provider: &CudaKernelProvider,
1739 result: &EpistemicGpuExecutionResult,
1740 ) -> Result<GpuSolverAcceptedCandidateState> {
1741 self.require_accepted_gpu_solver_evidence(provider, result)
1742 }
1743
1744 fn accepted_solver_results_from_gpu_batch_execution_evidence<'a>(
1745 &mut self,
1746 provider: &CudaKernelProvider,
1747 evidence: GpuSolverProductionBatchExecutionEvidence<'a>,
1748 ) -> Result<Vec<&'a EpistemicGpuExecutionResult>> {
1749 self.require_adapter_provider_identity(provider)?;
1750 require_accepted_gpu_solver_batch_evidence(provider, evidence.batch)
1751 }
1752
1753 fn record_accepted_gpu_batch_candidate_evidence(
1754 &mut self,
1755 component_count: usize,
1756 ) -> Result<()> {
1757 self.trace.accepted_gpu_batch_candidate_evidence_consumed =
1758 Self::checked_trace_counter_add(
1759 self.trace.accepted_gpu_batch_candidate_evidence_consumed,
1760 1,
1761 "accepted_gpu_batch_candidate_evidence_consumed",
1762 )?;
1763 self.trace
1764 .accepted_gpu_batch_candidate_component_evidence_consumed =
1765 Self::checked_trace_counter_add(
1766 self.trace
1767 .accepted_gpu_batch_candidate_component_evidence_consumed,
1768 component_count as u64,
1769 "accepted_gpu_batch_candidate_component_evidence_consumed",
1770 )?;
1771 Ok(())
1772 }
1773
1774 fn with_trace_rollback<T>(&mut self, action: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
1775 let trace_before = self.trace;
1776 match action(self) {
1777 Ok(value) => Ok(value),
1778 Err(err) => {
1779 self.trace = trace_before;
1780 Err(err)
1781 }
1782 }
1783 }
1784
1785 fn with_trace_rollback_preserving_reuse_rejections<T>(
1786 &mut self,
1787 action: impl FnOnce(&mut Self) -> Result<T>,
1788 ) -> Result<T> {
1789 let trace_before = self.trace;
1790 match action(self) {
1791 Ok(value) => Ok(value),
1792 Err(err) => {
1793 let reuse_rejections_after = self.trace.gpu_learned_clause_reuse_rejections;
1794 let reuse_rejections_before = trace_before.gpu_learned_clause_reuse_rejections;
1795 self.trace = trace_before;
1796 let reuse_rejection_delta = Self::checked_report_counter_delta(
1797 reuse_rejections_after,
1798 reuse_rejections_before,
1799 "gpu_learned_clause_reuse_rejections",
1800 )?;
1801 self.trace.gpu_learned_clause_reuse_rejections = Self::checked_trace_counter_add(
1802 self.trace.gpu_learned_clause_reuse_rejections,
1803 reuse_rejection_delta,
1804 "gpu_learned_clause_reuse_rejections",
1805 )?;
1806 Err(err)
1807 }
1808 }
1809 }
1810
1811 pub fn new_workspace(&self, max_var_cap: u32, max_clause_cap: u32) -> Result<GpuCdclWorkspace> {
1813 self.solver.new_workspace(max_var_cap, max_clause_cap)
1814 }
1815
1816 fn require_workspace_on_adapter_provider(&self, workspace: &GpuCdclWorkspace) -> Result<()> {
1817 self.solver.require_workspace_on_provider(workspace)
1818 }
1819
1820 fn record_accepted_gpu_candidate_state(
1821 &mut self,
1822 state: &GpuSolverAcceptedCandidateState,
1823 ) -> Result<()> {
1824 self.trace.accepted_gpu_candidate_evidence_consumed = Self::checked_trace_counter_add(
1825 self.trace.accepted_gpu_candidate_evidence_consumed,
1826 state.evidence_records,
1827 "accepted_gpu_candidate_evidence_consumed",
1828 )?;
1829 self.trace.accepted_gpu_candidate_state_transitions = Self::checked_trace_counter_add(
1830 self.trace.accepted_gpu_candidate_state_transitions,
1831 state.accepted_candidates,
1832 "accepted_gpu_candidate_state_transitions",
1833 )?;
1834 self.trace.accepted_gpu_world_view_state_transitions = Self::checked_trace_counter_add(
1835 self.trace.accepted_gpu_world_view_state_transitions,
1836 state.accepted_world_views,
1837 "accepted_gpu_world_view_state_transitions",
1838 )?;
1839 self.trace.accepted_gpu_candidate_final_output_rows_consumed =
1840 Self::checked_trace_counter_add(
1841 self.trace.accepted_gpu_candidate_final_output_rows_consumed,
1842 state.final_output_rows,
1843 "accepted_gpu_candidate_final_output_rows_consumed",
1844 )?;
1845 if state.g91_mode {
1846 self.trace.accepted_g91_gpu_candidate_evidence_consumed =
1847 Self::checked_trace_counter_add(
1848 self.trace.accepted_g91_gpu_candidate_evidence_consumed,
1849 1,
1850 "accepted_g91_gpu_candidate_evidence_consumed",
1851 )?;
1852 }
1853 if state.faeel_mode {
1854 self.trace.accepted_faeel_gpu_candidate_evidence_consumed =
1855 Self::checked_trace_counter_add(
1856 self.trace.accepted_faeel_gpu_candidate_evidence_consumed,
1857 1,
1858 "accepted_faeel_gpu_candidate_evidence_consumed",
1859 )?;
1860 }
1861 if state.has_know_operator {
1862 self.trace.accepted_know_gpu_candidate_evidence_consumed =
1863 Self::checked_trace_counter_add(
1864 self.trace.accepted_know_gpu_candidate_evidence_consumed,
1865 1,
1866 "accepted_know_gpu_candidate_evidence_consumed",
1867 )?;
1868 }
1869 if state.has_possible_operator {
1870 self.trace.accepted_possible_gpu_candidate_evidence_consumed =
1871 Self::checked_trace_counter_add(
1872 self.trace.accepted_possible_gpu_candidate_evidence_consumed,
1873 1,
1874 "accepted_possible_gpu_candidate_evidence_consumed",
1875 )?;
1876 }
1877 if state.has_not_possible_operator {
1878 self.trace
1879 .accepted_not_possible_gpu_candidate_evidence_consumed =
1880 Self::checked_trace_counter_add(
1881 self.trace
1882 .accepted_not_possible_gpu_candidate_evidence_consumed,
1883 1,
1884 "accepted_not_possible_gpu_candidate_evidence_consumed",
1885 )?;
1886 }
1887 if state.has_not_know_operator {
1888 self.trace.accepted_not_know_gpu_candidate_evidence_consumed =
1889 Self::checked_trace_counter_add(
1890 self.trace.accepted_not_know_gpu_candidate_evidence_consumed,
1891 1,
1892 "accepted_not_know_gpu_candidate_evidence_consumed",
1893 )?;
1894 }
1895 if state.has_nonzero_arity_tuple_keys {
1896 self.trace
1897 .accepted_nonzero_arity_gpu_candidate_evidence_consumed =
1898 Self::checked_trace_counter_add(
1899 self.trace
1900 .accepted_nonzero_arity_gpu_candidate_evidence_consumed,
1901 1,
1902 "accepted_nonzero_arity_gpu_candidate_evidence_consumed",
1903 )?;
1904 }
1905 self.trace
1906 .accepted_gpu_candidate_tuple_key_column_reads_consumed =
1907 Self::checked_trace_counter_add(
1908 self.trace
1909 .accepted_gpu_candidate_tuple_key_column_reads_consumed,
1910 state.tuple_key_column_reads,
1911 "accepted_gpu_candidate_tuple_key_column_reads_consumed",
1912 )?;
1913 self.trace.accepted_solver_assumption_bindings_consumed = Self::checked_trace_counter_add(
1914 self.trace.accepted_solver_assumption_bindings_consumed,
1915 state.solver_assumption_bindings,
1916 "accepted_solver_assumption_bindings_consumed",
1917 )?;
1918 self.trace.accepted_solver_required_capabilities_consumed =
1919 Self::checked_trace_counter_add(
1920 self.trace.accepted_solver_required_capabilities_consumed,
1921 state.solver_required_capabilities,
1922 "accepted_solver_required_capabilities_consumed",
1923 )?;
1924 self.trace.accepted_solver_required_statuses_consumed = Self::checked_trace_counter_add(
1925 self.trace.accepted_solver_required_statuses_consumed,
1926 state.solver_required_statuses,
1927 "accepted_solver_required_statuses_consumed",
1928 )?;
1929 self.trace.accepted_gpu_final_tuple_row_filters_consumed = Self::checked_trace_counter_add(
1930 self.trace.accepted_gpu_final_tuple_row_filters_consumed,
1931 state.final_tuple_row_filters,
1932 "accepted_gpu_final_tuple_row_filters_consumed",
1933 )?;
1934 self.trace
1935 .accepted_gpu_final_tuple_negated_row_filters_consumed =
1936 Self::checked_trace_counter_add(
1937 self.trace
1938 .accepted_gpu_final_tuple_negated_row_filters_consumed,
1939 state.final_tuple_negated_row_filters,
1940 "accepted_gpu_final_tuple_negated_row_filters_consumed",
1941 )?;
1942 self.trace
1943 .accepted_gpu_row_specific_membership_row_capacity_consumed =
1944 Self::checked_trace_counter_add(
1945 self.trace
1946 .accepted_gpu_row_specific_membership_row_capacity_consumed,
1947 state.row_specific_membership_row_capacity,
1948 "accepted_gpu_row_specific_membership_row_capacity_consumed",
1949 )?;
1950 self.trace
1951 .accepted_gpu_row_filter_fallback_row_capacity_consumed =
1952 Self::checked_trace_counter_add(
1953 self.trace
1954 .accepted_gpu_row_filter_fallback_row_capacity_consumed,
1955 state.row_filter_fallback_row_capacity,
1956 "accepted_gpu_row_filter_fallback_row_capacity_consumed",
1957 )?;
1958 self.trace
1959 .accepted_gpu_constraint_relations_checked_consumed = Self::checked_trace_counter_add(
1960 self.trace
1961 .accepted_gpu_constraint_relations_checked_consumed,
1962 state.checked_constraint_relations,
1963 "accepted_gpu_constraint_relations_checked_consumed",
1964 )?;
1965 self.trace
1966 .accepted_gpu_constraint_row_count_device_reads_consumed =
1967 Self::checked_trace_counter_add(
1968 self.trace
1969 .accepted_gpu_constraint_row_count_device_reads_consumed,
1970 state.constraint_row_count_device_reads,
1971 "accepted_gpu_constraint_row_count_device_reads_consumed",
1972 )?;
1973 Ok(())
1974 }
1975
1976 fn record_accepted_gpu_solver_production_path_events_since(
1977 &mut self,
1978 events_before: GpuSolverAcceptedPathEventSnapshot,
1979 state: &GpuSolverAcceptedCandidateState,
1980 ) -> Result<()> {
1981 let events_after = self.trace.accepted_path_event_snapshot()?;
1982 let production_delta = events_after
1983 .production
1984 .checked_sub(events_before.production)
1985 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1986 construct: "GPU solver production trace accounting".to_string(),
1987 context: format!(
1988 "accepted GPU solver production events decreased from {} to {}",
1989 events_before.production, events_after.production
1990 ),
1991 })?;
1992 let status_delta = events_after
1993 .status
1994 .checked_sub(events_before.status)
1995 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
1996 construct: "GPU solver production trace accounting".to_string(),
1997 context: format!(
1998 "accepted GPU solver status events decreased from {} to {}",
1999 events_before.status, events_after.status
2000 ),
2001 })?;
2002 let accepted_delta = Self::checked_report_counter_add(
2003 production_delta,
2004 status_delta,
2005 "accepted_gpu_solver_path_events",
2006 )?;
2007 if accepted_delta < state.accepted_candidates {
2008 return Err(XlogError::UnsupportedEpistemicConstruct {
2009 construct: "GPU solver production trace accounting".to_string(),
2010 context: format!(
2011 "accepted GPU solver production/status work must cover every accepted \
2012 candidate state before evidence is recorded, got production_events={} \
2013 status_events={} candidate_states={}",
2014 production_delta, status_delta, state.accepted_candidates
2015 ),
2016 });
2017 }
2018 self.trace.accepted_gpu_solver_production_path_events = Self::checked_trace_counter_add(
2019 self.trace.accepted_gpu_solver_production_path_events,
2020 accepted_delta,
2021 "accepted_gpu_solver_production_path_events",
2022 )?;
2023 Ok(())
2024 }
2025
2026 pub fn solve_expect_sat(&mut self, cnf: &GpuCnf) -> Result<TrackedCudaSlice<i8>> {
2028 self.require_cnf_on_adapter_provider(cnf, "GPU solver production SAT")?;
2029 let assignment = self.solver.solve_expect_sat(cnf)?;
2030 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2031 Ok(assignment)
2032 }
2033
2034 pub fn solve_expect_sat_with_gpu_execution_result(
2036 &mut self,
2037 provider: &CudaKernelProvider,
2038 result: &EpistemicGpuExecutionResult,
2039 cnf: &GpuCnf,
2040 ) -> Result<TrackedCudaSlice<i8>> {
2041 self.with_trace_rollback(|this| {
2042 this.require_cnf_on_adapter_provider(cnf, "GPU solver production SAT")?;
2043 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2044 let events_before = this.trace.accepted_path_event_snapshot()?;
2045 let assignment = this.solve_expect_sat(cnf)?;
2046 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2047 this.record_accepted_gpu_candidate_state(&state)?;
2048 Ok(assignment)
2049 })
2050 }
2051
2052 pub fn solve_expect_unsat_with_gpu_execution_result(
2054 &mut self,
2055 provider: &CudaKernelProvider,
2056 result: &EpistemicGpuExecutionResult,
2057 cnf: &GpuCnf,
2058 ) -> Result<()> {
2059 self.with_trace_rollback(|this| {
2060 this.require_cnf_on_adapter_provider(cnf, "GPU solver production UNSAT")?;
2061 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2062 let events_before = this.trace.accepted_path_event_snapshot()?;
2063 this.solve_expect_unsat(cnf)?;
2064 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2065 this.record_accepted_gpu_candidate_state(&state)?;
2066 Ok(())
2067 })
2068 }
2069
2070 pub fn solve_expect_unsat(&mut self, cnf: &GpuCnf) -> Result<()> {
2072 self.require_cnf_on_adapter_provider(cnf, "GPU solver production UNSAT")?;
2073 self.solver.solve_expect_unsat(cnf)?;
2074 checked_solver_trace_counter_inc!(self, gpu_cdcl_unsat_solves);
2075 Ok(())
2076 }
2077
2078 pub fn solve_expect_unsat_with_branch_limit_ws(
2080 &mut self,
2081 workspace: &mut GpuCdclWorkspace,
2082 cnf: &GpuCnf,
2083 branch_var_limit: &TrackedCudaSlice<u32>,
2084 ) -> Result<()> {
2085 self.require_workspace_on_adapter_provider(workspace)?;
2086 self.require_solver_artifact_on_adapter_provider(
2087 cnf,
2088 branch_var_limit,
2089 "GPU solver production workspace UNSAT",
2090 )?;
2091 self.require_workspace_capacity_for_cnf(
2092 workspace,
2093 cnf,
2094 "GPU solver production workspace UNSAT",
2095 )?;
2096 self.solver
2097 .solve_expect_unsat_with_branch_limit_ws(workspace, cnf, branch_var_limit)?;
2098 checked_solver_trace_counter_inc!(self, gpu_cdcl_workspace_unsat_solves);
2099 Ok(())
2100 }
2101
2102 pub fn solve_expect_unsat_with_branch_limit_ws_with_gpu_execution_result(
2104 &mut self,
2105 provider: &CudaKernelProvider,
2106 result: &EpistemicGpuExecutionResult,
2107 workspace: &mut GpuCdclWorkspace,
2108 cnf: &GpuCnf,
2109 branch_var_limit: &TrackedCudaSlice<u32>,
2110 ) -> Result<()> {
2111 self.with_trace_rollback(|this| {
2112 this.require_workspace_on_adapter_provider(workspace)?;
2113 this.require_solver_artifact_on_adapter_provider(
2114 cnf,
2115 branch_var_limit,
2116 "GPU solver production workspace UNSAT",
2117 )?;
2118 this.require_workspace_capacity_for_cnf(
2119 workspace,
2120 cnf,
2121 "GPU solver production workspace UNSAT",
2122 )?;
2123 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2124 let events_before = this.trace.accepted_path_event_snapshot()?;
2125 this.solve_expect_unsat_with_branch_limit_ws(workspace, cnf, branch_var_limit)?;
2126 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2127 this.record_accepted_gpu_candidate_state(&state)?;
2128 Ok(())
2129 })
2130 }
2131
2132 fn solve_assumption_lifecycle_steps(
2133 &mut self,
2134 workspace: &mut GpuCdclWorkspace,
2135 steps: &[GpuSolverProductionLifecycleStep<'_>],
2136 ) -> Result<GpuSolverProductionLifecycleReport> {
2137 self.with_trace_rollback(|this| {
2138 this.solve_assumption_lifecycle_steps_impl(workspace, steps)
2139 })
2140 }
2141
2142 fn solve_assumption_lifecycle_steps_impl(
2143 &mut self,
2144 workspace: &mut GpuCdclWorkspace,
2145 steps: &[GpuSolverProductionLifecycleStep<'_>],
2146 ) -> Result<GpuSolverProductionLifecycleReport> {
2147 Self::require_assumption_lifecycle_steps(steps)?;
2148 self.require_workspace_on_adapter_provider(workspace)?;
2149
2150 let pushes_before = self.trace.gpu_assumption_pushes;
2151 let retractions_before = self.trace.gpu_assumption_retractions;
2152 let workspace_reuses_before = self.trace.gpu_lifecycle_workspace_reuses;
2153 let unknown_steps_before = self.trace.gpu_lifecycle_unknown_status_steps;
2154 let timeout_steps_before = self.trace.gpu_lifecycle_timeout_status_steps;
2155 let mut sat_steps = 0u64;
2156 let mut unsat_steps = 0u64;
2157
2158 self.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2159
2160 for step in steps {
2161 checked_solver_trace_counter_inc!(self, gpu_assumption_pushes);
2162 match step.expectation {
2163 GpuSolverProductionExpectation::Sat => {
2164 self.solver
2165 .solve_expect_sat_with_branch_limit(step.cnf, step.branch_var_limit)?;
2166 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2167 sat_steps =
2168 Self::checked_trace_counter_add(sat_steps, 1, "lifecycle_sat_steps")?;
2169 }
2170 GpuSolverProductionExpectation::Unsat => {
2171 let assign_ptr_before = workspace.assign_device_ptr();
2172 self.solve_expect_unsat_with_branch_limit_ws(
2173 workspace,
2174 step.cnf,
2175 step.branch_var_limit,
2176 )?;
2177 if workspace.assign_device_ptr() == assign_ptr_before {
2178 checked_solver_trace_counter_inc!(self, gpu_lifecycle_workspace_reuses);
2179 }
2180 unsat_steps =
2181 Self::checked_trace_counter_add(unsat_steps, 1, "lifecycle_unsat_steps")?;
2182 }
2183 GpuSolverProductionExpectation::Unknown { .. } => {
2184 checked_solver_trace_counter_inc!(self, gpu_lifecycle_unknown_status_steps);
2185 }
2186 GpuSolverProductionExpectation::Timeout { .. } => {
2187 checked_solver_trace_counter_inc!(self, gpu_lifecycle_timeout_status_steps);
2188 }
2189 };
2190 checked_solver_trace_counter_inc!(self, gpu_assumption_retractions);
2191 }
2192
2193 let assumption_pushes = Self::checked_report_counter_delta(
2194 self.trace.gpu_assumption_pushes,
2195 pushes_before,
2196 "gpu_assumption_pushes",
2197 )?;
2198 let assumption_retractions = Self::checked_report_counter_delta(
2199 self.trace.gpu_assumption_retractions,
2200 retractions_before,
2201 "gpu_assumption_retractions",
2202 )?;
2203 if assumption_pushes != assumption_retractions {
2204 return Err(XlogError::UnsupportedEpistemicConstruct {
2205 construct: "GPU solver production lifecycle".to_string(),
2206 context: format!(
2207 "assumption push/retract mismatch: pushes={} retractions={}",
2208 assumption_pushes, assumption_retractions
2209 ),
2210 });
2211 }
2212 let unknown_steps = Self::checked_report_counter_delta(
2213 self.trace.gpu_lifecycle_unknown_status_steps,
2214 unknown_steps_before,
2215 "gpu_lifecycle_unknown_status_steps",
2216 )?;
2217 let timeout_steps = Self::checked_report_counter_delta(
2218 self.trace.gpu_lifecycle_timeout_status_steps,
2219 timeout_steps_before,
2220 "gpu_lifecycle_timeout_status_steps",
2221 )?;
2222 let accounted_sat_unsat =
2223 Self::checked_report_counter_add(sat_steps, unsat_steps, "lifecycle_status_steps")?;
2224 let accounted_known_unknown = Self::checked_report_counter_add(
2225 accounted_sat_unsat,
2226 unknown_steps,
2227 "lifecycle_status_steps",
2228 )?;
2229 let accounted_status_steps = Self::checked_report_counter_add(
2230 accounted_known_unknown,
2231 timeout_steps,
2232 "lifecycle_status_steps",
2233 )?;
2234 if accounted_status_steps != steps.len() as u64 {
2235 return Err(XlogError::UnsupportedEpistemicConstruct {
2236 construct: "GPU solver production lifecycle".to_string(),
2237 context: format!(
2238 "lifecycle status accounting mismatch: sat={}, unsat={}, unknown={}, \
2239 timeout={}, steps={}",
2240 sat_steps,
2241 unsat_steps,
2242 unknown_steps,
2243 timeout_steps,
2244 steps.len()
2245 ),
2246 });
2247 }
2248
2249 Ok(GpuSolverProductionLifecycleReport {
2250 candidate_evidence_records: 0,
2251 steps: steps.len() as u64,
2252 sat_steps,
2253 unsat_steps,
2254 assumption_pushes,
2255 assumption_retractions,
2256 workspace_reuses: Self::checked_report_counter_delta(
2257 self.trace.gpu_lifecycle_workspace_reuses,
2258 workspace_reuses_before,
2259 "gpu_lifecycle_workspace_reuses",
2260 )?,
2261 unknown_steps,
2262 timeout_steps,
2263 })
2264 }
2265
2266 fn require_assumption_lifecycle_steps(
2267 steps: &[GpuSolverProductionLifecycleStep<'_>],
2268 ) -> Result<()> {
2269 if steps.is_empty() {
2270 return Err(XlogError::UnsupportedEpistemicConstruct {
2271 construct: "GPU solver production lifecycle".to_string(),
2272 context: "accepted solver lifecycle requires at least one step".to_string(),
2273 });
2274 }
2275
2276 for step in steps {
2277 match step.expectation {
2278 GpuSolverProductionExpectation::Unknown { reason } => {
2279 if reason.trim().is_empty() {
2280 return Err(XlogError::UnsupportedEpistemicConstruct {
2281 construct: "GPU solver production lifecycle".to_string(),
2282 context: "UNKNOWN lifecycle status requires a diagnostic reason"
2283 .to_string(),
2284 });
2285 }
2286 }
2287 GpuSolverProductionExpectation::Timeout { budget_micros } => {
2288 if budget_micros == 0 {
2289 return Err(XlogError::UnsupportedEpistemicConstruct {
2290 construct: "GPU solver production lifecycle".to_string(),
2291 context: "TIMEOUT lifecycle status requires a nonzero budget"
2292 .to_string(),
2293 });
2294 }
2295 }
2296 GpuSolverProductionExpectation::Sat | GpuSolverProductionExpectation::Unsat => {}
2297 }
2298 }
2299 Ok(())
2300 }
2301
2302 pub fn solve_assumption_lifecycle_with_gpu_execution_result(
2304 &mut self,
2305 provider: &CudaKernelProvider,
2306 result: &EpistemicGpuExecutionResult,
2307 workspace: &mut GpuCdclWorkspace,
2308 steps: &[GpuSolverProductionLifecycleStep<'_>],
2309 ) -> Result<GpuSolverProductionLifecycleReport> {
2310 self.with_trace_rollback(|this| {
2311 Self::require_assumption_lifecycle_steps(steps)?;
2312 this.require_workspace_on_adapter_provider(workspace)?;
2313 this.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2314 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2315 let events_before = this.trace.accepted_path_event_snapshot()?;
2316 let mut report = this.solve_assumption_lifecycle_steps(workspace, steps)?;
2317 report.candidate_evidence_records = 1;
2318 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2319 this.record_accepted_gpu_candidate_state(&state)?;
2320 Ok(report)
2321 })
2322 }
2323
2324 pub fn solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results(
2329 &mut self,
2330 provider: &CudaKernelProvider,
2331 results: &[&EpistemicGpuExecutionResult],
2332 workspace: &mut GpuCdclWorkspace,
2333 steps: &[GpuSolverProductionLifecycleStep<'_>],
2334 ) -> Result<GpuSolverProductionLifecycleReport> {
2335 self.with_trace_rollback(|this| {
2336 this.solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results_impl(
2337 provider, results, workspace, steps,
2338 )
2339 })
2340 }
2341
2342 fn solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results_impl(
2343 &mut self,
2344 provider: &CudaKernelProvider,
2345 results: &[&EpistemicGpuExecutionResult],
2346 workspace: &mut GpuCdclWorkspace,
2347 steps: &[GpuSolverProductionLifecycleStep<'_>],
2348 ) -> Result<GpuSolverProductionLifecycleReport> {
2349 if results.is_empty() {
2350 return Err(XlogError::UnsupportedEpistemicConstruct {
2351 construct: "GPU solver production lifecycle".to_string(),
2352 context:
2353 "multi-candidate solver lifecycle requires at least one accepted GPU result"
2354 .to_string(),
2355 });
2356 }
2357 Self::require_assumption_lifecycle_steps(steps)?;
2358 self.require_workspace_on_adapter_provider(workspace)?;
2359 self.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2360
2361 let states = self.require_accepted_gpu_solver_states(provider, results)?;
2362
2363 let mut report = GpuSolverProductionLifecycleReport::default();
2364 for state in &states {
2365 let events_before = self.trace.accepted_path_event_snapshot()?;
2366 let step_report = self.solve_assumption_lifecycle_steps(workspace, steps)?;
2367 checked_solver_report_counter_inc!(report, candidate_evidence_records);
2368 checked_solver_report_counter_add!(report, steps, step_report.steps);
2369 checked_solver_report_counter_add!(report, sat_steps, step_report.sat_steps);
2370 checked_solver_report_counter_add!(report, unsat_steps, step_report.unsat_steps);
2371 checked_solver_report_counter_add!(
2372 report,
2373 assumption_pushes,
2374 step_report.assumption_pushes
2375 );
2376 checked_solver_report_counter_add!(
2377 report,
2378 assumption_retractions,
2379 step_report.assumption_retractions
2380 );
2381 checked_solver_report_counter_add!(
2382 report,
2383 workspace_reuses,
2384 step_report.workspace_reuses
2385 );
2386 checked_solver_report_counter_add!(report, unknown_steps, step_report.unknown_steps);
2387 checked_solver_report_counter_add!(report, timeout_steps, step_report.timeout_steps);
2388 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
2389 self.record_accepted_gpu_candidate_state(state)?;
2390 }
2391
2392 Ok(report)
2393 }
2394
2395 pub fn solve_assumption_lifecycle_with_gpu_batch_execution_result(
2401 &mut self,
2402 provider: &CudaKernelProvider,
2403 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
2404 workspace: &mut GpuCdclWorkspace,
2405 steps: &[GpuSolverProductionLifecycleStep<'_>],
2406 ) -> Result<GpuSolverProductionLifecycleReport> {
2407 self.with_trace_rollback(|this| {
2408 Self::require_assumption_lifecycle_steps(steps)?;
2409 this.require_workspace_on_adapter_provider(workspace)?;
2410 this.require_assumption_lifecycle_step_artifacts(workspace, steps)?;
2411 let results =
2412 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
2413 let report = this
2414 .solve_multi_candidate_assumption_lifecycle_with_gpu_execution_results(
2415 provider, &results, workspace, steps,
2416 )?;
2417 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
2418 Ok(report)
2419 })
2420 }
2421
2422 pub fn solve_unsat_and_publish_learned_clause_arena_with_gpu_execution_result(
2428 &mut self,
2429 provider: &CudaKernelProvider,
2430 result: &EpistemicGpuExecutionResult,
2431 workspace: &mut GpuCdclWorkspace,
2432 cnf: &GpuCnf,
2433 branch_var_limit: &TrackedCudaSlice<u32>,
2434 ) -> Result<GpuSolverProductionLearnedClauseArenaReport> {
2435 self.with_trace_rollback(|this| {
2436 this.solve_unsat_and_publish_learned_clause_arena_with_gpu_execution_result_impl(
2437 provider,
2438 result,
2439 workspace,
2440 cnf,
2441 branch_var_limit,
2442 )
2443 })
2444 }
2445
2446 fn solve_unsat_and_publish_learned_clause_arena_with_gpu_execution_result_impl(
2447 &mut self,
2448 provider: &CudaKernelProvider,
2449 result: &EpistemicGpuExecutionResult,
2450 workspace: &mut GpuCdclWorkspace,
2451 cnf: &GpuCnf,
2452 branch_var_limit: &TrackedCudaSlice<u32>,
2453 ) -> Result<GpuSolverProductionLearnedClauseArenaReport> {
2454 self.require_workspace_on_adapter_provider(workspace)?;
2455 self.require_learned_clause_publication_artifacts(workspace, cnf, branch_var_limit)?;
2456 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
2457 let events_before = self.trace.accepted_path_event_snapshot()?;
2458
2459 let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
2460 let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
2461 let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
2462 let proof_data_ptr = workspace.proof_data.device_ptr_value();
2463 let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
2464
2465 self.solve_expect_unsat_with_branch_limit_ws(workspace, cnf, branch_var_limit)?;
2466
2467 if learned_offsets_ptr == 0
2468 || learned_lits_ptr == 0
2469 || proof_offsets_ptr == 0
2470 || proof_data_ptr == 0
2471 || learned_count_ptr == 0
2472 {
2473 return Err(XlogError::UnsupportedEpistemicConstruct {
2474 construct: "GPU solver learned-clause arena".to_string(),
2475 context: "learned-clause publication requires non-null GPU arena buffers"
2476 .to_string(),
2477 });
2478 }
2479 if workspace.learned_offsets.device_ptr_value() != learned_offsets_ptr
2480 || workspace.learned_lits.device_ptr_value() != learned_lits_ptr
2481 || workspace.proof_offsets.device_ptr_value() != proof_offsets_ptr
2482 || workspace.proof_data.device_ptr_value() != proof_data_ptr
2483 || workspace.out_learned_count.device_ptr_value() != learned_count_ptr
2484 {
2485 return Err(XlogError::UnsupportedEpistemicConstruct {
2486 construct: "GPU solver learned-clause arena".to_string(),
2487 context: "learned-clause publication must keep the reusable GPU workspace arena"
2488 .to_string(),
2489 });
2490 }
2491
2492 checked_solver_trace_counter_inc!(self, gpu_learned_clause_arena_publications);
2493 checked_solver_trace_counter_inc!(self, gpu_learned_count_buffer_publications);
2494 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2495 self.record_accepted_gpu_candidate_state(&state)?;
2496
2497 Ok(GpuSolverProductionLearnedClauseArenaReport {
2498 unsat_solves: 1,
2499 gpu_learned_clause_arena_publications: 1,
2500 gpu_learned_count_buffer_publications: 1,
2501 })
2502 }
2503
2504 fn solve_unsat_then_reuse_learned_clauses(
2505 &mut self,
2506 workspace: &mut GpuCdclWorkspace,
2507 source_cnf: &GpuCnf,
2508 source_branch_var_limit: &TrackedCudaSlice<u32>,
2509 target_cnf: &GpuCnf,
2510 target_branch_var_limit: &TrackedCudaSlice<u32>,
2511 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2512 self.solve_unsat_then_reuse_learned_clauses_impl(
2513 workspace,
2514 source_cnf,
2515 source_branch_var_limit,
2516 target_cnf,
2517 target_branch_var_limit,
2518 )
2519 }
2520
2521 fn solve_unsat_then_reuse_learned_clauses_impl(
2522 &mut self,
2523 workspace: &mut GpuCdclWorkspace,
2524 source_cnf: &GpuCnf,
2525 source_branch_var_limit: &TrackedCudaSlice<u32>,
2526 target_cnf: &GpuCnf,
2527 target_branch_var_limit: &TrackedCudaSlice<u32>,
2528 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2529 self.require_workspace_on_adapter_provider(workspace)?;
2530 self.require_learned_clause_reuse_inputs(
2531 workspace,
2532 source_cnf,
2533 source_branch_var_limit,
2534 target_cnf,
2535 target_branch_var_limit,
2536 )?;
2537
2538 let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
2539 let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
2540 let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
2541 let proof_data_ptr = workspace.proof_data.device_ptr_value();
2542 let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
2543 if learned_offsets_ptr == 0
2544 || learned_lits_ptr == 0
2545 || proof_offsets_ptr == 0
2546 || proof_data_ptr == 0
2547 || learned_count_ptr == 0
2548 {
2549 return Err(XlogError::UnsupportedEpistemicConstruct {
2550 construct: "GPU solver learned-clause reuse".to_string(),
2551 context: "learned-clause reuse requires non-null GPU arena buffers".to_string(),
2552 });
2553 }
2554
2555 self.solve_expect_unsat_with_branch_limit_ws(
2556 workspace,
2557 source_cnf,
2558 source_branch_var_limit,
2559 )?;
2560 require_stable_learned_clause_arena(
2561 "publication",
2562 workspace,
2563 learned_offsets_ptr,
2564 learned_lits_ptr,
2565 proof_offsets_ptr,
2566 proof_data_ptr,
2567 learned_count_ptr,
2568 )?;
2569
2570 checked_solver_trace_counter_inc!(self, gpu_learned_clause_arena_publications);
2571 checked_solver_trace_counter_inc!(self, gpu_learned_count_buffer_publications);
2572
2573 self.solver
2574 .solve_expect_unsat_with_branch_limit_ws_importing_learned(
2575 workspace,
2576 target_cnf,
2577 target_branch_var_limit,
2578 )?;
2579 checked_solver_trace_counter_inc!(self, gpu_cdcl_workspace_unsat_solves);
2580 require_stable_learned_clause_arena(
2581 "import",
2582 workspace,
2583 learned_offsets_ptr,
2584 learned_lits_ptr,
2585 proof_offsets_ptr,
2586 proof_data_ptr,
2587 learned_count_ptr,
2588 )?;
2589
2590 checked_solver_trace_counter_inc!(self, gpu_learned_clause_imports);
2591 checked_solver_trace_counter_inc!(self, gpu_learned_clause_reused_solves);
2592
2593 Ok(GpuSolverProductionLearnedClauseReuseReport {
2594 candidate_evidence_records: 0,
2595 candidates: 2,
2596 unsat_solves: 2,
2597 gpu_learned_clause_arena_publications: 1,
2598 gpu_learned_clause_imports: 1,
2599 gpu_learned_clause_reused_solves: 1,
2600 })
2601 }
2602
2603 #[expect(
2608 clippy::too_many_arguments,
2609 reason = "accepted-evidence reuse keeps the evidence provider, workspace, and both CNF decision-bound pairs explicit"
2610 )]
2611 pub fn solve_unsat_then_reuse_learned_clauses_with_gpu_execution_result(
2612 &mut self,
2613 provider: &CudaKernelProvider,
2614 result: &EpistemicGpuExecutionResult,
2615 workspace: &mut GpuCdclWorkspace,
2616 source_cnf: &GpuCnf,
2617 source_branch_var_limit: &TrackedCudaSlice<u32>,
2618 target_cnf: &GpuCnf,
2619 target_branch_var_limit: &TrackedCudaSlice<u32>,
2620 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2621 self.with_trace_rollback_preserving_reuse_rejections(|this| {
2622 this.require_workspace_on_adapter_provider(workspace)?;
2623 this.require_learned_clause_reuse_inputs(
2624 workspace,
2625 source_cnf,
2626 source_branch_var_limit,
2627 target_cnf,
2628 target_branch_var_limit,
2629 )?;
2630 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2631 let events_before = this.trace.accepted_path_event_snapshot()?;
2632 let mut report = this.solve_unsat_then_reuse_learned_clauses(
2633 workspace,
2634 source_cnf,
2635 source_branch_var_limit,
2636 target_cnf,
2637 target_branch_var_limit,
2638 )?;
2639 report.candidate_evidence_records = 1;
2640 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2641 this.record_accepted_gpu_candidate_state(&state)?;
2642 Ok(report)
2643 })
2644 }
2645
2646 #[expect(
2648 clippy::too_many_arguments,
2649 reason = "multi-candidate reuse adds an accepted-result set to the same explicit source and target CNF contract"
2650 )]
2651 pub fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2652 &mut self,
2653 provider: &CudaKernelProvider,
2654 results: &[&EpistemicGpuExecutionResult],
2655 workspace: &mut GpuCdclWorkspace,
2656 source_cnf: &GpuCnf,
2657 source_branch_var_limit: &TrackedCudaSlice<u32>,
2658 target_cnf: &GpuCnf,
2659 target_branch_var_limit: &TrackedCudaSlice<u32>,
2660 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2661 self.with_trace_rollback_preserving_reuse_rejections(|this| {
2662 this.solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2663 provider,
2664 results,
2665 workspace,
2666 source_cnf,
2667 source_branch_var_limit,
2668 target_cnf,
2669 target_branch_var_limit,
2670 )
2671 })
2672 }
2673
2674 #[expect(
2675 clippy::too_many_arguments,
2676 reason = "the internal multi-candidate operation carries the public evidence and source-target reuse contract unchanged"
2677 )]
2678 fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2679 &mut self,
2680 provider: &CudaKernelProvider,
2681 results: &[&EpistemicGpuExecutionResult],
2682 workspace: &mut GpuCdclWorkspace,
2683 source_cnf: &GpuCnf,
2684 source_branch_var_limit: &TrackedCudaSlice<u32>,
2685 target_cnf: &GpuCnf,
2686 target_branch_var_limit: &TrackedCudaSlice<u32>,
2687 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2688 if results.is_empty() {
2689 return Err(XlogError::UnsupportedEpistemicConstruct {
2690 construct: "GPU solver learned-clause reuse".to_string(),
2691 context:
2692 "multi-candidate learned-clause reuse requires at least one accepted GPU result"
2693 .to_string(),
2694 });
2695 }
2696 self.require_workspace_on_adapter_provider(workspace)?;
2697 self.require_learned_clause_reuse_inputs(
2698 workspace,
2699 source_cnf,
2700 source_branch_var_limit,
2701 target_cnf,
2702 target_branch_var_limit,
2703 )?;
2704 let states = self.require_accepted_gpu_solver_states(provider, results)?;
2705
2706 let mut report = GpuSolverProductionLearnedClauseReuseReport::default();
2707 for state in &states {
2708 let events_before = self.trace.accepted_path_event_snapshot()?;
2709 let step_report = self.solve_unsat_then_reuse_learned_clauses(
2710 workspace,
2711 source_cnf,
2712 source_branch_var_limit,
2713 target_cnf,
2714 target_branch_var_limit,
2715 )?;
2716 checked_solver_report_counter_inc!(report, candidate_evidence_records);
2717 checked_solver_report_counter_add!(report, candidates, step_report.candidates);
2718 checked_solver_report_counter_add!(report, unsat_solves, step_report.unsat_solves);
2719 checked_solver_report_counter_add!(
2720 report,
2721 gpu_learned_clause_arena_publications,
2722 step_report.gpu_learned_clause_arena_publications
2723 );
2724 checked_solver_report_counter_add!(
2725 report,
2726 gpu_learned_clause_imports,
2727 step_report.gpu_learned_clause_imports
2728 );
2729 checked_solver_report_counter_add!(
2730 report,
2731 gpu_learned_clause_reused_solves,
2732 step_report.gpu_learned_clause_reused_solves
2733 );
2734 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
2735 self.record_accepted_gpu_candidate_state(state)?;
2736 }
2737
2738 Ok(report)
2739 }
2740
2741 #[expect(
2747 clippy::too_many_arguments,
2748 reason = "batch-evidence reuse keeps the accepted batch proof, workspace, and both CNF decision-bound pairs explicit"
2749 )]
2750 pub fn solve_learned_clause_reuse_with_gpu_batch_execution_result(
2751 &mut self,
2752 provider: &CudaKernelProvider,
2753 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
2754 workspace: &mut GpuCdclWorkspace,
2755 source_cnf: &GpuCnf,
2756 source_branch_var_limit: &TrackedCudaSlice<u32>,
2757 target_cnf: &GpuCnf,
2758 target_branch_var_limit: &TrackedCudaSlice<u32>,
2759 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2760 self.with_trace_rollback_preserving_reuse_rejections(|this| {
2761 this.require_workspace_on_adapter_provider(workspace)?;
2762 this.require_learned_clause_reuse_inputs(
2763 workspace,
2764 source_cnf,
2765 source_branch_var_limit,
2766 target_cnf,
2767 target_branch_var_limit,
2768 )?;
2769 let results =
2770 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
2771 let report = this
2772 .solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2773 provider,
2774 &results,
2775 workspace,
2776 source_cnf,
2777 source_branch_var_limit,
2778 target_cnf,
2779 target_branch_var_limit,
2780 )?;
2781 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
2782 Ok(report)
2783 })
2784 }
2785
2786 fn solve_weighted_maxsat_candidates(
2787 &mut self,
2788 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2789 frontier_upper_bound_certificates: u64,
2790 ) -> Result<GpuSolverProductionMaxSatReport> {
2791 self.with_trace_rollback(|this| {
2792 this.solve_weighted_maxsat_candidates_impl(
2793 candidates,
2794 frontier_upper_bound_certificates,
2795 )
2796 })
2797 }
2798
2799 fn solve_weighted_maxsat_candidates_impl(
2800 &mut self,
2801 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2802 frontier_upper_bound_certificates: u64,
2803 ) -> Result<GpuSolverProductionMaxSatReport> {
2804 self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2805
2806 let solves_before = self.trace.gpu_maxsat_candidate_solves;
2807 let mut optimum_score = 0u64;
2808 for candidate in candidates {
2809 let _assignment = self
2810 .solver
2811 .solve_expect_sat_with_branch_limit(candidate.cnf, candidate.branch_var_limit)?;
2812 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2813 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
2814 optimum_score = optimum_score.max(candidate.score);
2815 }
2816 checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
2817 let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
2818 self.trace.gpu_maxsat_candidate_solves,
2819 solves_before,
2820 "gpu_maxsat_candidate_solves",
2821 )?;
2822 if frontier_upper_bound_certificates != 0 {
2823 self.trace.gpu_maxsat_frontier_certified_candidate_solves =
2824 Self::checked_trace_counter_add(
2825 self.trace.gpu_maxsat_frontier_certified_candidate_solves,
2826 gpu_cdcl_candidate_solves,
2827 "gpu_maxsat_frontier_certified_candidate_solves",
2828 )?;
2829 }
2830
2831 Ok(GpuSolverProductionMaxSatReport {
2832 candidate_evidence_records: 0,
2833 optimum_score,
2834 candidates_checked: candidates.len() as u64,
2835 satisfiable_candidates: candidates.len() as u64,
2836 unsat_candidates_pruned: 0,
2837 gpu_cdcl_candidate_encodes: 0,
2838 gpu_cdcl_candidate_solves,
2839 frontier_upper_bound_certificates,
2840 })
2841 }
2842
2843 fn require_weighted_maxsat_candidates(
2844 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2845 ) -> Result<()> {
2846 if candidates.is_empty() {
2847 return Err(XlogError::UnsupportedEpistemicConstruct {
2848 construct: "GPU solver production MaxSAT".to_string(),
2849 context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2850 });
2851 }
2852 Ok(())
2853 }
2854
2855 pub fn solve_weighted_maxsat_candidates_with_gpu_execution_result(
2861 &mut self,
2862 provider: &CudaKernelProvider,
2863 result: &EpistemicGpuExecutionResult,
2864 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2865 ) -> Result<GpuSolverProductionMaxSatReport> {
2866 self.with_trace_rollback(|this| {
2867 this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2868 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2869 let events_before = this.trace.accepted_path_event_snapshot()?;
2870 let mut report = this.solve_weighted_maxsat_candidates(candidates, 0)?;
2871 report.candidate_evidence_records = 1;
2872 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2873 this.record_accepted_gpu_candidate_state(&state)?;
2874 Ok(report)
2875 })
2876 }
2877
2878 fn require_maxsat_lifecycle_inputs(
2879 steps: &[GpuSolverProductionLifecycleStep<'_>],
2880 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2881 ) -> Result<()> {
2882 if steps.is_empty() {
2883 return Err(XlogError::UnsupportedEpistemicConstruct {
2884 construct: "GPU solver production MaxSAT lifecycle".to_string(),
2885 context: "accepted MaxSAT lifecycle requires at least one lifecycle step"
2886 .to_string(),
2887 });
2888 }
2889 if candidates.is_empty() {
2890 return Err(XlogError::UnsupportedEpistemicConstruct {
2891 construct: "GPU solver production MaxSAT lifecycle".to_string(),
2892 context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2893 });
2894 }
2895 Self::require_assumption_lifecycle_steps(steps)?;
2896 Ok(())
2897 }
2898
2899 pub fn solve_maxsat_lifecycle_with_gpu_execution_result(
2905 &mut self,
2906 provider: &CudaKernelProvider,
2907 result: &EpistemicGpuExecutionResult,
2908 workspace: &mut GpuCdclWorkspace,
2909 steps: &[GpuSolverProductionLifecycleStep<'_>],
2910 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2911 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2912 self.with_trace_rollback(|this| {
2913 this.solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2914 provider, result, workspace, steps, candidates,
2915 )
2916 })
2917 }
2918
2919 fn solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2920 &mut self,
2921 provider: &CudaKernelProvider,
2922 result: &EpistemicGpuExecutionResult,
2923 workspace: &mut GpuCdclWorkspace,
2924 steps: &[GpuSolverProductionLifecycleStep<'_>],
2925 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2926 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2927 Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
2928 self.require_workspace_on_adapter_provider(workspace)?;
2929 self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
2930 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
2931
2932 let events_before = self.trace.accepted_path_event_snapshot()?;
2933 let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
2934 let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
2935 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2936 self.record_accepted_gpu_candidate_state(&state)?;
2937
2938 Ok(GpuSolverProductionMaxSatLifecycleReport {
2939 candidate_evidence_records: 1,
2940 lifecycle,
2941 maxsat,
2942 })
2943 }
2944
2945 fn add_maxsat_lifecycle_step_report(
2946 report: &mut GpuSolverProductionMaxSatLifecycleReport,
2947 step_report: GpuSolverProductionMaxSatLifecycleReport,
2948 ) -> Result<()> {
2949 report.candidate_evidence_records = Self::checked_report_counter_add(
2950 report.candidate_evidence_records,
2951 step_report.candidate_evidence_records,
2952 "candidate_evidence_records",
2953 )?;
2954 report.lifecycle.steps = Self::checked_report_counter_add(
2955 report.lifecycle.steps,
2956 step_report.lifecycle.steps,
2957 "lifecycle.steps",
2958 )?;
2959 report.lifecycle.sat_steps = Self::checked_report_counter_add(
2960 report.lifecycle.sat_steps,
2961 step_report.lifecycle.sat_steps,
2962 "lifecycle.sat_steps",
2963 )?;
2964 report.lifecycle.unsat_steps = Self::checked_report_counter_add(
2965 report.lifecycle.unsat_steps,
2966 step_report.lifecycle.unsat_steps,
2967 "lifecycle.unsat_steps",
2968 )?;
2969 report.lifecycle.assumption_pushes = Self::checked_report_counter_add(
2970 report.lifecycle.assumption_pushes,
2971 step_report.lifecycle.assumption_pushes,
2972 "lifecycle.assumption_pushes",
2973 )?;
2974 report.lifecycle.assumption_retractions = Self::checked_report_counter_add(
2975 report.lifecycle.assumption_retractions,
2976 step_report.lifecycle.assumption_retractions,
2977 "lifecycle.assumption_retractions",
2978 )?;
2979 report.lifecycle.workspace_reuses = Self::checked_report_counter_add(
2980 report.lifecycle.workspace_reuses,
2981 step_report.lifecycle.workspace_reuses,
2982 "lifecycle.workspace_reuses",
2983 )?;
2984 report.lifecycle.unknown_steps = Self::checked_report_counter_add(
2985 report.lifecycle.unknown_steps,
2986 step_report.lifecycle.unknown_steps,
2987 "lifecycle.unknown_steps",
2988 )?;
2989 report.lifecycle.timeout_steps = Self::checked_report_counter_add(
2990 report.lifecycle.timeout_steps,
2991 step_report.lifecycle.timeout_steps,
2992 "lifecycle.timeout_steps",
2993 )?;
2994 report.maxsat.optimum_score = report
2995 .maxsat
2996 .optimum_score
2997 .max(step_report.maxsat.optimum_score);
2998 report.maxsat.candidates_checked = Self::checked_report_counter_add(
2999 report.maxsat.candidates_checked,
3000 step_report.maxsat.candidates_checked,
3001 "maxsat.candidates_checked",
3002 )?;
3003 report.maxsat.satisfiable_candidates = Self::checked_report_counter_add(
3004 report.maxsat.satisfiable_candidates,
3005 step_report.maxsat.satisfiable_candidates,
3006 "maxsat.satisfiable_candidates",
3007 )?;
3008 report.maxsat.unsat_candidates_pruned = Self::checked_report_counter_add(
3009 report.maxsat.unsat_candidates_pruned,
3010 step_report.maxsat.unsat_candidates_pruned,
3011 "maxsat.unsat_candidates_pruned",
3012 )?;
3013 report.maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_add(
3014 report.maxsat.gpu_cdcl_candidate_encodes,
3015 step_report.maxsat.gpu_cdcl_candidate_encodes,
3016 "maxsat.gpu_cdcl_candidate_encodes",
3017 )?;
3018 report.maxsat.gpu_cdcl_candidate_solves = Self::checked_report_counter_add(
3019 report.maxsat.gpu_cdcl_candidate_solves,
3020 step_report.maxsat.gpu_cdcl_candidate_solves,
3021 "maxsat.gpu_cdcl_candidate_solves",
3022 )?;
3023 report.maxsat.frontier_upper_bound_certificates = Self::checked_report_counter_add(
3024 report.maxsat.frontier_upper_bound_certificates,
3025 step_report.maxsat.frontier_upper_bound_certificates,
3026 "maxsat.frontier_upper_bound_certificates",
3027 )?;
3028 Ok(())
3029 }
3030
3031 pub fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3033 &mut self,
3034 provider: &CudaKernelProvider,
3035 results: &[&EpistemicGpuExecutionResult],
3036 workspace: &mut GpuCdclWorkspace,
3037 steps: &[GpuSolverProductionLifecycleStep<'_>],
3038 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3039 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3040 self.with_trace_rollback(|this| {
3041 this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3042 provider, results, workspace, steps, candidates,
3043 )
3044 })
3045 }
3046
3047 fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3048 &mut self,
3049 provider: &CudaKernelProvider,
3050 results: &[&EpistemicGpuExecutionResult],
3051 workspace: &mut GpuCdclWorkspace,
3052 steps: &[GpuSolverProductionLifecycleStep<'_>],
3053 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3054 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3055 if results.is_empty() {
3056 return Err(XlogError::UnsupportedEpistemicConstruct {
3057 construct: "GPU solver production MaxSAT lifecycle".to_string(),
3058 context:
3059 "multi-candidate MaxSAT lifecycle requires at least one accepted GPU result"
3060 .to_string(),
3061 });
3062 }
3063 Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3064 self.require_workspace_on_adapter_provider(workspace)?;
3065 self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3066 let states = self.require_accepted_gpu_solver_states(provider, results)?;
3067
3068 let mut report = GpuSolverProductionMaxSatLifecycleReport::default();
3069 for state in &states {
3070 let events_before = self.trace.accepted_path_event_snapshot()?;
3071 let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
3072 let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3073 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3074 self.record_accepted_gpu_candidate_state(state)?;
3075 Self::add_maxsat_lifecycle_step_report(
3076 &mut report,
3077 GpuSolverProductionMaxSatLifecycleReport {
3078 candidate_evidence_records: 1,
3079 lifecycle,
3080 maxsat,
3081 },
3082 )?;
3083 }
3084
3085 Ok(report)
3086 }
3087
3088 pub fn solve_maxsat_lifecycle_with_gpu_batch_execution_result(
3090 &mut self,
3091 provider: &CudaKernelProvider,
3092 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3093 workspace: &mut GpuCdclWorkspace,
3094 steps: &[GpuSolverProductionLifecycleStep<'_>],
3095 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3096 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3097 self.with_trace_rollback(|this| {
3098 Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3099 this.require_workspace_on_adapter_provider(workspace)?;
3100 this.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3101 let results =
3102 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3103 let report = this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3104 provider, &results, workspace, steps, candidates,
3105 )?;
3106 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3107 Ok(report)
3108 })
3109 }
3110
3111 pub fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3113 &mut self,
3114 provider: &CudaKernelProvider,
3115 results: &[&EpistemicGpuExecutionResult],
3116 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3117 ) -> Result<GpuSolverProductionMaxSatReport> {
3118 self.with_trace_rollback(|this| {
3119 this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3120 provider, results, candidates,
3121 )
3122 })
3123 }
3124
3125 fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3126 &mut self,
3127 provider: &CudaKernelProvider,
3128 results: &[&EpistemicGpuExecutionResult],
3129 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3130 ) -> Result<GpuSolverProductionMaxSatReport> {
3131 if results.is_empty() {
3132 return Err(XlogError::UnsupportedEpistemicConstruct {
3133 construct: "GPU solver production MaxSAT".to_string(),
3134 context: "multi-candidate MaxSAT requires at least one accepted GPU result"
3135 .to_string(),
3136 });
3137 }
3138 self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3139 let states = self.require_accepted_gpu_solver_states(provider, results)?;
3140
3141 let mut report = GpuSolverProductionMaxSatReport::default();
3142 for state in &states {
3143 let events_before = self.trace.accepted_path_event_snapshot()?;
3144 let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3145 checked_solver_report_counter_inc!(report, candidate_evidence_records);
3146 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
3147 checked_solver_report_counter_add!(
3148 report,
3149 candidates_checked,
3150 step_report.candidates_checked
3151 );
3152 checked_solver_report_counter_add!(
3153 report,
3154 satisfiable_candidates,
3155 step_report.satisfiable_candidates
3156 );
3157 checked_solver_report_counter_add!(
3158 report,
3159 unsat_candidates_pruned,
3160 step_report.unsat_candidates_pruned
3161 );
3162 checked_solver_report_counter_add!(
3163 report,
3164 gpu_cdcl_candidate_encodes,
3165 step_report.gpu_cdcl_candidate_encodes
3166 );
3167 checked_solver_report_counter_add!(
3168 report,
3169 gpu_cdcl_candidate_solves,
3170 step_report.gpu_cdcl_candidate_solves
3171 );
3172 checked_solver_report_counter_add!(
3173 report,
3174 frontier_upper_bound_certificates,
3175 step_report.frontier_upper_bound_certificates
3176 );
3177 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3178 self.record_accepted_gpu_candidate_state(state)?;
3179 }
3180
3181 Ok(report)
3182 }
3183
3184 pub fn solve_weighted_maxsat_candidates_with_gpu_batch_execution_result(
3190 &mut self,
3191 provider: &CudaKernelProvider,
3192 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3193 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3194 ) -> Result<GpuSolverProductionMaxSatReport> {
3195 self.with_trace_rollback(|this| {
3196 this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3197 let results =
3198 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3199 let report = this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3200 provider, &results, candidates,
3201 )?;
3202 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3203 Ok(report)
3204 })
3205 }
3206
3207 fn encode_weighted_maxsat_search_candidates(
3208 &mut self,
3209 weighted: &SolveInstance,
3210 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3211 ) -> Result<Vec<GpuSolverProductionEncodedMaxSatSearchCandidate>> {
3212 Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
3213
3214 let weights =
3215 weighted
3216 .weights
3217 .as_ref()
3218 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3219 construct: "GPU solver production MaxSAT encoding".to_string(),
3220 context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3221 .to_string(),
3222 })?;
3223
3224 let frontier =
3225 Self::complete_weighted_maxsat_frontier_selections(weighted, weights, selections)?;
3226 if frontier.completion_candidate_count != 0 {
3227 self.trace.gpu_maxsat_frontier_completion_candidate_encodes =
3228 Self::checked_trace_counter_add(
3229 self.trace.gpu_maxsat_frontier_completion_candidate_encodes,
3230 frontier.completion_candidate_count,
3231 "gpu_maxsat_frontier_completion_candidate_encodes",
3232 )?;
3233 }
3234 checked_solver_trace_counter_inc!(self, gpu_maxsat_frontier_upper_bound_certificates);
3235 let mut encoded = Vec::with_capacity(frontier.selections.len());
3236 for selection in &frontier.selections {
3237 encoded.push(self.encode_weighted_maxsat_subset(
3238 weighted,
3239 weights,
3240 &selection.soft_clause_indices,
3241 selection.status,
3242 )?);
3243 }
3244
3245 Ok(encoded)
3246 }
3247
3248 fn encode_weighted_maxsat_subset(
3249 &mut self,
3250 weighted: &SolveInstance,
3251 weights: &[f64],
3252 soft_clause_indices: &[usize],
3253 status: GpuSolverProductionMaxSatSearchStatus,
3254 ) -> Result<GpuSolverProductionEncodedMaxSatSearchCandidate> {
3255 let mut score = 0u64;
3256 let mut clauses = Vec::with_capacity(soft_clause_indices.len());
3257 for &idx in soft_clause_indices {
3258 let clause = &weighted.clauses[idx];
3259 let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3260 score = score.checked_add(weight).ok_or_else(|| {
3261 XlogError::UnsupportedEpistemicConstruct {
3262 construct: "GPU solver production MaxSAT encoding".to_string(),
3263 context: format!(
3264 "soft-clause selection score overflowed u64 while adding index {}",
3265 idx
3266 ),
3267 }
3268 })?;
3269 clauses.push(clause.clone());
3270 }
3271
3272 let candidate_instance = SolveInstance::new(weighted.num_vars, clauses);
3273 let data_plane_before = self.provider.host_transfer_stats();
3274 let launch_metadata_before = self.provider.host_launch_metadata_transfer_stats();
3275 let cnf = GpuCnf::from_host(&candidate_instance, &self.provider)?;
3276 let data_plane_after = self.provider.host_transfer_stats();
3277 let launch_metadata_after = self.provider.host_launch_metadata_transfer_stats();
3278 self.record_encoded_maxsat_cnf_upload_transfer_delta(
3279 data_plane_before,
3280 data_plane_after,
3281 launch_metadata_before,
3282 launch_metadata_after,
3283 )?;
3284 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_encodes);
3285 Ok(GpuSolverProductionEncodedMaxSatSearchCandidate { score, cnf, status })
3286 }
3287
3288 fn record_encoded_maxsat_cnf_upload_transfer_delta(
3289 &mut self,
3290 data_plane_before: xlog_cuda::provider::HostTransferStats,
3291 data_plane_after: xlog_cuda::provider::HostTransferStats,
3292 launch_metadata_before: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3293 launch_metadata_after: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3294 ) -> Result<()> {
3295 let data_plane_htod_calls = Self::checked_report_counter_delta(
3296 data_plane_after.htod_calls,
3297 data_plane_before.htod_calls,
3298 "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3299 )?;
3300 let data_plane_htod_bytes = Self::checked_report_counter_delta(
3301 data_plane_after.htod_bytes,
3302 data_plane_before.htod_bytes,
3303 "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3304 )?;
3305 let data_plane_dtoh_calls = Self::checked_report_counter_delta(
3306 data_plane_after.dtoh_calls,
3307 data_plane_before.dtoh_calls,
3308 "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3309 )?;
3310 let data_plane_dtoh_bytes = Self::checked_report_counter_delta(
3311 data_plane_after.dtoh_bytes,
3312 data_plane_before.dtoh_bytes,
3313 "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3314 )?;
3315 let launch_metadata_htod_calls = Self::checked_report_counter_delta(
3316 launch_metadata_after.htod_calls,
3317 launch_metadata_before.htod_calls,
3318 "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3319 )?;
3320 let launch_metadata_htod_bytes = Self::checked_report_counter_delta(
3321 launch_metadata_after.htod_bytes,
3322 launch_metadata_before.htod_bytes,
3323 "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3324 )?;
3325
3326 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls =
3327 Self::checked_trace_counter_add(
3328 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
3329 data_plane_htod_calls,
3330 "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3331 )?;
3332 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes =
3333 Self::checked_trace_counter_add(
3334 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
3335 data_plane_htod_bytes,
3336 "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3337 )?;
3338 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls =
3339 Self::checked_trace_counter_add(
3340 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls,
3341 data_plane_dtoh_calls,
3342 "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3343 )?;
3344 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes =
3345 Self::checked_trace_counter_add(
3346 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes,
3347 data_plane_dtoh_bytes,
3348 "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3349 )?;
3350 self.trace
3351 .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls = Self::checked_trace_counter_add(
3352 self.trace
3353 .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
3354 launch_metadata_htod_calls,
3355 "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3356 )?;
3357 self.trace
3358 .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes = Self::checked_trace_counter_add(
3359 self.trace
3360 .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes,
3361 launch_metadata_htod_bytes,
3362 "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3363 )?;
3364 Ok(())
3365 }
3366
3367 fn solve_weighted_maxsat_search_candidates(
3368 &mut self,
3369 workspace: &mut GpuCdclWorkspace,
3370 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3371 frontier_upper_bound_certificates: u64,
3372 ) -> Result<GpuSolverProductionMaxSatReport> {
3373 self.require_workspace_on_adapter_provider(workspace)?;
3374 self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3375
3376 let solves_before = self.trace.gpu_maxsat_candidate_solves;
3377 let unsat_prunes_before = self.trace.gpu_maxsat_unsat_candidate_prunes;
3378 let mut optimum_score = 0u64;
3379 let mut satisfiable_candidates = 0u64;
3380
3381 for candidate in candidates {
3382 match candidate.status {
3383 GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3384 let _assignment = self.solver.solve_expect_sat_with_branch_limit(
3385 candidate.cnf,
3386 candidate.branch_var_limit,
3387 )?;
3388 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
3389 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3390 satisfiable_candidates = Self::checked_trace_counter_add(
3391 satisfiable_candidates,
3392 1,
3393 "maxsat_satisfiable_candidates",
3394 )?;
3395 optimum_score = optimum_score.max(candidate.score);
3396 }
3397 GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {
3398 self.solve_expect_unsat_with_branch_limit_ws(
3399 workspace,
3400 candidate.cnf,
3401 candidate.branch_var_limit,
3402 )?;
3403 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3404 checked_solver_trace_counter_inc!(self, gpu_maxsat_unsat_candidate_prunes);
3405 }
3406 }
3407 }
3408
3409 checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
3410 let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
3411 self.trace.gpu_maxsat_candidate_solves,
3412 solves_before,
3413 "gpu_maxsat_candidate_solves",
3414 )?;
3415 if frontier_upper_bound_certificates != 0 {
3416 self.trace.gpu_maxsat_frontier_certified_candidate_solves =
3417 Self::checked_trace_counter_add(
3418 self.trace.gpu_maxsat_frontier_certified_candidate_solves,
3419 gpu_cdcl_candidate_solves,
3420 "gpu_maxsat_frontier_certified_candidate_solves",
3421 )?;
3422 }
3423
3424 Ok(GpuSolverProductionMaxSatReport {
3425 candidate_evidence_records: 0,
3426 optimum_score,
3427 candidates_checked: candidates.len() as u64,
3428 satisfiable_candidates,
3429 unsat_candidates_pruned: Self::checked_report_counter_delta(
3430 self.trace.gpu_maxsat_unsat_candidate_prunes,
3431 unsat_prunes_before,
3432 "gpu_maxsat_unsat_candidate_prunes",
3433 )?,
3434 gpu_cdcl_candidate_encodes: 0,
3435 gpu_cdcl_candidate_solves,
3436 frontier_upper_bound_certificates,
3437 })
3438 }
3439
3440 fn require_weighted_maxsat_search_candidates(
3441 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3442 ) -> Result<()> {
3443 if candidates.is_empty() {
3444 return Err(XlogError::UnsupportedEpistemicConstruct {
3445 construct: "GPU solver production MaxSAT search".to_string(),
3446 context: "bounded MaxSAT search requires at least one candidate CNF".to_string(),
3447 });
3448 }
3449 if !candidates.iter().any(|candidate| {
3450 matches!(
3451 candidate.status,
3452 GpuSolverProductionMaxSatSearchStatus::Satisfiable
3453 )
3454 }) {
3455 return Err(XlogError::UnsupportedEpistemicConstruct {
3456 construct: "GPU solver production MaxSAT search".to_string(),
3457 context: "bounded MaxSAT search requires at least one satisfiable GPU candidate"
3458 .to_string(),
3459 });
3460 }
3461 Ok(())
3462 }
3463
3464 fn require_weighted_maxsat_search_selections(
3465 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3466 ) -> Result<()> {
3467 if selections.is_empty() {
3468 return Err(XlogError::UnsupportedEpistemicConstruct {
3469 construct: "GPU solver production MaxSAT encoding".to_string(),
3470 context: "weighted MaxSAT encoding requires at least one selection".to_string(),
3471 });
3472 }
3473 Ok(())
3474 }
3475
3476 fn require_weighted_maxsat_encoding_inputs(
3477 weighted: &SolveInstance,
3478 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3479 ) -> Result<()> {
3480 Self::require_weighted_maxsat_search_selections(selections)?;
3481
3482 if weighted.objective != Objective::MaxSat {
3483 return Err(XlogError::UnsupportedEpistemicConstruct {
3484 construct: "GPU solver production MaxSAT encoding".to_string(),
3485 context: format!(
3486 "weighted MaxSAT encoding requires Objective::MaxSat, got {:?}",
3487 weighted.objective
3488 ),
3489 });
3490 }
3491 if weighted.num_vars == 0 {
3492 return Err(XlogError::UnsupportedEpistemicConstruct {
3493 construct: "GPU solver production MaxSAT encoding".to_string(),
3494 context: "weighted MaxSAT encoding requires num_vars > 0".to_string(),
3495 });
3496 }
3497
3498 let weights =
3499 weighted
3500 .weights
3501 .as_ref()
3502 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3503 construct: "GPU solver production MaxSAT encoding".to_string(),
3504 context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3505 .to_string(),
3506 })?;
3507 if weights.len() != weighted.clauses.len() {
3508 return Err(XlogError::UnsupportedEpistemicConstruct {
3509 construct: "GPU solver production MaxSAT encoding".to_string(),
3510 context: format!(
3511 "soft-clause weights length {} does not match clause count {}",
3512 weights.len(),
3513 weighted.clauses.len()
3514 ),
3515 });
3516 }
3517
3518 for selection in selections {
3519 if selection.soft_clause_indices.is_empty() {
3520 return Err(XlogError::UnsupportedEpistemicConstruct {
3521 construct: "GPU solver production MaxSAT encoding".to_string(),
3522 context:
3523 "weighted MaxSAT search selections must include at least one soft clause"
3524 .to_string(),
3525 });
3526 }
3527 let mut seen_indices = BTreeSet::new();
3528 for (position, &idx) in selection.soft_clause_indices.iter().enumerate() {
3529 if !seen_indices.insert(idx) {
3530 return Err(XlogError::UnsupportedEpistemicConstruct {
3531 construct: "GPU solver production MaxSAT encoding".to_string(),
3532 context: format!(
3533 "soft-clause selection duplicates index {} at position {}; \
3534 weighted MaxSAT candidates must count each soft clause at most once",
3535 idx, position
3536 ),
3537 });
3538 }
3539 }
3540 for &idx in selection.soft_clause_indices {
3541 let _clause = weighted.clauses.get(idx).ok_or_else(|| {
3542 XlogError::UnsupportedEpistemicConstruct {
3543 construct: "GPU solver production MaxSAT encoding".to_string(),
3544 context: format!(
3545 "soft-clause selection index {} is out of range for {} clauses",
3546 idx,
3547 weighted.clauses.len()
3548 ),
3549 }
3550 })?;
3551 let weight =
3552 *weights
3553 .get(idx)
3554 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3555 construct: "GPU solver production MaxSAT encoding".to_string(),
3556 context: format!(
3557 "soft-clause weight index {} is out of range for {} weights",
3558 idx,
3559 weights.len()
3560 ),
3561 })?;
3562 let _ = Self::soft_clause_weight_score(idx, weight)?;
3563 }
3564 }
3565 Ok(())
3566 }
3567
3568 fn complete_weighted_maxsat_frontier_selections(
3569 weighted: &SolveInstance,
3570 weights: &[f64],
3571 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3572 ) -> Result<GpuSolverProductionCompletedWeightedMaxSatFrontier> {
3573 let mut completed = Vec::with_capacity(selections.len());
3574 let mut completion_candidate_count = 0u64;
3575 let mut seen = BTreeMap::new();
3576
3577 for selection in selections {
3578 let mut indices = selection.soft_clause_indices.to_vec();
3579 indices.sort_unstable();
3580 match seen.get(&indices) {
3581 Some(status) if *status != selection.status => {
3582 return Err(XlogError::UnsupportedEpistemicConstruct {
3583 construct: "GPU solver production MaxSAT encoding".to_string(),
3584 context: format!(
3585 "soft-clause selection {:?} has conflicting statuses {:?} and {:?}",
3586 indices, status, selection.status
3587 ),
3588 });
3589 }
3590 Some(_) => continue,
3591 None => {
3592 seen.insert(indices.clone(), selection.status);
3593 completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3594 soft_clause_indices: indices,
3595 status: selection.status,
3596 });
3597 }
3598 }
3599 }
3600
3601 let all_clause_indices: Vec<_> = (0..weighted.clauses.len()).collect();
3602 let certificates = Self::unsat_frontier_certificates(weights, &completed)?;
3603 let disjoint_frontier = certificates.len() > 1
3604 && Self::frontier_certificates_are_pairwise_disjoint(&certificates);
3605 Self::require_weighted_maxsat_frontier_completion_bound(&certificates, disjoint_frontier)?;
3606 if disjoint_frontier {
3607 let mut exclusions = Vec::with_capacity(certificates.len());
3608 Self::complete_disjoint_unsat_frontier_boundaries(
3609 &certificates,
3610 0,
3611 &mut exclusions,
3612 &all_clause_indices,
3613 &mut seen,
3614 &mut completed,
3615 &mut completion_candidate_count,
3616 )?;
3617 } else {
3618 for certificate in &certificates {
3619 for &excluded_idx in &certificate.min_weight_indices {
3620 let boundary: Vec<_> = all_clause_indices
3621 .iter()
3622 .copied()
3623 .filter(|idx| *idx != excluded_idx)
3624 .collect();
3625 Self::push_completed_frontier_candidate(
3626 boundary,
3627 &mut seen,
3628 &mut completed,
3629 &mut completion_candidate_count,
3630 )?;
3631 }
3632 }
3633 }
3634
3635 Self::require_weighted_maxsat_frontier_upper_bound(weights, &completed)?;
3636 Ok(GpuSolverProductionCompletedWeightedMaxSatFrontier {
3637 selections: completed,
3638 completion_candidate_count,
3639 })
3640 }
3641
3642 fn require_weighted_maxsat_frontier_completion_bound(
3643 certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3644 disjoint_frontier: bool,
3645 ) -> Result<()> {
3646 let implied_candidates = if disjoint_frontier {
3647 certificates.iter().try_fold(1u64, |acc, certificate| {
3648 acc.checked_mul(certificate.min_weight_indices.len() as u64)
3649 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3650 construct: "GPU solver production MaxSAT encoding".to_string(),
3651 context: "weighted MaxSAT disjoint frontier completion bound overflowed"
3652 .to_string(),
3653 })
3654 })?
3655 } else {
3656 certificates.iter().try_fold(0u64, |acc, certificate| {
3657 acc.checked_add(certificate.min_weight_indices.len() as u64)
3658 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3659 construct: "GPU solver production MaxSAT encoding".to_string(),
3660 context: "weighted MaxSAT frontier completion bound overflowed".to_string(),
3661 })
3662 })?
3663 };
3664
3665 if implied_candidates > MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3666 return Err(XlogError::UnsupportedEpistemicConstruct {
3667 construct: "GPU solver production MaxSAT encoding".to_string(),
3668 context: format!(
3669 "weighted MaxSAT frontier completion would require {} CPU-generated \
3670 boundary candidates, exceeding production bound {}; provide explicit \
3671 GPU scheduler selections",
3672 implied_candidates, MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3673 ),
3674 });
3675 }
3676 Ok(())
3677 }
3678
3679 fn unsat_frontier_certificates(
3680 weights: &[f64],
3681 selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3682 ) -> Result<Vec<GpuSolverProductionUnsatFrontierCertificate>> {
3683 let mut certificates = Vec::new();
3684 for selection in selections {
3685 if selection.status != GpuSolverProductionMaxSatSearchStatus::Unsatisfiable {
3686 continue;
3687 }
3688 let mut indices = selection.soft_clause_indices.clone();
3689 indices.sort_unstable();
3690 let mut min_weight = None;
3691 let mut min_weight_indices = Vec::new();
3692 for &idx in &indices {
3693 let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3694 match min_weight {
3695 None => {
3696 min_weight = Some(weight);
3697 min_weight_indices.push(idx);
3698 }
3699 Some(current) if weight < current => {
3700 min_weight = Some(weight);
3701 min_weight_indices.clear();
3702 min_weight_indices.push(idx);
3703 }
3704 Some(current) if weight == current => min_weight_indices.push(idx),
3705 Some(_) => {}
3706 }
3707 }
3708 if let Some(min_weight) = min_weight {
3709 certificates.push(GpuSolverProductionUnsatFrontierCertificate {
3710 indices,
3711 min_weight,
3712 min_weight_indices,
3713 });
3714 }
3715 }
3716 Ok(certificates)
3717 }
3718
3719 fn frontier_certificates_are_pairwise_disjoint(
3720 certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3721 ) -> bool {
3722 let mut seen = BTreeSet::new();
3723 for certificate in certificates {
3724 for &idx in &certificate.indices {
3725 if !seen.insert(idx) {
3726 return false;
3727 }
3728 }
3729 }
3730 true
3731 }
3732
3733 fn complete_disjoint_unsat_frontier_boundaries(
3734 certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3735 depth: usize,
3736 exclusions: &mut Vec<usize>,
3737 all_clause_indices: &[usize],
3738 seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3739 completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3740 completion_candidate_count: &mut u64,
3741 ) -> Result<()> {
3742 if depth == certificates.len() {
3743 let exclusion_set: BTreeSet<_> = exclusions.iter().copied().collect();
3744 let boundary: Vec<_> = all_clause_indices
3745 .iter()
3746 .copied()
3747 .filter(|idx| !exclusion_set.contains(idx))
3748 .collect();
3749 return Self::push_completed_frontier_candidate(
3750 boundary,
3751 seen,
3752 completed,
3753 completion_candidate_count,
3754 );
3755 }
3756
3757 for &excluded_idx in &certificates[depth].min_weight_indices {
3758 exclusions.push(excluded_idx);
3759 Self::complete_disjoint_unsat_frontier_boundaries(
3760 certificates,
3761 depth + 1,
3762 exclusions,
3763 all_clause_indices,
3764 seen,
3765 completed,
3766 completion_candidate_count,
3767 )?;
3768 exclusions.pop();
3769 }
3770 Ok(())
3771 }
3772
3773 fn push_completed_frontier_candidate(
3774 boundary: Vec<usize>,
3775 seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3776 completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3777 completion_candidate_count: &mut u64,
3778 ) -> Result<()> {
3779 if boundary.is_empty() || seen.contains_key(&boundary) {
3780 return Ok(());
3781 }
3782 if *completion_candidate_count >= MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3783 return Err(XlogError::UnsupportedEpistemicConstruct {
3784 construct: "GPU solver production MaxSAT encoding".to_string(),
3785 context: format!(
3786 "weighted MaxSAT frontier completion exceeded production bound {}; \
3787 provide explicit GPU scheduler selections",
3788 MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3789 ),
3790 });
3791 }
3792 seen.insert(
3793 boundary.clone(),
3794 GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3795 );
3796 *completion_candidate_count =
3797 completion_candidate_count.checked_add(1).ok_or_else(|| {
3798 XlogError::UnsupportedEpistemicConstruct {
3799 construct: "GPU solver production MaxSAT encoding".to_string(),
3800 context: "frontier completion candidate count overflowed".to_string(),
3801 }
3802 })?;
3803 completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3804 soft_clause_indices: boundary,
3805 status: GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3806 });
3807 Ok(())
3808 }
3809
3810 fn require_weighted_maxsat_frontier_upper_bound(
3811 weights: &[f64],
3812 selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3813 ) -> Result<()> {
3814 let mut total_score = 0u64;
3815 for (idx, &weight) in weights.iter().enumerate() {
3816 total_score = total_score
3817 .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3818 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3819 construct: "GPU solver production MaxSAT encoding".to_string(),
3820 context: format!(
3821 "weighted MaxSAT total score overflowed while adding soft clause {}",
3822 idx
3823 ),
3824 })?;
3825 }
3826
3827 let mut upper_bound = total_score;
3828 let mut best_satisfiable_score = 0u64;
3829 let certificates = Self::unsat_frontier_certificates(weights, selections)?;
3830
3831 for selection in selections {
3832 let mut indices = selection.soft_clause_indices.to_vec();
3833 indices.sort_unstable();
3834 let score = Self::weighted_maxsat_selection_score(weights, &indices)?;
3835 match selection.status {
3836 GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3837 best_satisfiable_score = best_satisfiable_score.max(score);
3838 }
3839 GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {}
3840 }
3841 }
3842
3843 if certificates.len() > 1
3844 && Self::frontier_certificates_are_pairwise_disjoint(&certificates)
3845 {
3846 let certified_loss = certificates.iter().try_fold(0u64, |acc, certificate| {
3847 acc.checked_add(certificate.min_weight).ok_or_else(|| {
3848 XlogError::UnsupportedEpistemicConstruct {
3849 construct: "GPU solver production MaxSAT encoding".to_string(),
3850 context: "weighted MaxSAT disjoint frontier loss overflowed".to_string(),
3851 }
3852 })
3853 })?;
3854 upper_bound = total_score.checked_sub(certified_loss).ok_or_else(|| {
3855 XlogError::UnsupportedEpistemicConstruct {
3856 construct: "GPU solver production MaxSAT encoding".to_string(),
3857 context: format!(
3858 "weighted MaxSAT disjoint frontier loss {} exceeds total score {}",
3859 certified_loss, total_score
3860 ),
3861 }
3862 })?;
3863 } else {
3864 for certificate in &certificates {
3865 let certificate_bound =
3866 total_score
3867 .checked_sub(certificate.min_weight)
3868 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3869 construct: "GPU solver production MaxSAT encoding".to_string(),
3870 context: format!(
3871 "weighted MaxSAT UNSAT certificate {:?} has minimum weight {} above total score {}",
3872 certificate.indices, certificate.min_weight, total_score
3873 ),
3874 })?;
3875 upper_bound = upper_bound.min(certificate_bound);
3876 }
3877 }
3878
3879 if best_satisfiable_score < upper_bound {
3880 return Err(XlogError::UnsupportedEpistemicConstruct {
3881 construct: "GPU solver production MaxSAT encoding".to_string(),
3882 context: format!(
3883 "weighted MaxSAT frontier is incomplete: best GPU-certified satisfiable score {} is below the certified upper bound {}",
3884 best_satisfiable_score, upper_bound
3885 ),
3886 });
3887 }
3888 Ok(())
3889 }
3890
3891 fn weighted_maxsat_selection_score(weights: &[f64], indices: &[usize]) -> Result<u64> {
3892 let mut score = 0u64;
3893 for &idx in indices {
3894 let weight =
3895 *weights
3896 .get(idx)
3897 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3898 construct: "GPU solver production MaxSAT encoding".to_string(),
3899 context: format!(
3900 "soft-clause weight index {} is out of range for {} weights",
3901 idx,
3902 weights.len()
3903 ),
3904 })?;
3905 score = score
3906 .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3907 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3908 construct: "GPU solver production MaxSAT encoding".to_string(),
3909 context: format!(
3910 "soft-clause selection score overflowed u64 while adding index {}",
3911 idx
3912 ),
3913 })?;
3914 }
3915 Ok(score)
3916 }
3917
3918 fn soft_clause_weight_score(idx: usize, weight: f64) -> Result<u64> {
3919 if !weight.is_finite() || weight < 0.0 || weight.fract() != 0.0 {
3920 return Err(XlogError::UnsupportedEpistemicConstruct {
3921 construct: "GPU solver production MaxSAT encoding".to_string(),
3922 context: format!(
3923 "soft-clause weight at index {} must be a finite nonnegative integer, got {}",
3924 idx, weight
3925 ),
3926 });
3927 }
3928 if weight >= u64::MAX as f64 {
3929 return Err(XlogError::UnsupportedEpistemicConstruct {
3930 construct: "GPU solver production MaxSAT encoding".to_string(),
3931 context: format!(
3932 "soft-clause weight at index {} exceeds u64 score range",
3933 idx
3934 ),
3935 });
3936 }
3937 Ok(weight as u64)
3938 }
3939
3940 pub fn solve_weighted_maxsat_search_with_gpu_execution_result(
3947 &mut self,
3948 provider: &CudaKernelProvider,
3949 result: &EpistemicGpuExecutionResult,
3950 workspace: &mut GpuCdclWorkspace,
3951 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3952 ) -> Result<GpuSolverProductionMaxSatReport> {
3953 self.with_trace_rollback(|this| {
3954 this.solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3955 provider, result, workspace, candidates,
3956 )
3957 })
3958 }
3959
3960 fn solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3961 &mut self,
3962 provider: &CudaKernelProvider,
3963 result: &EpistemicGpuExecutionResult,
3964 workspace: &mut GpuCdclWorkspace,
3965 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3966 ) -> Result<GpuSolverProductionMaxSatReport> {
3967 self.require_workspace_on_adapter_provider(workspace)?;
3968 self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3969 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
3970 let events_before = self.trace.accepted_path_event_snapshot()?;
3971 let mut report = self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
3972 report.candidate_evidence_records = 1;
3973 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
3974 self.record_accepted_gpu_candidate_state(&state)?;
3975 Ok(report)
3976 }
3977
3978 pub fn solve_weighted_maxsat_search_with_gpu_batch_execution_result(
3980 &mut self,
3981 provider: &CudaKernelProvider,
3982 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3983 workspace: &mut GpuCdclWorkspace,
3984 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3985 ) -> Result<GpuSolverProductionMaxSatReport> {
3986 self.with_trace_rollback(|this| {
3987 this.require_workspace_on_adapter_provider(workspace)?;
3988 this.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3989 let results =
3990 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3991 let report = this
3992 .solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
3993 provider, &results, workspace, candidates,
3994 )?;
3995 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3996 Ok(report)
3997 })
3998 }
3999
4000 pub fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
4002 &mut self,
4003 provider: &CudaKernelProvider,
4004 results: &[&EpistemicGpuExecutionResult],
4005 workspace: &mut GpuCdclWorkspace,
4006 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
4007 ) -> Result<GpuSolverProductionMaxSatReport> {
4008 self.with_trace_rollback(|this| {
4009 this.solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
4010 provider, results, workspace, candidates,
4011 )
4012 })
4013 }
4014
4015 fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
4016 &mut self,
4017 provider: &CudaKernelProvider,
4018 results: &[&EpistemicGpuExecutionResult],
4019 workspace: &mut GpuCdclWorkspace,
4020 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
4021 ) -> Result<GpuSolverProductionMaxSatReport> {
4022 if results.is_empty() {
4023 return Err(XlogError::UnsupportedEpistemicConstruct {
4024 construct: "GPU solver production MaxSAT search".to_string(),
4025 context: "multi-candidate MaxSAT search requires at least one accepted GPU result"
4026 .to_string(),
4027 });
4028 }
4029 self.require_workspace_on_adapter_provider(workspace)?;
4030 self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
4031 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4032
4033 let mut report = GpuSolverProductionMaxSatReport::default();
4034 for state in &states {
4035 let events_before = self.trace.accepted_path_event_snapshot()?;
4036 let step_report =
4037 self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4038 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4039 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4040 checked_solver_report_counter_add!(
4041 report,
4042 candidates_checked,
4043 step_report.candidates_checked
4044 );
4045 checked_solver_report_counter_add!(
4046 report,
4047 satisfiable_candidates,
4048 step_report.satisfiable_candidates
4049 );
4050 checked_solver_report_counter_add!(
4051 report,
4052 unsat_candidates_pruned,
4053 step_report.unsat_candidates_pruned
4054 );
4055 checked_solver_report_counter_add!(
4056 report,
4057 gpu_cdcl_candidate_encodes,
4058 step_report.gpu_cdcl_candidate_encodes
4059 );
4060 checked_solver_report_counter_add!(
4061 report,
4062 gpu_cdcl_candidate_solves,
4063 step_report.gpu_cdcl_candidate_solves
4064 );
4065 checked_solver_report_counter_add!(
4066 report,
4067 frontier_upper_bound_certificates,
4068 step_report.frontier_upper_bound_certificates
4069 );
4070 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4071 self.record_accepted_gpu_candidate_state(state)?;
4072 }
4073
4074 Ok(report)
4075 }
4076
4077 pub fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result(
4084 &mut self,
4085 provider: &CudaKernelProvider,
4086 result: &EpistemicGpuExecutionResult,
4087 workspace: &mut GpuCdclWorkspace,
4088 weighted: &SolveInstance,
4089 branch_var_limit: &TrackedCudaSlice<u32>,
4090 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4091 ) -> Result<GpuSolverProductionMaxSatReport> {
4092 self.with_trace_rollback(|this| {
4093 this.solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4094 provider,
4095 result,
4096 workspace,
4097 weighted,
4098 branch_var_limit,
4099 selections,
4100 )
4101 })
4102 }
4103
4104 fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4105 &mut self,
4106 provider: &CudaKernelProvider,
4107 result: &EpistemicGpuExecutionResult,
4108 workspace: &mut GpuCdclWorkspace,
4109 weighted: &SolveInstance,
4110 branch_var_limit: &TrackedCudaSlice<u32>,
4111 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4112 ) -> Result<GpuSolverProductionMaxSatReport> {
4113 self.require_workspace_on_adapter_provider(workspace)?;
4114 self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4115 workspace,
4116 weighted,
4117 branch_var_limit,
4118 selections,
4119 )?;
4120 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4121 let events_before = self.trace.accepted_path_event_snapshot()?;
4122 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4123 let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4124 let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4125 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4126 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4127 certificates_before,
4128 "gpu_maxsat_frontier_upper_bound_certificates",
4129 )?;
4130 let search_candidates: Vec<_> = encoded
4131 .iter()
4132 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4133 score: candidate.score,
4134 cnf: &candidate.cnf,
4135 branch_var_limit,
4136 status: candidate.status,
4137 })
4138 .collect();
4139 let mut report = self.solve_weighted_maxsat_search_candidates(
4140 workspace,
4141 &search_candidates,
4142 frontier_upper_bound_certificates,
4143 )?;
4144 report.candidate_evidence_records = 1;
4145 report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4146 self.trace.gpu_maxsat_candidate_encodes,
4147 encodes_before,
4148 "gpu_cdcl_candidate_encodes",
4149 )?;
4150 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4151 self.record_accepted_gpu_candidate_state(&state)?;
4152 Ok(report)
4153 }
4154
4155 pub fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4163 &mut self,
4164 provider: &CudaKernelProvider,
4165 results: &[&EpistemicGpuExecutionResult],
4166 workspace: &mut GpuCdclWorkspace,
4167 weighted: &SolveInstance,
4168 branch_var_limit: &TrackedCudaSlice<u32>,
4169 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4170 ) -> Result<GpuSolverProductionMaxSatReport> {
4171 self.with_trace_rollback(|this| {
4172 this.solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4173 provider,
4174 results,
4175 workspace,
4176 weighted,
4177 branch_var_limit,
4178 selections,
4179 )
4180 })
4181 }
4182
4183 fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4184 &mut self,
4185 provider: &CudaKernelProvider,
4186 results: &[&EpistemicGpuExecutionResult],
4187 workspace: &mut GpuCdclWorkspace,
4188 weighted: &SolveInstance,
4189 branch_var_limit: &TrackedCudaSlice<u32>,
4190 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4191 ) -> Result<GpuSolverProductionMaxSatReport> {
4192 if results.is_empty() {
4193 return Err(XlogError::UnsupportedEpistemicConstruct {
4194 construct: "GPU solver production MaxSAT encoding".to_string(),
4195 context:
4196 "multi-candidate weighted MaxSAT encoded search requires at least one accepted GPU result"
4197 .to_string(),
4198 });
4199 }
4200 self.require_workspace_on_adapter_provider(workspace)?;
4201 self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4202 workspace,
4203 weighted,
4204 branch_var_limit,
4205 selections,
4206 )?;
4207 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4208
4209 let mut report = GpuSolverProductionMaxSatReport::default();
4210 for state in &states {
4211 let events_before = self.trace.accepted_path_event_snapshot()?;
4212 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4213 let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4214 let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4215 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4216 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4217 certificates_before,
4218 "gpu_maxsat_frontier_upper_bound_certificates",
4219 )?;
4220 let search_candidates: Vec<_> = encoded
4221 .iter()
4222 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4223 score: candidate.score,
4224 cnf: &candidate.cnf,
4225 branch_var_limit,
4226 status: candidate.status,
4227 })
4228 .collect();
4229 let step_report = self.solve_weighted_maxsat_search_candidates(
4230 workspace,
4231 &search_candidates,
4232 frontier_upper_bound_certificates,
4233 )?;
4234 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4235 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4236 checked_solver_report_counter_add!(
4237 report,
4238 candidates_checked,
4239 step_report.candidates_checked
4240 );
4241 checked_solver_report_counter_add!(
4242 report,
4243 satisfiable_candidates,
4244 step_report.satisfiable_candidates
4245 );
4246 checked_solver_report_counter_add!(
4247 report,
4248 unsat_candidates_pruned,
4249 step_report.unsat_candidates_pruned
4250 );
4251 let encoded_delta = Self::checked_report_counter_delta(
4252 self.trace.gpu_maxsat_candidate_encodes,
4253 encodes_before,
4254 "gpu_cdcl_candidate_encodes",
4255 )?;
4256 checked_solver_report_counter_add!(report, gpu_cdcl_candidate_encodes, encoded_delta);
4257 checked_solver_report_counter_add!(
4258 report,
4259 gpu_cdcl_candidate_solves,
4260 step_report.gpu_cdcl_candidate_solves
4261 );
4262 checked_solver_report_counter_add!(
4263 report,
4264 frontier_upper_bound_certificates,
4265 step_report.frontier_upper_bound_certificates
4266 );
4267 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4268 self.record_accepted_gpu_candidate_state(state)?;
4269 }
4270
4271 Ok(report)
4272 }
4273
4274 pub fn solve_weighted_maxsat_encoded_search_with_gpu_batch_execution_result(
4280 &mut self,
4281 provider: &CudaKernelProvider,
4282 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4283 workspace: &mut GpuCdclWorkspace,
4284 weighted: &SolveInstance,
4285 branch_var_limit: &TrackedCudaSlice<u32>,
4286 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4287 ) -> Result<GpuSolverProductionMaxSatReport> {
4288 self.with_trace_rollback(|this| {
4289 this.require_workspace_on_adapter_provider(workspace)?;
4290 this.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4291 workspace,
4292 weighted,
4293 branch_var_limit,
4294 selections,
4295 )?;
4296 let results =
4297 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4298 let report = this
4299 .solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4300 provider,
4301 &results,
4302 workspace,
4303 weighted,
4304 branch_var_limit,
4305 selections,
4306 )?;
4307 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4308 Ok(report)
4309 })
4310 }
4311
4312 fn add_maxsat_schedule_step_report(
4313 report: &mut GpuSolverProductionMaxSatScheduleReport,
4314 step_report: GpuSolverProductionMaxSatReport,
4315 ) -> Result<()> {
4316 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4317 checked_solver_report_counter_add!(
4318 report,
4319 candidates_checked,
4320 step_report.candidates_checked
4321 );
4322 checked_solver_report_counter_add!(
4323 report,
4324 satisfiable_candidates,
4325 step_report.satisfiable_candidates
4326 );
4327 checked_solver_report_counter_add!(
4328 report,
4329 unsat_candidates_pruned,
4330 step_report.unsat_candidates_pruned
4331 );
4332 checked_solver_report_counter_add!(
4333 report,
4334 gpu_cdcl_candidate_encodes,
4335 step_report.gpu_cdcl_candidate_encodes
4336 );
4337 checked_solver_report_counter_add!(
4338 report,
4339 gpu_cdcl_candidate_solves,
4340 step_report.gpu_cdcl_candidate_solves
4341 );
4342 checked_solver_report_counter_add!(
4343 report,
4344 frontier_upper_bound_certificates,
4345 step_report.frontier_upper_bound_certificates
4346 );
4347 Ok(())
4348 }
4349
4350 fn solve_maxsat_schedule_jobs(
4351 &mut self,
4352 workspace: &mut GpuCdclWorkspace,
4353 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4354 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4355 self.require_workspace_on_adapter_provider(workspace)?;
4356 Self::require_maxsat_schedule_jobs(jobs)?;
4357 self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4358
4359 let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4360 for job in jobs {
4361 checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_jobs);
4362 checked_solver_report_counter_inc!(report, jobs);
4363
4364 match job {
4365 GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4366 checked_solver_trace_counter_inc!(
4367 self,
4368 gpu_maxsat_scheduler_candidate_set_jobs
4369 );
4370 checked_solver_report_counter_inc!(report, candidate_set_jobs);
4371 let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4372 Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4373 }
4374 GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4375 checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_search_jobs);
4376 checked_solver_report_counter_inc!(report, search_jobs);
4377 let step_report =
4378 self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4379 Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4380 }
4381 GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4382 weighted,
4383 branch_var_limit,
4384 selections,
4385 } => {
4386 checked_solver_trace_counter_inc!(
4387 self,
4388 gpu_maxsat_scheduler_encoded_search_jobs
4389 );
4390 checked_solver_report_counter_inc!(report, encoded_search_jobs);
4391 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4392 let certificates_before =
4393 self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4394 let encoded =
4395 self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4396 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4397 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4398 certificates_before,
4399 "gpu_maxsat_frontier_upper_bound_certificates",
4400 )?;
4401 let search_candidates: Vec<_> = encoded
4402 .iter()
4403 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4404 score: candidate.score,
4405 cnf: &candidate.cnf,
4406 branch_var_limit,
4407 status: candidate.status,
4408 })
4409 .collect();
4410 let mut step_report = self.solve_weighted_maxsat_search_candidates(
4411 workspace,
4412 &search_candidates,
4413 frontier_upper_bound_certificates,
4414 )?;
4415 step_report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4416 self.trace.gpu_maxsat_candidate_encodes,
4417 encodes_before,
4418 "gpu_cdcl_candidate_encodes",
4419 )?;
4420 Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4421 }
4422 GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4423 if reason.trim().is_empty() {
4424 return Err(XlogError::UnsupportedEpistemicConstruct {
4425 construct: "GPU solver production MaxSAT scheduler".to_string(),
4426 context: "UNKNOWN scheduler status requires a diagnostic reason"
4427 .to_string(),
4428 });
4429 }
4430 checked_solver_trace_counter_inc!(
4431 self,
4432 gpu_maxsat_scheduler_unknown_status_jobs
4433 );
4434 checked_solver_report_counter_inc!(report, unknown_jobs);
4435 }
4436 GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4437 if *budget_micros == 0 {
4438 return Err(XlogError::UnsupportedEpistemicConstruct {
4439 construct: "GPU solver production MaxSAT scheduler".to_string(),
4440 context: "TIMEOUT scheduler status requires a nonzero budget"
4441 .to_string(),
4442 });
4443 }
4444 checked_solver_trace_counter_inc!(
4445 self,
4446 gpu_maxsat_scheduler_timeout_status_jobs
4447 );
4448 checked_solver_report_counter_inc!(report, timeout_jobs);
4449 }
4450 }
4451 }
4452
4453 Ok(report)
4454 }
4455
4456 fn require_maxsat_schedule_jobs(
4457 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4458 ) -> Result<()> {
4459 if jobs.is_empty() {
4460 return Err(XlogError::UnsupportedEpistemicConstruct {
4461 construct: "GPU solver production MaxSAT scheduler".to_string(),
4462 context: "accepted MaxSAT scheduler requires at least one GPU job".to_string(),
4463 });
4464 }
4465
4466 for job in jobs {
4467 match job {
4468 GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4469 Self::require_weighted_maxsat_candidates(candidates)?;
4470 }
4471 GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4472 Self::require_weighted_maxsat_search_candidates(candidates)?;
4473 }
4474 GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4475 weighted,
4476 selections,
4477 ..
4478 } => {
4479 Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4480 }
4481 GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4482 if reason.trim().is_empty() {
4483 return Err(XlogError::UnsupportedEpistemicConstruct {
4484 construct: "GPU solver production MaxSAT scheduler".to_string(),
4485 context: "UNKNOWN scheduler status requires a diagnostic reason"
4486 .to_string(),
4487 });
4488 }
4489 }
4490 GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4491 if *budget_micros == 0 {
4492 return Err(XlogError::UnsupportedEpistemicConstruct {
4493 construct: "GPU solver production MaxSAT scheduler".to_string(),
4494 context: "TIMEOUT scheduler status requires a nonzero budget"
4495 .to_string(),
4496 });
4497 }
4498 }
4499 }
4500 }
4501 Ok(())
4502 }
4503
4504 fn require_maxsat_schedule_job_artifacts(
4505 &self,
4506 workspace: &GpuCdclWorkspace,
4507 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4508 ) -> Result<()> {
4509 for job in jobs {
4510 match job {
4511 GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4512 self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4513 }
4514 GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4515 self.require_weighted_maxsat_search_candidates_and_artifacts(
4516 workspace, candidates,
4517 )?;
4518 }
4519 GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4520 weighted,
4521 branch_var_limit,
4522 ..
4523 } => {
4524 self.require_workspace_capacity_for_weighted_maxsat_encoding(
4525 workspace,
4526 weighted,
4527 "GPU solver production MaxSAT scheduler",
4528 )?;
4529 self.require_branch_var_limit_on_adapter_provider(
4530 branch_var_limit,
4531 "GPU solver production MaxSAT scheduler",
4532 )?;
4533 }
4534 GpuSolverProductionMaxSatScheduleJob::Unknown { .. }
4535 | GpuSolverProductionMaxSatScheduleJob::Timeout { .. } => {}
4536 }
4537 }
4538 Ok(())
4539 }
4540
4541 pub fn solve_maxsat_schedule_with_gpu_execution_results(
4549 &mut self,
4550 provider: &CudaKernelProvider,
4551 results: &[&EpistemicGpuExecutionResult],
4552 workspace: &mut GpuCdclWorkspace,
4553 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4554 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4555 self.with_trace_rollback(|this| {
4556 this.solve_maxsat_schedule_with_gpu_execution_results_impl(
4557 provider, results, workspace, jobs,
4558 )
4559 })
4560 }
4561
4562 fn solve_maxsat_schedule_with_gpu_execution_results_impl(
4563 &mut self,
4564 provider: &CudaKernelProvider,
4565 results: &[&EpistemicGpuExecutionResult],
4566 workspace: &mut GpuCdclWorkspace,
4567 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4568 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4569 if results.is_empty() {
4570 return Err(XlogError::UnsupportedEpistemicConstruct {
4571 construct: "GPU solver production MaxSAT scheduler".to_string(),
4572 context: "MaxSAT scheduler requires at least one accepted GPU result".to_string(),
4573 });
4574 }
4575 Self::require_maxsat_schedule_jobs(jobs)?;
4576 self.require_workspace_on_adapter_provider(workspace)?;
4577 self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4578 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4579
4580 let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4581 for state in &states {
4582 let events_before = self.trace.accepted_path_event_snapshot()?;
4583 let step_report = self.solve_maxsat_schedule_jobs(workspace, jobs)?;
4584 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4585 checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4586 checked_solver_report_counter_add!(
4587 report,
4588 candidate_set_jobs,
4589 step_report.candidate_set_jobs
4590 );
4591 checked_solver_report_counter_add!(report, search_jobs, step_report.search_jobs);
4592 checked_solver_report_counter_add!(
4593 report,
4594 encoded_search_jobs,
4595 step_report.encoded_search_jobs
4596 );
4597 checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4598 checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4599 Self::add_maxsat_schedule_step_report(
4600 &mut report,
4601 GpuSolverProductionMaxSatReport {
4602 optimum_score: step_report.optimum_score,
4603 candidates_checked: step_report.candidates_checked,
4604 satisfiable_candidates: step_report.satisfiable_candidates,
4605 unsat_candidates_pruned: step_report.unsat_candidates_pruned,
4606 gpu_cdcl_candidate_encodes: step_report.gpu_cdcl_candidate_encodes,
4607 gpu_cdcl_candidate_solves: step_report.gpu_cdcl_candidate_solves,
4608 frontier_upper_bound_certificates: step_report
4609 .frontier_upper_bound_certificates,
4610 ..GpuSolverProductionMaxSatReport::default()
4611 },
4612 )?;
4613 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4614 self.record_accepted_gpu_candidate_state(state)?;
4615 }
4616
4617 Ok(report)
4618 }
4619
4620 pub fn solve_maxsat_schedule_with_gpu_batch_execution_result(
4627 &mut self,
4628 provider: &CudaKernelProvider,
4629 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4630 workspace: &mut GpuCdclWorkspace,
4631 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4632 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4633 self.with_trace_rollback(|this| {
4634 Self::require_maxsat_schedule_jobs(jobs)?;
4635 this.require_workspace_on_adapter_provider(workspace)?;
4636 this.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4637 let results =
4638 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4639 let report = this.solve_maxsat_schedule_with_gpu_execution_results(
4640 provider, &results, workspace, jobs,
4641 )?;
4642 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4643 Ok(report)
4644 })
4645 }
4646
4647 fn solve_portfolio_jobs(
4648 &mut self,
4649 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4650 ) -> Result<GpuSolverProductionPortfolioReport> {
4651 self.require_portfolio_jobs_and_artifacts(jobs)?;
4652
4653 let mut report = GpuSolverProductionPortfolioReport::default();
4654 for job in jobs {
4655 checked_solver_trace_counter_inc!(self, gpu_portfolio_jobs);
4656 checked_solver_report_counter_inc!(report, jobs);
4657
4658 match job {
4659 GpuSolverProductionPortfolioJob::Sat {
4660 cnf,
4661 branch_var_limit,
4662 } => {
4663 let _assignment = self
4664 .solver
4665 .solve_expect_sat_with_branch_limit(cnf, branch_var_limit)?;
4666 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
4667 checked_solver_trace_counter_inc!(self, gpu_portfolio_sat_jobs);
4668 checked_solver_report_counter_inc!(report, sat_jobs);
4669 }
4670 GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4671 let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4672 checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4673 checked_solver_report_counter_inc!(report, maxsat_jobs);
4674 Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4675 }
4676 GpuSolverProductionPortfolioJob::EncodedMaxSat {
4677 weighted,
4678 branch_var_limit,
4679 selections,
4680 } => {
4681 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4682 let certificates_before =
4683 self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4684 let encoded =
4685 self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4686 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4687 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4688 certificates_before,
4689 "gpu_maxsat_frontier_upper_bound_certificates",
4690 )?;
4691 let search_candidates: Vec<_> = encoded
4692 .iter()
4693 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4694 score: candidate.score,
4695 cnf: &candidate.cnf,
4696 branch_var_limit,
4697 status: candidate.status,
4698 })
4699 .collect();
4700 let mut workspace = self.new_workspace(
4701 weighted.num_vars,
4702 Self::checked_workspace_clause_cap(weighted)?,
4703 )?;
4704 let mut maxsat = self.solve_weighted_maxsat_search_candidates(
4705 &mut workspace,
4706 &search_candidates,
4707 frontier_upper_bound_certificates,
4708 )?;
4709 maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4710 self.trace.gpu_maxsat_candidate_encodes,
4711 encodes_before,
4712 "gpu_cdcl_candidate_encodes",
4713 )?;
4714 checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4715 checked_solver_report_counter_inc!(report, maxsat_jobs);
4716 Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4717 }
4718 GpuSolverProductionPortfolioJob::Unknown { .. } => {
4719 checked_solver_trace_counter_inc!(self, gpu_portfolio_unknown_status_jobs);
4720 checked_solver_report_counter_inc!(report, unknown_jobs);
4721 }
4722 GpuSolverProductionPortfolioJob::Timeout { .. } => {
4723 checked_solver_trace_counter_inc!(self, gpu_portfolio_timeout_status_jobs);
4724 checked_solver_report_counter_inc!(report, timeout_jobs);
4725 }
4726 }
4727 }
4728
4729 Ok(report)
4730 }
4731
4732 fn require_portfolio_jobs(jobs: &[GpuSolverProductionPortfolioJob<'_>]) -> Result<()> {
4733 if jobs.is_empty() {
4734 return Err(XlogError::UnsupportedEpistemicConstruct {
4735 construct: "GPU solver production portfolio".to_string(),
4736 context: "accepted solver portfolio requires at least one GPU job".to_string(),
4737 });
4738 }
4739
4740 for job in jobs {
4741 match job {
4742 GpuSolverProductionPortfolioJob::Sat { .. } => {}
4743 GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4744 Self::require_weighted_maxsat_candidates(candidates)?;
4745 }
4746 GpuSolverProductionPortfolioJob::EncodedMaxSat {
4747 weighted,
4748 selections,
4749 ..
4750 } => {
4751 Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4752 Self::checked_workspace_clause_cap(weighted)?;
4753 }
4754 GpuSolverProductionPortfolioJob::Unknown { reason } => {
4755 if reason.trim().is_empty() {
4756 return Err(XlogError::UnsupportedEpistemicConstruct {
4757 construct: "GPU solver production portfolio".to_string(),
4758 context: "UNKNOWN portfolio status requires a diagnostic reason"
4759 .to_string(),
4760 });
4761 }
4762 }
4763 GpuSolverProductionPortfolioJob::Timeout { budget_micros } => {
4764 if *budget_micros == 0 {
4765 return Err(XlogError::UnsupportedEpistemicConstruct {
4766 construct: "GPU solver production portfolio".to_string(),
4767 context: "TIMEOUT portfolio status requires a nonzero budget"
4768 .to_string(),
4769 });
4770 }
4771 }
4772 }
4773 }
4774 Ok(())
4775 }
4776
4777 fn require_portfolio_job_artifacts(
4778 &self,
4779 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4780 ) -> Result<()> {
4781 for job in jobs {
4782 match job {
4783 GpuSolverProductionPortfolioJob::Sat {
4784 cnf,
4785 branch_var_limit,
4786 } => {
4787 self.require_solver_artifact_on_adapter_provider(
4788 cnf,
4789 branch_var_limit,
4790 "GPU solver production portfolio",
4791 )?;
4792 }
4793 GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4794 self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4795 }
4796 GpuSolverProductionPortfolioJob::EncodedMaxSat {
4797 branch_var_limit, ..
4798 } => {
4799 self.require_branch_var_limit_on_adapter_provider(
4800 branch_var_limit,
4801 "GPU solver production portfolio",
4802 )?;
4803 }
4804 GpuSolverProductionPortfolioJob::Unknown { .. }
4805 | GpuSolverProductionPortfolioJob::Timeout { .. } => {}
4806 }
4807 }
4808 Ok(())
4809 }
4810
4811 fn require_portfolio_jobs_and_artifacts(
4812 &self,
4813 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4814 ) -> Result<()> {
4815 Self::require_portfolio_jobs(jobs)?;
4816 self.require_portfolio_job_artifacts(jobs)
4817 }
4818
4819 fn add_portfolio_maxsat_report(
4820 report: &mut GpuSolverProductionPortfolioReport,
4821 maxsat: GpuSolverProductionMaxSatReport,
4822 ) -> Result<()> {
4823 checked_solver_report_counter_add!(report, maxsat_optimum_scores, maxsat.optimum_score);
4824 checked_solver_report_counter_add!(
4825 report,
4826 maxsat_candidates_checked,
4827 maxsat.candidates_checked
4828 );
4829 checked_solver_report_counter_add!(
4830 report,
4831 maxsat_satisfiable_candidates,
4832 maxsat.satisfiable_candidates
4833 );
4834 checked_solver_report_counter_add!(
4835 report,
4836 maxsat_unsat_candidates_pruned,
4837 maxsat.unsat_candidates_pruned
4838 );
4839 checked_solver_report_counter_add!(
4840 report,
4841 maxsat_gpu_cdcl_candidate_encodes,
4842 maxsat.gpu_cdcl_candidate_encodes
4843 );
4844 checked_solver_report_counter_add!(
4845 report,
4846 maxsat_gpu_cdcl_candidate_solves,
4847 maxsat.gpu_cdcl_candidate_solves
4848 );
4849 checked_solver_report_counter_add!(
4850 report,
4851 maxsat_frontier_upper_bound_certificates,
4852 maxsat.frontier_upper_bound_certificates
4853 );
4854 Ok(())
4855 }
4856
4857 fn add_portfolio_report(
4858 report: &mut GpuSolverProductionPortfolioReport,
4859 step_report: GpuSolverProductionPortfolioReport,
4860 ) -> Result<()> {
4861 checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4862 checked_solver_report_counter_add!(report, sat_jobs, step_report.sat_jobs);
4863 checked_solver_report_counter_add!(report, maxsat_jobs, step_report.maxsat_jobs);
4864 checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4865 checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4866 checked_solver_report_counter_add!(
4867 report,
4868 maxsat_optimum_scores,
4869 step_report.maxsat_optimum_scores
4870 );
4871 checked_solver_report_counter_add!(
4872 report,
4873 maxsat_candidates_checked,
4874 step_report.maxsat_candidates_checked
4875 );
4876 checked_solver_report_counter_add!(
4877 report,
4878 maxsat_satisfiable_candidates,
4879 step_report.maxsat_satisfiable_candidates
4880 );
4881 checked_solver_report_counter_add!(
4882 report,
4883 maxsat_unsat_candidates_pruned,
4884 step_report.maxsat_unsat_candidates_pruned
4885 );
4886 checked_solver_report_counter_add!(
4887 report,
4888 maxsat_gpu_cdcl_candidate_encodes,
4889 step_report.maxsat_gpu_cdcl_candidate_encodes
4890 );
4891 checked_solver_report_counter_add!(
4892 report,
4893 maxsat_gpu_cdcl_candidate_solves,
4894 step_report.maxsat_gpu_cdcl_candidate_solves
4895 );
4896 checked_solver_report_counter_add!(
4897 report,
4898 maxsat_frontier_upper_bound_certificates,
4899 step_report.maxsat_frontier_upper_bound_certificates
4900 );
4901 Ok(())
4902 }
4903
4904 pub fn solve_portfolio_with_gpu_execution_result(
4910 &mut self,
4911 provider: &CudaKernelProvider,
4912 result: &EpistemicGpuExecutionResult,
4913 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4914 ) -> Result<GpuSolverProductionPortfolioReport> {
4915 self.with_trace_rollback(|this| {
4916 this.solve_portfolio_with_gpu_execution_result_impl(provider, result, jobs)
4917 })
4918 }
4919
4920 fn solve_portfolio_with_gpu_execution_result_impl(
4921 &mut self,
4922 provider: &CudaKernelProvider,
4923 result: &EpistemicGpuExecutionResult,
4924 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4925 ) -> Result<GpuSolverProductionPortfolioReport> {
4926 self.require_portfolio_jobs_and_artifacts(jobs)?;
4927 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4928
4929 let events_before = self.trace.accepted_path_event_snapshot()?;
4930 let mut report = self.solve_portfolio_jobs(jobs)?;
4931 report.candidate_evidence_records = 1;
4932
4933 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4934 self.record_accepted_gpu_candidate_state(&state)?;
4935 Ok(report)
4936 }
4937
4938 pub fn solve_multi_candidate_portfolio_with_gpu_execution_results(
4940 &mut self,
4941 provider: &CudaKernelProvider,
4942 results: &[&EpistemicGpuExecutionResult],
4943 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4944 ) -> Result<GpuSolverProductionPortfolioReport> {
4945 self.with_trace_rollback(|this| {
4946 this.solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4947 provider, results, jobs,
4948 )
4949 })
4950 }
4951
4952 fn solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4953 &mut self,
4954 provider: &CudaKernelProvider,
4955 results: &[&EpistemicGpuExecutionResult],
4956 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4957 ) -> Result<GpuSolverProductionPortfolioReport> {
4958 if results.is_empty() {
4959 return Err(XlogError::UnsupportedEpistemicConstruct {
4960 construct: "GPU solver production portfolio".to_string(),
4961 context: "multi-candidate portfolio requires at least one accepted GPU result"
4962 .to_string(),
4963 });
4964 }
4965 self.require_portfolio_jobs_and_artifacts(jobs)?;
4966 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4967
4968 let mut report = GpuSolverProductionPortfolioReport::default();
4969 for state in &states {
4970 let events_before = self.trace.accepted_path_event_snapshot()?;
4971 let step_report = self.solve_portfolio_jobs(jobs)?;
4972 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4973 Self::add_portfolio_report(&mut report, step_report)?;
4974 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4975 self.record_accepted_gpu_candidate_state(state)?;
4976 }
4977
4978 Ok(report)
4979 }
4980
4981 pub fn solve_portfolio_with_gpu_batch_execution_result(
4987 &mut self,
4988 provider: &CudaKernelProvider,
4989 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4990 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4991 ) -> Result<GpuSolverProductionPortfolioReport> {
4992 self.with_trace_rollback(|this| {
4993 this.require_portfolio_jobs_and_artifacts(jobs)?;
4994 let results =
4995 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4996 let report = this.solve_multi_candidate_portfolio_with_gpu_execution_results(
4997 provider, &results, jobs,
4998 )?;
4999 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
5000 Ok(report)
5001 })
5002 }
5003}
5004
5005fn require_accepted_gpu_solver_states(
5006 provider: &CudaKernelProvider,
5007 results: &[&EpistemicGpuExecutionResult],
5008) -> Result<Vec<GpuSolverAcceptedCandidateState>> {
5009 results
5010 .iter()
5011 .map(|result| require_accepted_gpu_solver_evidence(provider, result))
5012 .collect()
5013}
5014
5015fn require_accepted_gpu_solver_evidence(
5016 provider: &CudaKernelProvider,
5017 result: &EpistemicGpuExecutionResult,
5018) -> Result<GpuSolverAcceptedCandidateState> {
5019 let provider_identity = EpistemicGpuProviderIdentity::from_provider(provider);
5020 if result.provider_identity != provider_identity {
5021 return Err(XlogError::UnsupportedEpistemicConstruct {
5022 construct: "accepted GPU solver candidate evidence".to_string(),
5023 context: format!(
5024 "solver evidence provider mismatch: result device={} provider device={} \
5025 result_device_ptr={} provider_device_ptr={} result_memory_ptr={} \
5026 provider_memory_ptr={}",
5027 result.provider_identity.device_ordinal,
5028 provider_identity.device_ordinal,
5029 result.provider_identity.device_ptr,
5030 provider_identity.device_ptr,
5031 result.provider_identity.memory_ptr,
5032 provider_identity.memory_ptr
5033 ),
5034 });
5035 }
5036 if result.candidate_generation.literal_count == 0
5040 || result.prepared.preflight.tuple_membership_binding_count == 0
5041 {
5042 return Err(XlogError::UnsupportedEpistemicConstruct {
5043 construct: "accepted GPU solver candidate evidence".to_string(),
5044 context: format!(
5045 "solver evidence requires at least one GPU-validated epistemic literal and \
5046 tuple-membership binding, got literals={} bindings={}",
5047 result.candidate_generation.literal_count,
5048 result.prepared.preflight.tuple_membership_binding_count
5049 ),
5050 });
5051 }
5052 if result.prepared.preflight.solver_assumption_binding_count == 0 {
5053 return Err(XlogError::UnsupportedEpistemicConstruct {
5054 construct: "accepted GPU solver candidate evidence".to_string(),
5055 context: "solver evidence requires planner-exported solver assumption bindings"
5056 .to_string(),
5057 });
5058 }
5059 if result.prepared.preflight.solver_required_capability_count
5060 < PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT as usize
5061 || result.prepared.preflight.solver_required_status_count
5062 < PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT as usize
5063 {
5064 return Err(XlogError::UnsupportedEpistemicConstruct {
5065 construct: "accepted GPU solver candidate evidence".to_string(),
5066 context: format!(
5067 "solver evidence requires the production capability/status contract, got \
5068 capabilities={} statuses={}",
5069 result.prepared.preflight.solver_required_capability_count,
5070 result.prepared.preflight.solver_required_status_count
5071 ),
5072 });
5073 }
5074 result.require_runtime_dispatch_certification()?;
5075 result
5076 .model_membership
5077 .require_stable_model_tuple_source()?;
5078 if result.constraint_validation.violated_constraint_relations != 0 {
5079 return Err(XlogError::UnsupportedEpistemicConstruct {
5080 construct: "accepted GPU solver candidate evidence".to_string(),
5081 context: format!(
5082 "solver evidence requires zero reduced constraint violations, got {} across {} \
5083 checked constraint relations",
5084 result.constraint_validation.violated_constraint_relations,
5085 result.constraint_validation.checked_constraint_relations
5086 ),
5087 });
5088 }
5089 if result.constraint_validation.row_count_device_reads as usize
5090 > result.constraint_validation.checked_constraint_relations
5091 {
5092 return Err(XlogError::UnsupportedEpistemicConstruct {
5093 construct: "accepted GPU solver candidate evidence".to_string(),
5094 context: format!(
5095 "solver evidence constraint metadata reads cannot exceed checked reduced \
5096 constraint relations, got reads={} checked={}",
5097 result.constraint_validation.row_count_device_reads,
5098 result.constraint_validation.checked_constraint_relations
5099 ),
5100 });
5101 }
5102 require_gpu_kernel_trace(
5103 "candidate generation",
5104 result.candidate_generation.kernel_launches,
5105 result.candidate_generation.host_write_ops,
5106 result.candidate_generation.kernel_timing,
5107 )?;
5108 require_gpu_kernel_trace(
5109 "candidate propagation",
5110 result.propagation.kernel_launches,
5111 result.propagation.host_write_ops,
5112 result.propagation.kernel_timing,
5113 )?;
5114 require_gpu_kernel_trace(
5115 "candidate validation",
5116 result.candidate_validation.kernel_launches,
5117 result.candidate_validation.host_write_ops,
5118 result.candidate_validation.kernel_timing,
5119 )?;
5120 require_gpu_kernel_trace(
5121 "model membership",
5122 result.model_membership.kernel_launches,
5123 result.model_membership.host_write_ops,
5124 result.model_membership.kernel_timing,
5125 )?;
5126 require_gpu_kernel_trace(
5127 "world-view validation",
5128 result.world_view_validation.kernel_launches,
5129 result.world_view_validation.host_write_ops,
5130 result.world_view_validation.kernel_timing,
5131 )?;
5132 require_gpu_kernel_trace(
5133 "accepted-candidate materialization",
5134 result.materialization.kernel_launches,
5135 result.materialization.host_write_ops,
5136 result.materialization.kernel_timing,
5137 )?;
5138 require_gpu_kernel_trace(
5139 "final-result materialization",
5140 result.final_result_materialization.kernel_launches,
5141 result.final_result_materialization.host_write_ops,
5142 result.final_result_materialization.kernel_timing,
5143 )?;
5144 require_gpu_kernel_trace(
5145 "final tuple materialization",
5146 result.final_tuple_materialization.kernel_launches,
5147 result.final_tuple_materialization.host_write_ops,
5148 result.final_tuple_materialization.kernel_timing,
5149 )?;
5150 let accepted_rows = result.final_result_transfer.final_output_rows;
5153 result
5154 .final_tuple_materialization
5155 .require_row_filter_materialization_evidence(
5156 "accepted GPU solver candidate evidence",
5157 accepted_rows,
5158 )?;
5159 if result.transfer_budget.tracked_dtoh_calls != 0
5160 || result.transfer_budget.tracked_htod_calls != 0
5161 || result.transfer_budget.tracked_data_plane_htod_calls != 0
5162 || result.transfer_budget.per_candidate_host_round_trips != 0
5163 {
5164 return Err(XlogError::UnsupportedEpistemicConstruct {
5165 construct: "accepted GPU solver candidate evidence".to_string(),
5166 context: format!(
5167 "solver evidence requires zero hot-path transfers outside bounded launch \
5168 metadata, got dtoh_calls={}, htod_calls={}, data_plane_htod_calls={}, \
5169 launch_metadata_htod_calls={}, per_candidate_round_trips={}",
5170 result.transfer_budget.tracked_dtoh_calls,
5171 result.transfer_budget.tracked_htod_calls,
5172 result.transfer_budget.tracked_data_plane_htod_calls,
5173 result.transfer_budget.tracked_launch_metadata_htod_calls,
5174 result.transfer_budget.per_candidate_host_round_trips
5175 ),
5176 });
5177 }
5178 require_accepted_gpu_solver_semantic_trace(result)?;
5179
5180 if accepted_rows == 0 {
5181 return Err(XlogError::UnsupportedEpistemicConstruct {
5182 construct: "accepted GPU solver candidate evidence".to_string(),
5183 context: "solver evidence requires non-empty accepted GPU final output".to_string(),
5184 });
5185 }
5186 if result.semantic_trace.accepted_candidates == 0 {
5187 return Err(XlogError::UnsupportedEpistemicConstruct {
5188 construct: "accepted GPU solver candidate evidence".to_string(),
5189 context: "solver evidence requires at least one GPU-accepted candidate".to_string(),
5190 });
5191 }
5192
5193 Ok(GpuSolverAcceptedCandidateState::from_validated_result(
5194 result,
5195 accepted_rows,
5196 ))
5197}
5198
5199fn require_accepted_gpu_solver_semantic_trace(result: &EpistemicGpuExecutionResult) -> Result<()> {
5200 let trace = &result.semantic_trace;
5201 let accounted_candidates = trace
5202 .accepted_candidates
5203 .checked_add(trace.rejected_candidates)
5204 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5205 construct: "accepted GPU solver candidate evidence".to_string(),
5206 context: format!(
5207 "solver evidence semantic trace candidate accounting overflowed: accepted={} \
5208 rejected={}",
5209 trace.accepted_candidates, trace.rejected_candidates
5210 ),
5211 })?;
5212 if trace.generated_candidates != result.candidate_generation.generated_candidates
5213 || trace.tested_candidates != result.world_view_validation.candidates_checked
5214 || trace.accepted_candidates != trace.accepted_candidate_indices.len()
5215 || trace.rejected_candidates != trace.rejected_candidate_indices.len()
5216 || trace.accepted_world_views != trace.accepted_candidates
5217 || accounted_candidates != trace.generated_candidates
5218 {
5219 return Err(XlogError::UnsupportedEpistemicConstruct {
5220 construct: "accepted GPU solver candidate evidence".to_string(),
5221 context: format!(
5222 "solver evidence requires a consistent GPU semantic trace, got \
5223 generated={}, tested={}, expected_generated={}, \
5224 expected_tested={}, accepted={} accepted_indices={}, accepted_world_views={}, \
5225 rejected={} rejected_indices={}",
5226 trace.generated_candidates,
5227 trace.tested_candidates,
5228 result.candidate_generation.generated_candidates,
5229 result.world_view_validation.candidates_checked,
5230 trace.accepted_candidates,
5231 trace.accepted_candidate_indices.len(),
5232 trace.accepted_world_views,
5233 trace.rejected_candidates,
5234 trace.rejected_candidate_indices.len()
5235 ),
5236 });
5237 }
5238 Ok(())
5239}
5240
5241fn require_accepted_gpu_solver_batch_evidence<'a>(
5242 provider: &CudaKernelProvider,
5243 batch: &'a EpistemicGpuBatchExecutionResult,
5244) -> Result<Vec<&'a EpistemicGpuExecutionResult>> {
5245 if batch.results.is_empty() {
5246 return Err(XlogError::UnsupportedEpistemicConstruct {
5247 construct: "accepted GPU solver batch evidence".to_string(),
5248 context: "solver batch evidence requires at least one accepted GPU component"
5249 .to_string(),
5250 });
5251 }
5252 let trace = batch.trace;
5253 if trace.component_count != batch.results.len()
5254 || trace.gpu_runtime_component_executions != batch.results.len()
5255 || trace.tracked_dtoh_calls != 0
5256 || trace.tracked_htod_calls != 0
5257 || trace.tracked_data_plane_htod_calls != 0
5258 || trace.per_candidate_host_round_trips != 0
5259 || trace.violated_constraint_relations != 0
5260 || !trace.aggregate_kernel_timing.is_recorded()
5261 {
5262 return Err(XlogError::UnsupportedEpistemicConstruct {
5263 construct: "accepted GPU solver batch evidence".to_string(),
5264 context: format!(
5265 "solver batch evidence requires complete GPU component execution, zero \
5266 observed hot-path transfers outside bounded launch metadata, and aggregate \
5267 CUDA-event timing, got components={}/{}, dtoh_calls={}, \
5268 htod_calls={}, data_plane_htod_calls={}, launch_metadata_htod_calls={}, \
5269 round_trips={}, constraint_violations={}, aggregate_timing_recorded={}",
5270 trace.gpu_runtime_component_executions,
5271 trace.component_count,
5272 trace.tracked_dtoh_calls,
5273 trace.tracked_htod_calls,
5274 trace.tracked_data_plane_htod_calls,
5275 trace.tracked_launch_metadata_htod_calls,
5276 trace.per_candidate_host_round_trips,
5277 trace.violated_constraint_relations,
5278 trace.aggregate_kernel_timing.is_recorded()
5279 ),
5280 });
5281 }
5282 batch.require_trace_matches_components("accepted GPU solver batch evidence")?;
5283
5284 let mut results = Vec::with_capacity(batch.results.len());
5285 for result in &batch.results {
5286 require_accepted_gpu_solver_evidence(provider, result)?;
5287 results.push(result);
5288 }
5289 Ok(results)
5290}
5291
5292fn require_same_gpu_cnf_for_learned_clause_reuse(source: &GpuCnf, target: &GpuCnf) -> Result<()> {
5293 let same_shape = source.var_cap == target.var_cap
5294 && source.clause_cap == target.clause_cap
5295 && source.lit_cap == target.lit_cap;
5296 let same_buffers = source.num_vars.device_ptr_value() == target.num_vars.device_ptr_value()
5297 && source.num_clauses.device_ptr_value() == target.num_clauses.device_ptr_value()
5298 && source.num_lits.device_ptr_value() == target.num_lits.device_ptr_value()
5299 && source.clause_offsets.device_ptr_value() == target.clause_offsets.device_ptr_value()
5300 && source.literals.device_ptr_value() == target.literals.device_ptr_value();
5301 if !same_shape || !same_buffers {
5302 return Err(XlogError::UnsupportedEpistemicConstruct {
5303 construct: "GPU solver learned-clause reuse".to_string(),
5304 context: "learned-clause import is currently certified only for the same \
5305 device-resident CNF; distinct candidate CNFs must not reuse imported clauses"
5306 .to_string(),
5307 });
5308 }
5309 Ok(())
5310}
5311
5312fn require_stable_learned_clause_arena(
5313 phase: &'static str,
5314 workspace: &GpuCdclWorkspace,
5315 learned_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5316 learned_lits_ptr: cudarc::driver::sys::CUdeviceptr,
5317 proof_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5318 proof_data_ptr: cudarc::driver::sys::CUdeviceptr,
5319 learned_count_ptr: cudarc::driver::sys::CUdeviceptr,
5320) -> Result<()> {
5321 if workspace.learned_offsets.device_ptr_value() != learned_offsets_ptr
5322 || workspace.learned_lits.device_ptr_value() != learned_lits_ptr
5323 || workspace.proof_offsets.device_ptr_value() != proof_offsets_ptr
5324 || workspace.proof_data.device_ptr_value() != proof_data_ptr
5325 || workspace.out_learned_count.device_ptr_value() != learned_count_ptr
5326 {
5327 return Err(XlogError::UnsupportedEpistemicConstruct {
5328 construct: "GPU solver learned-clause reuse".to_string(),
5329 context: format!("learned-clause {phase} must keep the reusable GPU workspace arena"),
5330 });
5331 }
5332 Ok(())
5333}
5334
5335fn require_gpu_kernel_trace(
5336 phase: &'static str,
5337 kernel_launches: u32,
5338 host_write_ops: u32,
5339 kernel_timing: EpistemicGpuKernelTimingTrace,
5340) -> Result<()> {
5341 if kernel_launches == 0 || host_write_ops != 0 || !kernel_timing.is_recorded() {
5342 return Err(XlogError::UnsupportedEpistemicConstruct {
5343 construct: "accepted GPU solver candidate evidence".to_string(),
5344 context: format!(
5345 "solver evidence requires GPU {phase} trace with nonzero launches and \
5346 zero host writes plus CUDA-event timing, got launches={kernel_launches}, \
5347 host_writes={host_write_ops}, timing_recorded={}",
5348 kernel_timing.is_recorded()
5349 ),
5350 });
5351 }
5352 Ok(())
5353}
5354
5355#[cfg(test)]
5356mod tests {
5357 use std::sync::Arc;
5358
5359 use xlog_core::MemoryBudget;
5360 use xlog_cuda::{CudaKernelProvider, CudaProviderBuilder};
5361
5362 use super::*;
5363 use crate::{Clause, Literal};
5364
5365 fn try_provider() -> Option<Arc<CudaKernelProvider>> {
5366 let budget = MemoryBudget::with_limit(1024 * 1024 * 1024);
5367 match CudaProviderBuilder::new(0, budget).build() {
5368 Ok(provider) => Some(Arc::new(provider)),
5369 Err(err) => {
5370 eprintln!("Skipping test: failed to create CUDA kernel provider: {err}");
5371 None
5372 }
5373 }
5374 }
5375
5376 fn alloc_u32(
5377 provider: &Arc<CudaKernelProvider>,
5378 value: u32,
5379 ) -> xlog_cuda::memory::TrackedCudaSlice<u32> {
5380 let memory = provider.memory();
5381 let mut slot = memory.alloc::<u32>(1).expect("alloc u32 scalar");
5382 provider
5383 .device()
5384 .inner()
5385 .htod_sync_copy_into(&[value], &mut slot)
5386 .expect("upload u32 scalar");
5387 slot
5388 }
5389
5390 fn assert_raw_gpu_trace_requires_accepted_evidence(trace: GpuSolverProductionTrace) {
5391 assert_eq!(trace.accepted_gpu_candidate_evidence_consumed, 0);
5392 assert_eq!(trace.accepted_gpu_solver_production_path_events, 0);
5393 let error = trace
5394 .require_production_metric_eligibility()
5395 .expect_err("raw GPU solver work must not satisfy production metrics");
5396 let XlogError::UnsupportedEpistemicConstruct { construct, context } = error else {
5397 panic!("expected accepted-evidence metric rejection, got {error:?}");
5398 };
5399 assert_eq!(construct, "GPU solver production metric gate");
5400 assert_eq!(
5401 context,
5402 "production solver metrics require accepted GPU candidate evidence"
5403 );
5404 }
5405
5406 #[test]
5407 fn weighted_maxsat_frontier_completion_fails_closed_before_cpu_expansion() {
5408 let clauses: Vec<_> = (0..18)
5409 .map(|idx| {
5410 let lit = if idx % 2 == 0 {
5411 Literal::positive(0)
5412 } else {
5413 Literal::negative(0)
5414 };
5415 Clause::new(vec![lit])
5416 })
5417 .collect();
5418 let weighted = SolveInstance::with_weights(1, clauses, vec![1.0; 18]);
5419 let first_frontier: Vec<_> = (0..9).collect();
5420 let second_frontier: Vec<_> = (9..18).collect();
5421 let selections = [
5422 GpuSolverProductionWeightedMaxSatSelection {
5423 soft_clause_indices: &first_frontier,
5424 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5425 },
5426 GpuSolverProductionWeightedMaxSatSelection {
5427 soft_clause_indices: &second_frontier,
5428 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5429 },
5430 ];
5431
5432 let result = GpuSolverProductionAdapter::complete_weighted_maxsat_frontier_selections(
5433 &weighted,
5434 weighted.weights.as_ref().expect("weighted MaxSAT weights"),
5435 &selections,
5436 );
5437 let Err(err) = result else {
5438 panic!("frontier completion should reject CPU combinatorial expansion");
5439 };
5440 let message = err.to_string();
5441 assert!(message.contains("frontier completion"));
5442 assert!(message.contains("explicit GPU scheduler selections"));
5443 }
5444
5445 #[test]
5446 fn raw_encoded_maxsat_search_runs_gpu_candidates_but_requires_accepted_evidence() {
5447 let Some(provider) = try_provider() else {
5448 return;
5449 };
5450
5451 let weighted = SolveInstance::with_weights(
5452 1,
5453 vec![
5454 Clause::new(vec![Literal::positive(0)]),
5455 Clause::new(vec![Literal::negative(0)]),
5456 ],
5457 vec![2.0, 1.0],
5458 );
5459 let mut adapter =
5460 GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5461 let mut workspace = adapter
5462 .new_workspace(weighted.num_vars, weighted.clauses.len() as u32)
5463 .expect("new MaxSAT workspace");
5464 let branch_limit = alloc_u32(&provider, weighted.num_vars);
5465 let contradictory_selection = [0usize, 1usize];
5466 let selections = [GpuSolverProductionWeightedMaxSatSelection {
5467 soft_clause_indices: &contradictory_selection,
5468 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5469 }];
5470
5471 let certificates_before = adapter.trace.gpu_maxsat_frontier_upper_bound_certificates;
5472 let encoded = adapter
5473 .encode_weighted_maxsat_search_candidates(&weighted, &selections)
5474 .expect("encode weighted MaxSAT candidates");
5475 let frontier_upper_bound_certificates =
5476 GpuSolverProductionAdapter::checked_report_counter_delta(
5477 adapter.trace.gpu_maxsat_frontier_upper_bound_certificates,
5478 certificates_before,
5479 "gpu_maxsat_frontier_upper_bound_certificates",
5480 )
5481 .expect("frontier certificate delta");
5482 let search_candidates: Vec<_> = encoded
5483 .iter()
5484 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
5485 score: candidate.score,
5486 cnf: &candidate.cnf,
5487 branch_var_limit: &branch_limit,
5488 status: candidate.status,
5489 })
5490 .collect();
5491
5492 let report = adapter
5493 .solve_weighted_maxsat_search_candidates(
5494 &mut workspace,
5495 &search_candidates,
5496 frontier_upper_bound_certificates,
5497 )
5498 .expect("GPU weighted MaxSAT search");
5499
5500 assert_eq!(report.candidate_evidence_records, 0);
5501 assert_eq!(report.optimum_score, 2);
5502 assert_eq!(report.candidates_checked, 2);
5503 assert_eq!(report.satisfiable_candidates, 1);
5504 assert_eq!(report.unsat_candidates_pruned, 1);
5505 assert_eq!(report.gpu_cdcl_candidate_solves, 2);
5506 assert_eq!(report.frontier_upper_bound_certificates, 1);
5507
5508 let trace = adapter.trace();
5509 assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5510 assert_eq!(trace.gpu_maxsat_frontier_completion_candidate_encodes, 1);
5511 assert_eq!(trace.gpu_maxsat_frontier_upper_bound_certificates, 1);
5512 assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5513 assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5514 assert_eq!(trace.gpu_cdcl_sat_solves, 1);
5515 assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5516 assert_eq!(trace.gpu_maxsat_optima, 1);
5517 assert_raw_gpu_trace_requires_accepted_evidence(trace);
5518 }
5519
5520 #[test]
5521 fn raw_portfolio_dispatches_gpu_paths_but_requires_accepted_evidence() {
5522 let Some(provider) = try_provider() else {
5523 return;
5524 };
5525
5526 let sat_instance = SolveInstance::new(1, vec![Clause::new(vec![Literal::positive(0)])]);
5527 let sat_cnf = GpuCnf::from_host(&sat_instance, &provider).expect("SAT GpuCnf upload");
5528 let weighted = SolveInstance::with_weights(
5529 1,
5530 vec![
5531 Clause::new(vec![Literal::positive(0)]),
5532 Clause::new(vec![Literal::negative(0)]),
5533 ],
5534 vec![2.0, 1.0],
5535 );
5536 let branch_limit = alloc_u32(&provider, weighted.num_vars);
5537 let contradictory_selection = [0usize, 1usize];
5538 let selections = [GpuSolverProductionWeightedMaxSatSelection {
5539 soft_clause_indices: &contradictory_selection,
5540 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5541 }];
5542 let jobs = [
5543 GpuSolverProductionPortfolioJob::Sat {
5544 cnf: &sat_cnf,
5545 branch_var_limit: &branch_limit,
5546 },
5547 GpuSolverProductionPortfolioJob::EncodedMaxSat {
5548 weighted: &weighted,
5549 branch_var_limit: &branch_limit,
5550 selections: &selections,
5551 },
5552 GpuSolverProductionPortfolioJob::Unknown {
5553 reason: "bounded portfolio diagnostic",
5554 },
5555 GpuSolverProductionPortfolioJob::Timeout { budget_micros: 1 },
5556 ];
5557 let mut adapter =
5558 GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5559
5560 let report = adapter
5561 .solve_portfolio_jobs(&jobs)
5562 .expect("GPU production portfolio jobs");
5563
5564 assert_eq!(report.candidate_evidence_records, 0);
5565 assert_eq!(report.jobs, 4);
5566 assert_eq!(report.sat_jobs, 1);
5567 assert_eq!(report.maxsat_jobs, 1);
5568 assert_eq!(report.unknown_jobs, 1);
5569 assert_eq!(report.timeout_jobs, 1);
5570 assert_eq!(report.maxsat_optimum_scores, 2);
5571 assert_eq!(report.maxsat_candidates_checked, 2);
5572 assert_eq!(report.maxsat_satisfiable_candidates, 1);
5573 assert_eq!(report.maxsat_unsat_candidates_pruned, 1);
5574 assert_eq!(report.maxsat_gpu_cdcl_candidate_encodes, 2);
5575 assert_eq!(report.maxsat_gpu_cdcl_candidate_solves, 2);
5576 assert_eq!(report.maxsat_frontier_upper_bound_certificates, 1);
5577
5578 let trace = adapter.trace();
5579 assert_eq!(trace.gpu_portfolio_jobs, 4);
5580 assert_eq!(trace.gpu_portfolio_sat_jobs, 1);
5581 assert_eq!(trace.gpu_portfolio_maxsat_jobs, 1);
5582 assert_eq!(trace.gpu_portfolio_unknown_status_jobs, 1);
5583 assert_eq!(trace.gpu_portfolio_timeout_status_jobs, 1);
5584 assert_eq!(trace.gpu_cdcl_sat_solves, 2);
5585 assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5586 assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5587 assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5588 assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5589 assert_eq!(trace.gpu_maxsat_optima, 1);
5590 assert_raw_gpu_trace_requires_accepted_evidence(trace);
5591 }
5592
5593 #[test]
5594 fn raw_learned_clause_reuse_preserves_gpu_arena_but_requires_accepted_evidence() {
5595 let Some(provider) = try_provider() else {
5596 return;
5597 };
5598
5599 let unsat_instance = SolveInstance::new(
5600 1,
5601 vec![
5602 Clause::new(vec![Literal::positive(0)]),
5603 Clause::new(vec![Literal::negative(0)]),
5604 ],
5605 );
5606 let cnf = GpuCnf::from_host(&unsat_instance, &provider).expect("UNSAT GpuCnf upload");
5607 let branch_limit = alloc_u32(&provider, cnf.var_cap as u32);
5608 let mut adapter =
5609 GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5610 let mut workspace = adapter
5611 .new_workspace(cnf.var_cap, cnf.clause_cap)
5612 .expect("new learned-clause workspace");
5613
5614 let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
5615 let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
5616 let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
5617 let proof_data_ptr = workspace.proof_data.device_ptr_value();
5618 let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
5619
5620 let report = adapter
5621 .solve_unsat_then_reuse_learned_clauses(
5622 &mut workspace,
5623 &cnf,
5624 &branch_limit,
5625 &cnf,
5626 &branch_limit,
5627 )
5628 .expect("GPU learned-clause reuse");
5629
5630 assert_eq!(report.candidate_evidence_records, 0);
5631 assert_eq!(report.candidates, 2);
5632 assert_eq!(report.unsat_solves, 2);
5633 assert_eq!(report.gpu_learned_clause_arena_publications, 1);
5634 assert_eq!(report.gpu_learned_clause_imports, 1);
5635 assert_eq!(report.gpu_learned_clause_reused_solves, 1);
5636 assert_eq!(
5637 workspace.learned_offsets.device_ptr_value(),
5638 learned_offsets_ptr
5639 );
5640 assert_eq!(workspace.learned_lits.device_ptr_value(), learned_lits_ptr);
5641 assert_eq!(
5642 workspace.proof_offsets.device_ptr_value(),
5643 proof_offsets_ptr
5644 );
5645 assert_eq!(workspace.proof_data.device_ptr_value(), proof_data_ptr);
5646 assert_eq!(
5647 workspace.out_learned_count.device_ptr_value(),
5648 learned_count_ptr
5649 );
5650
5651 let trace = adapter.trace();
5652 assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 2);
5653 assert_eq!(trace.gpu_learned_clause_arena_publications, 1);
5654 assert_eq!(trace.gpu_learned_count_buffer_publications, 1);
5655 assert_eq!(trace.gpu_learned_clause_imports, 1);
5656 assert_eq!(trace.gpu_learned_clause_reused_solves, 1);
5657 assert_raw_gpu_trace_requires_accepted_evidence(trace);
5658 }
5659}