/*
* tests/query/ExecutionOutputUTest.cxxtest
*
* Copyright (C) 2009 Linas Vepstas <linasvepstas@gmail.com>
* All Rights Reserved
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License v3 as
* published by the Free Software Foundation and including the exceptions
* at http://opencog.org/wiki/Licenses
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program; if not, write to:
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <opencog/util/Logger.h>
#include <opencog/atoms/base/Link.h>
#include <opencog/atoms/core/NumberNode.h>
#include <opencog/atoms/pattern/QueryLink.h>
#include <opencog/atoms/value/LinkValue.h>
#include <opencog/atoms/value/QueueValue.h>
#include <opencog/atoms/value/StringValue.h>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/guile/SchemeEval.h>
#include <opencog/query/Implicator.h>
#include "imply.h"
using namespace opencog;
class ExecutionOutputUTest : public CxxTest::TestSuite
{
private:
AtomSpacePtr as;
SchemeEval* eval;
public:
ExecutionOutputUTest(void) : as(createAtomSpace()), eval(SchemeEval::get_scheme_evaluator(as))
{
logger().set_level(Logger::DEBUG);
logger().set_print_to_stdout_flag(true);
eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")");
}
~ExecutionOutputUTest()
{
// erase the log file if no assertions failed
if (!CxxTest::TestTracker::tracker().suiteFailed())
std::remove(logger().get_filename().c_str());
}
void setUp(void);
void tearDown(void);
void test_exec(void);
void test_varsub(void);
void test_query_exec(void);
void test_clear_box_single_argument(void);
void test_clear_box_single_argument_link(void);
void test_value(void);
void test_recursion(void);
void test_function(void);
void test_lambda(void);
void test_lambda_get(void);
void test_variable_schema(void);
void test_exec_variable(void);
};
#define an as->add_node
#define al as->add_link
#define getarity(hand) hand->get_arity()
#define getlink(hand,pos) hand->getOutgoingAtom(pos)
void ExecutionOutputUTest::tearDown(void)
{
as->clear();
}
void ExecutionOutputUTest::setUp(void)
{
}
/*
* preposition reasoning test.
*
* Expect to get the following result, given the above data:
*
* EvaluationLink
* DefinedLinguisticRelationshipNode "capital_of"
* ListLink
* WordNode "capital"
* DefinedLinguisticRelationshipNode "of"
*/
void ExecutionOutputUTest::test_exec(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
/*
* Create an implication link that will be tested.
* This sets up the following structures:
* # IF _obj(be, $var1) ^
* $prep($var1, $var2)
* THEN %ExecutionOutputLink("make-it", $var1, $prep)
*/
Handle clauses =
al(AND_LINK,
al(EDGE_LINK,
an(PREDICATE_NODE, "_obj"),
al(LIST_LINK,
an(CONCEPT_NODE, "be"),
an(VARIABLE_NODE, "$var1")
)
),
al(EDGE_LINK,
an(VARIABLE_NODE, "$prep"),
al(LIST_LINK,
an(VARIABLE_NODE, "$var1"),
an(VARIABLE_NODE, "$var2")
)
)
);
Handle implicand =
al(EXECUTION_OUTPUT_LINK,
an(GROUNDED_SCHEMA_NODE, "scm:make-verb-prep"),
al(LIST_LINK,
an(VARIABLE_NODE, "$var1"),
an(VARIABLE_NODE, "$prep")
)
);
Handle hcapital;
// Create data on which the above pattern should match
al(EDGE_LINK,
an(PREDICATE_NODE, "_obj"),
al(LIST_LINK,
an(CONCEPT_NODE, "be"),
hcapital = an(CONCEPT_NODE, "capital")));
Handle hprep;
al(EDGE_LINK,
hprep = an(PREDICATE_NODE, "of"),
al(LIST_LINK,
an(CONCEPT_NODE, "capital"),
an(CONCEPT_NODE, "Spain")));
const char * str =
"(define (make-verb-prep verb prep) \n"
" (Edge \n"
" (Concept \n"
" (string-append (cog-name verb) \"_\" (cog-name prep))) \n"
" (List\n"
" (Concept (cog-name verb)) \n"
" (Predicate (cog-name prep)))))\n";
eval->eval(str);
// Result should be a ListLink
Handle result = imply(as, clauses, implicand);
// There should be only one solution: the Berlin one.
// The Madrid graph should be rejected because of the
// NOT definite link.
TSM_ASSERT_EQUALS("wrong number of solutions found", 1, getarity(result));
// Then, evaluationLink, as above.
Handle heval = getlink(result, 0);
Handle vcap_of = getlink(heval, 0);
Handle hlist = getlink(heval, 1);
Handle vcapital = getlink(hlist, 0);
Handle vprep = getlink(hlist, 1);
TSM_ASSERT_EQUALS("incorrect capital", hcapital, vcapital);
TSM_ASSERT_EQUALS("incorrect prep", hprep, vprep);
logger().debug("END TEST: %s", __FUNCTION__);
}
/*
* Test execution of variables.
* During pattern matching, variables can bind to ExecutionOutputLinks
* If they are then repeated in the implcand, the ExecutionOutputLink
* must be executed there, with the resulting answer returned.
*/
void ExecutionOutputUTest::test_varsub(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
const char * str = "(define (retarg x) x)\n";
eval->eval(str);
Handle resolution;
// Raw data
al(CONTEXT_LINK,
an(CONCEPT_NODE, "situation"),
al(EXECUTION_OUTPUT_LINK,
an(GROUNDED_SCHEMA_NODE, "scm: retarg"),
al(LIST_LINK,
resolution = an(CONCEPT_NODE, "resolution")
)
)
);
Handle deliverance;
al(CONTEXT_LINK,
an(CONCEPT_NODE, "predicament"),
al(EXECUTION_OUTPUT_LINK,
an(GROUNDED_SCHEMA_NODE, "scm: retarg"),
al(LIST_LINK,
deliverance = an(CONCEPT_NODE, "deliverance")
)
)
);
Handle situ =
al(QUERY_LINK,
al(CONTEXT_LINK,
an(CONCEPT_NODE, "situation"),
an(VARIABLE_NODE, "$action")
),
an(VARIABLE_NODE, "$action")
);
QueryLinkPtr situation(QueryLinkCast(situ));
Handle pred =
al(QUERY_LINK,
al(CONTEXT_LINK,
an(CONCEPT_NODE, "predicament"),
an(VARIABLE_NODE, "$action")
),
an(VARIABLE_NODE, "$action")
);
QueryLinkPtr predicament(QueryLinkCast(pred));
// Now perform the search.
QueueValuePtr qvp(createQueueValue());
ContainerValuePtr cvp(qvp);
qvp->close();
Implicator simpl(as.get(), cvp);
simpl.satisfy(situation);
// The result_list contains a list of the grounded expressions.
qvp->open();
// If it's empty, it will hang ...
TSM_ASSERT("no results returned", 0 != qvp->size());
Handle res = HandleCast(qvp->remove());
printf("res is %s\n", res->to_short_string().c_str());
TSM_ASSERT_EQUALS("incorrect resolution", res, resolution);
// Now perform the search.
qvp->close();
Implicator pimpl(as.get(), cvp);
pimpl.satisfy(predicament);
// The result_list contains a list of the grounded expressions.
qvp->open();
Handle del = HandleCast(qvp->remove());
printf("del is %s\n", del->to_short_string().c_str());
TSM_ASSERT_EQUALS("incorrect delivarance", del, deliverance);
logger().debug("END TEST: %s", __FUNCTION__);
}
/*
* Query ExecutionOutputLink atoms, with varying schemas.
*/
void ExecutionOutputUTest::test_query_exec(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/query/exec.scm\")");
Handle exec_query = eval->eval_h("exec-query");
Handle results = HandleCast(exec_query->execute(as.get()));
TS_ASSERT_EQUALS(results->get_arity(), 4);
exec_query = eval->eval_h("quote-exec-query");
results = HandleCast(exec_query->execute(as.get()));
TS_ASSERT_EQUALS(results->get_arity(), 3);
logger().debug("END TEST: %s", __FUNCTION__);
}
/**
* Execute clear box with single argument
*/
void ExecutionOutputUTest::test_clear_box_single_argument(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
Handle X = an(VARIABLE_NODE, "$X"),
PXX = al(PLUS_LINK, X, X),
twice = al(LAMBDA_LINK, PXX),
N21 = an(NUMBER_NODE, "21"),
twice21 = al(EXECUTION_OUTPUT_LINK, twice, N21);
Handle expected = an(NUMBER_NODE, "42");
Handle result = HandleCast(twice21->execute(as.get()));
result = as->add_atom(result);
TS_ASSERT_EQUALS(expected, result);
// should work with variable inside the ListLink too
Handle N21_LST = al(LIST_LINK, N21);
Handle twice21_lst = al(EXECUTION_OUTPUT_LINK, twice, N21_LST);
Handle result_lst = HandleCast(twice21_lst->execute(as.get()));
result_lst = as->add_atom(result_lst);
TS_ASSERT_EQUALS(expected, result_lst);
logger().debug("END TEST: %s", __FUNCTION__);
}
/**
* Execute clear box with single argument (a link different than list)
*/
void ExecutionOutputUTest::test_clear_box_single_argument_link(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
Handle AB =
al(INHERITANCE_LINK,
an(CONCEPT_NODE, "A"),
an(CONCEPT_NODE, "B"));
Handle identity =
al(LAMBDA_LINK,
an(VARIABLE_NODE, "$X"),
an(VARIABLE_NODE, "$X"));
Handle exec =
al(EXECUTION_OUTPUT_LINK, identity, AB);
Handle expected = AB;
Handle result = HandleCast(exec->execute(as.get()));
TS_ASSERT_EQUALS(expected, result);
logger().debug("END TEST: %s", __FUNCTION__);
}
void ExecutionOutputUTest::test_value(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/query/exec-gsn.scm\")");
ValuePtr result = eval->eval_v("(cog-execute! exec-query-handle)");
TS_ASSERT(nameserver().isA(result->get_type(), LINK_VALUE));
ValueSeq vals = LinkValueCast(result)->value();
TS_ASSERT_EQUALS(vals.size(), 3);
for (const ValuePtr& va : vals)
TS_ASSERT_EQUALS(va->get_type(), UNORDERED_LINK);
//------------
result = eval->eval_v("(cog-execute! exec-query-link-value)");
TS_ASSERT(nameserver().isA(result->get_type(), LINK_VALUE));
vals = LinkValueCast(result)->value();
TS_ASSERT_EQUALS(vals.size(), 3);
for (const ValuePtr& va : vals)
TS_ASSERT_EQUALS(va->get_type(), LINK_VALUE);
//------------
result = eval->eval_v("(cog-execute! exec-query-string-value)");
TS_ASSERT(nameserver().isA(result->get_type(), LINK_VALUE));
vals = LinkValueCast(result)->value();
TS_ASSERT_EQUALS(vals.size(), 3);
for (const ValuePtr& va : vals)
TS_ASSERT_EQUALS(va->get_type(), STRING_VALUE);
//------------
// The ret-float-value function returns identical FloatValue(0.6, 0.3)
// for all matches. With proper content-based comparison, these should
// deduplicate to 1 unique result (even though there were 3 matches).
// This is expected behavior - see commit 658c9688a "One more bug-fix
// for grouping" which explicitly documents result collapsing.
result = eval->eval_v("(cog-execute! exec-query-float-value)");
TS_ASSERT(nameserver().isA(result->get_type(), LINK_VALUE));
vals = LinkValueCast(result)->value();
TS_ASSERT_EQUALS(vals.size(), 1); // 3 matches collapse to 1 unique result
for (const ValuePtr& va : vals)
{
TS_ASSERT_EQUALS(va->get_type(), FLOAT_VALUE);
FloatValuePtr fvp = FloatValueCast(va);
const std::vector<double>& fvals = fvp->value();
TS_ASSERT_EQUALS(fvals.size(), 2);
TS_ASSERT_DELTA(fvals[0], 0.6, 1.0e-8);
TS_ASSERT_DELTA(fvals[1], 0.3, 1.0e-8);
}
logger().debug("END TEST: %s", __FUNCTION__);
}
/**
* Execute defined function
*/
void ExecutionOutputUTest::test_function(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
al(DEFINE_LINK,
an(DEFINED_PROCEDURE_NODE, "remainder"),
al(LAMBDA_LINK,
an(VARIABLE_NODE, "X"),
al(MINUS_LINK,
an(VARIABLE_NODE, "X"),
al(FLOOR_LINK,
an(VARIABLE_NODE, "X")))));
Handle exout =
al(EXECUTION_OUTPUT_LINK, {
an(DEFINED_PROCEDURE_NODE, "remainder"),
an(NUMBER_NODE, "2.1 4.2 5.3 2.4 6.5 7.6 0.7 -0.2 -8.1")});
ValuePtr result = exout->execute(as.get());
printf("Got: %s\n", result->to_string().c_str());
NumberNodePtr nnp = NumberNodeCast(result);
const std::vector<double>& val = nnp->value();
TS_ASSERT_EQUALS(val.size(), 9);
// The atoms won't be the same, due to rounding errors.
for (int i=0; i<9; i++)
{
TS_ASSERT(fabs(val[i]-0.1*(i+1))< 1.0e-9);
}
logger().debug("END TEST: %s", __FUNCTION__);
}
// This tests recursion of ExecutionOutputLinks, on a classic algo:
// the factorial algo. This test does not actually test the pattern
// matcher, and so does not really belong in this directory...
void ExecutionOutputUTest::test_recursion(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/query/exec-factorial.scm\")");
Handle result = eval->eval_h(
"(cog-execute! (ExecutionOutput"
" (DefinedSchema \"factorial\") (Number 5)))");
TS_ASSERT_EQUALS(result->get_type(), NUMBER_NODE);
double ans = NumberNodeCast(result)->get_value();
TS_ASSERT_DELTA(ans, 120.0, 1.0e-6);
logger().debug("END TEST: %s", __FUNCTION__);
}
// This tests reconstructing a lambda link with a matched variable
// declaration and body
void ExecutionOutputUTest::test_lambda(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/query/exec-lambda.scm\")");
Handle bl = eval->eval_h("bl");
ValuePtr result = bl->execute(as.get());
Handle lamb = al(LAMBDA_LINK, {
an(VARIABLE_NODE, "$X"),
al(EDGE_LINK, {
an(PREDICATE_NODE, "P"),
an(CONCEPT_NODE, "A")})});
ValuePtr expect = createLinkValue(ValueSeq({lamb}));
logger().debug() << "result = " << oc_to_string(result);
logger().debug() << "expect = " << oc_to_string(expect);
TS_ASSERT(*expect == *result);
logger().debug("END TEST: %s", __FUNCTION__);
}
// This tests one of the demo examples that users are told to run.
void ExecutionOutputUTest::test_lambda_get(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);
eval->eval("(load-from-path \"tests/query/exec-lambda-get.scm\")");
Handle exo = eval->eval_h("exo");
Handle result = as->add_atom(HandleCast(exo->execute(as.get())));
Handle expect = al(SET_LINK, {an(NUMBER_NODE, "0.6")});
logger().debug() << "result = " << oc_to_string(result);
logger().debug() << "expect = " << oc_to_string(expect);
TS_ASSERT_EQUALS(expect, result);
logger().debug("END TEST: %s", __FUNCTION__);
}
void ExecutionOutputUTest::test_variable_schema(void)
{
al(DEFINE_LINK, {
an(DEFINED_SCHEMA_NODE, "sum"),
al(LAMBDA_LINK, {
al(VARIABLE_LIST, {
an(VARIABLE_NODE, "x"),
an(VARIABLE_NODE, "y")}),
al(PLUS_LINK, {
an(VARIABLE_NODE, "x"),
an(VARIABLE_NODE, "y")})
})});
al(DEFINE_LINK, {
an(DEFINED_SCHEMA_NODE, "mul"),
al(LAMBDA_LINK, {
al(VARIABLE_LIST, {
an(VARIABLE_NODE, "x"),
an(VARIABLE_NODE, "y")}),
al(TIMES_LINK, {
an(VARIABLE_NODE, "x"),
an(VARIABLE_NODE, "y")})
})});
al(INHERITANCE_LINK, {
an(DEFINED_SCHEMA_NODE, "mul"),
an(CONCEPT_NODE, "current-schema")
});
Handle get_link =
al(COLLECTION_OF_LINK, {
al(QUERY_LINK, {
al(TYPED_VARIABLE_LINK, {
an(VARIABLE_NODE, "$SCHEMA"), an(TYPE_NODE, "DefinedSchemaNode")}),
al(AND_LINK, {
al(INHERITANCE_LINK, {
an(VARIABLE_NODE, "$SCHEMA"),
an(CONCEPT_NODE, "current-schema")
})
}),
al(EXECUTION_OUTPUT_LINK, {
an(VARIABLE_NODE, "$SCHEMA"),
al(LIST_LINK, {an(NUMBER_NODE, "6"), an(NUMBER_NODE, "7")})
})
})});
ValuePtr result = get_link->execute(as.get());
TS_ASSERT_EQUALS(result, al(SET_LINK, {an(NUMBER_NODE, "42")}));
}
void ExecutionOutputUTest::test_exec_variable(void)
{
al(DEFINE_LINK, {
an(DEFINED_SCHEMA_NODE, "x*y"),
al(LAMBDA_LINK, {
al(VARIABLE_LIST, {
an(VARIABLE_NODE, "x"),
an(VARIABLE_NODE, "y")}),
al(TIMES_LINK, {
an(VARIABLE_NODE, "x"),
an(VARIABLE_NODE, "y")})
})});
Handle bind_link =
al(COLLECTION_OF_LINK, {
al(QUERY_LINK, {
al(TYPED_VARIABLE_LINK, {
an(VARIABLE_NODE, "x"), an(TYPE_NODE, "DefinedSchemaNode")}),
al(AND_LINK, {al(PRESENT_LINK, {an(VARIABLE_NODE, "x")})}),
al(EXECUTION_OUTPUT_LINK, {
an(VARIABLE_NODE, "x"),
al(LIST_LINK, {an(NUMBER_NODE, "6"), an(NUMBER_NODE, "7")})
})
})});
ValuePtr result = bind_link->execute(as.get());
TS_ASSERT_EQUALS(result, al(SET_LINK, {an(NUMBER_NODE, "42")}));
}