1#![allow(missing_docs)] #![allow(
4 clippy::large_enum_variant,
5 clippy::needless_range_loop,
6 clippy::too_many_arguments,
7 clippy::type_complexity
8)]
9
10use std::collections::{HashMap, HashSet};
11use std::os::raw::{c_char, c_void};
12use std::sync::Arc;
13
14use pyo3::exceptions::{PyBufferError, PyMemoryError, PyRuntimeError, PyValueError};
15use pyo3::prelude::*;
16use pyo3::types::{PyDict, PyList};
17
18use xlog_core::{MemoryBudget, Schema};
19use xlog_cuda::{
20 device_runtime::{
21 AsyncCudaResource, DeviceMemoryResource, GlobalDeviceBudget, StreamPool, XlogDeviceRuntime,
22 },
23 CudaBuffer, CudaDevice, CudaKernelProvider, DlpackManagedTensor, GpuMemoryManager,
24};
25#[cfg(feature = "arrow-device-import")]
26use xlog_cuda::{ArrowDeviceArray, ArrowDeviceArrayOwned};
27use xlog_gpu::logic as gpu_logic;
28use xlog_logic::ast::ProbEngine;
29use xlog_neural::{NetworkRegistry, TensorSourceRegistry};
30use xlog_prob::exact::GpuConfig;
31
32use xlog_core::RelId;
33use xlog_ir::ExecutionPlan;
34use xlog_logic::ast::Program as AstProgram;
35use xlog_runtime::{Executor, RelationStore};
36
37mod neural_registry;
38use neural_registry::NeuralPredicateRegistry;
39mod diagnostic_serialization;
40mod dlpack;
41mod epistemic;
42mod ilp;
43mod ilp_exact;
44mod ilp_gpu;
45mod joint_carrier;
46mod logic;
47mod neural;
48mod program;
49mod relation_metadata;
50mod training;
51mod types;
52pub(crate) use diagnostic_serialization::{pack_query_proof_traces, pack_rule_provenance};
53pub(crate) use program::{
54 CachedCircuit, CompiledProbProgram, HardFilter, InputSource, JoinPlan, NeuralGroup,
55 QuerySignature,
56};
57use relation_metadata::RelationMetadataStore;
58
59const DLPACK_CAPSULE_NAME: &[u8] = b"dltensor\0";
60const USED_DLPACK_CAPSULE_NAME: &[u8] = b"used_dltensor\0";
61const DLPACK_CUDA_LEGACY_DEFAULT_STREAM: i64 = 1;
64
65#[cfg(feature = "arrow-device-import")]
66const ARROW_DEVICE_ARRAY_CAPSULE_NAME: &[u8] = b"arrow_device_array\0";
67#[cfg(feature = "arrow-device-import")]
68const USED_ARROW_DEVICE_ARRAY_CAPSULE_NAME: &[u8] = b"used_arrow_device_array\0";
69
70unsafe extern "C" fn dlpack_capsule_destructor(capsule: *mut pyo3::ffi::PyObject) {
71 if capsule.is_null() {
72 return;
73 }
74
75 let valid =
76 pyo3::ffi::PyCapsule_IsValid(capsule, DLPACK_CAPSULE_NAME.as_ptr() as *const c_char);
77 if valid == 0 {
78 return;
79 }
80
81 let ptr =
82 pyo3::ffi::PyCapsule_GetPointer(capsule, DLPACK_CAPSULE_NAME.as_ptr() as *const c_char);
83 if ptr.is_null() {
84 pyo3::ffi::PyErr_Clear();
85 return;
86 }
87
88 let managed = ptr as *mut xlog_cuda::DLManagedTensor;
89 drop(DlpackManagedTensor::from_raw(managed));
90}
91
92pub(crate) fn dlpack_capsule_from_tensor(
93 py: Python<'_>,
94 tensor: DlpackManagedTensor,
95) -> PyResult<Py<PyAny>> {
96 let raw = tensor.into_raw();
97 let ptr = raw as *mut c_void;
98 let capsule = unsafe {
100 pyo3::ffi::PyCapsule_New(
101 ptr,
102 DLPACK_CAPSULE_NAME.as_ptr() as *const c_char,
103 Some(dlpack_capsule_destructor),
104 )
105 };
106 if capsule.is_null() {
107 unsafe {
109 drop(DlpackManagedTensor::from_raw(raw));
110 }
111 return Err(PyRuntimeError::new_err("Failed to create DLPack capsule"));
112 }
113 let obj: Py<PyAny> = unsafe { Bound::from_owned_ptr(py, capsule) }.unbind();
115 Ok(obj)
116}
117
118#[cfg(feature = "arrow-device-import")]
119unsafe extern "C" fn arrow_device_array_capsule_destructor(capsule: *mut pyo3::ffi::PyObject) {
120 if capsule.is_null() {
121 return;
122 }
123
124 let valid = pyo3::ffi::PyCapsule_IsValid(
125 capsule,
126 ARROW_DEVICE_ARRAY_CAPSULE_NAME.as_ptr() as *const c_char,
127 );
128 if valid == 0 {
129 return;
130 }
131
132 let ptr = pyo3::ffi::PyCapsule_GetPointer(
133 capsule,
134 ARROW_DEVICE_ARRAY_CAPSULE_NAME.as_ptr() as *const c_char,
135 );
136 if ptr.is_null() {
137 pyo3::ffi::PyErr_Clear();
138 return;
139 }
140
141 drop(ArrowDeviceArrayOwned::from_raw(
142 ptr as *mut ArrowDeviceArray,
143 ));
144}
145
146#[cfg(feature = "arrow-device-import")]
147pub(crate) fn arrow_device_capsule_from_device_array(
148 py: Python<'_>,
149 device_array: ArrowDeviceArrayOwned,
150) -> PyResult<Py<PyAny>> {
151 let raw = device_array.into_raw();
152 let ptr = raw as *mut c_void;
153 let capsule = unsafe {
155 pyo3::ffi::PyCapsule_New(
156 ptr,
157 ARROW_DEVICE_ARRAY_CAPSULE_NAME.as_ptr() as *const c_char,
158 Some(arrow_device_array_capsule_destructor),
159 )
160 };
161 if capsule.is_null() {
162 unsafe {
164 drop(ArrowDeviceArrayOwned::from_raw(raw));
165 }
166 return Err(PyRuntimeError::new_err(
167 "Failed to create Arrow device array capsule",
168 ));
169 }
170 let obj: Py<PyAny> = unsafe { Bound::from_owned_ptr(py, capsule) }.unbind();
172 Ok(obj)
173}
174
175#[cfg(feature = "arrow-device-import")]
176pub(crate) fn arrow_device_from_py(obj: &Bound<'_, PyAny>) -> PyResult<ArrowDeviceArrayOwned> {
177 if unsafe {
179 pyo3::ffi::PyCapsule_IsValid(
180 obj.as_ptr(),
181 ARROW_DEVICE_ARRAY_CAPSULE_NAME.as_ptr() as *const c_char,
182 )
183 } == 0
184 {
185 return Err(PyValueError::new_err(
186 "Expected an Arrow device array capsule (arrow_device_array)",
187 ));
188 }
189
190 let ptr = unsafe {
192 pyo3::ffi::PyCapsule_GetPointer(
193 obj.as_ptr(),
194 ARROW_DEVICE_ARRAY_CAPSULE_NAME.as_ptr() as *const c_char,
195 )
196 };
197 if ptr.is_null() {
198 return Err(PyRuntimeError::new_err(
199 "Failed to get Arrow device array pointer",
200 ));
201 }
202
203 let rc = unsafe {
206 pyo3::ffi::PyCapsule_SetName(
207 obj.as_ptr(),
208 USED_ARROW_DEVICE_ARRAY_CAPSULE_NAME.as_ptr() as *const c_char,
209 )
210 };
211 if rc != 0 {
212 return Err(PyRuntimeError::new_err(
213 "Failed to mark Arrow device array capsule as consumed",
214 ));
215 }
216
217 Ok(unsafe { ArrowDeviceArrayOwned::from_raw(ptr as *mut ArrowDeviceArray) })
219}
220
221pub(crate) fn provider_from_config(config: GpuConfig) -> xlog_core::Result<CudaKernelProvider> {
222 let device = Arc::new(CudaDevice::new(config.device_ordinal)?);
223 let stream_pool = Arc::new(StreamPool::with_defaults(Arc::clone(&device)));
224 let async_resource: Box<dyn DeviceMemoryResource + Send + Sync> =
225 Box::new(AsyncCudaResource::new(
226 Arc::clone(&device),
227 config.device_ordinal as u32,
228 Arc::clone(&stream_pool),
229 ));
230 let budget_limit = usize::try_from(config.memory_bytes).unwrap_or(usize::MAX);
231 let budgeted: Box<dyn DeviceMemoryResource + Send + Sync> =
232 Box::new(GlobalDeviceBudget::new(async_resource, budget_limit));
233 let runtime = Arc::new(XlogDeviceRuntime::with_resource(
234 Arc::clone(&device),
235 config.device_ordinal as u32,
236 stream_pool,
237 budgeted,
238 ));
239 let memory = Arc::new(GpuMemoryManager::with_runtime(
240 device.clone(),
241 MemoryBudget::with_limit(config.memory_bytes),
242 runtime,
243 ));
244 CudaKernelProvider::with_runtime(device, memory)
245}
246
247pub(crate) fn enforce_call_memory_limit(
248 provider: &Arc<CudaKernelProvider>,
249 memory_mb: Option<u64>,
250) -> PyResult<()> {
251 let Some(memory_mb) = memory_mb else {
252 return Ok(());
253 };
254 if memory_mb == 0 {
255 return Err(PyValueError::new_err("memory_mb must be > 0"));
256 }
257 let memory_limit_bytes = memory_mb.saturating_mul(1024 * 1024);
258 let allocated_bytes = provider.memory().allocated_bytes();
259 if allocated_bytes > memory_limit_bytes {
260 return Err(PyMemoryError::new_err(format!(
261 "per-call memory limit exceeded before evaluation: allocated_bytes={} memory_limit_bytes={}",
262 allocated_bytes, memory_limit_bytes
263 )));
264 }
265 Ok(())
266}
267
268pub(crate) fn provider_memory_stats(
269 py: Python<'_>,
270 provider: &Arc<CudaKernelProvider>,
271) -> PyResult<Py<PyAny>> {
272 let dict = PyDict::new(py);
273 let memory = provider.memory();
274 dict.set_item("allocated_bytes", memory.allocated_bytes())?;
275 dict.set_item("memory_limit_bytes", memory.budget().device_bytes)?;
276 dict.set_item("peak_memory_bytes", memory.peak_bytes())?;
277 dict.set_item("status", "available")?;
278 Ok(dict.into())
279}
280
281pub(crate) fn parse_prob_engine_override(s: &str) -> PyResult<ProbEngine> {
282 let v = s.trim().to_ascii_lowercase();
283 match v.as_str() {
284 "exact_ddnnf" | "exact" | "ddnnf" => Ok(ProbEngine::ExactDdnnf),
285 "mc" => Ok(ProbEngine::Mc),
286 other => Err(PyValueError::new_err(format!(
287 "Unknown prob_engine '{}'; expected 'exact_ddnnf' or 'mc'",
288 other
289 ))),
290 }
291}
292
293pub(crate) fn dlpack_from_py(obj: &Bound<'_, PyAny>) -> PyResult<DlpackManagedTensor> {
294 let py = obj.py();
295
296 let capsule_obj: Bound<'_, PyAny> = if unsafe {
298 pyo3::ffi::PyCapsule_IsValid(obj.as_ptr(), DLPACK_CAPSULE_NAME.as_ptr() as *const c_char)
299 } != 0
300 {
301 obj.clone()
302 } else if obj.hasattr("__dlpack__")? {
303 let (device_type, device_id): (i32, i32) =
304 obj.call_method0("__dlpack_device__")?.extract()?;
305 if device_type != xlog_cuda::dlpack::K_DLCUDA {
306 return Err(PyBufferError::new_err(format!(
307 "Unsupported DLPack producer device type {device_type} (device {device_id}); \
308 XLOG requires CUDA device memory (kDLCUDA=2)"
309 )));
310 }
311 let kwargs = PyDict::new(py);
316 kwargs.set_item("stream", DLPACK_CUDA_LEGACY_DEFAULT_STREAM)?;
317 obj.call_method("__dlpack__", (), Some(&kwargs))?
318 } else {
319 return Err(PyValueError::new_err(
320 "Expected a DLPack capsule or an object with __dlpack__",
321 ));
322 };
323
324 if unsafe {
326 pyo3::ffi::PyCapsule_IsValid(
327 capsule_obj.as_ptr(),
328 DLPACK_CAPSULE_NAME.as_ptr() as *const c_char,
329 )
330 } == 0
331 {
332 return Err(PyValueError::new_err("Invalid DLPack capsule"));
333 }
334
335 let ptr = unsafe {
337 pyo3::ffi::PyCapsule_GetPointer(
338 capsule_obj.as_ptr(),
339 DLPACK_CAPSULE_NAME.as_ptr() as *const c_char,
340 )
341 };
342 if ptr.is_null() {
343 return Err(PyRuntimeError::new_err("Failed to get DLPack pointer"));
344 }
345
346 let rc = unsafe {
348 pyo3::ffi::PyCapsule_SetName(
349 capsule_obj.as_ptr(),
350 USED_DLPACK_CAPSULE_NAME.as_ptr() as *const c_char,
351 )
352 };
353 if rc != 0 {
354 return Err(PyRuntimeError::new_err(
355 "Failed to mark DLPack capsule as consumed",
356 ));
357 }
358
359 Ok(unsafe { DlpackManagedTensor::from_raw(ptr as *mut xlog_cuda::DLManagedTensor) })
361}
362
363#[pyfunction]
364fn dlpack_is_cuda(obj: &Bound<'_, PyAny>) -> PyResult<bool> {
365 if unsafe {
368 pyo3::ffi::PyCapsule_IsValid(obj.as_ptr(), DLPACK_CAPSULE_NAME.as_ptr() as *const c_char)
369 } == 0
370 {
371 return Err(PyValueError::new_err(
372 "Expected a DLPack capsule (dltensor)",
373 ));
374 }
375
376 let ptr = unsafe {
378 pyo3::ffi::PyCapsule_GetPointer(obj.as_ptr(), DLPACK_CAPSULE_NAME.as_ptr() as *const c_char)
379 };
380 if ptr.is_null() {
381 return Err(PyRuntimeError::new_err("Failed to get DLPack pointer"));
382 }
383
384 let managed = unsafe { &*(ptr as *const xlog_cuda::DLManagedTensor) };
386 Ok(managed.dl_tensor.device.device_type == xlog_cuda::dlpack::K_DLCUDA)
387}
388
389#[pyclass(name = "DifferentiableProofTraceMap")]
390pub struct PyDifferentiableProofTraceMap {
391 inner: xlog_logic::DifferentiableProofTraceMap,
392}
393
394fn pack_differentiable_proof_trace(
395 py: Python<'_>,
396 trace: &xlog_logic::ProofTrace,
397) -> PyResult<Py<PyAny>> {
398 let dict = PyDict::new(py);
399 dict.set_item("proof_id", trace.proof_id)?;
400 dict.set_item("answer_key", &trace.answer_key)?;
401 dict.set_item("clause_id", &trace.clause_id)?;
402 dict.set_item("support_atoms", &trace.support_atoms)?;
403 dict.set_item("weight", trace.weight)?;
404 dict.set_item("gradient", trace.gradient)?;
405 Ok(dict.into())
406}
407
408#[pymethods]
409impl PyDifferentiableProofTraceMap {
410 #[new]
411 fn new() -> Self {
412 Self {
413 inner: xlog_logic::DifferentiableProofTraceMap::new(),
414 }
415 }
416
417 fn insert(
418 &mut self,
419 answer_key: String,
420 clause_id: String,
421 support_atoms: Vec<String>,
422 initial_weight: f64,
423 ) -> PyResult<u64> {
424 if !initial_weight.is_finite() {
425 return Err(PyValueError::new_err(
426 "initial_weight must be a finite float",
427 ));
428 }
429 Ok(self.inner.insert(xlog_logic::ProofTraceSpec {
430 answer_key,
431 clause_id,
432 support_atoms,
433 initial_weight,
434 }))
435 }
436
437 fn trace(&self, py: Python<'_>, proof_id: u64) -> PyResult<Option<Py<PyAny>>> {
438 self.inner
439 .trace(proof_id)
440 .map(|trace| pack_differentiable_proof_trace(py, trace))
441 .transpose()
442 }
443
444 fn traces(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
445 let list = PyList::empty(py);
446 for trace in self.inner.traces() {
447 list.append(pack_differentiable_proof_trace(py, trace)?)?;
448 }
449 Ok(list.into())
450 }
451
452 fn accumulate_binary_logistic_gradients(
453 &mut self,
454 targets: Vec<(String, f64)>,
455 ) -> PyResult<f64> {
456 if targets.iter().any(|(_, target)| !target.is_finite()) {
457 return Err(PyValueError::new_err("targets must be finite floats"));
458 }
459 Ok(self.inner.accumulate_binary_logistic_gradients(&targets))
460 }
461
462 fn apply_gradients(&mut self, learning_rate: f64) -> PyResult<()> {
463 if !learning_rate.is_finite() || learning_rate < 0.0 {
464 return Err(PyValueError::new_err(
465 "learning_rate must be a finite non-negative float",
466 ));
467 }
468 self.inner.apply_gradients(learning_rate);
469 Ok(())
470 }
471}
472
473#[pyclass]
474pub struct Program;
475
476#[pyclass]
477pub struct CompiledProgram {
478 pub(crate) program: CompiledProbProgram,
479 pub(crate) output_provider: Arc<CudaKernelProvider>,
480 pub(crate) network_registry: NetworkRegistry,
482 pub(crate) neural_registry: NeuralPredicateRegistry,
484 pub(crate) declared_networks: HashSet<String>,
486 pub(crate) declared_network_forms: HashMap<String, bool>,
488 pub(crate) tensor_sources: TensorSourceRegistry,
490 pub(crate) domain_source: Option<String>,
495 pub(crate) domain_ids: Vec<i64>,
518 pub(crate) _source: String,
520 pub(crate) ast: xlog_logic::ast::Program,
522 pub(crate) _gpu_config: GpuConfig,
524 pub(crate) _prob_engine: ProbEngine,
526 pub(crate) query_signature_cache: HashMap<String, QuerySignature>,
528 pub(crate) circuit_cache: HashMap<String, CachedCircuit>,
530 pub(crate) circuit_cache_hits: usize,
532 pub(crate) circuit_cache_misses: usize,
534 pub(crate) template_compile_count: usize,
536 pub(crate) batch_queries: bool,
538 pub(crate) last_compile_profile: Option<xlog_prob::compilation::CircuitCompileProfile>,
540}
541
542#[pyclass]
543pub struct LogicProgram;
544
545#[pyclass]
546pub struct CompiledLogicProgram {
547 pub(crate) program: Arc<gpu_logic::LogicProgram>,
548 pub(crate) provider: Arc<CudaKernelProvider>,
549}
550
551#[pyclass(skip_from_py_object)]
556#[derive(Clone)]
557pub struct CompiledConditionedProgram {
558 #[cfg(feature = "host-io")]
559 pub(crate) program: xlog_prob::epistemic_production::PreparedConditionedProgram,
560 #[cfg(feature = "host-io")]
561 pub(crate) result_provider: Arc<CudaKernelProvider>,
562}
563
564#[pyclass]
565pub struct LogicRelationSession {
566 pub(crate) program: Arc<gpu_logic::LogicProgram>,
567 pub(crate) provider: Arc<CudaKernelProvider>,
568 pub(crate) relation_store: RelationStore,
569 pub(crate) evaluation_store: Option<gpu_logic::LogicMaterializedStore>,
570 pub(crate) session_runtime: Option<gpu_logic::LogicSessionRuntime>,
571 pub(crate) last_delta_stats: Option<LogicDeltaStats>,
572 pub(crate) relation_callbacks: Vec<RelationChangeCallback>,
573 pub(crate) next_relation_callback_id: u64,
574 pub(crate) relation_generations: HashMap<String, u64>,
575 pub(crate) relation_metadata: RelationMetadataStore,
576}
577
578pub(crate) struct RelationChangeCallback {
579 pub id: u64,
580 pub callback: Py<PyAny>,
581}
582
583#[derive(Clone, Debug)]
584pub(crate) struct LogicDeltaStats {
585 pub input_delta_count: usize,
586 pub changed_relations: usize,
587 pub changed_relation_names: Vec<String>,
588 pub insert_rows: u64,
589 pub delete_rows: u64,
590 pub has_deletes: bool,
591 pub affected_sccs: usize,
592 pub recomputed_sccs: usize,
593 pub incremental_sccs: usize,
594 pub coalesced_insert_rows: u64,
595 pub coalesced_delete_rows: u64,
596 pub canceled_rows: u64,
597 pub equivalent_to_full_recompute: Option<bool>,
598 pub planner_telemetry: gpu_logic::DeltaPlannerTelemetry,
599 pub debug_trace: Vec<String>,
600}
601
602#[pyclass]
603pub struct LogicQueryResult {
604 #[pyo3(get)]
605 pub relation_name: String,
606 #[pyo3(get)]
607 pub columns: Vec<String>,
608 #[pyo3(get)]
609 pub sort_labels: Vec<String>,
610 #[pyo3(get)]
611 pub tensors: Vec<Py<PyAny>>,
612 #[pyo3(get)]
613 pub num_rows: usize,
614 #[pyo3(get)]
615 pub is_true: bool,
616}
617
618#[pyclass]
619pub struct LogicEvalResult {
620 #[pyo3(get)]
621 pub queries: Vec<Py<LogicQueryResult>>,
622}
623
624#[pyclass]
625pub struct IlpTaggedCreditDeviceResult {
626 #[pyo3(get)]
627 pub fact_row_offsets: Py<PyAny>,
628 #[pyo3(get)]
629 pub entry_indices: Py<PyAny>,
630 #[pyo3(get)]
631 pub entry_i: Py<PyAny>,
632 #[pyo3(get)]
633 pub entry_j: Py<PyAny>,
634 #[pyo3(get)]
635 pub entry_k: Py<PyAny>,
636}
637
638#[pyclass]
639pub struct McDeviceEvalResult {
640 #[pyo3(get)]
642 pub query_counts: Py<PyAny>,
643 #[pyo3(get)]
645 pub evidence_count: Py<PyAny>,
646 #[pyo3(get)]
647 pub total_samples: usize,
648 #[pyo3(get)]
649 pub seed: u64,
650 #[pyo3(get)]
651 pub confidence: f64,
652 #[pyo3(get)]
653 pub nonmonotone_semantics: String,
654 #[pyo3(get)]
655 pub nonmonotone_sccs: usize,
656 #[pyo3(get)]
657 pub nonmonotone_cycles: usize,
658 #[pyo3(get)]
659 pub nonmonotone_iteration_limit_hits: usize,
660 #[pyo3(get)]
661 pub sampling_method: String,
662 #[pyo3(get)]
663 pub resident_no_host_certified: bool,
664 #[pyo3(get)]
665 pub resident_no_host_policy_result: String,
666 #[pyo3(get)]
667 pub resident_no_host_tracked_dtoh_calls: u64,
668 #[pyo3(get)]
669 pub resident_no_host_tracked_htod_calls: u64,
670 #[pyo3(get)]
671 pub resident_no_host_host_loop_iterations: u64,
672 #[pyo3(get)]
673 pub resident_no_host_per_sample_host_launches: u64,
674 #[pyo3(get)]
675 pub resident_no_host_untracked_metadata_reads: u64,
676 #[pyo3(get)]
677 pub resident_no_host_engine_launches: u64,
678 #[pyo3(get)]
679 pub resident_no_host_host_fixpoint_iterations: u64,
680 #[pyo3(get)]
681 pub resident_no_host_per_operator_host_allocations: u64,
682}
683
684#[pyclass]
685pub struct EvalResult {
686 #[pyo3(get)]
687 pub atoms: Vec<String>,
688 #[pyo3(get)]
689 pub prob: Py<PyAny>,
690 #[pyo3(get)]
691 pub log_prob: Py<PyAny>,
692 #[pyo3(get)]
693 pub num_vars: usize,
694 #[pyo3(get)]
696 pub log_z_e: Option<f64>,
697 #[pyo3(get)]
698 pub grad_true: Option<Vec<Py<PyAny>>>,
699 #[pyo3(get)]
700 pub grad_false: Option<Vec<Py<PyAny>>>,
701 #[pyo3(get)]
702 pub approx: bool,
703 #[pyo3(get)]
704 pub stderr: Option<Py<PyAny>>,
705 #[pyo3(get)]
706 pub ci_low: Option<Py<PyAny>>,
707 #[pyo3(get)]
708 pub ci_high: Option<Py<PyAny>>,
709 #[pyo3(get)]
710 pub samples: Option<usize>,
711 #[pyo3(get)]
712 pub evidence_samples: Option<usize>,
713 #[pyo3(get)]
714 pub seed: Option<u64>,
715 #[pyo3(get)]
716 pub confidence: Option<f64>,
717 #[pyo3(get)]
718 pub nonmonotone_semantics: Option<String>,
719 #[pyo3(get)]
720 pub nonmonotone_sccs: Option<usize>,
721 #[pyo3(get)]
722 pub nonmonotone_cycles: Option<usize>,
723 #[pyo3(get)]
724 pub nonmonotone_iteration_limit_hits: Option<usize>,
725 #[pyo3(get)]
726 pub sampling_method: Option<String>,
727 #[pyo3(get)]
731 pub mc_engine: Option<String>,
732}
733
734#[pyclass]
748pub struct EpistemicEvalResult {
749 #[pyo3(get)]
750 pub atoms: Vec<String>,
751 #[pyo3(get)]
752 pub prob: Py<PyAny>,
753 #[pyo3(get)]
754 pub log_prob: Py<PyAny>,
755 #[pyo3(get)]
756 pub log_z_e: f64,
757 #[pyo3(get)]
758 pub trace: Py<PyAny>,
759}
760
761#[pyclass]
769pub struct EpistemicEvidence {
770 #[pyo3(get)]
771 pub epistemic_mode: String,
772 #[pyo3(get)]
773 pub know_operator_count: usize,
774 #[pyo3(get)]
775 pub possible_operator_count: usize,
776 #[pyo3(get)]
777 pub accepted_candidates: usize,
778 #[pyo3(get)]
779 pub rejected_candidates: usize,
780 #[pyo3(get)]
781 pub accepted_world_views: usize,
782 #[pyo3(get)]
783 pub final_output_rows: usize,
784}
785
786#[pyclass(skip_from_py_object)]
794#[derive(Clone)]
795pub struct EpochStats {
796 #[pyo3(get)]
798 pub avg_loss: f64,
799 #[pyo3(get)]
801 pub num_batches: usize,
802 #[pyo3(get)]
804 pub total_queries: usize,
805}
806
807#[pyclass(skip_from_py_object)]
811#[derive(Clone)]
812pub struct TrainingHistory {
813 #[pyo3(get)]
815 pub epoch_losses: Vec<f64>,
816 #[pyo3(get)]
818 pub epoch_times: Vec<f64>,
819 #[pyo3(get)]
821 pub batch_losses: Vec<f64>,
822 #[pyo3(get)]
824 pub stopped_early: bool,
825}
826
827#[pyclass]
828pub struct IlpProgramFactory;
829
830#[pyclass]
831pub struct CompiledIlpProgram {
832 pub(crate) base_source: String,
833 pub(crate) _learnable_source: String,
834 pub(crate) ast: AstProgram,
835 pub(crate) executor: Executor,
836 pub(crate) provider: Arc<CudaKernelProvider>,
837 pub(crate) plan: ExecutionPlan,
838 pub(crate) rel_index: Vec<(RelId, String)>,
839 pub(crate) schemas: HashMap<String, Schema>,
840 pub(crate) left_keys: Vec<usize>,
841 pub(crate) right_keys: Vec<usize>,
842 pub(crate) head_projection: Vec<usize>,
843 pub(crate) compiled_schema_size: usize,
844 pub(crate) head_rel_name: String,
845 pub(crate) max_active_rules: usize,
846 pub(crate) candidate_map: Option<HashMap<(u32, u32, u32), u32>>,
847 pub(crate) candidate_order: Option<Vec<(u32, u32, u32)>>,
848 pub(crate) relation_overrides: HashMap<String, CudaBuffer>,
849 pub(crate) coo_chunk_budget: u64,
853 pub(crate) strict_zero_dtoh: bool,
856 pub(crate) compile_timing: Vec<(&'static str, f64)>,
858}
859
860#[pymodule]
861#[pyo3(name = "_native")]
862fn pyxlog(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
863 m.add("__version__", env!("CARGO_PKG_VERSION"))?;
864 m.add_class::<Program>()?;
865 m.add_class::<CompiledProgram>()?;
866 m.add_class::<LogicProgram>()?;
867 m.add_class::<CompiledLogicProgram>()?;
868 m.add_class::<CompiledConditionedProgram>()?;
869 m.add_class::<LogicRelationSession>()?;
870 m.add_class::<relation_metadata::RelationEvidence>()?;
871 m.add_class::<LogicQueryResult>()?;
872 m.add_class::<LogicEvalResult>()?;
873 m.add_class::<McDeviceEvalResult>()?;
874 m.add_class::<EvalResult>()?;
875 m.add_class::<EpistemicEvalResult>()?;
876 m.add_class::<EpistemicEvidence>()?;
877 m.add_class::<PyDifferentiableProofTraceMap>()?;
879 m.add_class::<EpochStats>()?;
880 m.add_class::<TrainingHistory>()?;
881 m.add_class::<IlpProgramFactory>()?;
883 m.add_class::<CompiledIlpProgram>()?;
884 m.add_class::<IlpTaggedCreditDeviceResult>()?;
885 m.add_function(wrap_pyfunction!(training::train_model, m)?)?;
886 m.add_function(wrap_pyfunction!(training::train_model_tensor, m)?)?;
887 m.add_function(wrap_pyfunction!(dlpack::dlpack_roundtrip, m)?)?;
888 m.add_function(wrap_pyfunction!(dlpack_is_cuda, m)?)?;
889 #[cfg(feature = "arrow-device-import")]
890 m.add_function(wrap_pyfunction!(dlpack::export_arrow_device, m)?)?;
891 #[cfg(feature = "arrow-device-import")]
892 m.add_function(wrap_pyfunction!(dlpack::import_arrow_device, m)?)?;
893 m.add_class::<joint_carrier::JointConstraintCarrier>()?;
895 m.add(
896 "CarrierRefused",
897 _py.get_type::<joint_carrier::CarrierRefused>(),
898 )?;
899 m.add(
900 "SolverResourceExhausted",
901 _py.get_type::<joint_carrier::SolverResourceExhausted>(),
902 )?;
903 m.add(
904 "RelationMetadataError",
905 _py.get_type::<relation_metadata::RelationMetadataError>(),
906 )?;
907 m.add("SOLVER_ABI_IDENTITY", xlog_cuda::SOLVER_ABI_IDENTITY)?;
908 Ok(())
909}