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