OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-ure / tests / ure / forwardchainer / ForwardChainerUTest.cxxtest

469 lines · cxxtest ref:
/*
 * 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