OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-generate / tests / generate / AggregationUTest.cxxtest

482 lines · cxxtest ref:
/*
 * AggregationUTest.cxxtest
 *
 * Basic does-it-work? unit test. Explores some very very simple
 * natural-language grammars that generate a handful (less than a dozen)
 * different sentences. No combinatoric explosions, no stochastic
 * elements.  Uses only the SimpleCallback for everything.
 *
 * Copyright (C) 2020 Linas Vepstas <linasvepstas@gmail.com>
 * All Rights Reserved
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <opencog/atoms/atom_types/atom_types.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/guile/SchemeEval.h>
#include <opencog/generate/Aggregate.h>
#include <opencog/generate/SimpleCallback.h>

#include <cxxtest/TestSuite.h>

using namespace opencog;

#define al as->add_link
#define an as->add_node

class AggregationUTest: public CxxTest::TestSuite
{
private:
	AtomSpacePtr asp;
	AtomSpace* as;
	SchemeEval* eval;
	Aggregate* ag;
	Dictionary* dict;

public:
	AggregationUTest();
	~AggregationUTest();

	void setUp();
	void tearDown();

	void setup_dict();

	void test_hello();
	void test_tree();
	void test_loop();
	void test_biloop();
	void test_quad();
	void test_biquad();
	void test_triquad();
	void test_mixed();
	void test_multi_root();
};

AggregationUTest::AggregationUTest()
{
	logger().set_level(Logger::DEBUG);
	logger().set_print_to_stdout_flag(true);
	logger().set_timestamp_flag(false);
}

AggregationUTest::~AggregationUTest()
{
	logger().info("Completed running AggregationUTest");

	// erase the log file if no assertions failed
	if (!CxxTest::TestTracker::tracker().suiteFailed())
		std::remove(logger().get_filename().c_str());
	else
	{
		logger().info("AggregationUTest failed!");
		logger().flush();
	}
}

void AggregationUTest::setUp()
{
	asp = createAtomSpace();
	as = asp.get();
	eval = SchemeEval::get_scheme_evaluator(as);
	eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")");
	ag = new Aggregate(as);
}

void AggregationUTest::tearDown()
{
	delete ag;
}

void AggregationUTest::setup_dict()
{
	dict = new Dictionary(as);

	// The directions to connect.
	Handle plus = an(CONNECTOR_DIR_NODE, "+");
	Handle minus = an(CONNECTOR_DIR_NODE, "-");
	dict->add_pole_pair(plus, minus);
	dict->add_pole_pair(minus, plus);

	// The lexis to use
	UnorderedHandleSet lex;
	as->get_handles_by_type(lex, SECTION);
	dict->add_to_lexis(lex);
}

// Single generation: "Hello world"
void AggregationUTest::test_hello()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-helloworld.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);
	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// We expect the solution to be LEFT_WALL --W--> Hello --OH--> world
	// printf("Hello-world result is  %s\n", result->to_string().c_str());
	logger().debug("Expecting 1 solution, got %d", result->get_arity());
	TSM_ASSERT("Bad result set!", result->get_arity() == 1);
	Handle soln = result->getOutgoingAtom(0);

	TSM_ASSERT("Bad section!", soln->get_arity() == 3);

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Four generations: "John/Mary saw a dog/cat", as an acyclic tree.
void AggregationUTest::test_tree()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-tree.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);

	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// We expect the solution to be loop-free (acyclic)
	//                    +----O-----+
	//    +--->W---+---S--+    +--D--+
	//    |        |      |    |     |
	// LEFT-WALL John    saw   a    cat
	//
	// printf("Tree result is  %s\n", result->to_string().c_str());

	logger().debug("Expecting 4 solutions, got %d", result->get_arity());
	TSM_ASSERT("Bad result set!", result->get_arity() == 4);

	// All of the solutions should be the same size, substituting
	// only John/Mary and dog/cat in the parse tree.
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 5 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 5);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Four generations: "John/Mary saw a dog/cat", as graph w/ one cycle.
void AggregationUTest::test_loop()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-loop.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);

	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// We expect the solution to have a loop
	//    +-------WV------+----O-----+
	//    +--->W---+---S--+    +--D--+
	//    |        |      |    |     |
	// LEFT-WALL John    saw   a    cat
	//
	// printf("Loop result is %s\n", result->to_string().c_str());
	printf("Loop result size is %lu expecting 4\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 4);

	// All of the solutions should be the same size, substituting
	// only John/Mary and dog/cat in the parse.
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 5 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 5);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Four generations: "John thinks Mary saw a bird", and v.v. as graph
// w/ two cycles.
void AggregationUTest::test_biloop()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-biloop.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);

	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// We expect the solution to have two loops
	//
	//     +------WV-----+------CV----+--O---+
	//     +---W---+--S--+--Ce--+--S--+  +-D-+
	//     |       |     |      |     |  |   |
	// LEFT-WALL Mary thinks  John   saw a  bird
	//
	// printf("Loop result is %s\n", result->to_string().c_str());
	printf("Loop result size is %lu expecting 4\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 4);

	// All of the solutions should be the same size, substituting
	// only John/Mary being swapped in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 7 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 7);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Four generations: "Mary could see the dog", and v.v. as graph
// w/ one quadrilateral cycle.
void AggregationUTest::test_quad()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-quad.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);

	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// We expect the solution to have one quad
	//
	//    +----------WV--------+----O-----+
	//    +----W---+--S--+--I--+    +--D--+
	//    |        |     |     |    |     |
	// LEFT-WALL Mary  could  see  the   cat
	//
	// printf("Loop result is %s\n", result->to_string().c_str());
	printf("Loop result size is %lu expecting 4\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 4);

	// All of the solutions should be the same size, substituting
	// only John/Mary and see/hear being swapped in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 6 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 6);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Four generations: "Mary might think that John could fall", and v.v.
// as graph w/ two quadrilateral cycles.
void AggregationUTest::test_biquad()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-biquad.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);

	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// We expect the solution to have two quads
	//
	// printf("Loop result is %s\n", result->to_string().c_str());
	printf("Loop result size is %lu expecting 4\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 4);

	// All of the solutions should be the same size, substituting
	// only might/could in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 7 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 7);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Three coupled cycles.
void AggregationUTest::test_triquad()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-triquad.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);

	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// We expect the solution to have three quads
	//
	// printf("Loop result is %s\n", result->to_string().c_str());
	printf("Loop result size is %lu expecting 4\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 4);

	// All of the solutions should be the same size, substituting
	// only might/could in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 8 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 8);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Mixture of trees and cycles.
void AggregationUTest::test_mixed()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-mixed.scm\")");
	Handle wall = eval->eval_h("left-wall");

	setup_dict();
	SimpleCallback cb(as, *dict);

	ag->aggregate({wall}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// printf("Mixed result is %s\n", result->to_string().c_str());
	printf("Mixed result size is %lu expecting 8\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 8);

	// All of the solutions should be the same size, substituting
	// only might/could in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 10 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 10);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}

// Multiple nucleation points
void AggregationUTest::test_multi_root()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	eval->eval("(load-from-path \"tests/generate/dict-mixed.scm\")");
	Handle wall = eval->eval_h("left-wall");
	Handle black = eval->eval_h("black");
	Handle dog = eval->eval_h("dog");
	Handle bark = eval->eval_h("bark");

	setup_dict();
	SimpleCallback cb(as, *dict);

	// ----------------------------------------------------
	ag->aggregate({wall, black}, cb);
	Handle result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// printf("wall,black result is %s\n", result->to_string().c_str());
	printf("wall,black result size is %lu expecting 4\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 4);

	// All of the solutions should be the same size, substituting
	// only might/could in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	int cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 10 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 10);
	}

	// ----------------------------------------------------
	ag->aggregate({wall, black, dog}, cb);
	result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// printf("wall,black result is %s\n", result->to_string().c_str());
	printf("wall,black,dog result size is %lu expecting 2\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 2);

	// All of the solutions should be the same size, substituting
	// only might/could in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 10 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 10);
	}

	// ----------------------------------------------------
	ag->aggregate({wall, black, dog, bark}, cb);
	result = cb.get_solutions();

	TSM_ASSERT("Bad result!", result != Handle::UNDEFINED);

	// printf("wall,black result is %s\n", result->to_string().c_str());
	printf("wall,black,dog,bark result size is %lu expecting 1\n", result->get_arity());
	TSM_ASSERT("Bad loop result set!", result->get_arity() == 1);

	// All of the solutions should be the same size, substituting
	// only might/could in the parse.
	// printf("Parse is %s\n", result->getOutgoingAtom(0)->to_string().c_str());
	cnt = 0;
	for (const Handle& soln: result->getOutgoingSet())
	{
		logger().debug("   Soln %d expecting 10 words, got %d",
			++cnt, soln->get_arity());
		TSM_ASSERT("Bad section!", soln->get_arity() == 10);
	}

	logger().debug("END TEST: %s", __FUNCTION__);
}