1use std::collections::{BTreeMap, BTreeSet};
8use std::sync::Arc;
9
10use xlog_core::{Result, XlogError};
11use xlog_cuda::memory::TrackedCudaSlice;
12use xlog_cuda::{CudaKernelProvider, DeviceSlice};
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 #[allow(clippy::too_many_arguments)]
2608 pub fn solve_unsat_then_reuse_learned_clauses_with_gpu_execution_result(
2609 &mut self,
2610 provider: &CudaKernelProvider,
2611 result: &EpistemicGpuExecutionResult,
2612 workspace: &mut GpuCdclWorkspace,
2613 source_cnf: &GpuCnf,
2614 source_branch_var_limit: &TrackedCudaSlice<u32>,
2615 target_cnf: &GpuCnf,
2616 target_branch_var_limit: &TrackedCudaSlice<u32>,
2617 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2618 self.with_trace_rollback_preserving_reuse_rejections(|this| {
2619 this.require_workspace_on_adapter_provider(workspace)?;
2620 this.require_learned_clause_reuse_inputs(
2621 workspace,
2622 source_cnf,
2623 source_branch_var_limit,
2624 target_cnf,
2625 target_branch_var_limit,
2626 )?;
2627 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2628 let events_before = this.trace.accepted_path_event_snapshot()?;
2629 let mut report = this.solve_unsat_then_reuse_learned_clauses(
2630 workspace,
2631 source_cnf,
2632 source_branch_var_limit,
2633 target_cnf,
2634 target_branch_var_limit,
2635 )?;
2636 report.candidate_evidence_records = 1;
2637 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2638 this.record_accepted_gpu_candidate_state(&state)?;
2639 Ok(report)
2640 })
2641 }
2642
2643 #[allow(clippy::too_many_arguments)]
2645 pub fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2646 &mut self,
2647 provider: &CudaKernelProvider,
2648 results: &[&EpistemicGpuExecutionResult],
2649 workspace: &mut GpuCdclWorkspace,
2650 source_cnf: &GpuCnf,
2651 source_branch_var_limit: &TrackedCudaSlice<u32>,
2652 target_cnf: &GpuCnf,
2653 target_branch_var_limit: &TrackedCudaSlice<u32>,
2654 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2655 self.with_trace_rollback_preserving_reuse_rejections(|this| {
2656 this.solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2657 provider,
2658 results,
2659 workspace,
2660 source_cnf,
2661 source_branch_var_limit,
2662 target_cnf,
2663 target_branch_var_limit,
2664 )
2665 })
2666 }
2667
2668 #[allow(clippy::too_many_arguments)]
2669 fn solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results_impl(
2670 &mut self,
2671 provider: &CudaKernelProvider,
2672 results: &[&EpistemicGpuExecutionResult],
2673 workspace: &mut GpuCdclWorkspace,
2674 source_cnf: &GpuCnf,
2675 source_branch_var_limit: &TrackedCudaSlice<u32>,
2676 target_cnf: &GpuCnf,
2677 target_branch_var_limit: &TrackedCudaSlice<u32>,
2678 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2679 if results.is_empty() {
2680 return Err(XlogError::UnsupportedEpistemicConstruct {
2681 construct: "GPU solver learned-clause reuse".to_string(),
2682 context:
2683 "multi-candidate learned-clause reuse requires at least one accepted GPU result"
2684 .to_string(),
2685 });
2686 }
2687 self.require_workspace_on_adapter_provider(workspace)?;
2688 self.require_learned_clause_reuse_inputs(
2689 workspace,
2690 source_cnf,
2691 source_branch_var_limit,
2692 target_cnf,
2693 target_branch_var_limit,
2694 )?;
2695 let states = self.require_accepted_gpu_solver_states(provider, results)?;
2696
2697 let mut report = GpuSolverProductionLearnedClauseReuseReport::default();
2698 for state in &states {
2699 let events_before = self.trace.accepted_path_event_snapshot()?;
2700 let step_report = self.solve_unsat_then_reuse_learned_clauses(
2701 workspace,
2702 source_cnf,
2703 source_branch_var_limit,
2704 target_cnf,
2705 target_branch_var_limit,
2706 )?;
2707 checked_solver_report_counter_inc!(report, candidate_evidence_records);
2708 checked_solver_report_counter_add!(report, candidates, step_report.candidates);
2709 checked_solver_report_counter_add!(report, unsat_solves, step_report.unsat_solves);
2710 checked_solver_report_counter_add!(
2711 report,
2712 gpu_learned_clause_arena_publications,
2713 step_report.gpu_learned_clause_arena_publications
2714 );
2715 checked_solver_report_counter_add!(
2716 report,
2717 gpu_learned_clause_imports,
2718 step_report.gpu_learned_clause_imports
2719 );
2720 checked_solver_report_counter_add!(
2721 report,
2722 gpu_learned_clause_reused_solves,
2723 step_report.gpu_learned_clause_reused_solves
2724 );
2725 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
2726 self.record_accepted_gpu_candidate_state(state)?;
2727 }
2728
2729 Ok(report)
2730 }
2731
2732 #[allow(clippy::too_many_arguments)]
2738 pub fn solve_learned_clause_reuse_with_gpu_batch_execution_result(
2739 &mut self,
2740 provider: &CudaKernelProvider,
2741 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
2742 workspace: &mut GpuCdclWorkspace,
2743 source_cnf: &GpuCnf,
2744 source_branch_var_limit: &TrackedCudaSlice<u32>,
2745 target_cnf: &GpuCnf,
2746 target_branch_var_limit: &TrackedCudaSlice<u32>,
2747 ) -> Result<GpuSolverProductionLearnedClauseReuseReport> {
2748 self.with_trace_rollback_preserving_reuse_rejections(|this| {
2749 this.require_workspace_on_adapter_provider(workspace)?;
2750 this.require_learned_clause_reuse_inputs(
2751 workspace,
2752 source_cnf,
2753 source_branch_var_limit,
2754 target_cnf,
2755 target_branch_var_limit,
2756 )?;
2757 let results =
2758 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
2759 let report = this
2760 .solve_multi_candidate_learned_clause_reuse_with_gpu_execution_results(
2761 provider,
2762 &results,
2763 workspace,
2764 source_cnf,
2765 source_branch_var_limit,
2766 target_cnf,
2767 target_branch_var_limit,
2768 )?;
2769 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
2770 Ok(report)
2771 })
2772 }
2773
2774 fn solve_weighted_maxsat_candidates(
2775 &mut self,
2776 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2777 frontier_upper_bound_certificates: u64,
2778 ) -> Result<GpuSolverProductionMaxSatReport> {
2779 self.with_trace_rollback(|this| {
2780 this.solve_weighted_maxsat_candidates_impl(
2781 candidates,
2782 frontier_upper_bound_certificates,
2783 )
2784 })
2785 }
2786
2787 fn solve_weighted_maxsat_candidates_impl(
2788 &mut self,
2789 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2790 frontier_upper_bound_certificates: u64,
2791 ) -> Result<GpuSolverProductionMaxSatReport> {
2792 self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2793
2794 let solves_before = self.trace.gpu_maxsat_candidate_solves;
2795 let mut optimum_score = 0u64;
2796 for candidate in candidates {
2797 let _assignment = self
2798 .solver
2799 .solve_expect_sat_with_branch_limit(candidate.cnf, candidate.branch_var_limit)?;
2800 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
2801 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
2802 optimum_score = optimum_score.max(candidate.score);
2803 }
2804 checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
2805 let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
2806 self.trace.gpu_maxsat_candidate_solves,
2807 solves_before,
2808 "gpu_maxsat_candidate_solves",
2809 )?;
2810 if frontier_upper_bound_certificates != 0 {
2811 self.trace.gpu_maxsat_frontier_certified_candidate_solves =
2812 Self::checked_trace_counter_add(
2813 self.trace.gpu_maxsat_frontier_certified_candidate_solves,
2814 gpu_cdcl_candidate_solves,
2815 "gpu_maxsat_frontier_certified_candidate_solves",
2816 )?;
2817 }
2818
2819 Ok(GpuSolverProductionMaxSatReport {
2820 candidate_evidence_records: 0,
2821 optimum_score,
2822 candidates_checked: candidates.len() as u64,
2823 satisfiable_candidates: candidates.len() as u64,
2824 unsat_candidates_pruned: 0,
2825 gpu_cdcl_candidate_encodes: 0,
2826 gpu_cdcl_candidate_solves,
2827 frontier_upper_bound_certificates,
2828 })
2829 }
2830
2831 fn require_weighted_maxsat_candidates(
2832 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2833 ) -> Result<()> {
2834 if candidates.is_empty() {
2835 return Err(XlogError::UnsupportedEpistemicConstruct {
2836 construct: "GPU solver production MaxSAT".to_string(),
2837 context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2838 });
2839 }
2840 Ok(())
2841 }
2842
2843 pub fn solve_weighted_maxsat_candidates_with_gpu_execution_result(
2849 &mut self,
2850 provider: &CudaKernelProvider,
2851 result: &EpistemicGpuExecutionResult,
2852 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2853 ) -> Result<GpuSolverProductionMaxSatReport> {
2854 self.with_trace_rollback(|this| {
2855 this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
2856 let state = this.require_accepted_gpu_solver_evidence(provider, result)?;
2857 let events_before = this.trace.accepted_path_event_snapshot()?;
2858 let mut report = this.solve_weighted_maxsat_candidates(candidates, 0)?;
2859 report.candidate_evidence_records = 1;
2860 this.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2861 this.record_accepted_gpu_candidate_state(&state)?;
2862 Ok(report)
2863 })
2864 }
2865
2866 fn require_maxsat_lifecycle_inputs(
2867 steps: &[GpuSolverProductionLifecycleStep<'_>],
2868 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2869 ) -> Result<()> {
2870 if steps.is_empty() {
2871 return Err(XlogError::UnsupportedEpistemicConstruct {
2872 construct: "GPU solver production MaxSAT lifecycle".to_string(),
2873 context: "accepted MaxSAT lifecycle requires at least one lifecycle step"
2874 .to_string(),
2875 });
2876 }
2877 if candidates.is_empty() {
2878 return Err(XlogError::UnsupportedEpistemicConstruct {
2879 construct: "GPU solver production MaxSAT lifecycle".to_string(),
2880 context: "bounded MaxSAT adapter requires at least one candidate CNF".to_string(),
2881 });
2882 }
2883 Self::require_assumption_lifecycle_steps(steps)?;
2884 Ok(())
2885 }
2886
2887 pub fn solve_maxsat_lifecycle_with_gpu_execution_result(
2893 &mut self,
2894 provider: &CudaKernelProvider,
2895 result: &EpistemicGpuExecutionResult,
2896 workspace: &mut GpuCdclWorkspace,
2897 steps: &[GpuSolverProductionLifecycleStep<'_>],
2898 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2899 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2900 self.with_trace_rollback(|this| {
2901 this.solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2902 provider, result, workspace, steps, candidates,
2903 )
2904 })
2905 }
2906
2907 fn solve_maxsat_lifecycle_with_gpu_execution_result_impl(
2908 &mut self,
2909 provider: &CudaKernelProvider,
2910 result: &EpistemicGpuExecutionResult,
2911 workspace: &mut GpuCdclWorkspace,
2912 steps: &[GpuSolverProductionLifecycleStep<'_>],
2913 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
2914 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
2915 Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
2916 self.require_workspace_on_adapter_provider(workspace)?;
2917 self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
2918 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
2919
2920 let events_before = self.trace.accepted_path_event_snapshot()?;
2921 let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
2922 let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
2923 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
2924 self.record_accepted_gpu_candidate_state(&state)?;
2925
2926 Ok(GpuSolverProductionMaxSatLifecycleReport {
2927 candidate_evidence_records: 1,
2928 lifecycle,
2929 maxsat,
2930 })
2931 }
2932
2933 fn add_maxsat_lifecycle_step_report(
2934 report: &mut GpuSolverProductionMaxSatLifecycleReport,
2935 step_report: GpuSolverProductionMaxSatLifecycleReport,
2936 ) -> Result<()> {
2937 report.candidate_evidence_records = Self::checked_report_counter_add(
2938 report.candidate_evidence_records,
2939 step_report.candidate_evidence_records,
2940 "candidate_evidence_records",
2941 )?;
2942 report.lifecycle.steps = Self::checked_report_counter_add(
2943 report.lifecycle.steps,
2944 step_report.lifecycle.steps,
2945 "lifecycle.steps",
2946 )?;
2947 report.lifecycle.sat_steps = Self::checked_report_counter_add(
2948 report.lifecycle.sat_steps,
2949 step_report.lifecycle.sat_steps,
2950 "lifecycle.sat_steps",
2951 )?;
2952 report.lifecycle.unsat_steps = Self::checked_report_counter_add(
2953 report.lifecycle.unsat_steps,
2954 step_report.lifecycle.unsat_steps,
2955 "lifecycle.unsat_steps",
2956 )?;
2957 report.lifecycle.assumption_pushes = Self::checked_report_counter_add(
2958 report.lifecycle.assumption_pushes,
2959 step_report.lifecycle.assumption_pushes,
2960 "lifecycle.assumption_pushes",
2961 )?;
2962 report.lifecycle.assumption_retractions = Self::checked_report_counter_add(
2963 report.lifecycle.assumption_retractions,
2964 step_report.lifecycle.assumption_retractions,
2965 "lifecycle.assumption_retractions",
2966 )?;
2967 report.lifecycle.workspace_reuses = Self::checked_report_counter_add(
2968 report.lifecycle.workspace_reuses,
2969 step_report.lifecycle.workspace_reuses,
2970 "lifecycle.workspace_reuses",
2971 )?;
2972 report.lifecycle.unknown_steps = Self::checked_report_counter_add(
2973 report.lifecycle.unknown_steps,
2974 step_report.lifecycle.unknown_steps,
2975 "lifecycle.unknown_steps",
2976 )?;
2977 report.lifecycle.timeout_steps = Self::checked_report_counter_add(
2978 report.lifecycle.timeout_steps,
2979 step_report.lifecycle.timeout_steps,
2980 "lifecycle.timeout_steps",
2981 )?;
2982 report.maxsat.optimum_score = report
2983 .maxsat
2984 .optimum_score
2985 .max(step_report.maxsat.optimum_score);
2986 report.maxsat.candidates_checked = Self::checked_report_counter_add(
2987 report.maxsat.candidates_checked,
2988 step_report.maxsat.candidates_checked,
2989 "maxsat.candidates_checked",
2990 )?;
2991 report.maxsat.satisfiable_candidates = Self::checked_report_counter_add(
2992 report.maxsat.satisfiable_candidates,
2993 step_report.maxsat.satisfiable_candidates,
2994 "maxsat.satisfiable_candidates",
2995 )?;
2996 report.maxsat.unsat_candidates_pruned = Self::checked_report_counter_add(
2997 report.maxsat.unsat_candidates_pruned,
2998 step_report.maxsat.unsat_candidates_pruned,
2999 "maxsat.unsat_candidates_pruned",
3000 )?;
3001 report.maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_add(
3002 report.maxsat.gpu_cdcl_candidate_encodes,
3003 step_report.maxsat.gpu_cdcl_candidate_encodes,
3004 "maxsat.gpu_cdcl_candidate_encodes",
3005 )?;
3006 report.maxsat.gpu_cdcl_candidate_solves = Self::checked_report_counter_add(
3007 report.maxsat.gpu_cdcl_candidate_solves,
3008 step_report.maxsat.gpu_cdcl_candidate_solves,
3009 "maxsat.gpu_cdcl_candidate_solves",
3010 )?;
3011 report.maxsat.frontier_upper_bound_certificates = Self::checked_report_counter_add(
3012 report.maxsat.frontier_upper_bound_certificates,
3013 step_report.maxsat.frontier_upper_bound_certificates,
3014 "maxsat.frontier_upper_bound_certificates",
3015 )?;
3016 Ok(())
3017 }
3018
3019 pub fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3021 &mut self,
3022 provider: &CudaKernelProvider,
3023 results: &[&EpistemicGpuExecutionResult],
3024 workspace: &mut GpuCdclWorkspace,
3025 steps: &[GpuSolverProductionLifecycleStep<'_>],
3026 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3027 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3028 self.with_trace_rollback(|this| {
3029 this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3030 provider, results, workspace, steps, candidates,
3031 )
3032 })
3033 }
3034
3035 fn solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results_impl(
3036 &mut self,
3037 provider: &CudaKernelProvider,
3038 results: &[&EpistemicGpuExecutionResult],
3039 workspace: &mut GpuCdclWorkspace,
3040 steps: &[GpuSolverProductionLifecycleStep<'_>],
3041 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3042 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3043 if results.is_empty() {
3044 return Err(XlogError::UnsupportedEpistemicConstruct {
3045 construct: "GPU solver production MaxSAT lifecycle".to_string(),
3046 context:
3047 "multi-candidate MaxSAT lifecycle requires at least one accepted GPU result"
3048 .to_string(),
3049 });
3050 }
3051 Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3052 self.require_workspace_on_adapter_provider(workspace)?;
3053 self.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3054 let states = self.require_accepted_gpu_solver_states(provider, results)?;
3055
3056 let mut report = GpuSolverProductionMaxSatLifecycleReport::default();
3057 for state in &states {
3058 let events_before = self.trace.accepted_path_event_snapshot()?;
3059 let lifecycle = self.solve_assumption_lifecycle_steps(workspace, steps)?;
3060 let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3061 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3062 self.record_accepted_gpu_candidate_state(state)?;
3063 Self::add_maxsat_lifecycle_step_report(
3064 &mut report,
3065 GpuSolverProductionMaxSatLifecycleReport {
3066 candidate_evidence_records: 1,
3067 lifecycle,
3068 maxsat,
3069 },
3070 )?;
3071 }
3072
3073 Ok(report)
3074 }
3075
3076 pub fn solve_maxsat_lifecycle_with_gpu_batch_execution_result(
3078 &mut self,
3079 provider: &CudaKernelProvider,
3080 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3081 workspace: &mut GpuCdclWorkspace,
3082 steps: &[GpuSolverProductionLifecycleStep<'_>],
3083 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3084 ) -> Result<GpuSolverProductionMaxSatLifecycleReport> {
3085 self.with_trace_rollback(|this| {
3086 Self::require_maxsat_lifecycle_inputs(steps, candidates)?;
3087 this.require_workspace_on_adapter_provider(workspace)?;
3088 this.require_maxsat_lifecycle_artifacts(workspace, steps, candidates)?;
3089 let results =
3090 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3091 let report = this.solve_multi_candidate_maxsat_lifecycle_with_gpu_execution_results(
3092 provider, &results, workspace, steps, candidates,
3093 )?;
3094 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3095 Ok(report)
3096 })
3097 }
3098
3099 pub fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3101 &mut self,
3102 provider: &CudaKernelProvider,
3103 results: &[&EpistemicGpuExecutionResult],
3104 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3105 ) -> Result<GpuSolverProductionMaxSatReport> {
3106 self.with_trace_rollback(|this| {
3107 this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3108 provider, results, candidates,
3109 )
3110 })
3111 }
3112
3113 fn solve_multi_candidate_weighted_maxsat_with_gpu_execution_results_impl(
3114 &mut self,
3115 provider: &CudaKernelProvider,
3116 results: &[&EpistemicGpuExecutionResult],
3117 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3118 ) -> Result<GpuSolverProductionMaxSatReport> {
3119 if results.is_empty() {
3120 return Err(XlogError::UnsupportedEpistemicConstruct {
3121 construct: "GPU solver production MaxSAT".to_string(),
3122 context: "multi-candidate MaxSAT requires at least one accepted GPU result"
3123 .to_string(),
3124 });
3125 }
3126 self.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3127 let states = self.require_accepted_gpu_solver_states(provider, results)?;
3128
3129 let mut report = GpuSolverProductionMaxSatReport::default();
3130 for state in &states {
3131 let events_before = self.trace.accepted_path_event_snapshot()?;
3132 let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
3133 checked_solver_report_counter_inc!(report, candidate_evidence_records);
3134 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
3135 checked_solver_report_counter_add!(
3136 report,
3137 candidates_checked,
3138 step_report.candidates_checked
3139 );
3140 checked_solver_report_counter_add!(
3141 report,
3142 satisfiable_candidates,
3143 step_report.satisfiable_candidates
3144 );
3145 checked_solver_report_counter_add!(
3146 report,
3147 unsat_candidates_pruned,
3148 step_report.unsat_candidates_pruned
3149 );
3150 checked_solver_report_counter_add!(
3151 report,
3152 gpu_cdcl_candidate_encodes,
3153 step_report.gpu_cdcl_candidate_encodes
3154 );
3155 checked_solver_report_counter_add!(
3156 report,
3157 gpu_cdcl_candidate_solves,
3158 step_report.gpu_cdcl_candidate_solves
3159 );
3160 checked_solver_report_counter_add!(
3161 report,
3162 frontier_upper_bound_certificates,
3163 step_report.frontier_upper_bound_certificates
3164 );
3165 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
3166 self.record_accepted_gpu_candidate_state(state)?;
3167 }
3168
3169 Ok(report)
3170 }
3171
3172 pub fn solve_weighted_maxsat_candidates_with_gpu_batch_execution_result(
3178 &mut self,
3179 provider: &CudaKernelProvider,
3180 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3181 candidates: &[GpuSolverProductionMaxSatCandidate<'_>],
3182 ) -> Result<GpuSolverProductionMaxSatReport> {
3183 self.with_trace_rollback(|this| {
3184 this.require_weighted_maxsat_candidates_and_artifacts(candidates)?;
3185 let results =
3186 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3187 let report = this.solve_multi_candidate_weighted_maxsat_with_gpu_execution_results(
3188 provider, &results, candidates,
3189 )?;
3190 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3191 Ok(report)
3192 })
3193 }
3194
3195 fn encode_weighted_maxsat_search_candidates(
3196 &mut self,
3197 weighted: &SolveInstance,
3198 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3199 ) -> Result<Vec<GpuSolverProductionEncodedMaxSatSearchCandidate>> {
3200 Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
3201
3202 let weights =
3203 weighted
3204 .weights
3205 .as_ref()
3206 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3207 construct: "GPU solver production MaxSAT encoding".to_string(),
3208 context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3209 .to_string(),
3210 })?;
3211
3212 let frontier =
3213 Self::complete_weighted_maxsat_frontier_selections(weighted, weights, selections)?;
3214 if frontier.completion_candidate_count != 0 {
3215 self.trace.gpu_maxsat_frontier_completion_candidate_encodes =
3216 Self::checked_trace_counter_add(
3217 self.trace.gpu_maxsat_frontier_completion_candidate_encodes,
3218 frontier.completion_candidate_count,
3219 "gpu_maxsat_frontier_completion_candidate_encodes",
3220 )?;
3221 }
3222 checked_solver_trace_counter_inc!(self, gpu_maxsat_frontier_upper_bound_certificates);
3223 let mut encoded = Vec::with_capacity(frontier.selections.len());
3224 for selection in &frontier.selections {
3225 encoded.push(self.encode_weighted_maxsat_subset(
3226 weighted,
3227 weights,
3228 &selection.soft_clause_indices,
3229 selection.status,
3230 )?);
3231 }
3232
3233 Ok(encoded)
3234 }
3235
3236 fn encode_weighted_maxsat_subset(
3237 &mut self,
3238 weighted: &SolveInstance,
3239 weights: &[f64],
3240 soft_clause_indices: &[usize],
3241 status: GpuSolverProductionMaxSatSearchStatus,
3242 ) -> Result<GpuSolverProductionEncodedMaxSatSearchCandidate> {
3243 let mut score = 0u64;
3244 let mut clauses = Vec::with_capacity(soft_clause_indices.len());
3245 for &idx in soft_clause_indices {
3246 let clause = &weighted.clauses[idx];
3247 let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3248 score = score.checked_add(weight).ok_or_else(|| {
3249 XlogError::UnsupportedEpistemicConstruct {
3250 construct: "GPU solver production MaxSAT encoding".to_string(),
3251 context: format!(
3252 "soft-clause selection score overflowed u64 while adding index {}",
3253 idx
3254 ),
3255 }
3256 })?;
3257 clauses.push(clause.clone());
3258 }
3259
3260 let candidate_instance = SolveInstance::new(weighted.num_vars, clauses);
3261 let data_plane_before = self.provider.host_transfer_stats();
3262 let launch_metadata_before = self.provider.host_launch_metadata_transfer_stats();
3263 let cnf = GpuCnf::from_host(&candidate_instance, &self.provider)?;
3264 let data_plane_after = self.provider.host_transfer_stats();
3265 let launch_metadata_after = self.provider.host_launch_metadata_transfer_stats();
3266 self.record_encoded_maxsat_cnf_upload_transfer_delta(
3267 data_plane_before,
3268 data_plane_after,
3269 launch_metadata_before,
3270 launch_metadata_after,
3271 )?;
3272 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_encodes);
3273 Ok(GpuSolverProductionEncodedMaxSatSearchCandidate { score, cnf, status })
3274 }
3275
3276 fn record_encoded_maxsat_cnf_upload_transfer_delta(
3277 &mut self,
3278 data_plane_before: xlog_cuda::provider::HostTransferStats,
3279 data_plane_after: xlog_cuda::provider::HostTransferStats,
3280 launch_metadata_before: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3281 launch_metadata_after: xlog_cuda::provider::HostLaunchMetadataTransferStats,
3282 ) -> Result<()> {
3283 let data_plane_htod_calls = Self::checked_report_counter_delta(
3284 data_plane_after.htod_calls,
3285 data_plane_before.htod_calls,
3286 "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3287 )?;
3288 let data_plane_htod_bytes = Self::checked_report_counter_delta(
3289 data_plane_after.htod_bytes,
3290 data_plane_before.htod_bytes,
3291 "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3292 )?;
3293 let data_plane_dtoh_calls = Self::checked_report_counter_delta(
3294 data_plane_after.dtoh_calls,
3295 data_plane_before.dtoh_calls,
3296 "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3297 )?;
3298 let data_plane_dtoh_bytes = Self::checked_report_counter_delta(
3299 data_plane_after.dtoh_bytes,
3300 data_plane_before.dtoh_bytes,
3301 "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3302 )?;
3303 let launch_metadata_htod_calls = Self::checked_report_counter_delta(
3304 launch_metadata_after.htod_calls,
3305 launch_metadata_before.htod_calls,
3306 "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3307 )?;
3308 let launch_metadata_htod_bytes = Self::checked_report_counter_delta(
3309 launch_metadata_after.htod_bytes,
3310 launch_metadata_before.htod_bytes,
3311 "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3312 )?;
3313
3314 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls =
3315 Self::checked_trace_counter_add(
3316 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_calls,
3317 data_plane_htod_calls,
3318 "gpu_maxsat_candidate_cnf_data_plane_htod_calls",
3319 )?;
3320 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes =
3321 Self::checked_trace_counter_add(
3322 self.trace.gpu_maxsat_candidate_cnf_data_plane_htod_bytes,
3323 data_plane_htod_bytes,
3324 "gpu_maxsat_candidate_cnf_data_plane_htod_bytes",
3325 )?;
3326 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls =
3327 Self::checked_trace_counter_add(
3328 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_calls,
3329 data_plane_dtoh_calls,
3330 "gpu_maxsat_candidate_cnf_data_plane_dtoh_calls",
3331 )?;
3332 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes =
3333 Self::checked_trace_counter_add(
3334 self.trace.gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes,
3335 data_plane_dtoh_bytes,
3336 "gpu_maxsat_candidate_cnf_data_plane_dtoh_bytes",
3337 )?;
3338 self.trace
3339 .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls = Self::checked_trace_counter_add(
3340 self.trace
3341 .gpu_maxsat_candidate_cnf_launch_metadata_htod_calls,
3342 launch_metadata_htod_calls,
3343 "gpu_maxsat_candidate_cnf_launch_metadata_htod_calls",
3344 )?;
3345 self.trace
3346 .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes = Self::checked_trace_counter_add(
3347 self.trace
3348 .gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes,
3349 launch_metadata_htod_bytes,
3350 "gpu_maxsat_candidate_cnf_launch_metadata_htod_bytes",
3351 )?;
3352 Ok(())
3353 }
3354
3355 fn solve_weighted_maxsat_search_candidates(
3356 &mut self,
3357 workspace: &mut GpuCdclWorkspace,
3358 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3359 frontier_upper_bound_certificates: u64,
3360 ) -> Result<GpuSolverProductionMaxSatReport> {
3361 self.require_workspace_on_adapter_provider(workspace)?;
3362 self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3363
3364 let solves_before = self.trace.gpu_maxsat_candidate_solves;
3365 let unsat_prunes_before = self.trace.gpu_maxsat_unsat_candidate_prunes;
3366 let mut optimum_score = 0u64;
3367 let mut satisfiable_candidates = 0u64;
3368
3369 for candidate in candidates {
3370 match candidate.status {
3371 GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3372 let _assignment = self.solver.solve_expect_sat_with_branch_limit(
3373 candidate.cnf,
3374 candidate.branch_var_limit,
3375 )?;
3376 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
3377 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3378 satisfiable_candidates = Self::checked_trace_counter_add(
3379 satisfiable_candidates,
3380 1,
3381 "maxsat_satisfiable_candidates",
3382 )?;
3383 optimum_score = optimum_score.max(candidate.score);
3384 }
3385 GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {
3386 self.solve_expect_unsat_with_branch_limit_ws(
3387 workspace,
3388 candidate.cnf,
3389 candidate.branch_var_limit,
3390 )?;
3391 checked_solver_trace_counter_inc!(self, gpu_maxsat_candidate_solves);
3392 checked_solver_trace_counter_inc!(self, gpu_maxsat_unsat_candidate_prunes);
3393 }
3394 }
3395 }
3396
3397 checked_solver_trace_counter_inc!(self, gpu_maxsat_optima);
3398 let gpu_cdcl_candidate_solves = Self::checked_report_counter_delta(
3399 self.trace.gpu_maxsat_candidate_solves,
3400 solves_before,
3401 "gpu_maxsat_candidate_solves",
3402 )?;
3403 if frontier_upper_bound_certificates != 0 {
3404 self.trace.gpu_maxsat_frontier_certified_candidate_solves =
3405 Self::checked_trace_counter_add(
3406 self.trace.gpu_maxsat_frontier_certified_candidate_solves,
3407 gpu_cdcl_candidate_solves,
3408 "gpu_maxsat_frontier_certified_candidate_solves",
3409 )?;
3410 }
3411
3412 Ok(GpuSolverProductionMaxSatReport {
3413 candidate_evidence_records: 0,
3414 optimum_score,
3415 candidates_checked: candidates.len() as u64,
3416 satisfiable_candidates,
3417 unsat_candidates_pruned: Self::checked_report_counter_delta(
3418 self.trace.gpu_maxsat_unsat_candidate_prunes,
3419 unsat_prunes_before,
3420 "gpu_maxsat_unsat_candidate_prunes",
3421 )?,
3422 gpu_cdcl_candidate_encodes: 0,
3423 gpu_cdcl_candidate_solves,
3424 frontier_upper_bound_certificates,
3425 })
3426 }
3427
3428 fn require_weighted_maxsat_search_candidates(
3429 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3430 ) -> Result<()> {
3431 if candidates.is_empty() {
3432 return Err(XlogError::UnsupportedEpistemicConstruct {
3433 construct: "GPU solver production MaxSAT search".to_string(),
3434 context: "bounded MaxSAT search requires at least one candidate CNF".to_string(),
3435 });
3436 }
3437 if !candidates.iter().any(|candidate| {
3438 matches!(
3439 candidate.status,
3440 GpuSolverProductionMaxSatSearchStatus::Satisfiable
3441 )
3442 }) {
3443 return Err(XlogError::UnsupportedEpistemicConstruct {
3444 construct: "GPU solver production MaxSAT search".to_string(),
3445 context: "bounded MaxSAT search requires at least one satisfiable GPU candidate"
3446 .to_string(),
3447 });
3448 }
3449 Ok(())
3450 }
3451
3452 fn require_weighted_maxsat_search_selections(
3453 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3454 ) -> Result<()> {
3455 if selections.is_empty() {
3456 return Err(XlogError::UnsupportedEpistemicConstruct {
3457 construct: "GPU solver production MaxSAT encoding".to_string(),
3458 context: "weighted MaxSAT encoding requires at least one selection".to_string(),
3459 });
3460 }
3461 Ok(())
3462 }
3463
3464 fn require_weighted_maxsat_encoding_inputs(
3465 weighted: &SolveInstance,
3466 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3467 ) -> Result<()> {
3468 Self::require_weighted_maxsat_search_selections(selections)?;
3469
3470 if weighted.objective != Objective::MaxSat {
3471 return Err(XlogError::UnsupportedEpistemicConstruct {
3472 construct: "GPU solver production MaxSAT encoding".to_string(),
3473 context: format!(
3474 "weighted MaxSAT encoding requires Objective::MaxSat, got {:?}",
3475 weighted.objective
3476 ),
3477 });
3478 }
3479 if weighted.num_vars == 0 {
3480 return Err(XlogError::UnsupportedEpistemicConstruct {
3481 construct: "GPU solver production MaxSAT encoding".to_string(),
3482 context: "weighted MaxSAT encoding requires num_vars > 0".to_string(),
3483 });
3484 }
3485
3486 let weights =
3487 weighted
3488 .weights
3489 .as_ref()
3490 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3491 construct: "GPU solver production MaxSAT encoding".to_string(),
3492 context: "weighted MaxSAT encoding requires explicit soft-clause weights"
3493 .to_string(),
3494 })?;
3495 if weights.len() != weighted.clauses.len() {
3496 return Err(XlogError::UnsupportedEpistemicConstruct {
3497 construct: "GPU solver production MaxSAT encoding".to_string(),
3498 context: format!(
3499 "soft-clause weights length {} does not match clause count {}",
3500 weights.len(),
3501 weighted.clauses.len()
3502 ),
3503 });
3504 }
3505
3506 for selection in selections {
3507 if selection.soft_clause_indices.is_empty() {
3508 return Err(XlogError::UnsupportedEpistemicConstruct {
3509 construct: "GPU solver production MaxSAT encoding".to_string(),
3510 context:
3511 "weighted MaxSAT search selections must include at least one soft clause"
3512 .to_string(),
3513 });
3514 }
3515 let mut seen_indices = BTreeSet::new();
3516 for (position, &idx) in selection.soft_clause_indices.iter().enumerate() {
3517 if !seen_indices.insert(idx) {
3518 return Err(XlogError::UnsupportedEpistemicConstruct {
3519 construct: "GPU solver production MaxSAT encoding".to_string(),
3520 context: format!(
3521 "soft-clause selection duplicates index {} at position {}; \
3522 weighted MaxSAT candidates must count each soft clause at most once",
3523 idx, position
3524 ),
3525 });
3526 }
3527 }
3528 for &idx in selection.soft_clause_indices {
3529 let _clause = weighted.clauses.get(idx).ok_or_else(|| {
3530 XlogError::UnsupportedEpistemicConstruct {
3531 construct: "GPU solver production MaxSAT encoding".to_string(),
3532 context: format!(
3533 "soft-clause selection index {} is out of range for {} clauses",
3534 idx,
3535 weighted.clauses.len()
3536 ),
3537 }
3538 })?;
3539 let weight =
3540 *weights
3541 .get(idx)
3542 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3543 construct: "GPU solver production MaxSAT encoding".to_string(),
3544 context: format!(
3545 "soft-clause weight index {} is out of range for {} weights",
3546 idx,
3547 weights.len()
3548 ),
3549 })?;
3550 let _ = Self::soft_clause_weight_score(idx, weight)?;
3551 }
3552 }
3553 Ok(())
3554 }
3555
3556 fn complete_weighted_maxsat_frontier_selections(
3557 weighted: &SolveInstance,
3558 weights: &[f64],
3559 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
3560 ) -> Result<GpuSolverProductionCompletedWeightedMaxSatFrontier> {
3561 let mut completed = Vec::with_capacity(selections.len());
3562 let mut completion_candidate_count = 0u64;
3563 let mut seen = BTreeMap::new();
3564
3565 for selection in selections {
3566 let mut indices = selection.soft_clause_indices.to_vec();
3567 indices.sort_unstable();
3568 match seen.get(&indices) {
3569 Some(status) if *status != selection.status => {
3570 return Err(XlogError::UnsupportedEpistemicConstruct {
3571 construct: "GPU solver production MaxSAT encoding".to_string(),
3572 context: format!(
3573 "soft-clause selection {:?} has conflicting statuses {:?} and {:?}",
3574 indices, status, selection.status
3575 ),
3576 });
3577 }
3578 Some(_) => continue,
3579 None => {
3580 seen.insert(indices.clone(), selection.status);
3581 completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3582 soft_clause_indices: indices,
3583 status: selection.status,
3584 });
3585 }
3586 }
3587 }
3588
3589 let all_clause_indices: Vec<_> = (0..weighted.clauses.len()).collect();
3590 let certificates = Self::unsat_frontier_certificates(weights, &completed)?;
3591 let disjoint_frontier = certificates.len() > 1
3592 && Self::frontier_certificates_are_pairwise_disjoint(&certificates);
3593 Self::require_weighted_maxsat_frontier_completion_bound(&certificates, disjoint_frontier)?;
3594 if disjoint_frontier {
3595 let mut exclusions = Vec::with_capacity(certificates.len());
3596 Self::complete_disjoint_unsat_frontier_boundaries(
3597 &certificates,
3598 0,
3599 &mut exclusions,
3600 &all_clause_indices,
3601 &mut seen,
3602 &mut completed,
3603 &mut completion_candidate_count,
3604 )?;
3605 } else {
3606 for certificate in &certificates {
3607 for &excluded_idx in &certificate.min_weight_indices {
3608 let boundary: Vec<_> = all_clause_indices
3609 .iter()
3610 .copied()
3611 .filter(|idx| *idx != excluded_idx)
3612 .collect();
3613 Self::push_completed_frontier_candidate(
3614 boundary,
3615 &mut seen,
3616 &mut completed,
3617 &mut completion_candidate_count,
3618 )?;
3619 }
3620 }
3621 }
3622
3623 Self::require_weighted_maxsat_frontier_upper_bound(weights, &completed)?;
3624 Ok(GpuSolverProductionCompletedWeightedMaxSatFrontier {
3625 selections: completed,
3626 completion_candidate_count,
3627 })
3628 }
3629
3630 fn require_weighted_maxsat_frontier_completion_bound(
3631 certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3632 disjoint_frontier: bool,
3633 ) -> Result<()> {
3634 let implied_candidates = if disjoint_frontier {
3635 certificates.iter().try_fold(1u64, |acc, certificate| {
3636 acc.checked_mul(certificate.min_weight_indices.len() as u64)
3637 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3638 construct: "GPU solver production MaxSAT encoding".to_string(),
3639 context: "weighted MaxSAT disjoint frontier completion bound overflowed"
3640 .to_string(),
3641 })
3642 })?
3643 } else {
3644 certificates.iter().try_fold(0u64, |acc, certificate| {
3645 acc.checked_add(certificate.min_weight_indices.len() as u64)
3646 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3647 construct: "GPU solver production MaxSAT encoding".to_string(),
3648 context: "weighted MaxSAT frontier completion bound overflowed".to_string(),
3649 })
3650 })?
3651 };
3652
3653 if implied_candidates > MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3654 return Err(XlogError::UnsupportedEpistemicConstruct {
3655 construct: "GPU solver production MaxSAT encoding".to_string(),
3656 context: format!(
3657 "weighted MaxSAT frontier completion would require {} CPU-generated \
3658 boundary candidates, exceeding production bound {}; provide explicit \
3659 GPU scheduler selections",
3660 implied_candidates, MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3661 ),
3662 });
3663 }
3664 Ok(())
3665 }
3666
3667 fn unsat_frontier_certificates(
3668 weights: &[f64],
3669 selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3670 ) -> Result<Vec<GpuSolverProductionUnsatFrontierCertificate>> {
3671 let mut certificates = Vec::new();
3672 for selection in selections {
3673 if selection.status != GpuSolverProductionMaxSatSearchStatus::Unsatisfiable {
3674 continue;
3675 }
3676 let mut indices = selection.soft_clause_indices.clone();
3677 indices.sort_unstable();
3678 let mut min_weight = None;
3679 let mut min_weight_indices = Vec::new();
3680 for &idx in &indices {
3681 let weight = Self::soft_clause_weight_score(idx, weights[idx])?;
3682 match min_weight {
3683 None => {
3684 min_weight = Some(weight);
3685 min_weight_indices.push(idx);
3686 }
3687 Some(current) if weight < current => {
3688 min_weight = Some(weight);
3689 min_weight_indices.clear();
3690 min_weight_indices.push(idx);
3691 }
3692 Some(current) if weight == current => min_weight_indices.push(idx),
3693 Some(_) => {}
3694 }
3695 }
3696 if let Some(min_weight) = min_weight {
3697 certificates.push(GpuSolverProductionUnsatFrontierCertificate {
3698 indices,
3699 min_weight,
3700 min_weight_indices,
3701 });
3702 }
3703 }
3704 Ok(certificates)
3705 }
3706
3707 fn frontier_certificates_are_pairwise_disjoint(
3708 certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3709 ) -> bool {
3710 let mut seen = BTreeSet::new();
3711 for certificate in certificates {
3712 for &idx in &certificate.indices {
3713 if !seen.insert(idx) {
3714 return false;
3715 }
3716 }
3717 }
3718 true
3719 }
3720
3721 fn complete_disjoint_unsat_frontier_boundaries(
3722 certificates: &[GpuSolverProductionUnsatFrontierCertificate],
3723 depth: usize,
3724 exclusions: &mut Vec<usize>,
3725 all_clause_indices: &[usize],
3726 seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3727 completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3728 completion_candidate_count: &mut u64,
3729 ) -> Result<()> {
3730 if depth == certificates.len() {
3731 let exclusion_set: BTreeSet<_> = exclusions.iter().copied().collect();
3732 let boundary: Vec<_> = all_clause_indices
3733 .iter()
3734 .copied()
3735 .filter(|idx| !exclusion_set.contains(idx))
3736 .collect();
3737 return Self::push_completed_frontier_candidate(
3738 boundary,
3739 seen,
3740 completed,
3741 completion_candidate_count,
3742 );
3743 }
3744
3745 for &excluded_idx in &certificates[depth].min_weight_indices {
3746 exclusions.push(excluded_idx);
3747 Self::complete_disjoint_unsat_frontier_boundaries(
3748 certificates,
3749 depth + 1,
3750 exclusions,
3751 all_clause_indices,
3752 seen,
3753 completed,
3754 completion_candidate_count,
3755 )?;
3756 exclusions.pop();
3757 }
3758 Ok(())
3759 }
3760
3761 fn push_completed_frontier_candidate(
3762 boundary: Vec<usize>,
3763 seen: &mut BTreeMap<Vec<usize>, GpuSolverProductionMaxSatSearchStatus>,
3764 completed: &mut Vec<GpuSolverProductionOwnedWeightedMaxSatSelection>,
3765 completion_candidate_count: &mut u64,
3766 ) -> Result<()> {
3767 if boundary.is_empty() || seen.contains_key(&boundary) {
3768 return Ok(());
3769 }
3770 if *completion_candidate_count >= MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES {
3771 return Err(XlogError::UnsupportedEpistemicConstruct {
3772 construct: "GPU solver production MaxSAT encoding".to_string(),
3773 context: format!(
3774 "weighted MaxSAT frontier completion exceeded production bound {}; \
3775 provide explicit GPU scheduler selections",
3776 MAX_WEIGHTED_MAXSAT_FRONTIER_COMPLETION_CANDIDATES
3777 ),
3778 });
3779 }
3780 seen.insert(
3781 boundary.clone(),
3782 GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3783 );
3784 *completion_candidate_count =
3785 completion_candidate_count.checked_add(1).ok_or_else(|| {
3786 XlogError::UnsupportedEpistemicConstruct {
3787 construct: "GPU solver production MaxSAT encoding".to_string(),
3788 context: "frontier completion candidate count overflowed".to_string(),
3789 }
3790 })?;
3791 completed.push(GpuSolverProductionOwnedWeightedMaxSatSelection {
3792 soft_clause_indices: boundary,
3793 status: GpuSolverProductionMaxSatSearchStatus::Satisfiable,
3794 });
3795 Ok(())
3796 }
3797
3798 fn require_weighted_maxsat_frontier_upper_bound(
3799 weights: &[f64],
3800 selections: &[GpuSolverProductionOwnedWeightedMaxSatSelection],
3801 ) -> Result<()> {
3802 let mut total_score = 0u64;
3803 for (idx, &weight) in weights.iter().enumerate() {
3804 total_score = total_score
3805 .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3806 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3807 construct: "GPU solver production MaxSAT encoding".to_string(),
3808 context: format!(
3809 "weighted MaxSAT total score overflowed while adding soft clause {}",
3810 idx
3811 ),
3812 })?;
3813 }
3814
3815 let mut upper_bound = total_score;
3816 let mut best_satisfiable_score = 0u64;
3817 let certificates = Self::unsat_frontier_certificates(weights, selections)?;
3818
3819 for selection in selections {
3820 let mut indices = selection.soft_clause_indices.to_vec();
3821 indices.sort_unstable();
3822 let score = Self::weighted_maxsat_selection_score(weights, &indices)?;
3823 match selection.status {
3824 GpuSolverProductionMaxSatSearchStatus::Satisfiable => {
3825 best_satisfiable_score = best_satisfiable_score.max(score);
3826 }
3827 GpuSolverProductionMaxSatSearchStatus::Unsatisfiable => {}
3828 }
3829 }
3830
3831 if certificates.len() > 1
3832 && Self::frontier_certificates_are_pairwise_disjoint(&certificates)
3833 {
3834 let certified_loss = certificates.iter().try_fold(0u64, |acc, certificate| {
3835 acc.checked_add(certificate.min_weight).ok_or_else(|| {
3836 XlogError::UnsupportedEpistemicConstruct {
3837 construct: "GPU solver production MaxSAT encoding".to_string(),
3838 context: "weighted MaxSAT disjoint frontier loss overflowed".to_string(),
3839 }
3840 })
3841 })?;
3842 upper_bound = total_score.checked_sub(certified_loss).ok_or_else(|| {
3843 XlogError::UnsupportedEpistemicConstruct {
3844 construct: "GPU solver production MaxSAT encoding".to_string(),
3845 context: format!(
3846 "weighted MaxSAT disjoint frontier loss {} exceeds total score {}",
3847 certified_loss, total_score
3848 ),
3849 }
3850 })?;
3851 } else {
3852 for certificate in &certificates {
3853 let certificate_bound =
3854 total_score
3855 .checked_sub(certificate.min_weight)
3856 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3857 construct: "GPU solver production MaxSAT encoding".to_string(),
3858 context: format!(
3859 "weighted MaxSAT UNSAT certificate {:?} has minimum weight {} above total score {}",
3860 certificate.indices, certificate.min_weight, total_score
3861 ),
3862 })?;
3863 upper_bound = upper_bound.min(certificate_bound);
3864 }
3865 }
3866
3867 if best_satisfiable_score < upper_bound {
3868 return Err(XlogError::UnsupportedEpistemicConstruct {
3869 construct: "GPU solver production MaxSAT encoding".to_string(),
3870 context: format!(
3871 "weighted MaxSAT frontier is incomplete: best GPU-certified satisfiable score {} is below the certified upper bound {}",
3872 best_satisfiable_score, upper_bound
3873 ),
3874 });
3875 }
3876 Ok(())
3877 }
3878
3879 fn weighted_maxsat_selection_score(weights: &[f64], indices: &[usize]) -> Result<u64> {
3880 let mut score = 0u64;
3881 for &idx in indices {
3882 let weight =
3883 *weights
3884 .get(idx)
3885 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3886 construct: "GPU solver production MaxSAT encoding".to_string(),
3887 context: format!(
3888 "soft-clause weight index {} is out of range for {} weights",
3889 idx,
3890 weights.len()
3891 ),
3892 })?;
3893 score = score
3894 .checked_add(Self::soft_clause_weight_score(idx, weight)?)
3895 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
3896 construct: "GPU solver production MaxSAT encoding".to_string(),
3897 context: format!(
3898 "soft-clause selection score overflowed u64 while adding index {}",
3899 idx
3900 ),
3901 })?;
3902 }
3903 Ok(score)
3904 }
3905
3906 fn soft_clause_weight_score(idx: usize, weight: f64) -> Result<u64> {
3907 if !weight.is_finite() || weight < 0.0 || weight.fract() != 0.0 {
3908 return Err(XlogError::UnsupportedEpistemicConstruct {
3909 construct: "GPU solver production MaxSAT encoding".to_string(),
3910 context: format!(
3911 "soft-clause weight at index {} must be a finite nonnegative integer, got {}",
3912 idx, weight
3913 ),
3914 });
3915 }
3916 if weight >= u64::MAX as f64 {
3917 return Err(XlogError::UnsupportedEpistemicConstruct {
3918 construct: "GPU solver production MaxSAT encoding".to_string(),
3919 context: format!(
3920 "soft-clause weight at index {} exceeds u64 score range",
3921 idx
3922 ),
3923 });
3924 }
3925 Ok(weight as u64)
3926 }
3927
3928 pub fn solve_weighted_maxsat_search_with_gpu_execution_result(
3935 &mut self,
3936 provider: &CudaKernelProvider,
3937 result: &EpistemicGpuExecutionResult,
3938 workspace: &mut GpuCdclWorkspace,
3939 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3940 ) -> Result<GpuSolverProductionMaxSatReport> {
3941 self.with_trace_rollback(|this| {
3942 this.solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3943 provider, result, workspace, candidates,
3944 )
3945 })
3946 }
3947
3948 fn solve_weighted_maxsat_search_with_gpu_execution_result_impl(
3949 &mut self,
3950 provider: &CudaKernelProvider,
3951 result: &EpistemicGpuExecutionResult,
3952 workspace: &mut GpuCdclWorkspace,
3953 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3954 ) -> Result<GpuSolverProductionMaxSatReport> {
3955 self.require_workspace_on_adapter_provider(workspace)?;
3956 self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3957 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
3958 let events_before = self.trace.accepted_path_event_snapshot()?;
3959 let mut report = self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
3960 report.candidate_evidence_records = 1;
3961 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
3962 self.record_accepted_gpu_candidate_state(&state)?;
3963 Ok(report)
3964 }
3965
3966 pub fn solve_weighted_maxsat_search_with_gpu_batch_execution_result(
3968 &mut self,
3969 provider: &CudaKernelProvider,
3970 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
3971 workspace: &mut GpuCdclWorkspace,
3972 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3973 ) -> Result<GpuSolverProductionMaxSatReport> {
3974 self.with_trace_rollback(|this| {
3975 this.require_workspace_on_adapter_provider(workspace)?;
3976 this.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
3977 let results =
3978 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
3979 let report = this
3980 .solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
3981 provider, &results, workspace, candidates,
3982 )?;
3983 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
3984 Ok(report)
3985 })
3986 }
3987
3988 pub fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results(
3990 &mut self,
3991 provider: &CudaKernelProvider,
3992 results: &[&EpistemicGpuExecutionResult],
3993 workspace: &mut GpuCdclWorkspace,
3994 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
3995 ) -> Result<GpuSolverProductionMaxSatReport> {
3996 self.with_trace_rollback(|this| {
3997 this.solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
3998 provider, results, workspace, candidates,
3999 )
4000 })
4001 }
4002
4003 fn solve_multi_candidate_weighted_maxsat_search_with_gpu_execution_results_impl(
4004 &mut self,
4005 provider: &CudaKernelProvider,
4006 results: &[&EpistemicGpuExecutionResult],
4007 workspace: &mut GpuCdclWorkspace,
4008 candidates: &[GpuSolverProductionMaxSatSearchCandidate<'_>],
4009 ) -> Result<GpuSolverProductionMaxSatReport> {
4010 if results.is_empty() {
4011 return Err(XlogError::UnsupportedEpistemicConstruct {
4012 construct: "GPU solver production MaxSAT search".to_string(),
4013 context: "multi-candidate MaxSAT search requires at least one accepted GPU result"
4014 .to_string(),
4015 });
4016 }
4017 self.require_workspace_on_adapter_provider(workspace)?;
4018 self.require_weighted_maxsat_search_candidates_and_artifacts(workspace, candidates)?;
4019 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4020
4021 let mut report = GpuSolverProductionMaxSatReport::default();
4022 for state in &states {
4023 let events_before = self.trace.accepted_path_event_snapshot()?;
4024 let step_report =
4025 self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4026 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4027 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4028 checked_solver_report_counter_add!(
4029 report,
4030 candidates_checked,
4031 step_report.candidates_checked
4032 );
4033 checked_solver_report_counter_add!(
4034 report,
4035 satisfiable_candidates,
4036 step_report.satisfiable_candidates
4037 );
4038 checked_solver_report_counter_add!(
4039 report,
4040 unsat_candidates_pruned,
4041 step_report.unsat_candidates_pruned
4042 );
4043 checked_solver_report_counter_add!(
4044 report,
4045 gpu_cdcl_candidate_encodes,
4046 step_report.gpu_cdcl_candidate_encodes
4047 );
4048 checked_solver_report_counter_add!(
4049 report,
4050 gpu_cdcl_candidate_solves,
4051 step_report.gpu_cdcl_candidate_solves
4052 );
4053 checked_solver_report_counter_add!(
4054 report,
4055 frontier_upper_bound_certificates,
4056 step_report.frontier_upper_bound_certificates
4057 );
4058 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4059 self.record_accepted_gpu_candidate_state(state)?;
4060 }
4061
4062 Ok(report)
4063 }
4064
4065 pub fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result(
4072 &mut self,
4073 provider: &CudaKernelProvider,
4074 result: &EpistemicGpuExecutionResult,
4075 workspace: &mut GpuCdclWorkspace,
4076 weighted: &SolveInstance,
4077 branch_var_limit: &TrackedCudaSlice<u32>,
4078 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4079 ) -> Result<GpuSolverProductionMaxSatReport> {
4080 self.with_trace_rollback(|this| {
4081 this.solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4082 provider,
4083 result,
4084 workspace,
4085 weighted,
4086 branch_var_limit,
4087 selections,
4088 )
4089 })
4090 }
4091
4092 fn solve_weighted_maxsat_encoded_search_with_gpu_execution_result_impl(
4093 &mut self,
4094 provider: &CudaKernelProvider,
4095 result: &EpistemicGpuExecutionResult,
4096 workspace: &mut GpuCdclWorkspace,
4097 weighted: &SolveInstance,
4098 branch_var_limit: &TrackedCudaSlice<u32>,
4099 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4100 ) -> Result<GpuSolverProductionMaxSatReport> {
4101 self.require_workspace_on_adapter_provider(workspace)?;
4102 self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4103 workspace,
4104 weighted,
4105 branch_var_limit,
4106 selections,
4107 )?;
4108 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4109 let events_before = self.trace.accepted_path_event_snapshot()?;
4110 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4111 let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4112 let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4113 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4114 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4115 certificates_before,
4116 "gpu_maxsat_frontier_upper_bound_certificates",
4117 )?;
4118 let search_candidates: Vec<_> = encoded
4119 .iter()
4120 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4121 score: candidate.score,
4122 cnf: &candidate.cnf,
4123 branch_var_limit,
4124 status: candidate.status,
4125 })
4126 .collect();
4127 let mut report = self.solve_weighted_maxsat_search_candidates(
4128 workspace,
4129 &search_candidates,
4130 frontier_upper_bound_certificates,
4131 )?;
4132 report.candidate_evidence_records = 1;
4133 report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4134 self.trace.gpu_maxsat_candidate_encodes,
4135 encodes_before,
4136 "gpu_cdcl_candidate_encodes",
4137 )?;
4138 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4139 self.record_accepted_gpu_candidate_state(&state)?;
4140 Ok(report)
4141 }
4142
4143 pub fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4151 &mut self,
4152 provider: &CudaKernelProvider,
4153 results: &[&EpistemicGpuExecutionResult],
4154 workspace: &mut GpuCdclWorkspace,
4155 weighted: &SolveInstance,
4156 branch_var_limit: &TrackedCudaSlice<u32>,
4157 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4158 ) -> Result<GpuSolverProductionMaxSatReport> {
4159 self.with_trace_rollback(|this| {
4160 this.solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4161 provider,
4162 results,
4163 workspace,
4164 weighted,
4165 branch_var_limit,
4166 selections,
4167 )
4168 })
4169 }
4170
4171 fn solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results_impl(
4172 &mut self,
4173 provider: &CudaKernelProvider,
4174 results: &[&EpistemicGpuExecutionResult],
4175 workspace: &mut GpuCdclWorkspace,
4176 weighted: &SolveInstance,
4177 branch_var_limit: &TrackedCudaSlice<u32>,
4178 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4179 ) -> Result<GpuSolverProductionMaxSatReport> {
4180 if results.is_empty() {
4181 return Err(XlogError::UnsupportedEpistemicConstruct {
4182 construct: "GPU solver production MaxSAT encoding".to_string(),
4183 context:
4184 "multi-candidate weighted MaxSAT encoded search requires at least one accepted GPU result"
4185 .to_string(),
4186 });
4187 }
4188 self.require_workspace_on_adapter_provider(workspace)?;
4189 self.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4190 workspace,
4191 weighted,
4192 branch_var_limit,
4193 selections,
4194 )?;
4195 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4196
4197 let mut report = GpuSolverProductionMaxSatReport::default();
4198 for state in &states {
4199 let events_before = self.trace.accepted_path_event_snapshot()?;
4200 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4201 let certificates_before = self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4202 let encoded = self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4203 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4204 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4205 certificates_before,
4206 "gpu_maxsat_frontier_upper_bound_certificates",
4207 )?;
4208 let search_candidates: Vec<_> = encoded
4209 .iter()
4210 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4211 score: candidate.score,
4212 cnf: &candidate.cnf,
4213 branch_var_limit,
4214 status: candidate.status,
4215 })
4216 .collect();
4217 let step_report = self.solve_weighted_maxsat_search_candidates(
4218 workspace,
4219 &search_candidates,
4220 frontier_upper_bound_certificates,
4221 )?;
4222 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4223 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4224 checked_solver_report_counter_add!(
4225 report,
4226 candidates_checked,
4227 step_report.candidates_checked
4228 );
4229 checked_solver_report_counter_add!(
4230 report,
4231 satisfiable_candidates,
4232 step_report.satisfiable_candidates
4233 );
4234 checked_solver_report_counter_add!(
4235 report,
4236 unsat_candidates_pruned,
4237 step_report.unsat_candidates_pruned
4238 );
4239 let encoded_delta = Self::checked_report_counter_delta(
4240 self.trace.gpu_maxsat_candidate_encodes,
4241 encodes_before,
4242 "gpu_cdcl_candidate_encodes",
4243 )?;
4244 checked_solver_report_counter_add!(report, gpu_cdcl_candidate_encodes, encoded_delta);
4245 checked_solver_report_counter_add!(
4246 report,
4247 gpu_cdcl_candidate_solves,
4248 step_report.gpu_cdcl_candidate_solves
4249 );
4250 checked_solver_report_counter_add!(
4251 report,
4252 frontier_upper_bound_certificates,
4253 step_report.frontier_upper_bound_certificates
4254 );
4255 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4256 self.record_accepted_gpu_candidate_state(state)?;
4257 }
4258
4259 Ok(report)
4260 }
4261
4262 pub fn solve_weighted_maxsat_encoded_search_with_gpu_batch_execution_result(
4268 &mut self,
4269 provider: &CudaKernelProvider,
4270 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4271 workspace: &mut GpuCdclWorkspace,
4272 weighted: &SolveInstance,
4273 branch_var_limit: &TrackedCudaSlice<u32>,
4274 selections: &[GpuSolverProductionWeightedMaxSatSelection<'_>],
4275 ) -> Result<GpuSolverProductionMaxSatReport> {
4276 self.with_trace_rollback(|this| {
4277 this.require_workspace_on_adapter_provider(workspace)?;
4278 this.require_weighted_maxsat_encoded_search_inputs_and_artifacts(
4279 workspace,
4280 weighted,
4281 branch_var_limit,
4282 selections,
4283 )?;
4284 let results =
4285 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4286 let report = this
4287 .solve_multi_candidate_weighted_maxsat_encoded_search_with_gpu_execution_results(
4288 provider,
4289 &results,
4290 workspace,
4291 weighted,
4292 branch_var_limit,
4293 selections,
4294 )?;
4295 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4296 Ok(report)
4297 })
4298 }
4299
4300 fn add_maxsat_schedule_step_report(
4301 report: &mut GpuSolverProductionMaxSatScheduleReport,
4302 step_report: GpuSolverProductionMaxSatReport,
4303 ) -> Result<()> {
4304 report.optimum_score = report.optimum_score.max(step_report.optimum_score);
4305 checked_solver_report_counter_add!(
4306 report,
4307 candidates_checked,
4308 step_report.candidates_checked
4309 );
4310 checked_solver_report_counter_add!(
4311 report,
4312 satisfiable_candidates,
4313 step_report.satisfiable_candidates
4314 );
4315 checked_solver_report_counter_add!(
4316 report,
4317 unsat_candidates_pruned,
4318 step_report.unsat_candidates_pruned
4319 );
4320 checked_solver_report_counter_add!(
4321 report,
4322 gpu_cdcl_candidate_encodes,
4323 step_report.gpu_cdcl_candidate_encodes
4324 );
4325 checked_solver_report_counter_add!(
4326 report,
4327 gpu_cdcl_candidate_solves,
4328 step_report.gpu_cdcl_candidate_solves
4329 );
4330 checked_solver_report_counter_add!(
4331 report,
4332 frontier_upper_bound_certificates,
4333 step_report.frontier_upper_bound_certificates
4334 );
4335 Ok(())
4336 }
4337
4338 fn solve_maxsat_schedule_jobs(
4339 &mut self,
4340 workspace: &mut GpuCdclWorkspace,
4341 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4342 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4343 self.require_workspace_on_adapter_provider(workspace)?;
4344 Self::require_maxsat_schedule_jobs(jobs)?;
4345 self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4346
4347 let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4348 for job in jobs {
4349 checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_jobs);
4350 checked_solver_report_counter_inc!(report, jobs);
4351
4352 match job {
4353 GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4354 checked_solver_trace_counter_inc!(
4355 self,
4356 gpu_maxsat_scheduler_candidate_set_jobs
4357 );
4358 checked_solver_report_counter_inc!(report, candidate_set_jobs);
4359 let step_report = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4360 Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4361 }
4362 GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4363 checked_solver_trace_counter_inc!(self, gpu_maxsat_scheduler_search_jobs);
4364 checked_solver_report_counter_inc!(report, search_jobs);
4365 let step_report =
4366 self.solve_weighted_maxsat_search_candidates(workspace, candidates, 0)?;
4367 Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4368 }
4369 GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4370 weighted,
4371 branch_var_limit,
4372 selections,
4373 } => {
4374 checked_solver_trace_counter_inc!(
4375 self,
4376 gpu_maxsat_scheduler_encoded_search_jobs
4377 );
4378 checked_solver_report_counter_inc!(report, encoded_search_jobs);
4379 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4380 let certificates_before =
4381 self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4382 let encoded =
4383 self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4384 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4385 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4386 certificates_before,
4387 "gpu_maxsat_frontier_upper_bound_certificates",
4388 )?;
4389 let search_candidates: Vec<_> = encoded
4390 .iter()
4391 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4392 score: candidate.score,
4393 cnf: &candidate.cnf,
4394 branch_var_limit,
4395 status: candidate.status,
4396 })
4397 .collect();
4398 let mut step_report = self.solve_weighted_maxsat_search_candidates(
4399 workspace,
4400 &search_candidates,
4401 frontier_upper_bound_certificates,
4402 )?;
4403 step_report.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4404 self.trace.gpu_maxsat_candidate_encodes,
4405 encodes_before,
4406 "gpu_cdcl_candidate_encodes",
4407 )?;
4408 Self::add_maxsat_schedule_step_report(&mut report, step_report)?;
4409 }
4410 GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4411 if reason.trim().is_empty() {
4412 return Err(XlogError::UnsupportedEpistemicConstruct {
4413 construct: "GPU solver production MaxSAT scheduler".to_string(),
4414 context: "UNKNOWN scheduler status requires a diagnostic reason"
4415 .to_string(),
4416 });
4417 }
4418 checked_solver_trace_counter_inc!(
4419 self,
4420 gpu_maxsat_scheduler_unknown_status_jobs
4421 );
4422 checked_solver_report_counter_inc!(report, unknown_jobs);
4423 }
4424 GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4425 if *budget_micros == 0 {
4426 return Err(XlogError::UnsupportedEpistemicConstruct {
4427 construct: "GPU solver production MaxSAT scheduler".to_string(),
4428 context: "TIMEOUT scheduler status requires a nonzero budget"
4429 .to_string(),
4430 });
4431 }
4432 checked_solver_trace_counter_inc!(
4433 self,
4434 gpu_maxsat_scheduler_timeout_status_jobs
4435 );
4436 checked_solver_report_counter_inc!(report, timeout_jobs);
4437 }
4438 }
4439 }
4440
4441 Ok(report)
4442 }
4443
4444 fn require_maxsat_schedule_jobs(
4445 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4446 ) -> Result<()> {
4447 if jobs.is_empty() {
4448 return Err(XlogError::UnsupportedEpistemicConstruct {
4449 construct: "GPU solver production MaxSAT scheduler".to_string(),
4450 context: "accepted MaxSAT scheduler requires at least one GPU job".to_string(),
4451 });
4452 }
4453
4454 for job in jobs {
4455 match job {
4456 GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4457 Self::require_weighted_maxsat_candidates(candidates)?;
4458 }
4459 GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4460 Self::require_weighted_maxsat_search_candidates(candidates)?;
4461 }
4462 GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4463 weighted,
4464 selections,
4465 ..
4466 } => {
4467 Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4468 }
4469 GpuSolverProductionMaxSatScheduleJob::Unknown { reason } => {
4470 if reason.trim().is_empty() {
4471 return Err(XlogError::UnsupportedEpistemicConstruct {
4472 construct: "GPU solver production MaxSAT scheduler".to_string(),
4473 context: "UNKNOWN scheduler status requires a diagnostic reason"
4474 .to_string(),
4475 });
4476 }
4477 }
4478 GpuSolverProductionMaxSatScheduleJob::Timeout { budget_micros } => {
4479 if *budget_micros == 0 {
4480 return Err(XlogError::UnsupportedEpistemicConstruct {
4481 construct: "GPU solver production MaxSAT scheduler".to_string(),
4482 context: "TIMEOUT scheduler status requires a nonzero budget"
4483 .to_string(),
4484 });
4485 }
4486 }
4487 }
4488 }
4489 Ok(())
4490 }
4491
4492 fn require_maxsat_schedule_job_artifacts(
4493 &self,
4494 workspace: &GpuCdclWorkspace,
4495 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4496 ) -> Result<()> {
4497 for job in jobs {
4498 match job {
4499 GpuSolverProductionMaxSatScheduleJob::CandidateSet { candidates } => {
4500 self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4501 }
4502 GpuSolverProductionMaxSatScheduleJob::Search { candidates } => {
4503 self.require_weighted_maxsat_search_candidates_and_artifacts(
4504 workspace, candidates,
4505 )?;
4506 }
4507 GpuSolverProductionMaxSatScheduleJob::EncodedSearch {
4508 weighted,
4509 branch_var_limit,
4510 ..
4511 } => {
4512 self.require_workspace_capacity_for_weighted_maxsat_encoding(
4513 workspace,
4514 weighted,
4515 "GPU solver production MaxSAT scheduler",
4516 )?;
4517 self.require_branch_var_limit_on_adapter_provider(
4518 branch_var_limit,
4519 "GPU solver production MaxSAT scheduler",
4520 )?;
4521 }
4522 GpuSolverProductionMaxSatScheduleJob::Unknown { .. }
4523 | GpuSolverProductionMaxSatScheduleJob::Timeout { .. } => {}
4524 }
4525 }
4526 Ok(())
4527 }
4528
4529 pub fn solve_maxsat_schedule_with_gpu_execution_results(
4537 &mut self,
4538 provider: &CudaKernelProvider,
4539 results: &[&EpistemicGpuExecutionResult],
4540 workspace: &mut GpuCdclWorkspace,
4541 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4542 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4543 self.with_trace_rollback(|this| {
4544 this.solve_maxsat_schedule_with_gpu_execution_results_impl(
4545 provider, results, workspace, jobs,
4546 )
4547 })
4548 }
4549
4550 fn solve_maxsat_schedule_with_gpu_execution_results_impl(
4551 &mut self,
4552 provider: &CudaKernelProvider,
4553 results: &[&EpistemicGpuExecutionResult],
4554 workspace: &mut GpuCdclWorkspace,
4555 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4556 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4557 if results.is_empty() {
4558 return Err(XlogError::UnsupportedEpistemicConstruct {
4559 construct: "GPU solver production MaxSAT scheduler".to_string(),
4560 context: "MaxSAT scheduler requires at least one accepted GPU result".to_string(),
4561 });
4562 }
4563 Self::require_maxsat_schedule_jobs(jobs)?;
4564 self.require_workspace_on_adapter_provider(workspace)?;
4565 self.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4566 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4567
4568 let mut report = GpuSolverProductionMaxSatScheduleReport::default();
4569 for state in &states {
4570 let events_before = self.trace.accepted_path_event_snapshot()?;
4571 let step_report = self.solve_maxsat_schedule_jobs(workspace, jobs)?;
4572 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4573 checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4574 checked_solver_report_counter_add!(
4575 report,
4576 candidate_set_jobs,
4577 step_report.candidate_set_jobs
4578 );
4579 checked_solver_report_counter_add!(report, search_jobs, step_report.search_jobs);
4580 checked_solver_report_counter_add!(
4581 report,
4582 encoded_search_jobs,
4583 step_report.encoded_search_jobs
4584 );
4585 checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4586 checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4587 Self::add_maxsat_schedule_step_report(
4588 &mut report,
4589 GpuSolverProductionMaxSatReport {
4590 optimum_score: step_report.optimum_score,
4591 candidates_checked: step_report.candidates_checked,
4592 satisfiable_candidates: step_report.satisfiable_candidates,
4593 unsat_candidates_pruned: step_report.unsat_candidates_pruned,
4594 gpu_cdcl_candidate_encodes: step_report.gpu_cdcl_candidate_encodes,
4595 gpu_cdcl_candidate_solves: step_report.gpu_cdcl_candidate_solves,
4596 frontier_upper_bound_certificates: step_report
4597 .frontier_upper_bound_certificates,
4598 ..GpuSolverProductionMaxSatReport::default()
4599 },
4600 )?;
4601 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4602 self.record_accepted_gpu_candidate_state(state)?;
4603 }
4604
4605 Ok(report)
4606 }
4607
4608 pub fn solve_maxsat_schedule_with_gpu_batch_execution_result(
4615 &mut self,
4616 provider: &CudaKernelProvider,
4617 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4618 workspace: &mut GpuCdclWorkspace,
4619 jobs: &[GpuSolverProductionMaxSatScheduleJob<'_>],
4620 ) -> Result<GpuSolverProductionMaxSatScheduleReport> {
4621 self.with_trace_rollback(|this| {
4622 Self::require_maxsat_schedule_jobs(jobs)?;
4623 this.require_workspace_on_adapter_provider(workspace)?;
4624 this.require_maxsat_schedule_job_artifacts(workspace, jobs)?;
4625 let results =
4626 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4627 let report = this.solve_maxsat_schedule_with_gpu_execution_results(
4628 provider, &results, workspace, jobs,
4629 )?;
4630 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4631 Ok(report)
4632 })
4633 }
4634
4635 fn solve_portfolio_jobs(
4636 &mut self,
4637 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4638 ) -> Result<GpuSolverProductionPortfolioReport> {
4639 self.require_portfolio_jobs_and_artifacts(jobs)?;
4640
4641 let mut report = GpuSolverProductionPortfolioReport::default();
4642 for job in jobs {
4643 checked_solver_trace_counter_inc!(self, gpu_portfolio_jobs);
4644 checked_solver_report_counter_inc!(report, jobs);
4645
4646 match job {
4647 GpuSolverProductionPortfolioJob::Sat {
4648 cnf,
4649 branch_var_limit,
4650 } => {
4651 let _assignment = self
4652 .solver
4653 .solve_expect_sat_with_branch_limit(cnf, branch_var_limit)?;
4654 checked_solver_trace_counter_inc!(self, gpu_cdcl_sat_solves);
4655 checked_solver_trace_counter_inc!(self, gpu_portfolio_sat_jobs);
4656 checked_solver_report_counter_inc!(report, sat_jobs);
4657 }
4658 GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4659 let maxsat = self.solve_weighted_maxsat_candidates(candidates, 0)?;
4660 checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4661 checked_solver_report_counter_inc!(report, maxsat_jobs);
4662 Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4663 }
4664 GpuSolverProductionPortfolioJob::EncodedMaxSat {
4665 weighted,
4666 branch_var_limit,
4667 selections,
4668 } => {
4669 let encodes_before = self.trace.gpu_maxsat_candidate_encodes;
4670 let certificates_before =
4671 self.trace.gpu_maxsat_frontier_upper_bound_certificates;
4672 let encoded =
4673 self.encode_weighted_maxsat_search_candidates(weighted, selections)?;
4674 let frontier_upper_bound_certificates = Self::checked_report_counter_delta(
4675 self.trace.gpu_maxsat_frontier_upper_bound_certificates,
4676 certificates_before,
4677 "gpu_maxsat_frontier_upper_bound_certificates",
4678 )?;
4679 let search_candidates: Vec<_> = encoded
4680 .iter()
4681 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
4682 score: candidate.score,
4683 cnf: &candidate.cnf,
4684 branch_var_limit,
4685 status: candidate.status,
4686 })
4687 .collect();
4688 let mut workspace = self.new_workspace(
4689 weighted.num_vars,
4690 Self::checked_workspace_clause_cap(weighted)?,
4691 )?;
4692 let mut maxsat = self.solve_weighted_maxsat_search_candidates(
4693 &mut workspace,
4694 &search_candidates,
4695 frontier_upper_bound_certificates,
4696 )?;
4697 maxsat.gpu_cdcl_candidate_encodes = Self::checked_report_counter_delta(
4698 self.trace.gpu_maxsat_candidate_encodes,
4699 encodes_before,
4700 "gpu_cdcl_candidate_encodes",
4701 )?;
4702 checked_solver_trace_counter_inc!(self, gpu_portfolio_maxsat_jobs);
4703 checked_solver_report_counter_inc!(report, maxsat_jobs);
4704 Self::add_portfolio_maxsat_report(&mut report, maxsat)?;
4705 }
4706 GpuSolverProductionPortfolioJob::Unknown { .. } => {
4707 checked_solver_trace_counter_inc!(self, gpu_portfolio_unknown_status_jobs);
4708 checked_solver_report_counter_inc!(report, unknown_jobs);
4709 }
4710 GpuSolverProductionPortfolioJob::Timeout { .. } => {
4711 checked_solver_trace_counter_inc!(self, gpu_portfolio_timeout_status_jobs);
4712 checked_solver_report_counter_inc!(report, timeout_jobs);
4713 }
4714 }
4715 }
4716
4717 Ok(report)
4718 }
4719
4720 fn require_portfolio_jobs(jobs: &[GpuSolverProductionPortfolioJob<'_>]) -> Result<()> {
4721 if jobs.is_empty() {
4722 return Err(XlogError::UnsupportedEpistemicConstruct {
4723 construct: "GPU solver production portfolio".to_string(),
4724 context: "accepted solver portfolio requires at least one GPU job".to_string(),
4725 });
4726 }
4727
4728 for job in jobs {
4729 match job {
4730 GpuSolverProductionPortfolioJob::Sat { .. } => {}
4731 GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4732 Self::require_weighted_maxsat_candidates(candidates)?;
4733 }
4734 GpuSolverProductionPortfolioJob::EncodedMaxSat {
4735 weighted,
4736 selections,
4737 ..
4738 } => {
4739 Self::require_weighted_maxsat_encoding_inputs(weighted, selections)?;
4740 Self::checked_workspace_clause_cap(weighted)?;
4741 }
4742 GpuSolverProductionPortfolioJob::Unknown { reason } => {
4743 if reason.trim().is_empty() {
4744 return Err(XlogError::UnsupportedEpistemicConstruct {
4745 construct: "GPU solver production portfolio".to_string(),
4746 context: "UNKNOWN portfolio status requires a diagnostic reason"
4747 .to_string(),
4748 });
4749 }
4750 }
4751 GpuSolverProductionPortfolioJob::Timeout { budget_micros } => {
4752 if *budget_micros == 0 {
4753 return Err(XlogError::UnsupportedEpistemicConstruct {
4754 construct: "GPU solver production portfolio".to_string(),
4755 context: "TIMEOUT portfolio status requires a nonzero budget"
4756 .to_string(),
4757 });
4758 }
4759 }
4760 }
4761 }
4762 Ok(())
4763 }
4764
4765 fn require_portfolio_job_artifacts(
4766 &self,
4767 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4768 ) -> Result<()> {
4769 for job in jobs {
4770 match job {
4771 GpuSolverProductionPortfolioJob::Sat {
4772 cnf,
4773 branch_var_limit,
4774 } => {
4775 self.require_solver_artifact_on_adapter_provider(
4776 cnf,
4777 branch_var_limit,
4778 "GPU solver production portfolio",
4779 )?;
4780 }
4781 GpuSolverProductionPortfolioJob::MaxSat { candidates } => {
4782 self.require_weighted_maxsat_candidate_artifacts(candidates)?;
4783 }
4784 GpuSolverProductionPortfolioJob::EncodedMaxSat {
4785 branch_var_limit, ..
4786 } => {
4787 self.require_branch_var_limit_on_adapter_provider(
4788 branch_var_limit,
4789 "GPU solver production portfolio",
4790 )?;
4791 }
4792 GpuSolverProductionPortfolioJob::Unknown { .. }
4793 | GpuSolverProductionPortfolioJob::Timeout { .. } => {}
4794 }
4795 }
4796 Ok(())
4797 }
4798
4799 fn require_portfolio_jobs_and_artifacts(
4800 &self,
4801 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4802 ) -> Result<()> {
4803 Self::require_portfolio_jobs(jobs)?;
4804 self.require_portfolio_job_artifacts(jobs)
4805 }
4806
4807 fn add_portfolio_maxsat_report(
4808 report: &mut GpuSolverProductionPortfolioReport,
4809 maxsat: GpuSolverProductionMaxSatReport,
4810 ) -> Result<()> {
4811 checked_solver_report_counter_add!(report, maxsat_optimum_scores, maxsat.optimum_score);
4812 checked_solver_report_counter_add!(
4813 report,
4814 maxsat_candidates_checked,
4815 maxsat.candidates_checked
4816 );
4817 checked_solver_report_counter_add!(
4818 report,
4819 maxsat_satisfiable_candidates,
4820 maxsat.satisfiable_candidates
4821 );
4822 checked_solver_report_counter_add!(
4823 report,
4824 maxsat_unsat_candidates_pruned,
4825 maxsat.unsat_candidates_pruned
4826 );
4827 checked_solver_report_counter_add!(
4828 report,
4829 maxsat_gpu_cdcl_candidate_encodes,
4830 maxsat.gpu_cdcl_candidate_encodes
4831 );
4832 checked_solver_report_counter_add!(
4833 report,
4834 maxsat_gpu_cdcl_candidate_solves,
4835 maxsat.gpu_cdcl_candidate_solves
4836 );
4837 checked_solver_report_counter_add!(
4838 report,
4839 maxsat_frontier_upper_bound_certificates,
4840 maxsat.frontier_upper_bound_certificates
4841 );
4842 Ok(())
4843 }
4844
4845 fn add_portfolio_report(
4846 report: &mut GpuSolverProductionPortfolioReport,
4847 step_report: GpuSolverProductionPortfolioReport,
4848 ) -> Result<()> {
4849 checked_solver_report_counter_add!(report, jobs, step_report.jobs);
4850 checked_solver_report_counter_add!(report, sat_jobs, step_report.sat_jobs);
4851 checked_solver_report_counter_add!(report, maxsat_jobs, step_report.maxsat_jobs);
4852 checked_solver_report_counter_add!(report, unknown_jobs, step_report.unknown_jobs);
4853 checked_solver_report_counter_add!(report, timeout_jobs, step_report.timeout_jobs);
4854 checked_solver_report_counter_add!(
4855 report,
4856 maxsat_optimum_scores,
4857 step_report.maxsat_optimum_scores
4858 );
4859 checked_solver_report_counter_add!(
4860 report,
4861 maxsat_candidates_checked,
4862 step_report.maxsat_candidates_checked
4863 );
4864 checked_solver_report_counter_add!(
4865 report,
4866 maxsat_satisfiable_candidates,
4867 step_report.maxsat_satisfiable_candidates
4868 );
4869 checked_solver_report_counter_add!(
4870 report,
4871 maxsat_unsat_candidates_pruned,
4872 step_report.maxsat_unsat_candidates_pruned
4873 );
4874 checked_solver_report_counter_add!(
4875 report,
4876 maxsat_gpu_cdcl_candidate_encodes,
4877 step_report.maxsat_gpu_cdcl_candidate_encodes
4878 );
4879 checked_solver_report_counter_add!(
4880 report,
4881 maxsat_gpu_cdcl_candidate_solves,
4882 step_report.maxsat_gpu_cdcl_candidate_solves
4883 );
4884 checked_solver_report_counter_add!(
4885 report,
4886 maxsat_frontier_upper_bound_certificates,
4887 step_report.maxsat_frontier_upper_bound_certificates
4888 );
4889 Ok(())
4890 }
4891
4892 pub fn solve_portfolio_with_gpu_execution_result(
4898 &mut self,
4899 provider: &CudaKernelProvider,
4900 result: &EpistemicGpuExecutionResult,
4901 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4902 ) -> Result<GpuSolverProductionPortfolioReport> {
4903 self.with_trace_rollback(|this| {
4904 this.solve_portfolio_with_gpu_execution_result_impl(provider, result, jobs)
4905 })
4906 }
4907
4908 fn solve_portfolio_with_gpu_execution_result_impl(
4909 &mut self,
4910 provider: &CudaKernelProvider,
4911 result: &EpistemicGpuExecutionResult,
4912 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4913 ) -> Result<GpuSolverProductionPortfolioReport> {
4914 self.require_portfolio_jobs_and_artifacts(jobs)?;
4915 let state = self.require_accepted_gpu_solver_evidence(provider, result)?;
4916
4917 let events_before = self.trace.accepted_path_event_snapshot()?;
4918 let mut report = self.solve_portfolio_jobs(jobs)?;
4919 report.candidate_evidence_records = 1;
4920
4921 self.record_accepted_gpu_solver_production_path_events_since(events_before, &state)?;
4922 self.record_accepted_gpu_candidate_state(&state)?;
4923 Ok(report)
4924 }
4925
4926 pub fn solve_multi_candidate_portfolio_with_gpu_execution_results(
4928 &mut self,
4929 provider: &CudaKernelProvider,
4930 results: &[&EpistemicGpuExecutionResult],
4931 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4932 ) -> Result<GpuSolverProductionPortfolioReport> {
4933 self.with_trace_rollback(|this| {
4934 this.solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4935 provider, results, jobs,
4936 )
4937 })
4938 }
4939
4940 fn solve_multi_candidate_portfolio_with_gpu_execution_results_impl(
4941 &mut self,
4942 provider: &CudaKernelProvider,
4943 results: &[&EpistemicGpuExecutionResult],
4944 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4945 ) -> Result<GpuSolverProductionPortfolioReport> {
4946 if results.is_empty() {
4947 return Err(XlogError::UnsupportedEpistemicConstruct {
4948 construct: "GPU solver production portfolio".to_string(),
4949 context: "multi-candidate portfolio requires at least one accepted GPU result"
4950 .to_string(),
4951 });
4952 }
4953 self.require_portfolio_jobs_and_artifacts(jobs)?;
4954 let states = self.require_accepted_gpu_solver_states(provider, results)?;
4955
4956 let mut report = GpuSolverProductionPortfolioReport::default();
4957 for state in &states {
4958 let events_before = self.trace.accepted_path_event_snapshot()?;
4959 let step_report = self.solve_portfolio_jobs(jobs)?;
4960 checked_solver_report_counter_inc!(report, candidate_evidence_records);
4961 Self::add_portfolio_report(&mut report, step_report)?;
4962 self.record_accepted_gpu_solver_production_path_events_since(events_before, state)?;
4963 self.record_accepted_gpu_candidate_state(state)?;
4964 }
4965
4966 Ok(report)
4967 }
4968
4969 pub fn solve_portfolio_with_gpu_batch_execution_result(
4975 &mut self,
4976 provider: &CudaKernelProvider,
4977 evidence: GpuSolverProductionBatchExecutionEvidence<'_>,
4978 jobs: &[GpuSolverProductionPortfolioJob<'_>],
4979 ) -> Result<GpuSolverProductionPortfolioReport> {
4980 self.with_trace_rollback(|this| {
4981 this.require_portfolio_jobs_and_artifacts(jobs)?;
4982 let results =
4983 this.accepted_solver_results_from_gpu_batch_execution_evidence(provider, evidence)?;
4984 let report = this.solve_multi_candidate_portfolio_with_gpu_execution_results(
4985 provider, &results, jobs,
4986 )?;
4987 this.record_accepted_gpu_batch_candidate_evidence(results.len())?;
4988 Ok(report)
4989 })
4990 }
4991}
4992
4993fn require_accepted_gpu_solver_states(
4994 provider: &CudaKernelProvider,
4995 results: &[&EpistemicGpuExecutionResult],
4996) -> Result<Vec<GpuSolverAcceptedCandidateState>> {
4997 results
4998 .iter()
4999 .map(|result| require_accepted_gpu_solver_evidence(provider, result))
5000 .collect()
5001}
5002
5003fn require_accepted_gpu_solver_evidence(
5004 provider: &CudaKernelProvider,
5005 result: &EpistemicGpuExecutionResult,
5006) -> Result<GpuSolverAcceptedCandidateState> {
5007 let provider_identity = EpistemicGpuProviderIdentity::from_provider(provider);
5008 if result.provider_identity != provider_identity {
5009 return Err(XlogError::UnsupportedEpistemicConstruct {
5010 construct: "accepted GPU solver candidate evidence".to_string(),
5011 context: format!(
5012 "solver evidence provider mismatch: result device={} provider device={} \
5013 result_device_ptr={} provider_device_ptr={} result_memory_ptr={} \
5014 provider_memory_ptr={}",
5015 result.provider_identity.device_ordinal,
5016 provider_identity.device_ordinal,
5017 result.provider_identity.device_ptr,
5018 provider_identity.device_ptr,
5019 result.provider_identity.memory_ptr,
5020 provider_identity.memory_ptr
5021 ),
5022 });
5023 }
5024 if result.candidate_generation.literal_count == 0
5028 || result.prepared.preflight.tuple_membership_binding_count == 0
5029 {
5030 return Err(XlogError::UnsupportedEpistemicConstruct {
5031 construct: "accepted GPU solver candidate evidence".to_string(),
5032 context: format!(
5033 "solver evidence requires at least one GPU-validated epistemic literal and \
5034 tuple-membership binding, got literals={} bindings={}",
5035 result.candidate_generation.literal_count,
5036 result.prepared.preflight.tuple_membership_binding_count
5037 ),
5038 });
5039 }
5040 if result.prepared.preflight.solver_assumption_binding_count == 0 {
5041 return Err(XlogError::UnsupportedEpistemicConstruct {
5042 construct: "accepted GPU solver candidate evidence".to_string(),
5043 context: "solver evidence requires planner-exported solver assumption bindings"
5044 .to_string(),
5045 });
5046 }
5047 if result.prepared.preflight.solver_required_capability_count
5048 < PRODUCTION_SOLVER_REQUIRED_CAPABILITY_COUNT as usize
5049 || result.prepared.preflight.solver_required_status_count
5050 < PRODUCTION_SOLVER_REQUIRED_STATUS_COUNT as usize
5051 {
5052 return Err(XlogError::UnsupportedEpistemicConstruct {
5053 construct: "accepted GPU solver candidate evidence".to_string(),
5054 context: format!(
5055 "solver evidence requires the production capability/status contract, got \
5056 capabilities={} statuses={}",
5057 result.prepared.preflight.solver_required_capability_count,
5058 result.prepared.preflight.solver_required_status_count
5059 ),
5060 });
5061 }
5062 result.require_runtime_dispatch_certification()?;
5063 result
5064 .model_membership
5065 .require_stable_model_tuple_source()?;
5066 if result.constraint_validation.violated_constraint_relations != 0 {
5067 return Err(XlogError::UnsupportedEpistemicConstruct {
5068 construct: "accepted GPU solver candidate evidence".to_string(),
5069 context: format!(
5070 "solver evidence requires zero reduced constraint violations, got {} across {} \
5071 checked constraint relations",
5072 result.constraint_validation.violated_constraint_relations,
5073 result.constraint_validation.checked_constraint_relations
5074 ),
5075 });
5076 }
5077 if result.constraint_validation.row_count_device_reads as usize
5078 > result.constraint_validation.checked_constraint_relations
5079 {
5080 return Err(XlogError::UnsupportedEpistemicConstruct {
5081 construct: "accepted GPU solver candidate evidence".to_string(),
5082 context: format!(
5083 "solver evidence constraint metadata reads cannot exceed checked reduced \
5084 constraint relations, got reads={} checked={}",
5085 result.constraint_validation.row_count_device_reads,
5086 result.constraint_validation.checked_constraint_relations
5087 ),
5088 });
5089 }
5090 require_gpu_kernel_trace(
5091 "candidate generation",
5092 result.candidate_generation.kernel_launches,
5093 result.candidate_generation.host_write_ops,
5094 result.candidate_generation.kernel_timing,
5095 )?;
5096 require_gpu_kernel_trace(
5097 "candidate propagation",
5098 result.propagation.kernel_launches,
5099 result.propagation.host_write_ops,
5100 result.propagation.kernel_timing,
5101 )?;
5102 require_gpu_kernel_trace(
5103 "candidate validation",
5104 result.candidate_validation.kernel_launches,
5105 result.candidate_validation.host_write_ops,
5106 result.candidate_validation.kernel_timing,
5107 )?;
5108 require_gpu_kernel_trace(
5109 "model membership",
5110 result.model_membership.kernel_launches,
5111 result.model_membership.host_write_ops,
5112 result.model_membership.kernel_timing,
5113 )?;
5114 require_gpu_kernel_trace(
5115 "world-view validation",
5116 result.world_view_validation.kernel_launches,
5117 result.world_view_validation.host_write_ops,
5118 result.world_view_validation.kernel_timing,
5119 )?;
5120 require_gpu_kernel_trace(
5121 "accepted-candidate materialization",
5122 result.materialization.kernel_launches,
5123 result.materialization.host_write_ops,
5124 result.materialization.kernel_timing,
5125 )?;
5126 require_gpu_kernel_trace(
5127 "final-result materialization",
5128 result.final_result_materialization.kernel_launches,
5129 result.final_result_materialization.host_write_ops,
5130 result.final_result_materialization.kernel_timing,
5131 )?;
5132 require_gpu_kernel_trace(
5133 "final tuple materialization",
5134 result.final_tuple_materialization.kernel_launches,
5135 result.final_tuple_materialization.host_write_ops,
5136 result.final_tuple_materialization.kernel_timing,
5137 )?;
5138 let accepted_rows = result.final_result_transfer.final_output_rows;
5141 result
5142 .final_tuple_materialization
5143 .require_row_filter_materialization_evidence(
5144 "accepted GPU solver candidate evidence",
5145 accepted_rows,
5146 )?;
5147 if result.transfer_budget.tracked_dtoh_calls != 0
5148 || result.transfer_budget.tracked_htod_calls != 0
5149 || result.transfer_budget.tracked_data_plane_htod_calls != 0
5150 || result.transfer_budget.per_candidate_host_round_trips != 0
5151 {
5152 return Err(XlogError::UnsupportedEpistemicConstruct {
5153 construct: "accepted GPU solver candidate evidence".to_string(),
5154 context: format!(
5155 "solver evidence requires zero hot-path transfers outside bounded launch \
5156 metadata, got dtoh_calls={}, htod_calls={}, data_plane_htod_calls={}, \
5157 launch_metadata_htod_calls={}, per_candidate_round_trips={}",
5158 result.transfer_budget.tracked_dtoh_calls,
5159 result.transfer_budget.tracked_htod_calls,
5160 result.transfer_budget.tracked_data_plane_htod_calls,
5161 result.transfer_budget.tracked_launch_metadata_htod_calls,
5162 result.transfer_budget.per_candidate_host_round_trips
5163 ),
5164 });
5165 }
5166 require_accepted_gpu_solver_semantic_trace(result)?;
5167
5168 if accepted_rows == 0 {
5169 return Err(XlogError::UnsupportedEpistemicConstruct {
5170 construct: "accepted GPU solver candidate evidence".to_string(),
5171 context: "solver evidence requires non-empty accepted GPU final output".to_string(),
5172 });
5173 }
5174 if result.semantic_trace.accepted_candidates == 0 {
5175 return Err(XlogError::UnsupportedEpistemicConstruct {
5176 construct: "accepted GPU solver candidate evidence".to_string(),
5177 context: "solver evidence requires at least one GPU-accepted candidate".to_string(),
5178 });
5179 }
5180
5181 Ok(GpuSolverAcceptedCandidateState::from_validated_result(
5182 result,
5183 accepted_rows,
5184 ))
5185}
5186
5187fn require_accepted_gpu_solver_semantic_trace(result: &EpistemicGpuExecutionResult) -> Result<()> {
5188 let trace = &result.semantic_trace;
5189 let accounted_candidates = trace
5190 .accepted_candidates
5191 .checked_add(trace.rejected_candidates)
5192 .ok_or_else(|| XlogError::UnsupportedEpistemicConstruct {
5193 construct: "accepted GPU solver candidate evidence".to_string(),
5194 context: format!(
5195 "solver evidence semantic trace candidate accounting overflowed: accepted={} \
5196 rejected={}",
5197 trace.accepted_candidates, trace.rejected_candidates
5198 ),
5199 })?;
5200 if trace.generated_candidates != result.candidate_generation.generated_candidates
5201 || trace.tested_candidates != result.world_view_validation.candidates_checked
5202 || trace.accepted_candidates != trace.accepted_candidate_indices.len()
5203 || trace.rejected_candidates != trace.rejected_candidate_indices.len()
5204 || trace.accepted_world_views != trace.accepted_candidates
5205 || accounted_candidates != trace.generated_candidates
5206 {
5207 return Err(XlogError::UnsupportedEpistemicConstruct {
5208 construct: "accepted GPU solver candidate evidence".to_string(),
5209 context: format!(
5210 "solver evidence requires a consistent GPU semantic trace, got \
5211 generated={}, tested={}, expected_generated={}, \
5212 expected_tested={}, accepted={} accepted_indices={}, accepted_world_views={}, \
5213 rejected={} rejected_indices={}",
5214 trace.generated_candidates,
5215 trace.tested_candidates,
5216 result.candidate_generation.generated_candidates,
5217 result.world_view_validation.candidates_checked,
5218 trace.accepted_candidates,
5219 trace.accepted_candidate_indices.len(),
5220 trace.accepted_world_views,
5221 trace.rejected_candidates,
5222 trace.rejected_candidate_indices.len()
5223 ),
5224 });
5225 }
5226 Ok(())
5227}
5228
5229fn require_accepted_gpu_solver_batch_evidence<'a>(
5230 provider: &CudaKernelProvider,
5231 batch: &'a EpistemicGpuBatchExecutionResult,
5232) -> Result<Vec<&'a EpistemicGpuExecutionResult>> {
5233 if batch.results.is_empty() {
5234 return Err(XlogError::UnsupportedEpistemicConstruct {
5235 construct: "accepted GPU solver batch evidence".to_string(),
5236 context: "solver batch evidence requires at least one accepted GPU component"
5237 .to_string(),
5238 });
5239 }
5240 let trace = batch.trace;
5241 if trace.component_count != batch.results.len()
5242 || trace.gpu_runtime_component_executions != batch.results.len()
5243 || trace.tracked_dtoh_calls != 0
5244 || trace.tracked_htod_calls != 0
5245 || trace.tracked_data_plane_htod_calls != 0
5246 || trace.per_candidate_host_round_trips != 0
5247 || trace.violated_constraint_relations != 0
5248 || !trace.aggregate_kernel_timing.is_recorded()
5249 {
5250 return Err(XlogError::UnsupportedEpistemicConstruct {
5251 construct: "accepted GPU solver batch evidence".to_string(),
5252 context: format!(
5253 "solver batch evidence requires complete GPU component execution, zero \
5254 observed hot-path transfers outside bounded launch metadata, and aggregate \
5255 CUDA-event timing, got components={}/{}, dtoh_calls={}, \
5256 htod_calls={}, data_plane_htod_calls={}, launch_metadata_htod_calls={}, \
5257 round_trips={}, constraint_violations={}, aggregate_timing_recorded={}",
5258 trace.gpu_runtime_component_executions,
5259 trace.component_count,
5260 trace.tracked_dtoh_calls,
5261 trace.tracked_htod_calls,
5262 trace.tracked_data_plane_htod_calls,
5263 trace.tracked_launch_metadata_htod_calls,
5264 trace.per_candidate_host_round_trips,
5265 trace.violated_constraint_relations,
5266 trace.aggregate_kernel_timing.is_recorded()
5267 ),
5268 });
5269 }
5270 batch.require_trace_matches_components("accepted GPU solver batch evidence")?;
5271
5272 let mut results = Vec::with_capacity(batch.results.len());
5273 for result in &batch.results {
5274 require_accepted_gpu_solver_evidence(provider, result)?;
5275 results.push(result);
5276 }
5277 Ok(results)
5278}
5279
5280fn require_same_gpu_cnf_for_learned_clause_reuse(source: &GpuCnf, target: &GpuCnf) -> Result<()> {
5281 let same_shape = source.var_cap == target.var_cap
5282 && source.clause_cap == target.clause_cap
5283 && source.lit_cap == target.lit_cap;
5284 let same_buffers = source.num_vars.device_ptr_value() == target.num_vars.device_ptr_value()
5285 && source.num_clauses.device_ptr_value() == target.num_clauses.device_ptr_value()
5286 && source.num_lits.device_ptr_value() == target.num_lits.device_ptr_value()
5287 && source.clause_offsets.device_ptr_value() == target.clause_offsets.device_ptr_value()
5288 && source.literals.device_ptr_value() == target.literals.device_ptr_value();
5289 if !same_shape || !same_buffers {
5290 return Err(XlogError::UnsupportedEpistemicConstruct {
5291 construct: "GPU solver learned-clause reuse".to_string(),
5292 context: "learned-clause import is currently certified only for the same \
5293 device-resident CNF; distinct candidate CNFs must not reuse imported clauses"
5294 .to_string(),
5295 });
5296 }
5297 Ok(())
5298}
5299
5300fn require_stable_learned_clause_arena(
5301 phase: &'static str,
5302 workspace: &GpuCdclWorkspace,
5303 learned_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5304 learned_lits_ptr: cudarc::driver::sys::CUdeviceptr,
5305 proof_offsets_ptr: cudarc::driver::sys::CUdeviceptr,
5306 proof_data_ptr: cudarc::driver::sys::CUdeviceptr,
5307 learned_count_ptr: cudarc::driver::sys::CUdeviceptr,
5308) -> Result<()> {
5309 if workspace.learned_offsets.device_ptr_value() != learned_offsets_ptr
5310 || workspace.learned_lits.device_ptr_value() != learned_lits_ptr
5311 || workspace.proof_offsets.device_ptr_value() != proof_offsets_ptr
5312 || workspace.proof_data.device_ptr_value() != proof_data_ptr
5313 || workspace.out_learned_count.device_ptr_value() != learned_count_ptr
5314 {
5315 return Err(XlogError::UnsupportedEpistemicConstruct {
5316 construct: "GPU solver learned-clause reuse".to_string(),
5317 context: format!("learned-clause {phase} must keep the reusable GPU workspace arena"),
5318 });
5319 }
5320 Ok(())
5321}
5322
5323fn require_gpu_kernel_trace(
5324 phase: &'static str,
5325 kernel_launches: u32,
5326 host_write_ops: u32,
5327 kernel_timing: EpistemicGpuKernelTimingTrace,
5328) -> Result<()> {
5329 if kernel_launches == 0 || host_write_ops != 0 || !kernel_timing.is_recorded() {
5330 return Err(XlogError::UnsupportedEpistemicConstruct {
5331 construct: "accepted GPU solver candidate evidence".to_string(),
5332 context: format!(
5333 "solver evidence requires GPU {phase} trace with nonzero launches and \
5334 zero host writes plus CUDA-event timing, got launches={kernel_launches}, \
5335 host_writes={host_write_ops}, timing_recorded={}",
5336 kernel_timing.is_recorded()
5337 ),
5338 });
5339 }
5340 Ok(())
5341}
5342
5343#[cfg(test)]
5344mod tests {
5345 use std::sync::Arc;
5346
5347 use xlog_core::MemoryBudget;
5348 use xlog_cuda::{CudaDevice, CudaKernelProvider, GpuMemoryManager};
5349
5350 use super::*;
5351 use crate::{Clause, Literal};
5352
5353 fn try_provider() -> Option<Arc<CudaKernelProvider>> {
5354 let device = match CudaDevice::new(0) {
5355 Ok(device) => Arc::new(device),
5356 Err(err) => {
5357 eprintln!("Skipping test: CUDA runtime unavailable: {err}");
5358 return None;
5359 }
5360 };
5361 let budget = MemoryBudget::with_limit(1024 * 1024 * 1024);
5362 let memory = Arc::new(GpuMemoryManager::new(device.clone(), budget));
5363 match CudaKernelProvider::new(device, memory) {
5364 Ok(provider) => Some(Arc::new(provider)),
5365 Err(err) => {
5366 eprintln!("Skipping test: failed to create CUDA kernel provider: {err}");
5367 None
5368 }
5369 }
5370 }
5371
5372 fn alloc_u32(
5373 provider: &Arc<CudaKernelProvider>,
5374 value: u32,
5375 ) -> xlog_cuda::memory::TrackedCudaSlice<u32> {
5376 let memory = provider.memory();
5377 let mut slot = memory.alloc::<u32>(1).expect("alloc u32 scalar");
5378 provider
5379 .device()
5380 .inner()
5381 .htod_sync_copy_into(&[value], &mut slot)
5382 .expect("upload u32 scalar");
5383 slot
5384 }
5385
5386 #[test]
5387 fn weighted_maxsat_frontier_completion_fails_closed_before_cpu_expansion() {
5388 let clauses: Vec<_> = (0..18)
5389 .map(|idx| {
5390 let lit = if idx % 2 == 0 {
5391 Literal::positive(0)
5392 } else {
5393 Literal::negative(0)
5394 };
5395 Clause::new(vec![lit])
5396 })
5397 .collect();
5398 let weighted = SolveInstance::with_weights(1, clauses, vec![1.0; 18]);
5399 let first_frontier: Vec<_> = (0..9).collect();
5400 let second_frontier: Vec<_> = (9..18).collect();
5401 let selections = [
5402 GpuSolverProductionWeightedMaxSatSelection {
5403 soft_clause_indices: &first_frontier,
5404 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5405 },
5406 GpuSolverProductionWeightedMaxSatSelection {
5407 soft_clause_indices: &second_frontier,
5408 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5409 },
5410 ];
5411
5412 let result = GpuSolverProductionAdapter::complete_weighted_maxsat_frontier_selections(
5413 &weighted,
5414 weighted.weights.as_ref().expect("weighted MaxSAT weights"),
5415 &selections,
5416 );
5417 let Err(err) = result else {
5418 panic!("frontier completion should reject CPU combinatorial expansion");
5419 };
5420 let message = err.to_string();
5421 assert!(message.contains("frontier completion"));
5422 assert!(message.contains("explicit GPU scheduler selections"));
5423 }
5424
5425 #[test]
5426 fn encoded_weighted_maxsat_search_runs_real_gpu_sat_unsat_candidates() {
5427 let Some(provider) = try_provider() else {
5428 return;
5429 };
5430
5431 let weighted = SolveInstance::with_weights(
5432 1,
5433 vec![
5434 Clause::new(vec![Literal::positive(0)]),
5435 Clause::new(vec![Literal::negative(0)]),
5436 ],
5437 vec![2.0, 1.0],
5438 );
5439 let mut adapter =
5440 GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5441 let mut workspace = adapter
5442 .new_workspace(weighted.num_vars, weighted.clauses.len() as u32)
5443 .expect("new MaxSAT workspace");
5444 let branch_limit = alloc_u32(&provider, weighted.num_vars);
5445 let contradictory_selection = [0usize, 1usize];
5446 let selections = [GpuSolverProductionWeightedMaxSatSelection {
5447 soft_clause_indices: &contradictory_selection,
5448 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5449 }];
5450
5451 let certificates_before = adapter.trace.gpu_maxsat_frontier_upper_bound_certificates;
5452 let encoded = adapter
5453 .encode_weighted_maxsat_search_candidates(&weighted, &selections)
5454 .expect("encode weighted MaxSAT candidates");
5455 let frontier_upper_bound_certificates =
5456 GpuSolverProductionAdapter::checked_report_counter_delta(
5457 adapter.trace.gpu_maxsat_frontier_upper_bound_certificates,
5458 certificates_before,
5459 "gpu_maxsat_frontier_upper_bound_certificates",
5460 )
5461 .expect("frontier certificate delta");
5462 let search_candidates: Vec<_> = encoded
5463 .iter()
5464 .map(|candidate| GpuSolverProductionMaxSatSearchCandidate {
5465 score: candidate.score,
5466 cnf: &candidate.cnf,
5467 branch_var_limit: &branch_limit,
5468 status: candidate.status,
5469 })
5470 .collect();
5471
5472 let report = adapter
5473 .solve_weighted_maxsat_search_candidates(
5474 &mut workspace,
5475 &search_candidates,
5476 frontier_upper_bound_certificates,
5477 )
5478 .expect("GPU weighted MaxSAT search");
5479
5480 assert_eq!(report.candidate_evidence_records, 0);
5481 assert_eq!(report.optimum_score, 2);
5482 assert_eq!(report.candidates_checked, 2);
5483 assert_eq!(report.satisfiable_candidates, 1);
5484 assert_eq!(report.unsat_candidates_pruned, 1);
5485 assert_eq!(report.gpu_cdcl_candidate_solves, 2);
5486 assert_eq!(report.frontier_upper_bound_certificates, 1);
5487
5488 let trace = adapter.trace();
5489 assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5490 assert_eq!(trace.gpu_maxsat_frontier_completion_candidate_encodes, 1);
5491 assert_eq!(trace.gpu_maxsat_frontier_upper_bound_certificates, 1);
5492 assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5493 assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5494 assert_eq!(trace.gpu_cdcl_sat_solves, 1);
5495 assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5496 assert_eq!(trace.gpu_maxsat_optima, 1);
5497 trace
5498 .require_production_metric_eligibility()
5499 .expect("MaxSAT production search must not use CPU search");
5500 }
5501
5502 #[test]
5503 fn portfolio_jobs_dispatch_real_gpu_sat_and_encoded_maxsat_paths() {
5504 let Some(provider) = try_provider() else {
5505 return;
5506 };
5507
5508 let sat_instance = SolveInstance::new(1, vec![Clause::new(vec![Literal::positive(0)])]);
5509 let sat_cnf = GpuCnf::from_host(&sat_instance, &provider).expect("SAT GpuCnf upload");
5510 let weighted = SolveInstance::with_weights(
5511 1,
5512 vec![
5513 Clause::new(vec![Literal::positive(0)]),
5514 Clause::new(vec![Literal::negative(0)]),
5515 ],
5516 vec![2.0, 1.0],
5517 );
5518 let branch_limit = alloc_u32(&provider, weighted.num_vars);
5519 let contradictory_selection = [0usize, 1usize];
5520 let selections = [GpuSolverProductionWeightedMaxSatSelection {
5521 soft_clause_indices: &contradictory_selection,
5522 status: GpuSolverProductionMaxSatSearchStatus::Unsatisfiable,
5523 }];
5524 let jobs = [
5525 GpuSolverProductionPortfolioJob::Sat {
5526 cnf: &sat_cnf,
5527 branch_var_limit: &branch_limit,
5528 },
5529 GpuSolverProductionPortfolioJob::EncodedMaxSat {
5530 weighted: &weighted,
5531 branch_var_limit: &branch_limit,
5532 selections: &selections,
5533 },
5534 GpuSolverProductionPortfolioJob::Unknown {
5535 reason: "bounded portfolio diagnostic",
5536 },
5537 GpuSolverProductionPortfolioJob::Timeout { budget_micros: 1 },
5538 ];
5539 let mut adapter =
5540 GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5541
5542 let report = adapter
5543 .solve_portfolio_jobs(&jobs)
5544 .expect("GPU production portfolio jobs");
5545
5546 assert_eq!(report.candidate_evidence_records, 0);
5547 assert_eq!(report.jobs, 4);
5548 assert_eq!(report.sat_jobs, 1);
5549 assert_eq!(report.maxsat_jobs, 1);
5550 assert_eq!(report.unknown_jobs, 1);
5551 assert_eq!(report.timeout_jobs, 1);
5552 assert_eq!(report.maxsat_optimum_scores, 2);
5553 assert_eq!(report.maxsat_candidates_checked, 2);
5554 assert_eq!(report.maxsat_satisfiable_candidates, 1);
5555 assert_eq!(report.maxsat_unsat_candidates_pruned, 1);
5556 assert_eq!(report.maxsat_gpu_cdcl_candidate_encodes, 2);
5557 assert_eq!(report.maxsat_gpu_cdcl_candidate_solves, 2);
5558 assert_eq!(report.maxsat_frontier_upper_bound_certificates, 1);
5559
5560 let trace = adapter.trace();
5561 assert_eq!(trace.gpu_portfolio_jobs, 4);
5562 assert_eq!(trace.gpu_portfolio_sat_jobs, 1);
5563 assert_eq!(trace.gpu_portfolio_maxsat_jobs, 1);
5564 assert_eq!(trace.gpu_portfolio_unknown_status_jobs, 1);
5565 assert_eq!(trace.gpu_portfolio_timeout_status_jobs, 1);
5566 assert_eq!(trace.gpu_cdcl_sat_solves, 2);
5567 assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 1);
5568 assert_eq!(trace.gpu_maxsat_candidate_encodes, 2);
5569 assert_eq!(trace.gpu_maxsat_candidate_solves, 2);
5570 assert_eq!(trace.gpu_maxsat_unsat_candidate_prunes, 1);
5571 assert_eq!(trace.gpu_maxsat_optima, 1);
5572 trace
5573 .require_production_metric_eligibility()
5574 .expect("portfolio production search must not use CPU search");
5575 }
5576
5577 #[test]
5578 fn learned_clause_reuse_publishes_and_imports_gpu_workspace_arena() {
5579 let Some(provider) = try_provider() else {
5580 return;
5581 };
5582
5583 let unsat_instance = SolveInstance::new(
5584 1,
5585 vec![
5586 Clause::new(vec![Literal::positive(0)]),
5587 Clause::new(vec![Literal::negative(0)]),
5588 ],
5589 );
5590 let cnf = GpuCnf::from_host(&unsat_instance, &provider).expect("UNSAT GpuCnf upload");
5591 let branch_limit = alloc_u32(&provider, cnf.var_cap as u32);
5592 let mut adapter =
5593 GpuSolverProductionAdapter::new(provider.clone(), GpuCdclConfig::default());
5594 let mut workspace = adapter
5595 .new_workspace(cnf.var_cap, cnf.clause_cap)
5596 .expect("new learned-clause workspace");
5597
5598 let learned_offsets_ptr = workspace.learned_offsets.device_ptr_value();
5599 let learned_lits_ptr = workspace.learned_lits.device_ptr_value();
5600 let proof_offsets_ptr = workspace.proof_offsets.device_ptr_value();
5601 let proof_data_ptr = workspace.proof_data.device_ptr_value();
5602 let learned_count_ptr = workspace.out_learned_count.device_ptr_value();
5603
5604 let report = adapter
5605 .solve_unsat_then_reuse_learned_clauses(
5606 &mut workspace,
5607 &cnf,
5608 &branch_limit,
5609 &cnf,
5610 &branch_limit,
5611 )
5612 .expect("GPU learned-clause reuse");
5613
5614 assert_eq!(report.candidate_evidence_records, 0);
5615 assert_eq!(report.candidates, 2);
5616 assert_eq!(report.unsat_solves, 2);
5617 assert_eq!(report.gpu_learned_clause_arena_publications, 1);
5618 assert_eq!(report.gpu_learned_clause_imports, 1);
5619 assert_eq!(report.gpu_learned_clause_reused_solves, 1);
5620 assert_eq!(
5621 workspace.learned_offsets.device_ptr_value(),
5622 learned_offsets_ptr
5623 );
5624 assert_eq!(workspace.learned_lits.device_ptr_value(), learned_lits_ptr);
5625 assert_eq!(
5626 workspace.proof_offsets.device_ptr_value(),
5627 proof_offsets_ptr
5628 );
5629 assert_eq!(workspace.proof_data.device_ptr_value(), proof_data_ptr);
5630 assert_eq!(
5631 workspace.out_learned_count.device_ptr_value(),
5632 learned_count_ptr
5633 );
5634
5635 let trace = adapter.trace();
5636 assert_eq!(trace.gpu_cdcl_workspace_unsat_solves, 2);
5637 assert_eq!(trace.gpu_learned_clause_arena_publications, 1);
5638 assert_eq!(trace.gpu_learned_count_buffer_publications, 1);
5639 assert_eq!(trace.gpu_learned_clause_imports, 1);
5640 assert_eq!(trace.gpu_learned_clause_reused_solves, 1);
5641 trace
5642 .require_production_metric_eligibility()
5643 .expect("learned-clause production reuse must not use CPU search");
5644 }
5645}