1use std::collections::{BTreeMap, HashMap};
7#[cfg(feature = "host-io")]
8use std::sync::Arc;
9
10use pyo3::prelude::*;
11#[cfg(feature = "host-io")]
12use pyo3::types::PyDict;
13
14#[cfg(feature = "host-io")]
15use xlog_core::{ScalarType, Schema};
16#[cfg(feature = "host-io")]
17use xlog_cuda::{CudaKernelProvider, DlpackManagedTensor};
18#[cfg(feature = "host-io")]
19use xlog_prob::epistemic_production::{
20 EpistemicProbProductionAdapter, EpistemicProbProductionTrace,
21};
22#[cfg(feature = "host-io")]
23use xlog_prob::exact::{ExactResult, GpuConfig};
24
25#[cfg(feature = "host-io")]
26use super::program::atom_to_string;
27#[cfg(feature = "host-io")]
28use super::{dlpack_capsule_from_tensor, enforce_call_memory_limit};
29use super::{
30 types, CompiledConditionedProgram, CompiledLogicProgram, EpistemicEvalResult, EpistemicEvidence,
31};
32
33#[pymethods]
34impl CompiledLogicProgram {
35 #[cfg(feature = "host-io")]
41 #[pyo3(signature = (prob_source, memory_mb=None))]
42 pub fn prepare_conditioned(
43 &self,
44 py: Python<'_>,
45 prob_source: &str,
46 memory_mb: Option<u64>,
47 ) -> PyResult<CompiledConditionedProgram> {
48 enforce_call_memory_limit(&self.provider, memory_mb)?;
49 let logic_program = self.program.clone();
50 let evidence_provider = self.provider.clone();
51 let inputs = HashMap::new();
52 let prob_source = prob_source.to_owned();
53 let (program, result_provider) = py
54 .detach(move || {
55 let evidence =
56 logic_program.execute_epistemic_evidence(evidence_provider.clone(), inputs)?;
57 let mut config = GpuConfig::default();
58 config.device_ordinal = evidence_provider.device().ordinal();
59 config.memory_bytes = evidence_provider.memory().budget().device_bytes;
60 let mut adapter = EpistemicProbProductionAdapter::new(config);
61 let program = adapter.prepare_conditioned_source_with_gpu_execution_result(
62 &prob_source,
63 &evidence_provider,
64 &evidence,
65 Vec::new(),
66 )?;
67 Ok::<_, xlog_core::XlogError>((program, evidence_provider))
68 })
69 .map_err(types::xlog_err)?;
70 Ok(CompiledConditionedProgram {
71 program,
72 result_provider,
73 })
74 }
75
76 #[cfg(not(feature = "host-io"))]
77 #[pyo3(signature = (prob_source, memory_mb=None))]
78 pub fn prepare_conditioned(
79 &self,
80 _py: Python<'_>,
81 prob_source: &str,
82 memory_mb: Option<u64>,
83 ) -> PyResult<CompiledConditionedProgram> {
84 let _ = (prob_source, memory_mb);
85 Err(types::host_io_disabled_pyerr())
86 }
87
88 #[cfg(feature = "host-io")]
129 #[pyo3(signature = (prob_source, memory_mb=None))]
130 pub fn evaluate_conditioned(
131 &self,
132 py: Python<'_>,
133 prob_source: &str,
134 memory_mb: Option<u64>,
135 ) -> PyResult<EpistemicEvalResult> {
136 enforce_call_memory_limit(&self.provider, memory_mb)?;
137 let program = self.program.clone();
138 let provider = self.provider.clone();
139 let inputs = HashMap::new();
140 let prob_source = prob_source.to_owned();
141
142 let prepared = py
143 .detach(move || {
144 let evidence = program.execute_epistemic_evidence(provider.clone(), inputs)?;
145
146 let mut config = GpuConfig::default();
154 config.device_ordinal = provider.device().ordinal();
155 config.memory_bytes = provider.memory().budget().device_bytes;
156 let mut adapter = EpistemicProbProductionAdapter::new(config);
157 let exact = adapter
158 .compile_and_evaluate_conditioned_source_with_gpu_execution_result(
159 &prob_source,
160 &provider,
161 &evidence,
162 Vec::new(),
163 )?;
164 let trace = adapter.trace();
165
166 prepare_epistemic_eval_result(&provider, exact, trace)
167 })
168 .map_err(types::xlog_err)?;
169
170 pack_epistemic_eval_result(py, prepared)
171 }
172
173 #[cfg(not(feature = "host-io"))]
174 #[pyo3(signature = (prob_source, memory_mb=None))]
175 pub fn evaluate_conditioned(
176 &self,
177 _py: Python<'_>,
178 prob_source: &str,
179 memory_mb: Option<u64>,
180 ) -> PyResult<EpistemicEvalResult> {
181 let _ = (prob_source, memory_mb);
182 Err(types::host_io_disabled_pyerr())
183 }
184
185 pub fn epistemic_evidence(&self, py: Python<'_>) -> PyResult<EpistemicEvidence> {
201 let program = self.program.clone();
202 let provider = self.provider.clone();
203 let inputs = HashMap::new();
204 let result = py
205 .detach(move || program.execute_epistemic_evidence(provider, inputs))
206 .map_err(types::xlog_err)?;
207 let epistemic_mode = match result.prepared.preflight.epistemic_mode {
208 xlog_ir::EirEpistemicMode::G91 => "g91",
209 xlog_ir::EirEpistemicMode::Faeel => "faeel",
210 }
211 .to_string();
212 Ok(EpistemicEvidence {
213 epistemic_mode,
214 know_operator_count: result.prepared.preflight.know_operator_count,
215 possible_operator_count: result.prepared.preflight.possible_operator_count,
216 accepted_candidates: result.semantic_trace.accepted_candidates,
217 rejected_candidates: result.semantic_trace.rejected_candidates,
218 accepted_world_views: result.semantic_trace.accepted_world_views,
219 final_output_rows: result.final_result_transfer.final_output_rows,
220 })
221 }
222}
223
224#[pymethods]
225impl CompiledConditionedProgram {
226 #[cfg(feature = "host-io")]
228 pub fn evaluate(&self, py: Python<'_>) -> PyResult<EpistemicEvalResult> {
229 let program = self.program.clone();
230 let result_provider = self.result_provider.clone();
231 let prepared = py
232 .detach(move || {
233 let (result, trace) = program.evaluate()?;
234 prepare_epistemic_eval_result(&result_provider, result, trace)
235 })
236 .map_err(types::xlog_err)?;
237 pack_epistemic_eval_result(py, prepared)
238 }
239
240 #[cfg(not(feature = "host-io"))]
241 pub fn evaluate(&self, _py: Python<'_>) -> PyResult<EpistemicEvalResult> {
242 Err(types::host_io_disabled_pyerr())
243 }
244
245 #[cfg(feature = "host-io")]
247 pub fn set_fact_probabilities(
248 &self,
249 py: Python<'_>,
250 updates: BTreeMap<u32, f64>,
251 ) -> PyResult<()> {
252 let program = self.program.clone();
253 py.detach(move || program.set_fact_probabilities(&updates))
254 .map_err(types::xlog_err)
255 }
256
257 #[cfg(not(feature = "host-io"))]
258 pub fn set_fact_probabilities(
259 &self,
260 _py: Python<'_>,
261 updates: BTreeMap<u32, f64>,
262 ) -> PyResult<()> {
263 let _ = updates;
264 Err(types::host_io_disabled_pyerr())
265 }
266
267 #[cfg(feature = "host-io")]
269 pub fn prob_var_map(&self, py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
270 use xlog_prob::exact::ProbVarInfo;
271
272 let program = self.program.clone();
273 let entries = py
274 .detach(move || program.prob_var_map())
275 .map_err(types::xlog_err)?;
276 let mut out = Vec::with_capacity(entries.len());
277 for entry in entries {
278 let dict = PyDict::new(py);
279 match entry {
280 ProbVarInfo::Fact { atom, prob } => {
281 dict.set_item("kind", "fact")?;
282 dict.set_item("atom", atom_to_string(&atom))?;
283 dict.set_item("prob", prob)?;
284 }
285 ProbVarInfo::Choice {
286 choices,
287 choice_index,
288 prob,
289 } => {
290 dict.set_item("kind", "choice")?;
291 dict.set_item(
292 "atoms",
293 choices
294 .iter()
295 .map(|(atom, _)| atom_to_string(atom))
296 .collect::<Vec<_>>(),
297 )?;
298 dict.set_item(
299 "probs",
300 choices.iter().map(|(_, prob)| *prob).collect::<Vec<_>>(),
301 )?;
302 dict.set_item("choice_index", choice_index)?;
303 dict.set_item("prob", prob)?;
304 }
305 ProbVarInfo::Other => dict.set_item("kind", "other")?,
306 }
307 out.push(dict.into());
308 }
309 Ok(out)
310 }
311
312 #[cfg(not(feature = "host-io"))]
313 pub fn prob_var_map(&self, _py: Python<'_>) -> PyResult<Vec<Py<PyAny>>> {
314 Err(types::host_io_disabled_pyerr())
315 }
316}
317
318#[cfg(feature = "host-io")]
319struct PreparedEpistemicEvalResult {
320 atoms: Vec<String>,
321 prob_tensor: DlpackManagedTensor,
322 log_prob_tensor: DlpackManagedTensor,
323 log_z_e: f64,
324 trace: EpistemicProbProductionTrace,
325}
326
327#[cfg(feature = "host-io")]
328fn prepare_epistemic_eval_result(
329 provider: &Arc<CudaKernelProvider>,
330 result: ExactResult,
331 trace: EpistemicProbProductionTrace,
332) -> xlog_core::Result<PreparedEpistemicEvalResult> {
333 let mut atoms: Vec<String> = Vec::with_capacity(result.query_probs.len());
334 let mut probs: Vec<f64> = Vec::with_capacity(result.query_probs.len());
335 let mut log_probs: Vec<f64> = Vec::with_capacity(result.query_probs.len());
336
337 for q in result.query_probs {
338 atoms.push(atom_to_string(&q.atom));
339 probs.push(q.prob);
340 log_probs.push(q.log_prob);
341 }
342
343 let schema = Schema::new(vec![("col0".to_string(), ScalarType::F64)]);
344 let prob_buf = provider.create_buffer_from_slice::<f64>(&probs, schema.clone())?;
345 let log_prob_buf = provider.create_buffer_from_slice::<f64>(&log_probs, schema)?;
346 let prob_tensor = provider.to_dlpack_table(prob_buf).column(0)?;
347 let log_prob_tensor = provider.to_dlpack_table(log_prob_buf).column(0)?;
348
349 Ok(PreparedEpistemicEvalResult {
350 atoms,
351 prob_tensor,
352 log_prob_tensor,
353 log_z_e: result.log_z_e,
354 trace,
355 })
356}
357
358#[cfg(feature = "host-io")]
359fn pack_epistemic_eval_result(
360 py: Python<'_>,
361 prepared: PreparedEpistemicEvalResult,
362) -> PyResult<EpistemicEvalResult> {
363 let PreparedEpistemicEvalResult {
364 atoms,
365 prob_tensor,
366 log_prob_tensor,
367 log_z_e,
368 trace,
369 } = prepared;
370
371 let dict = PyDict::new(py);
372 dict.set_item(
373 "accepted_world_view_evidence_consumed",
374 trace.accepted_world_view_evidence_consumed,
375 )?;
376 dict.set_item(
377 "accepted_faeel_world_view_evidence_consumed",
378 trace.accepted_faeel_world_view_evidence_consumed,
379 )?;
380 dict.set_item(
384 "accepted_g91_world_view_evidence_consumed",
385 trace.accepted_g91_world_view_evidence_consumed,
386 )?;
387 dict.set_item(
388 "accepted_evidence_assumptions_consumed",
389 trace.accepted_evidence_assumptions_consumed,
390 )?;
391 dict.set_item(
392 "gpu_conditioned_evidence_facts",
393 trace.gpu_conditioned_evidence_facts,
394 )?;
395 dict.set_item(
401 "gpu_conditioned_know_evidence_facts",
402 trace.gpu_conditioned_know_evidence_facts,
403 )?;
404 dict.set_item(
405 "gpu_conditioned_possible_evidence_facts",
406 trace.gpu_conditioned_possible_evidence_facts,
407 )?;
408 dict.set_item(
409 "gpu_conditioned_not_known_evidence_facts",
410 trace.gpu_conditioned_not_known_evidence_facts,
411 )?;
412 dict.set_item(
413 "gpu_conditioned_not_possible_evidence_facts",
414 trace.gpu_conditioned_not_possible_evidence_facts,
415 )?;
416 dict.set_item(
417 "gpu_exact_query_evaluations",
418 trace.gpu_exact_query_evaluations,
419 )?;
420 dict.set_item("gpu_exact_source_compiles", trace.gpu_exact_source_compiles)?;
421 dict.set_item(
422 "gpu_exact_program_compiles",
423 trace.gpu_exact_program_compiles,
424 )?;
425 dict.set_item(
426 "gpu_conditioned_circuit_reuses",
427 trace.gpu_conditioned_circuit_reuses,
428 )?;
429 dict.set_item(
430 "gpu_conditioned_circuit_preparation_compiles",
431 trace.gpu_conditioned_circuit_preparation_compiles,
432 )?;
433 dict.set_item(
434 "gpu_conditioned_circuit_materializations",
435 trace.gpu_conditioned_circuit_materializations,
436 )?;
437 dict.set_item(
438 "gpu_conditioned_circuit_disk_cache_restores",
439 trace.gpu_conditioned_circuit_disk_cache_restores,
440 )?;
441 dict.set_item(
442 "gpu_conditioned_circuit_gpu_cache_hits",
443 trace.gpu_conditioned_circuit_gpu_cache_hits,
444 )?;
445 dict.set_item(
446 "gpu_conditioned_circuit_generation",
447 trace.gpu_conditioned_circuit_generation,
448 )?;
449 dict.set_item(
450 "gpu_conditioned_circuit_cache_slot",
451 trace.gpu_conditioned_circuit_cache_slot,
452 )?;
453 dict.set_item(
454 "gpu_knowledge_compilation_end_to_end_runs",
455 trace.gpu_knowledge_compilation_end_to_end_runs,
456 )?;
457 dict.set_item(
458 "accepted_gpu_production_path_events",
459 trace.accepted_gpu_production_path_events,
460 )?;
461
462 Ok(EpistemicEvalResult {
463 atoms,
464 prob: dlpack_capsule_from_tensor(py, prob_tensor)?,
465 log_prob: dlpack_capsule_from_tensor(py, log_prob_tensor)?,
466 log_z_e,
467 trace: dict.into(),
468 })
469}