OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / tests / atoms / value / StreamUTest.cxxtest

499 lines · cxxtest ref:
/*
 * tests/atoms/value/StreamUTest.cxxtest
 *
 * Copyright (C) 2018 Linas Vepstas
 * All Rights Reserved
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <opencog/atoms/core/FunctionLink.h>
#include <opencog/atoms/core/NumberNode.h>
#include <opencog/guile/SchemeEval.h>
#include <opencog/atoms/value/RandomStream.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/util/Logger.h>

#include <cxxtest/TestSuite.h>

using namespace opencog;

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

class StreamUTest : public CxxTest::TestSuite
{
private:
	AtomSpacePtr _asp;
	ValuePtr value;
	Handle key;
	Handle atom;

public:
	StreamUTest(void);

	void setUp(void);

	void check(void);
	double loop(const Handle&);

	void test_basic();
	void test_times();
	void test_plus();
	void test_times_plus();
	void test_minus();
	void test_divide();
	void test_number();
	void test_equals();

	void test_chaining();

	void test_guile();
};

StreamUTest::StreamUTest(void)
	: _asp(createAtomSpace())
{
	logger().set_level(Logger::DEBUG);
	logger().set_print_to_stdout_flag(true);
	logger().set_sync_flag(true);
	logger().set_timestamp_flag(false);
}

// This unit test is a stand-in test for the concept of a streaming API
// on Values. For lack of anything better to test, the RandomStream is
// tested -- it just generated random numbers, and we just make sure
// these average out to expected values. Its good enough, for now.
#define LEN 10
#define LOOPCNT 400

void StreamUTest::setUp(void)
{
	value = createRandomStream(LEN);
	key = an(PREDICATE_NODE, "my key");
	atom = an(CONCEPT_NODE, "some atom");
	atom->setValue(key, value);
}

// ====================================================================
// Run the value, repeatedly.
double StreamUTest::loop(const Handle& stream)
{
	FunctionLinkPtr flp = FunctionLinkCast(stream);

	double sum = 0.0;
	for (int i=0; i<LOOPCNT; i++)
	{
		ValuePtr pap = flp->execute();
		const std::vector<double>& rv = FloatValueCast(pap)->value();
		TS_ASSERT_EQUALS(rv.size(), LEN);

		for (int j=0; j<LEN; j++)
		{
			sum += rv[j];
		}
	}

	sum /= LOOPCNT * LEN;
	return sum;
}

// ====================================================================
// Make sure that streaming works.
void StreamUTest::test_basic()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

#define LEN 10
	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);
	double sum = loop(valof);

	printf("Expect 0.485 < %g < 0.515\n", sum);
	TS_ASSERT_LESS_THAN(0.485, sum);
	TS_ASSERT_LESS_THAN(sum, 0.515);

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

// ====================================================================
// Make sure TimesLink works
void StreamUTest::test_times()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);

	// unit scalar times vector --------------------------------------
	Handle times = al(TIMES_LINK, an(NUMBER_NODE, "1"), valof);

	double sum = loop(times);
	printf("Expect 0.45 < %g < 0.55\n", sum);
	TS_ASSERT_LESS_THAN(0.45, sum);
	TS_ASSERT_LESS_THAN(sum, 0.55);

	// scalar times vector --------------------------------------------
	times = al(TIMES_LINK, an(NUMBER_NODE, "3"), valof);

	sum = loop(times);
	printf("Expect 1.45 < %g < 1.55\n", sum);
	TS_ASSERT_LESS_THAN(1.45, sum);
	TS_ASSERT_LESS_THAN(sum, 1.55);

	// vector times scalar --------------------------------------------
	times = al(TIMES_LINK, valof, an(NUMBER_NODE, "7"));

	sum = loop(times);
	printf("Expect 3.40 < %g < 3.6\n", sum);
	TS_ASSERT_LESS_THAN(3.4, sum);
	TS_ASSERT_LESS_THAN(sum, 3.6);

	// vector times vector ----------------------------------------------
	times = al(TIMES_LINK, valof, valof);

	sum = loop(times);
	// Square of (Integral of x dx from 0 to 1) = 1/4
	// which is not the same as
	// Integral of x^2 dx from 0 to 1 = 1/3
	// Each `valof` is evaluated independently of the other.
	printf("Expect 0.25 so 0.245 < %g < 0.255\n", sum);
	TS_ASSERT_LESS_THAN(0.245, sum);
	TS_ASSERT_LESS_THAN(sum, 0.255);

	// vector times vector ----------------------------------------------
	times = al(TIMES_LINK, valof, valof, valof);

	sum = loop(times);
	// (Integral of x dx from 0 to y1)^3 = 1/8
	printf("Expect 0.125 so 0.122 < %g < 0.128\n", sum);
	TS_ASSERT_LESS_THAN(0.122, sum);
	TS_ASSERT_LESS_THAN(sum, 0.128);

	// vector times vector ----------------------------------------------
	times = al(TIMES_LINK, valof, valof, valof, valof);

	sum = loop(times);
	// (Integral of x dx from 0 to y1)^4 = 1/16
	printf("Expect 0.0625 so 0.0615 < %g < 0.0635\n", sum);
	TS_ASSERT_LESS_THAN(0.0615, sum);
	TS_ASSERT_LESS_THAN(sum, 0.0635);

	// vector times vector ----------------------------------------------
	times = al(TIMES_LINK, valof, valof, valof, valof, valof);

	sum = loop(times);
	// (Integral of x dx from 0 to y1)^5 = 1/32
	printf("Expect 0.03125 so 0.0300 < %g < 0.0325\n", sum);
	TS_ASSERT_LESS_THAN(0.0300, sum);
	TS_ASSERT_LESS_THAN(sum, 0.0325);

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

// ====================================================================
// Make sure PlusLink works
void StreamUTest::test_plus()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);

	// zero scalar plus vector --------------------------------------
	Handle plus = al(PLUS_LINK, an(NUMBER_NODE, "0"), valof);

	double sum = loop(plus);
	printf("Expect 0.49 < %g < 0.51\n", sum);
	TS_ASSERT_LESS_THAN(0.49, sum);
	TS_ASSERT_LESS_THAN(sum, 0.51);

	// vector plus vector --------------------------------------------
	std::vector<double> vthree;
	for (int i=0; i<LEN; i++) vthree.push_back(3.0);
	Handle three(createNumberNode(vthree));
	plus = al(PLUS_LINK, three, valof);

	sum = loop(plus);
	printf("Expect 3.49 < %g < 3.51\n", sum);
	TS_ASSERT_LESS_THAN(3.49, sum);
	TS_ASSERT_LESS_THAN(sum, 3.51);

	// vector plus scalar --------------------------------------------
	std::vector<double> vseven;
	for (int i=0; i<LEN; i++) vseven.push_back(7.0);
	Handle seven(createNumberNode(vseven));
	plus = al(PLUS_LINK, valof, seven);

	sum = loop(plus);
	printf("Expect 7.48 < %g < 7.52\n", sum);
	TS_ASSERT_LESS_THAN(7.48, sum);
	TS_ASSERT_LESS_THAN(sum, 7.52);

	// vector plus vector ----------------------------------------------
	plus = al(PLUS_LINK, valof, valof);

	sum = loop(plus);
	printf("Expect 0.96 < %g < 1.04\n", sum);
	TS_ASSERT_LESS_THAN(0.96, sum);
	TS_ASSERT_LESS_THAN(sum, 1.04);

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

// ====================================================================
// Make sure complex expressions work.
void StreamUTest::test_times_plus()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);

	// 5+3x --------------------------------------------
	std::vector<double> vfive;
	for (int i=0; i<LEN; i++) vfive.push_back(5.0);
	Handle five(createNumberNode(vfive));
	Handle times = al(PLUS_LINK, five,
		al(TIMES_LINK, an(NUMBER_NODE, "3"), valof));

	double sum = loop(times);
	printf("Expect 6.45 < %g < 6.55\n", sum);
	TS_ASSERT_LESS_THAN(6.45, sum);
	TS_ASSERT_LESS_THAN(sum, 6.55);

	// 5(3+x) --------------------------------------------
	std::vector<double> vthree;
	for (int i=0; i<LEN; i++) vthree.push_back(3.0);
	Handle three(createNumberNode(vthree));
	times = al(TIMES_LINK, an(NUMBER_NODE, "5"),
		al(PLUS_LINK, three, valof));

	sum = loop(times);
	printf("Expect 17.4 < %g < 17.6\n", sum);
	TS_ASSERT_LESS_THAN(17.4, sum);
	TS_ASSERT_LESS_THAN(sum, 17.6);

	// (x+3)5 --------------------------------------------
	times = al(TIMES_LINK,
		al(PLUS_LINK, valof, three),
		an(NUMBER_NODE, "5"));

	sum = loop(times);
	printf("Expect 17.4 < %g < 17.6\n", sum);
	TS_ASSERT_LESS_THAN(17.4, sum);
	TS_ASSERT_LESS_THAN(sum, 17.6);

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

// ====================================================================
// Make sure MinusLink works
void StreamUTest::test_minus()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);

	// 3-x --------------------------------------------
	std::vector<double> vthree;
	for (int i=0; i<LEN; i++) vthree.push_back(3.0);
	Handle three(createNumberNode(vthree));
	Handle minus = al(MINUS_LINK, three, valof);

	double sum = loop(minus);
	printf("Expect 2.45 < %g < 2.55\n", sum);
	TS_ASSERT_LESS_THAN(2.45, sum);
	TS_ASSERT_LESS_THAN(sum, 2.55);

	// x-7 --------------------------------------------
	std::vector<double> vseven;
	for (int i=0; i<LEN; i++) vseven.push_back(7.0);
	Handle seven(createNumberNode(vseven));
	minus = al(MINUS_LINK, valof, seven);

	sum = loop(minus);
	printf("Expect -6.7 < %g < -6.3\n", sum);
	TS_ASSERT_LESS_THAN(-6.7, sum);
	TS_ASSERT_LESS_THAN(sum, -6.3);

	// x-0 --------------------------------------------
	minus = al(MINUS_LINK, valof, an(NUMBER_NODE, "0"));

	sum = loop(minus);
	printf("Expect 0.45 < %g < 0.55\n", sum);
	TS_ASSERT_LESS_THAN(0.45, sum);
	TS_ASSERT_LESS_THAN(sum, 0.55);

	// vector minus vector ------------------------------------
	minus = al(MINUS_LINK, valof, valof);

	sum = loop(minus);
	printf("Expect -0.2 < %g < 0.2\n", sum);
	TS_ASSERT_LESS_THAN(-0.2, sum);
	TS_ASSERT_LESS_THAN(sum, 0.2);

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

// ====================================================================
// Make sure DivideLink works
void StreamUTest::test_divide()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);

	// x/4 --------------------------------------------
	Handle divide = al(DIVIDE_LINK, valof, an(NUMBER_NODE, "4"));

	double sum = loop(divide);
	printf("Expect 0.122 < %g < 0.128\n", sum);
	TS_ASSERT_LESS_THAN(0.122, sum);
	TS_ASSERT_LESS_THAN(sum, 0.128);

	// x/1 --------------------------------------------
	divide = al(DIVIDE_LINK, valof, an(NUMBER_NODE, "1"));

	sum = loop(divide);
	printf("Expect 0.45 < %g < 0.55\n", sum);
	TS_ASSERT_LESS_THAN(0.45, sum);
	TS_ASSERT_LESS_THAN(sum, 0.55);

	// 3/(x+1) --------------------------------------------
	std::vector<double> vone;
	for (int i=0; i<LEN; i++) vone.push_back(1.0);
	Handle one(createNumberNode(vone));
	divide = al(DIVIDE_LINK, an(NUMBER_NODE, "3"),
		al(PLUS_LINK, valof, one));

	sum = loop(divide);
	printf("Expect 1.85 < %g < 2.15\n", sum);
	TS_ASSERT_LESS_THAN(1.85, sum);
	TS_ASSERT_LESS_THAN(sum, 2.15);

	// vector divide vector ----------------------------------------------
	std::vector<double> vseven;
	for (int i=0; i<LEN; i++) vseven.push_back(7.0);
	Handle seven(createNumberNode(vseven));
	divide = al(DIVIDE_LINK,
		al(PLUS_LINK, valof, seven),
		al(PLUS_LINK, valof, seven));

	sum = loop(divide);
	printf("Expect 0.99 < %g < 1.01\n", sum);
	TS_ASSERT_LESS_THAN(0.99, sum);
	TS_ASSERT_LESS_THAN(sum, 1.01);

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

// ====================================================================
// Make sure RandomNumberLink works
void StreamUTest::test_number()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	value = al(RANDOM_NUMBER_LINK, an(NUMBER_NODE, "0"), an(NUMBER_NODE, "1"));
	atom->setValue(key, value);

	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);

	// x/4 --------------------------------------------
	Handle divide = al(DIVIDE_LINK, valof, an(NUMBER_NODE, "4"));

	FunctionLinkPtr flp = FunctionLinkCast(divide);

	double sum = 0.0;
	for (int i=0; i<LOOPCNT; i++)
	{
		ValuePtr pap = flp->execute();
		NumberNodePtr nn = NumberNodeCast(pap);
		sum += nn->get_value();
	}

	sum /= LOOPCNT;
	printf("Expect 0 < %g < 0.25\n", sum);
	TS_ASSERT_LESS_THAN(0, sum);
	TS_ASSERT_LESS_THAN(sum, 0.25);

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

// ====================================================================
// Test FloatValue::operator==()
void StreamUTest::test_equals()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);
	FloatValuePtr a = createFloatValue(1.0);
	FloatValuePtr b = createFloatValue(2.0);
	
	TS_ASSERT(*a != *b);
	
	logger().debug("END TEST: %s", __FUNCTION__);
}

// ====================================================================
// ====================================================================
// ====================================================================
// And now for something completely different....
// Chain together streams.
void StreamUTest::test_chaining()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	Handle valof = al(FLOAT_VALUE_OF_LINK, atom, key);

	Handle coinflip = al(GREATER_THAN_LINK, an(NUMBER_NODE, "0.5"), valof);
	Handle flipkey = an(PREDICATE_NODE, "*-binary-flip-*");
	atom->setValue(flipkey, coinflip);

	Handle chainof = al(BOOL_VALUE_OF_LINK, atom, flipkey);

	double sum = 0.0;
	for (int i=0; i<LOOPCNT*LEN; i++)
	{
		bool result = chainof->bevaluate(_asp.get());
		if (result) sum += 1.0;
	}

	sum /= LOOPCNT * LEN;

	printf("Expect 0.48 < %g < 0.52\n", sum);
	TS_ASSERT_LESS_THAN(0.48, sum);
	TS_ASSERT_LESS_THAN(sum, 0.52);

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

// ====================================================================
// Make sure the scheme bindings work.
void StreamUTest::test_guile()
{
	logger().debug("BEGIN TEST: %s", __FUNCTION__);

	SchemeEval* eval = SchemeEval::get_scheme_evaluator(_asp);
	eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")");
	eval->eval("(load-from-path \"tests/atoms/value/stream.scm\")");

	FloatValuePtr voex = createFloatValue(std::vector<double>{1.0, 2.0, 3.0});
	ValuePtr vo = eval->eval_v("(cog-execute! vo)");
	printf("     got: %s", vo->to_string().c_str());
	printf("expected: %s\n", voex->to_string().c_str());
	TS_ASSERT(*voex == *FloatValueCast(vo));

	FloatValuePtr tymex = createFloatValue(std::vector<double>{14.0, 16.0, 18.0});
	ValuePtr tym = eval->eval_v("(cog-execute! tym)");
	printf("     got: %s", tym->to_string().c_str());
	printf("expected: %s\n", tymex->to_string().c_str());
	TS_ASSERT(*tymex == *FloatValueCast(tym));

	ValuePtr tymb = eval->eval_v("(cog-execute! tymb)");
	printf("     got: %s", tymb->to_string().c_str());
	FloatValuePtr ftymb = FloatValueCast(tymb);
	const std::vector<double>& vals = ftymb->value();

	for (int i=0; i< 24; i++)
	{
		TS_ASSERT_LESS_THAN(11.99999, vals[i]);
		TS_ASSERT_LESS_THAN(vals[i], 14.000001);
	}

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