/*
* ForwardChainer.cxxtest
*
* Created on: Sep 2, 2014
* Author: misgana
*/
#include <boost/range/algorithm/find.hpp>
#include <opencog/util/random.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/guile/SchemeEval.h>
#include <opencog/ure/forwardchainer/ForwardChainer.h>
#include <cxxtest/TestSuite.h>
using namespace opencog;
#define al _as->add_link
#define an _as->add_node
#define CHKERR \
TSM_ASSERT("Caught scm error during eval", \
(false == _eval.eval_error()));
class ForwardChainerUTest: public CxxTest::TestSuite
{
private:
AtomSpacePtr _as;
SchemeEval _eval;
public:
ForwardChainerUTest() : _as(createAtomSpace()), _eval(_as)
{
#undef DEBUG
logger().set_level(Logger::INFO);
logger().set_timestamp_flag(false);
// logger().set_sync_flag(true);
logger().set_print_to_stdout_flag(true);
ure_logger().set_level(Logger::INFO);
ure_logger().set_timestamp_flag(false);
// ure_logger().set_sync_flag(true);
ure_logger().set_print_to_stdout_flag(true);
std::string
source_dir = std::string(PROJECT_SOURCE_DIR),
test_dir = source_dir + "/tests",
test_ure_dir = test_dir + "/ure",
test_forwardchainer_dir = test_ure_dir + "/forwardchainer",
test_scm_dir = test_forwardchainer_dir + "/scm",
ure_dir = source_dir + "/opencog/scm/opencog/ure";
std::vector<std::string> load_paths =
{ source_dir,
test_dir,
test_ure_dir,
test_forwardchainer_dir,
test_scm_dir,
ure_dir };
for (std::string& p : load_paths) {
std::string eval_str =
std::string("(add-to-load-path \"") + p + std::string("\")");
_eval.eval(eval_str);
}
_eval.eval("(use-modules (opencog))");
_eval.eval("(use-modules (opencog ure))");
_eval.eval("(use-modules (opencog logger))");
CHKERR;
}
void setUp();
void tearDown();
// Test auxiliary functions
void test_select_rule();
// Test forward chainer
void test_deduction();
void test_deduction_neg_max_iter();
void test_deduction_focus_set();
void test_fritz_green();
void test_tweety_not_green();
void test_fritz_green_alt();
void test_tweety_not_green_alt();
void test_unsatisfied_premise();
void test_negation_conflict();
void test_bindlink_no_vardecl();
};
void ForwardChainerUTest::setUp()
{
_as->clear();
// Load the simple deduction rbs to test it
std::string result; // to be able to read load errors
result = _eval.eval("(load-from-path \"fc-deduction-config.scm\")");
CHKERR;
// Load the simple deduction + modus ponens rbs to test it
result = _eval.eval("(load-from-path \"fc-config.scm\")");
CHKERR;
randGen().seed(3);
}
void ForwardChainerUTest::tearDown()
{
}
void ForwardChainerUTest::test_select_rule(void)
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
Handle h = _eval.eval_h("(InheritanceLink"
" (ConceptNode \"Cat\")"
" (ConceptNode \"Animal\"))");
TS_ASSERT_DIFFERS(h, Handle::UNDEFINED);
Handle rbs = _eval.eval_h("(ConceptNode \"fc-rule-base\")");
ForwardChainer fc(*_as.get(), rbs, h);
// Full unification
RuleProbabilityPair rule = fc.select_rule(h);
TS_ASSERT(rule.first->is_valid());
}
void ForwardChainerUTest::test_deduction()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Test simple deduction
//
// InheritanceLink A B
// InheritanceLink B C
// |-
// InheritanceLink A C
//
Handle A = _eval.eval_h("(ConceptNode \"A\" (stv 1 1))"),
B = _eval.eval_h("(ConceptNode \"B\")"),
C = _eval.eval_h("(ConceptNode \"C\")"),
AB = _eval.eval_h("(InheritanceLink (stv 1 1)"
" (ConceptNode \"A\")"
" (ConceptNode \"B\"))"),
BC = _eval.eval_h("(InheritanceLink (stv 1 1)"
" (ConceptNode \"B\")"
" (ConceptNode \"C\"))");
// Get the ConceptNode corresponding to the rule-based system to test
Handle rbs = an(CONCEPT_NODE, "fc-deduction-rule-base");
ForwardChainer fc(*_as.get(), rbs, AB);
fc.get_config().set_jobs(4);
// Run forward chainer
fc.do_chain();
// Collect the results
HandleSet results = fc.get_results_set();
// Check that AC is in the results
Handle AC = _as->add_link(INHERITANCE_LINK, A, C);
TS_ASSERT_DIFFERS(results.find(AC), results.end());
}
// Like test_deduction but set a negative number of iteration which
// means infinite number of iterations, or until saturation.
void ForwardChainerUTest::test_deduction_neg_max_iter()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Test simple deduction
//
// InheritanceLink A B
// InheritanceLink B C
// |-
// InheritanceLink A C
//
Handle A = _eval.eval_h("(ConceptNode \"A\" (stv 1 1))"),
B = _eval.eval_h("(ConceptNode \"B\")"),
C = _eval.eval_h("(ConceptNode \"C\")"),
AB = _eval.eval_h("(InheritanceLink (stv 1 1)"
" (ConceptNode \"A\")"
" (ConceptNode \"B\"))"),
BC = _eval.eval_h("(InheritanceLink (stv 1 1)"
" (ConceptNode \"B\")"
" (ConceptNode \"C\"))");
// Get the ConceptNode corresponding to the rule-based system to test
Handle rbs = an(CONCEPT_NODE, "fc-deduction-rule-base");
ForwardChainer fc(*_as.get(), rbs, AB);
fc.get_config().set_jobs(4);
fc.get_config().set_maximum_iterations(-1);
// Run forward chainer
fc.do_chain();
// Collect the results
HandleSet results = fc.get_results_set();
// Check that AC is in the results
Handle AC = _as->add_link(INHERITANCE_LINK, A, C);
TS_ASSERT_DIFFERS(results.find(AC), results.end());
}
// Like test_deduction() but operate on the focus set
void ForwardChainerUTest::test_deduction_focus_set()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Test simple deduction
//
// InheritanceLink A B
// InheritanceLink B C
// |-
// InheritanceLink A C
//
Handle A = _eval.eval_h("(ConceptNode \"A\" (stv 1 1))"),
B = _eval.eval_h("(ConceptNode \"B\")"),
C = _eval.eval_h("(ConceptNode \"C\")"),
AB = _eval.eval_h("(InheritanceLink (stv 1 1)"
" (ConceptNode \"A\")"
" (ConceptNode \"B\"))"),
BC = _eval.eval_h("(InheritanceLink (stv 1 1)"
" (ConceptNode \"B\")"
" (ConceptNode \"C\"))");
// Get the ConceptNode corresponding to the rule-based system to test
Handle rbs = an(CONCEPT_NODE, "fc-deduction-rule-base");
AtomSpacePtr empty_kb_as(createAtomSpace());
Handle vardecl = Handle::UNDEFINED;
AtomSpace* trace_as = nullptr;
HandleSeq focus_set{AB, BC};
ForwardChainer fc(*empty_kb_as.get(), *_as.get(), rbs, AB, vardecl, trace_as, focus_set);
// Run forward chainer
fc.do_chain();
// Collect the results
HandleSet results = fc.get_results_set();
// Check that AC is in the results
Handle AC = _as->add_link(INHERITANCE_LINK, A, C);
TS_ASSERT_DIFFERS(results.find(AC), results.end());
}
void ForwardChainerUTest::test_fritz_green()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
std::string result =
_eval.eval("(load-from-path \"fc-animals-config.scm\")");
logger().debug() << "result = " << result;
_eval.eval("(load-from-path \"animals.scm\")");
randGen().seed(0);
Handle top_rbs = _as->get_node(CONCEPT_NODE,
std::move(std::string(UREConfig::top_rbs_name)));
Handle Fritz = an(CONCEPT_NODE, "Fritz"),
croaks = an(PREDICATE_NODE, "croaks"),
green = an(CONCEPT_NODE, "green"),
source = al(EVALUATION_LINK, croaks, Fritz),
vardecl = Handle::UNDEFINED;
ForwardChainer fc(*_as.get(), top_rbs, source, vardecl);
fc.get_config().set_maximum_iterations(50);
fc.do_chain();
HandleSet results = fc.get_results_set();
Handle Fritz_green = al(INHERITANCE_LINK, Fritz, green);
logger().debug() << "results = " << oc_to_string(results);
// Check that Fritz_green is in the results
TS_ASSERT_DIFFERS(results.find(Fritz_green), results.end());
}
void ForwardChainerUTest::test_tweety_not_green()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Test that Tweety is not green
std::string result =
_eval.eval("(load-from-path \"fc-animals-config.scm\")");
logger().debug() << "result = " << result;
_eval.eval("(load-from-path \"animals.scm\")");
randGen().seed(0);
Handle top_rbs = _as->get_node(CONCEPT_NODE,
std::move(std::string(UREConfig::top_rbs_name)));
Handle Tweety = an(CONCEPT_NODE, "Tweety"),
eats_flies = an(PREDICATE_NODE, "croaks"),
green = an(CONCEPT_NODE, "green"),
source = al(EVALUATION_LINK, eats_flies, Tweety),
vardecl = Handle::UNDEFINED;
ForwardChainer fc(*_as.get(), top_rbs, source, vardecl);
// Put lots of evaluations to be sure to exhaust all possibilities
fc.get_config().set_maximum_iterations(50);
fc.do_chain();
HandleSet results = fc.get_results_set();
Handle Tweety_green = al(INHERITANCE_LINK, Tweety, green);
logger().debug() << "results = " << oc_to_string(results);
// Check that Tweety_green is NOT in the results
TS_ASSERT_EQUALS(results.find(Tweety_green), results.end());
}
void ForwardChainerUTest::test_fritz_green_alt()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Like test_fritz_green() but using the alternate rule base with
// customs conditional instantiations.
std::string result =
_eval.eval("(load-from-path \"fc-animals-alt-config.scm\")");
logger().debug() << "result = " << result;
_eval.eval("(load-from-path \"animals.scm\")");
randGen().seed(0);
Handle top_rbs = _as->get_node(CONCEPT_NODE,
std::move(std::string(UREConfig::top_rbs_name)));
Handle Fritz = an(CONCEPT_NODE, "Fritz"),
croaks = an(PREDICATE_NODE, "croaks"),
green = an(CONCEPT_NODE, "green"),
source = al(EVALUATION_LINK, croaks, Fritz),
vardecl = Handle::UNDEFINED;
ForwardChainer fc(*_as.get(), top_rbs, source, vardecl);
fc.get_config().set_maximum_iterations(50);
fc.do_chain();
HandleSet results = fc.get_results_set();
Handle Fritz_green = al(INHERITANCE_LINK, Fritz, green);
logger().debug() << "results = " << oc_to_string(results);
// Check that Fritz_green is in the results
TS_ASSERT_DIFFERS(results.find(Fritz_green), results.end());
}
void ForwardChainerUTest::test_tweety_not_green_alt()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
// Like test_tweety_not_green_alt() but using the alternate rule
// base with customs conditional instantiations.
std::string result =
_eval.eval("(load-from-path \"fc-animals-alt-config.scm\")");
logger().debug() << "result = " << result;
_eval.eval("(load-from-path \"animals.scm\")");
randGen().seed(0);
Handle top_rbs = _as->get_node(CONCEPT_NODE,
std::move(std::string(UREConfig::top_rbs_name)));
Handle Tweety = an(CONCEPT_NODE, "Tweety"),
eats_flies = an(PREDICATE_NODE, "eats_flies"),
green = an(CONCEPT_NODE, "green"),
source = al(EVALUATION_LINK, eats_flies, Tweety),
vardecl = Handle::UNDEFINED;
ForwardChainer fc(*_as.get(), top_rbs, source, vardecl);
// Put lots of evaluations to be sure to exhaust all possibilities
fc.get_config().set_maximum_iterations(50);
fc.do_chain();
HandleSet results = fc.get_results_set();
Handle Tweety_green = al(INHERITANCE_LINK, Tweety, green);
logger().debug() << "results = " << oc_to_string(results);
// Check that Tweety_green is NOT in the results
TS_ASSERT_EQUALS(results.find(Tweety_green), results.end());
}
/**
* ForwardChainer shouldn't return a result when there is
* unsatisfied premise.
*/
void ForwardChainerUTest::test_unsatisfied_premise(void)
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
_eval.eval("(load-from-path \"fc-unsatisfied-premise.scm\")");
Handle table = an(CONCEPT_NODE, "table"),
furniture = an(CONCEPT_NODE, "furniture"),
source = al(INHERITANCE_LINK, table, furniture),
vardecl = Handle::UNDEFINED,
top_rbs = _as->get_node(CONCEPT_NODE, "rule-base");
ForwardChainer fc(*_as.get(), top_rbs, source, vardecl);
fc.get_config().set_maximum_iterations(20);
fc.do_chain();
HandleSet results = fc.get_results_set();
TS_ASSERT_EQUALS(results.size(), 0);
}
/**
* Do not imply the result that conflicts with existing negation facts.
*/
void ForwardChainerUTest::test_negation_conflict()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
_eval.eval("(load-from-path \"negation-conflict.scm\")");
Handle vardecl = Handle::UNDEFINED;
Handle rbs = _as->get_node(CONCEPT_NODE, "Einstein-rbs");
Handle source = _eval.eval_h("source");
ForwardChainer fc(*_as.get(), rbs, source, vardecl);
fc.get_config().set_maximum_iterations(20);
fc.do_chain();
HandleSet resultSet = fc.get_results_set();
HandleSeq resultHandleSeq(resultSet.begin(), resultSet.end());
Handle results = al(SET_LINK, std::move(resultHandleSeq));
Handle American = an(CONCEPT_NODE, "American"),
German = an(CONCEPT_NODE, "German"),
cat = an(CONCEPT_NODE, "cat"),
dog = an(CONCEPT_NODE, "dog"),
keep = an(PREDICATE_NODE, "keep-pet"),
Gkeepcat = al(EVALUATION_LINK, keep, al(LIST_LINK, German, cat)),
Akeepdog = al(EVALUATION_LINK, keep, al(LIST_LINK, American, dog));
Handle expected = al(SET_LINK, Gkeepcat, Akeepdog);
logger().debug() << "results = " << oc_to_string(results);
logger().debug() << "expected = " << oc_to_string(expected);
// They should be equal, else test failure.
if (results != expected)
throw std::runtime_error("test failure");
}
void ForwardChainerUTest::test_bindlink_no_vardecl()
{
logger().info("BEGIN TEST: %s", __FUNCTION__);
_eval.eval("(load-from-path \"fc-bindlink-no-vardecl-config.scm\")");
Handle top_rbs = _as->get_node(CONCEPT_NODE,
std::move(std::string(UREConfig::top_rbs_name)));
Handle source = al(MEMBER_LINK, an(CONCEPT_NODE, "terrier"),
an(CONCEPT_NODE, "dog"));
Handle vardecl = Handle::UNDEFINED;
ForwardChainer fc(*_as.get(), top_rbs, source, vardecl);
fc.get_config().set_maximum_iterations(10);
fc.do_chain();
HandleSet results = fc.get_results_set();
logger().debug() << "results = " << oc_to_string(results);
Handle target = al(MEMBER_LINK, an(CONCEPT_NODE, "terrier"),
an(CONCEPT_NODE, "animal"));
TS_ASSERT_DIFFERS(results.find(target), results.end());
}
#undef al
#undef an