#![cfg(feature = "host-io")]
use xlog_cuda::CudaDevice;
use xlog_prob::mc::{McEngine, McEvalConfig, McProgram};
fn has_cuda_device() -> bool {
CudaDevice::new(0).is_ok()
}
const REJECTED_SRC: &str = r#"
0.5::flip().
p() :- flip().
q() :- not p().
p() :- not q().
query(p()).
"#;
#[test]
fn test_mc_evaluate_fails_closed_on_resident_rejection() {
if !has_cuda_device() {
eprintln!("Skipping: no CUDA device");
return;
}
let program = McProgram::compile_source(REJECTED_SRC).unwrap();
let cfg = McEvalConfig::default();
let err = program
.evaluate(cfg)
.expect_err("resident-rejected program must fail closed, not silently run the CPU oracle");
let msg = err.to_string();
assert!(
msg.contains("resident MC engine rejected program"),
"error must carry the typed rejection: {msg}"
);
}
#[test]
fn test_mc_cpu_oracle_requires_explicit_opt_in_and_is_labeled() {
if !has_cuda_device() {
eprintln!("Skipping: no CUDA device");
return;
}
let program = McProgram::compile_source(REJECTED_SRC).unwrap();
let mut cfg = McEvalConfig::default();
cfg.samples = 2_000;
cfg.seed = 999;
cfg.allow_cpu_oracle_fallback = true;
let result = program.evaluate(cfg).unwrap();
assert_eq!(result.engine, McEngine::CpuOracle);
assert_eq!(result.engine.as_str(), "cpu-oracle");
let p = result
.query_estimates
.iter()
.find(|q| q.atom.predicate == "p")
.expect("missing query for p()")
.prob;
assert!((p - 0.5).abs() < 0.08, "p={p}");
}
#[test]
fn test_mc_resident_engine_result_is_labeled_gpu() {
if !has_cuda_device() {
eprintln!("Skipping: no CUDA device");
return;
}
let src = r#"
0.7::rain().
query(rain()).
"#;
let program = McProgram::compile_source(src).unwrap();
let result = program.evaluate(McEvalConfig::default()).unwrap();
assert_eq!(result.engine, McEngine::GpuResident);
assert_eq!(result.engine.as_str(), "gpu-resident");
}