Query Operators
Spindle provides three query operators for interactive reasoning: what-if, why-not, and abduction.
What-If Queries
What-if performs hypothetical reasoning: "What would be concluded if these facts were true?"
API
#![allow(unused)] fn main() { use spindle_core::query::{what_if, HypotheticalClaim}; use spindle_core::literal::Literal; let hypotheticals = vec![ HypotheticalClaim::new(Literal::simple("wounded")) ]; let goal = Literal::negated("flies"); let result = what_if(&theory, hypotheticals, &goal); }
How It Works
- Create a copy of the theory
- Add hypothetical facts
- Reason over the modified theory
- Return new conclusions
Example
Original theory:
(given bird)
(normally r1 bird flies)
(normally r2 wounded (not flies))
(prefer r2 r1)
Query: What if wounded is true?
#![allow(unused)] fn main() { let hypotheticals = vec![HypotheticalClaim::new(Literal::simple("wounded"))]; let goal = Literal::negated("flies"); let result = what_if(&theory, hypotheticals, &goal); // result.is_provable() = true // result.new_conclusions = [Literal("~flies")] }
Use Cases
- Exploring consequences of decisions
- Scenario analysis
- Testing rule interactions
Why-Not Queries
Why-not explains why a literal is NOT provable.
API
#![allow(unused)] fn main() { use spindle_core::query::why_not; use spindle_core::literal::Literal; let literal = Literal::simple("flies"); let explanation = why_not(&theory, &literal); }
How It Works
- Check if the literal is actually unprovable
- Find rules that could prove it
- For each rule, identify what's blocking it:
- Missing body literals
- Defeated by superior rules
- Blocked by defeaters
Example
Theory:
(given bird)
(given penguin)
(normally r1 bird flies)
(normally r2 penguin (not flies))
(prefer r2 r1)
Query: Why is flies not provable?
#![allow(unused)] fn main() { let literal = Literal::simple("flies"); let explanation = why_not(&theory, &literal); // explanation.literal = Literal("flies") // explanation.would_derive = Some("r1") // explanation.blocked_by = [BlockingCondition { ... }] }
Result Structure
#![allow(unused)] fn main() { struct WhyNotResult { literal: Literal, // What we're asking about would_derive: Option<String>, // Rule that could prove it blocked_by: Vec<BlockingCondition>, // What's blocking } struct BlockingCondition { blocking_type: BlockingType, // Type of blocking rule_label: String, // The blocked rule missing_literals: Vec<Literal>, // Missing premises (if applicable) blocking_rule: Option<String>, // Blocking rule (if applicable) explanation: String, // Human-readable explanation } enum BlockingType { MissingPremise, // Body not satisfied Defeated, // Defeated by defeater Contradicted, // Complement is proven } }
Use Cases
- Debugging unexpected results
- Understanding rule interactions
- Explaining decisions to users
Abduction
Abduction finds hypotheses that would make a goal provable.
API
#![allow(unused)] fn main() { use spindle_core::query::abduce; use spindle_core::literal::Literal; let goal = Literal::simple("goal"); let result = abduce(&theory, &goal, max_solutions); }
How It Works
- Start with the goal literal
- Find rules that could prove it
- For each rule, identify missing body literals
- Recursively abduce missing literals
- Return minimal sets of assumptions
Example
Theory:
(normally r1 bird flies)
(normally r2 penguin (not flies))
(normally r3 (and bird healthy) strong-flyer)
(prefer r2 r1)
Query: What facts would make flies provable?
#![allow(unused)] fn main() { let goal = Literal::simple("flies"); let result = abduce(&theory, &goal, 3); // result.solutions[0].facts = HashSet { Literal("bird") } }
Solution Structure
#![allow(unused)] fn main() { struct AbductionResult { goal: Literal, solutions: Vec<AbductionSolution>, } struct AbductionSolution { facts: HashSet<Literal>, // Facts to assume rules_used: HashSet<String>, // Rules involved confidence: f64, // Trust score (if weighted) } }
Handling Conflicts
Abduction considers superiority:
(given penguin)
(normally r1 bird flies)
(normally r2 penguin (not flies))
(prefer r2 r1)
Abducing flies might return:
["bird", "healthy"]with a rule(normally r3 (and bird healthy) flies)where r3 > r2- Or indicate that
fliescannot be achieved givenpenguin
Use Cases
- Diagnosis: What conditions explain symptoms?
- Planning: What actions achieve a goal?
- Debugging: What facts would fix this?
Combined Queries
Debugging Workflow
- Observe:
fliesis not provable - Why-not: Discover r2 is blocking
- What-if: Test
what_if(["healthy"]) - Abduce: Find minimal fix
Example Session
#![allow(unused)] fn main() { // 1. Check current state let conclusions = theory.reason(); assert!(!is_provable(&conclusions, "flies")); // 2. Understand why let why = why_not(&theory, "flies"); println!("Blocked by: {:?}", why.blockers); // 3. Explore hypotheticals let hypo = what_if(&theory, &["super_bird"], "flies"); if hypo.provable { println!("Would work if super_bird"); } // 4. Find minimal solution let solutions = abduce(&theory, "flies", 3); println!("Minimal fixes: {:?}", solutions); }
WebAssembly API
The WASM bindings expose these operators:
import { Spindle } from 'spindle-wasm';
const spindle = new Spindle();
spindle.parseSpl(`
(given bird)
(given penguin)
(normally r1 bird flies)
(normally r2 penguin (not flies))
(prefer r2 r1)
`);
// What-if
const whatIf = spindle.whatIf(["healthy"], "flies");
console.log(whatIf);
// Why-not
const whyNot = spindle.whyNot("flies");
console.log(whyNot);
// Abduction
const abduce = spindle.abduce("flies", 3);
console.log(abduce);
Limitations
- Depth limits: Abduction has a configurable depth limit to avoid infinite search
- Minimal solutions: Abduction returns minimal sets, not all possible sets
- Grounded queries: Queries work on grounded theories; variables must be bound
- Performance: Deep abduction can be expensive for large theories