oxirs-rule 0.2.4

Forward/backward rule engine for RDFS, OWL, and SWRL reasoning
Documentation
//! Memory Optimization Benchmark
//!
//! Measures memory efficiency improvements in forward chaining:
//! - Substitution clone reduction
//! - Fact set clone optimization
//! - Active substitution tracking
//!
//! Run with: cargo run --example memory_benchmark --release

use once_cell::sync::Lazy;
use oxirs_rule::forward::ForwardChainer;
use oxirs_rule::{Rule, RuleAtom, Term};
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::Instant;

// Global metrics for tracking (using atomic counters for simplicity)
static SUBSTITUTION_CLONES: Lazy<AtomicU64> = Lazy::new(|| AtomicU64::new(0));
static FACT_SET_CLONES: Lazy<AtomicU64> = Lazy::new(|| AtomicU64::new(0));
static ACTIVE_SUBSTITUTIONS: Lazy<AtomicU64> = Lazy::new(|| AtomicU64::new(0));

fn main() {
    println!("═══════════════════════════════════════════════════════════");
    println!("       Forward Chaining Memory Optimization Benchmark");
    println!("═══════════════════════════════════════════════════════════\n");

    // Run benchmarks with different dataset sizes
    // Note: Transitive closure is O(n²), so we use smaller sizes
    let sizes = vec![10, 25, 50, 75, 100];

    for size in sizes {
        println!("Dataset Size: {} facts", size);
        println!("───────────────────────────────────────────────────────────");

        benchmark_transitive_reasoning(size);
        benchmark_can_derive(size);
        benchmark_derive_new_facts(size);

        println!();
    }

    println!("═══════════════════════════════════════════════════════════");
    println!("Benchmark completed successfully!\n");
    println!("Memory Optimization Results:");
    println!("  • Substitution clones reduced by only cloning on success");
    println!("  • Fact set clones minimized with efficient restoration");
    println!("  • Active substitution tracking enables monitoring");
    println!("  • Early-exit optimization for can_derive()");
    println!("═══════════════════════════════════════════════════════════");
}

fn benchmark_transitive_reasoning(fact_count: usize) {
    let mut chainer = ForwardChainer::new();

    // Add transitive closure rule: ancestor(X,Z) :- parent(X,Y), ancestor(Y,Z)
    chainer.add_rule(Rule {
        name: "transitive_ancestor".to_string(),
        body: vec![
            RuleAtom::Triple {
                subject: Term::Variable("X".to_string()),
                predicate: Term::Constant("parent".to_string()),
                object: Term::Variable("Y".to_string()),
            },
            RuleAtom::Triple {
                subject: Term::Variable("Y".to_string()),
                predicate: Term::Constant("ancestor".to_string()),
                object: Term::Variable("Z".to_string()),
            },
        ],
        head: vec![RuleAtom::Triple {
            subject: Term::Variable("X".to_string()),
            predicate: Term::Constant("ancestor".to_string()),
            object: Term::Variable("Z".to_string()),
        }],
    });

    // Add base rule: ancestor(X,Y) :- parent(X,Y)
    chainer.add_rule(Rule {
        name: "direct_ancestor".to_string(),
        body: vec![RuleAtom::Triple {
            subject: Term::Variable("X".to_string()),
            predicate: Term::Constant("parent".to_string()),
            object: Term::Variable("Y".to_string()),
        }],
        head: vec![RuleAtom::Triple {
            subject: Term::Variable("X".to_string()),
            predicate: Term::Constant("ancestor".to_string()),
            object: Term::Variable("Y".to_string()),
        }],
    });

    // Add parent facts (chain: 0 -> 1 -> 2 -> ... -> n)
    for i in 0..fact_count {
        chainer.add_fact(RuleAtom::Triple {
            subject: Term::Constant(format!("person_{}", i)),
            predicate: Term::Constant("parent".to_string()),
            object: Term::Constant(format!("person_{}", i + 1)),
        });
    }

    // Reset metrics
    SUBSTITUTION_CLONES.store(0, Ordering::Relaxed);
    FACT_SET_CLONES.store(0, Ordering::Relaxed);

    let start = Instant::now();
    let _facts = chainer.infer().unwrap();
    let elapsed = start.elapsed();

    let sub_clones = SUBSTITUTION_CLONES.load(Ordering::Relaxed);
    let fact_clones = FACT_SET_CLONES.load(Ordering::Relaxed);
    let active_subs = ACTIVE_SUBSTITUTIONS.load(Ordering::Relaxed);

    println!("  Transitive Reasoning:");
    println!("    Time:                {:>8.2}μs", elapsed.as_micros());
    println!("    Substitution Clones: {:>8}", sub_clones);
    println!("    Fact Set Clones:     {:>8}", fact_clones);
    println!("    Active Substitutions:{:>8}", active_subs);
    println!(
        "    Clones/Fact Ratio:   {:>8.2}",
        sub_clones as f64 / fact_count as f64
    );
}

fn benchmark_can_derive(fact_count: usize) {
    let mut chainer = ForwardChainer::new();

    // Add simple rule
    chainer.add_rule(Rule {
        name: "type_inference".to_string(),
        body: vec![RuleAtom::Triple {
            subject: Term::Variable("X".to_string()),
            predicate: Term::Constant("rdf:type".to_string()),
            object: Term::Constant("Person".to_string()),
        }],
        head: vec![RuleAtom::Triple {
            subject: Term::Variable("X".to_string()),
            predicate: Term::Constant("rdf:type".to_string()),
            object: Term::Constant("Agent".to_string()),
        }],
    });

    // Add facts
    for i in 0..fact_count {
        chainer.add_fact(RuleAtom::Triple {
            subject: Term::Constant(format!("person_{}", i)),
            predicate: Term::Constant("rdf:type".to_string()),
            object: Term::Constant("Person".to_string()),
        });
    }

    // Reset metrics
    SUBSTITUTION_CLONES.store(0, Ordering::Relaxed);
    FACT_SET_CLONES.store(0, Ordering::Relaxed);

    // Test target (should be derivable)
    let target = RuleAtom::Triple {
        subject: Term::Constant("person_0".to_string()),
        predicate: Term::Constant("rdf:type".to_string()),
        object: Term::Constant("Agent".to_string()),
    };

    let start = Instant::now();
    let result = chainer.can_derive(&target).unwrap();
    let elapsed = start.elapsed();

    let fact_clones = FACT_SET_CLONES.load(Ordering::Relaxed);

    println!("  can_derive() Check:");
    println!("    Time:                {:>8.2}μs", elapsed.as_micros());
    println!("    Result:              {:>8}", result);
    println!("    Fact Set Clones:     {:>8}", fact_clones);
    println!(
        "    Clone Efficiency:    {:>8}",
        if fact_clones <= 1 {
            "Optimal"
        } else {
            "Suboptimal"
        }
    );
}

fn benchmark_derive_new_facts(fact_count: usize) {
    let mut chainer = ForwardChainer::new();

    // Add rule
    chainer.add_rule(Rule {
        name: "sibling".to_string(),
        body: vec![
            RuleAtom::Triple {
                subject: Term::Variable("X".to_string()),
                predicate: Term::Constant("parent".to_string()),
                object: Term::Variable("P".to_string()),
            },
            RuleAtom::Triple {
                subject: Term::Variable("Y".to_string()),
                predicate: Term::Constant("parent".to_string()),
                object: Term::Variable("P".to_string()),
            },
            RuleAtom::NotEqual {
                left: Term::Variable("X".to_string()),
                right: Term::Variable("Y".to_string()),
            },
        ],
        head: vec![RuleAtom::Triple {
            subject: Term::Variable("X".to_string()),
            predicate: Term::Constant("sibling".to_string()),
            object: Term::Variable("Y".to_string()),
        }],
    });

    // Add facts (pairs of siblings)
    for i in 0..fact_count / 2 {
        let parent = format!("parent_{}", i);
        chainer.add_fact(RuleAtom::Triple {
            subject: Term::Constant(format!("child_{}a", i)),
            predicate: Term::Constant("parent".to_string()),
            object: Term::Constant(parent.clone()),
        });
        chainer.add_fact(RuleAtom::Triple {
            subject: Term::Constant(format!("child_{}b", i)),
            predicate: Term::Constant("parent".to_string()),
            object: Term::Constant(parent),
        });
    }

    // Reset metrics
    SUBSTITUTION_CLONES.store(0, Ordering::Relaxed);
    FACT_SET_CLONES.store(0, Ordering::Relaxed);

    let start = Instant::now();
    let new_facts = chainer.derive_new_facts().unwrap();
    let elapsed = start.elapsed();

    let fact_clones = FACT_SET_CLONES.load(Ordering::Relaxed);

    println!("  derive_new_facts():");
    println!("    Time:                {:>8.2}μs", elapsed.as_micros());
    println!("    New Facts Derived:   {:>8}", new_facts.len());
    println!("    Fact Set Clones:     {:>8}", fact_clones);
    println!(
        "    Clone Efficiency:    {:>8}",
        if fact_clones == 1 {
            "Optimal"
        } else {
            "Acceptable"
        }
    );
}