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