Files
semantica/tests/test_reasoning_inference.py

155 lines
5.1 KiB
Python

import pytest
from unittest.mock import MagicMock, patch
from semantica.reasoning import (
InferenceEngine,
RuleManager,
ExplanationGenerator,
InferenceResult,
Rule,
RuleType
)
# --- Fixtures ---
@pytest.fixture
def inference_engine():
return InferenceEngine()
@pytest.fixture
def rule_manager():
return RuleManager()
@pytest.fixture
def explanation_generator():
return ExplanationGenerator()
# --- Test Rule Parsing (RuleManager) ---
def test_rule_parsing_simple(rule_manager):
rule_def = "IF Person(?x) THEN Human(?x)"
rule = rule_manager.define_rule(rule_def, name="HumanRule")
assert rule.name == "HumanRule"
assert rule.rule_type == RuleType.IMPLICATION
assert len(rule.conditions) == 1
assert rule.conditions[0] == "Person(?x)"
assert rule.conclusion == "Human(?x)"
def test_rule_parsing_multiple_conditions(rule_manager):
rule_def = "IF Parent(?x, ?y) AND Parent(?y, ?z) THEN Grandparent(?x, ?z)"
rule = rule_manager.define_rule(rule_def)
assert len(rule.conditions) == 2
assert rule.conditions[0] == "Parent(?x, ?y)"
assert rule.conditions[1] == "Parent(?y, ?z)"
assert rule.conclusion == "Grandparent(?x, ?z)"
def test_rule_parsing_invalid(rule_manager):
with pytest.raises(Exception):
rule_manager.define_rule("INVALID RULE SYNTAX")
# --- Test InferenceEngine (Forward Chaining) ---
def test_forward_chaining_exact_match(inference_engine):
# Rule: IF A THEN B
inference_engine.add_rule("IF A THEN B")
inference_engine.add_fact("A")
results = inference_engine.forward_chain()
assert len(results) >= 1
inferred_facts = [res.conclusion for res in results]
assert "B" in inferred_facts
def test_forward_chaining_simple(inference_engine):
# Rule: IF Person(?x) THEN Human(?x)
inference_engine.add_rule("IF Person(?x) THEN Human(?x)")
# Fact: Person(Alice)
inference_engine.add_fact("Person(Alice)")
results = inference_engine.forward_chain()
assert len(results) >= 1
# Check if Human(Alice) is inferred
inferred_facts = [res.conclusion for res in results]
assert "Human(Alice)" in inferred_facts
def test_forward_chaining_transitive(inference_engine):
# Rule: IF Parent(?a, ?b) AND Parent(?b, ?c) THEN Grandparent(?a, ?c)
inference_engine.add_rule("IF Parent(?a, ?b) AND Parent(?b, ?c) THEN Grandparent(?a, ?c)")
inference_engine.add_fact("Parent(Alice, Bob)")
inference_engine.add_fact("Parent(Bob, Charlie)")
results = inference_engine.forward_chain()
inferred_facts = [res.conclusion for res in results]
assert "Grandparent(Alice, Charlie)" in inferred_facts
def test_forward_chaining_no_match(inference_engine):
inference_engine.add_rule("IF A(?x) THEN B(?x)")
inference_engine.add_fact("C(Item)")
results = inference_engine.forward_chain()
assert len(results) == 0
# --- Test InferenceEngine (Backward Chaining) ---
def test_backward_chaining_success(inference_engine):
# Rule: IF Parent(?a, ?b) AND Parent(?b, ?c) THEN Grandparent(?a, ?c)
inference_engine.add_rule("IF Parent(?a, ?b) AND Parent(?b, ?c) THEN Grandparent(?a, ?c)")
inference_engine.add_fact("Parent(Alice, Bob)")
inference_engine.add_fact("Parent(Bob, Charlie)")
# Goal: Prove Grandparent(Alice, Charlie)
proof = inference_engine.backward_chain("Grandparent(Alice, Charlie)")
assert proof is not None
# Depending on implementation, proof might be a boolean or a Proof object
# The notebook says: if proof: print(...)
assert bool(proof) is True
def test_backward_chaining_failure(inference_engine):
inference_engine.add_rule("IF A(?x) THEN B(?x)")
inference_engine.add_fact("A(1)")
proof = inference_engine.backward_chain("B(2)")
assert not proof
# --- Test ExplanationGenerator ---
def test_explanation_generation(explanation_generator):
# Create a dummy InferenceResult
rule = Rule("r1", "test_rule", ["A(?x)"], "B(?x)")
result = InferenceResult(
conclusion="B(1)",
premises=["A(1)"],
rule_used=rule,
confidence=1.0
)
explanation = explanation_generator.generate_explanation(result)
assert explanation is not None
assert explanation.conclusion == "B(1)"
# Check if natural language explanation is generated
assert isinstance(explanation.natural_language, str)
assert "B(1)" in explanation.natural_language
assert "test_rule" in explanation.natural_language or "r1" in explanation.natural_language
# --- Test Edge Cases ---
def test_duplicate_facts(inference_engine):
assert inference_engine.add_fact("A(1)") is True
assert inference_engine.add_fact("A(1)") is False # Duplicate
assert len(inference_engine.facts) == 1
def test_max_iterations(inference_engine):
# Create a rule that generates infinite facts if not controlled
# e.g., IF Num(?x) THEN Num(?x+1) - hard to simulate with string matching without eval
# Instead, let's just test config setting
engine = InferenceEngine(max_iterations=5)
assert engine.max_iterations == 5