OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / tests / atomspace / AtomSpaceUTest.cxxtest

682 lines · cxxtest ref:
/*
 * tests/atomspace/AtomSpaceUTest.cxxtest
 *
 * Copyright (C) 2002-2007 Novamente LLC
 * Copyright (C) 2008-2010 OpenCog Foundation
 * All Rights Reserved
 *
 * Written by Welter Silva <welter@vettalabs.com>
 *            Ricardo Bittencourt <ricbit@700km.com.br>
 *
 * 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 <algorithm>

#include <math.h>
#include <string.h>

#include <opencog/atoms/atom_types/types.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/atoms/base/Handle.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atoms/base/Link.h>
#include <opencog/atoms/value/FloatValue.h>
#include <opencog/util/Logger.h>
#include <opencog/util/platform.h>
#include <opencog/util/oc_assert.h>

#include <cxxtest/TestSuite.h>

using namespace opencog;
using namespace std;

#define FLOAT_ACCEPTABLE_ERROR 0.000001

#define NUM_NODES 6
#define NUM_FORCEUSER_LINKS 4
#define NUM_HUMAN_LINKS 4
#define NUM_VHS 4
#define NUM_VH_NODES 3

Handle addRealAtom(AtomSpace& as, Handle atom,
                   ValuePtr tvn = ValuePtr())
{
    ValuePtr newTV = tvn ? tvn: FloatValueCast(atom->getValue(truth_key()));
    // Check if the given Atom reference is of an atom
    // that was not inserted yet.  If so, adds the atom. Otherwise, just sets
    // result to the correct/valid handle.

    Handle result;
    NodePtr node(NodeCast(atom));
    if (node) {
        result = as.xget_handle(node->get_type(), node->get_name());
        if (result == Handle::UNDEFINED) {
            result = as.add_node(node->get_type(),
                                 std::move(std::string(node->get_name())));
            result->setValue(truth_key(), newTV);
            return result;
        }
    } else {
        LinkPtr link(LinkCast(atom));
        result = as.xget_handle(link->get_type(), link->getOutgoingSet());
        if (result == Handle::UNDEFINED) {
            result = as.add_link(link->get_type(),
                                 std::move(HandleSeq(link->getOutgoingSet())));
            result->setValue(truth_key(), newTV);
            return result;
        }
    }
    result->setValue(truth_key(), newTV);
    return result;
}

HandleSeq createSimpleGraph(AtomSpace* atomSpace, const char* baseName)
{
    char buf[256];
    memset(buf, 0, 256);
    int baseNameLength = strlen(baseName);
    strcpy(buf, baseName);

    ValuePtr tv1 = createFloatValue(std::vector<double>({0.001, 0.00001}));
    ValuePtr tv2 = createFloatValue(std::vector<double>({0.001, 0.00001}));
    ValuePtr tv3 = createFloatValue(std::vector<double>({0.5, 0.99}));
    buf[baseNameLength] = '1';
    Handle h1 = atomSpace->add_node(CONCEPT_NODE, buf);
    h1->setValue(truth_key(), tv1);
    buf[baseNameLength] = '2';
    Handle h2 = atomSpace->add_node(CONCEPT_NODE, buf);
    h2->setValue(truth_key(), tv2);
    buf[baseNameLength] = '3';
    Handle h3 = atomSpace->add_node(CONCEPT_NODE, buf);
    h3->setValue(truth_key(), tv3);

    HandleSeq outgoing1;
    outgoing1.push_back(h2);
    outgoing1.push_back(h3);
    Handle l1 = atomSpace->add_link(LIST_LINK, std::move(outgoing1));
    l1->setValue(truth_key(), tv1);
    HandleSeq outgoing2;
    outgoing2.push_back(h1);
    outgoing2.push_back(l1);
    Handle l2 = atomSpace->add_link(EVALUATION_LINK, std::move(outgoing2));
    l2->setValue(truth_key(), tv2);

    HandleSeq testAtoms;
    testAtoms.push_back(h1);
    testAtoms.push_back(h2);
    testAtoms.push_back(h3);
    testAtoms.push_back(l1);
    testAtoms.push_back(l2);

    return testAtoms;
}

class HandlePredicate {
public:
    inline bool operator()(const Handle& h) const { return this->test(h); }
    virtual bool test(const Handle&) const = 0;
};

class AtomComparator {
public:
    inline bool operator()(const Handle& a, const Handle& b) const
        { return this->test(a,b); }
    virtual bool test(const Handle&, const Handle&) const = 0;
};

/**
 * Filter handles from a sequence according to the given criterion.
 *
 * @param begin iterator for the sequence
 * @param end iterator for the sequence
 * @param struct or function embodying the criterion
 * @return The handles in the sequence that match the criterion.
 * @deprecated do not use in new code!
 */
template<typename InputIterator>
HandleSeq filter(InputIterator begin, InputIterator end, HandlePredicate& compare)
{
    HandleSeq result;
    std::copy_if(begin, end, back_inserter(result),
        [&](Handle h)->bool { return compare(h); } );
    return result;
}

/**
 * Gets a set of handles that matches with the given type
 * (subclasses optionally) and a given criterion.
 *
 * @param result An output iterator.
 * @param type The desired type.
 * @param subclass Whether type subclasses should be considered.
 * @param compare A criterion for including atoms. It must be something
 * that returns a bool when called.
 *
 * @return The set of atoms of a given type (subclasses optionally).
 *
 * @note The matched entries are appended to a container whose
 * OutputIterator is passed as the first argument.  Example of call to this
 * method, which would return all entries in AtomSpace beyond 500 LTI:
 * @code
 *         std::list<Handle> ret;
 *         atomSpace.getHandleSet(back_inserter(ret), ATOM, true, LTIAboveThreshold(500));
 * @endcode
 */
template <typename OutputIterator> OutputIterator
getHandleSetFiltered(AtomSpace* as, OutputIterator result,
             Type type,
             bool subclass,
             HandlePredicate& compare)
{
    HandleSeq hs;
    as->get_handles_by_type(hs, type, subclass);

    return std::copy_if(hs.begin(), hs.end(), result,
        [&](Handle h)->bool { return compare(h); } );
}

// Wrapper for comparing atoms from a HandleSeq
template <typename Compare>
struct compareAtom
{
    Compare* c;
    compareAtom(Compare* _c) : c(_c) {}

    bool operator()(Handle h1, Handle h2) {
        return (*c)(h1, h2);
    }
};


/**
 * Gets a set of handles that matches with the given type
 * (subclasses optionally), sorted according to the given comparison
 * structure.
 *
 * @param result An output iterator.
 * @param type The desired type.
 * @param subclass Whether type subclasses should be considered.
 * @param compare The comparison struct to use in the sort.
 *
 * @return The set of atoms of a given type (subclasses optionally).
 *
 * @note The matched entries are appended to a container whose
 * OutputIterator is passed as the first argument.
 */
template <typename OutputIterator, typename Compare> OutputIterator
getSortedHandleSet(AtomSpace* as,
             OutputIterator result,
             Type type,
             bool subclass,
             Compare compare)
{
    // get the handle set as a vector and sort it.
    HandleSeq hs;

    as->get_handles_by_type(hs, type, subclass);
    std::sort(hs.begin(), hs.end(), compareAtom<AtomComparator>(compare));
    return std::copy(hs.begin(), hs.end(), result);
}

template <typename OutputIterator> OutputIterator
get_handles_by_name(AtomSpace* as,
                    OutputIterator result,
                    const std::string& name,
                    Type type = NODE,
                    bool subclass = true)
{
    if (name.c_str()[0] == 0) {
        HandleSeq hset;
        as->get_handles_by_type(hset, type, subclass);
        return std::copy(hset.begin(), hset.end(), result);
    }

    if (false == subclass) {
        Handle h(as->xget_handle(type, name));
        if (h) *(result++) = h;
        return result;
    }
    nameserver().foreachRecursive(
        [&](Type t)->void {
            // Avoid illegal gets.
            if (nameserver().isA(t, NUMBER_NODE)) return;
            if (nameserver().isA(t, TYPE_NODE)) return;
            Handle h(as->xget_handle(t, name));
            if (h) *(result++) = h; }, type);

    return result;
}

class AtomSpaceUTest :  public CxxTest::TestSuite
{
private:

    AtomSpacePtr asp;
    AtomSpace* atomSpace;

public:
    AtomSpaceUTest() {
        logger().set_level(Logger::DEBUG);
        // logger().set_sync_flag(true);
        logger().set_print_to_stdout_flag(true);
    }

    void setUp() {
        asp = createAtomSpace();
        atomSpace = asp.get();
    }

    void tearDown() {
    }

    struct mean : public HandlePredicate {
        virtual bool test(const Handle& h) const {
            return FloatValueCast(h->getValue(truth_key()))->value()[0] > 0.8;
        }
    };

    struct everything : public HandlePredicate {
        virtual bool test(const Handle& h) const {
            return true;
        }
    };

    void testMiscellaneous()
    {
        logger().info("Begin testMiscellaneous()");

        const char* nodeNames[NUM_NODES] = {
            "Vader",
            "Luke",
            "Chewbacca",
            "R2D2",
            "Force",
            "Human"
        };

        Handle h[NUM_NODES];
        for (int i = 0; i < NUM_NODES; i++) {
            h[i] = atomSpace->add_node(CONCEPT_NODE, nodeNames[i]);
            h[i]->setValue(truth_key(), createFloatValue(std::vector<double>({0.001, 0.99})));
        }
        logger().debug("Nodes created");

        float ForceUser[NUM_FORCEUSER_LINKS] = {0.99, 0.99, 0.0, 0.5};
        Handle FU[NUM_FORCEUSER_LINKS];
        for (int i = 0; i < NUM_FORCEUSER_LINKS; i++) {
            HandleSeq temp(2);
            temp[0] = h[i];
            temp[1] = h[4];
            FU[i] = atomSpace->add_link(SUBSET_LINK, std::move(temp));
            FU[i]->setValue(truth_key(), createFloatValue(std::vector<double>({ForceUser[i], 0.99})));
        }
        logger().debug("ForceUser links created");

        float Human[4] = {0.99, 0.99, 0.5, 0.0f};
        HandleSeq out[4];
        Handle H[4];
        for (int i = 0; i < 4; i++) {
            out[i].push_back(h[i]);
            out[i].push_back(h[5]);
            H[i] = atomSpace->add_link(INHERITANCE_LINK,
                                       std::move(HandleSeq(out[i])));
            H[i]->setValue(truth_key(), createFloatValue(std::vector<double>({Human[i], 0.99})));
        }
        logger().debug("Human links created");

        // Check TVS
        for (int i = 0; i < NUM_NODES; i++) {
            FloatValuePtr tv = FloatValueCast(h[i]->getValue(truth_key()));
            logger().debug("h: tv mean = %f, atomSpace->getMean(h[i]) = %f",
                 tv->value()[0], FloatValueCast(h[i]->getValue(truth_key()))->value()[0]);
            TS_ASSERT(fabs(tv->value()[0] - 0.001) < FLOAT_ACCEPTABLE_ERROR);
            logger().debug("h: confidence = %f, diff = %f, error = %f", tv->value()[1], fabs(tv->value()[1] - 0.99), FLOAT_ACCEPTABLE_ERROR);
            TS_ASSERT(fabs(tv->value()[1] - 0.99) < FLOAT_ACCEPTABLE_ERROR);
            if (i < NUM_FORCEUSER_LINKS) {
                FloatValuePtr tv = FloatValueCast(FU[i]->getValue(truth_key()));
                logger().debug("h: tv mean = %f, atomSpace->getMean(h[i]) = %f",
                  tv->value()[0], FloatValueCast(h[i]->getValue(truth_key()))->value()[0]);
                logger().debug("FU: tv mean = %f, atomSpace->getMean(FU[i]) = %f\n",
                 tv->value()[0], FloatValueCast(FU[i]->getValue(truth_key()))->value()[0]);
                TS_ASSERT(fabs(tv->value()[0] - ForceUser[i]) < FLOAT_ACCEPTABLE_ERROR);
                logger().debug("FU: confidence = %f, diff = %f, error = %f", tv->value()[1], fabs(tv->value()[1] - 0.99), FLOAT_ACCEPTABLE_ERROR);
                TS_ASSERT(fabs(tv->value()[1] - 0.99) < FLOAT_ACCEPTABLE_ERROR);
            }
            if (i < NUM_HUMAN_LINKS) {
                FloatValuePtr tv = FloatValueCast(H[i]->getValue(truth_key()));
                logger().debug("h: tv mean = %f, atomSpace->getMean(h[i]) = %f",
                     tv->value()[0], FloatValueCast(h[i]->getValue(truth_key()))->value()[0]);
                TS_ASSERT(fabs(tv->value()[0] - Human[i]) < FLOAT_ACCEPTABLE_ERROR);
                logger().debug("H: confidence = %f, diff = %f, error = %f", tv->value()[1], fabs(tv->value()[1] - 0.99), FLOAT_ACCEPTABLE_ERROR);
                TS_ASSERT(fabs(tv->value()[1] - 0.99) < FLOAT_ACCEPTABLE_ERROR);
            }
        }
        logger().debug("TVS checked");

        TS_ASSERT(atomSpace->get_num_atoms_of_type(NODE, true) == NUM_NODES);
        TS_ASSERT(atomSpace->get_num_atoms_of_type(LINK, true) == 8);

        // Test filters
        for (int vhIdx = 0; vhIdx < NUM_VHS; vhIdx++) {

            mean m;
            HandleSeq high_mean;
            getHandleSetFiltered(atomSpace, back_inserter(high_mean), ATOM, true, m);
            logger().debug("Filter %d checked", vhIdx);

            //std::cout << "high mean size " << high_mean.size() << std::endl;
            //for (int jj = 0; jj < high_mean.size(); jj++) {
            //    std::cout << atomSpace->atomAsString(high_mean[jj],false) << std::endl;
            //}

            HandleSeq nodes;
            atomSpace->get_handles_by_type(nodes, NODE, true);
            logger().debug("Get nodes by type %d checked", vhIdx);

            HandleSeq links;
            atomSpace->get_handles_by_type(links, LINK, true);
            logger().debug("Get links by type %d checked", vhIdx);

            HandleSeq all;
            everything e;
            getHandleSetFiltered(atomSpace, back_inserter(all), ATOM, true, e);

            TS_ASSERT(high_mean.size() == 4);
            TS_ASSERT(nodes.size() == NUM_NODES);
            TS_ASSERT(links.size() == 8);
            TS_ASSERT(all.size() == 14);

            if (vhIdx == 0) {
                TS_ASSERT(find(high_mean.begin(), high_mean.end(), FU[0]) != high_mean.end());
                TS_ASSERT(find(high_mean.begin(), high_mean.end(), H[0]) != high_mean.end());
            }
            if (vhIdx == 0 || vhIdx == 1) {
                TS_ASSERT(find(high_mean.begin(), high_mean.end(), FU[1]) != high_mean.end());
                TS_ASSERT(find(high_mean.begin(), high_mean.end(), H[1]) != high_mean.end());
            }

            for (int i = 0; i < NUM_FORCEUSER_LINKS; i++) {
                if (vhIdx == 0 || (i % 4 == vhIdx)) {
                    TS_ASSERT (find(links.begin(), links.end(), FU[i]) != links.end());
                    TS_ASSERT (find(all.begin(), all.end(), FU[i]) != all.end());
                }
            }
            logger().debug("Forceuser %d checked", vhIdx);

            for (int i = 0; i < NUM_HUMAN_LINKS; i++) {
                if (vhIdx == 0 || (i % 4 == vhIdx)) {
                    TS_ASSERT (find(links.begin(), links.end(), H[i]) != links.end());
                    TS_ASSERT (find(all.begin(), all.end(), H[i]) != all.end());
                }
            }
            logger().debug("Human %d checked", vhIdx);

            for (int i = 0; i < NUM_NODES; i++) {
                if (vhIdx == 0 || (i % 4 == vhIdx)) {
                    TS_ASSERT (find(all.begin(), all.end(), h[i]) != all.end());
                    TS_ASSERT (find(nodes.begin(), nodes.end(), h[i]) != nodes.end());
                }
            }
            logger().debug("Rest of them %d checked", vhIdx);
        }

        // Test getHandleSet
        // OutputIterator getHandleSet(OutputIterator, Type, const string& name, bool acceptSubTypes=true) const;

        logger().debug("Start subset test");
        for (int vhIdx = 0; vhIdx < NUM_VHS; vhIdx++) {
            for (int i = 0; i < NUM_NODES; i++) {
                HandleSeq nodes;
                logger().debug("start get handle by name %d %d", vhIdx, i);
                get_handles_by_name(atomSpace, back_inserter(nodes), nodeNames[i], NODE, true);
                TS_ASSERT_EQUALS(nodes.size(), 1);
                for (int j = 0; j < NUM_NODES; j++) {
                    if (i == j) {
                        TS_ASSERT(find(nodes.begin(), nodes.end(), h[j]) != nodes.end());
                    } else {
                        TS_ASSERT(find(nodes.begin(), nodes.end(), h[j]) == nodes.end());
                    }
                }
            }

            logger().debug("Subset %d checked", vhIdx);

            HandleSeq links;
            get_handles_by_name(atomSpace, back_inserter(links), "", LINK, true);
            logger().debug("1) links.size() = %d Expected = %d", (int) links.size(), 8);
            TS_ASSERT(links.size() == 8);
            links.clear();
            get_handles_by_name(atomSpace, back_inserter(links), "", LINK, false);
            logger().debug("2) links.size() = %d", links.size());
            TS_ASSERT(links.size() == 0);
            HandleSeq allInhLinks;
            get_handles_by_name(atomSpace, back_inserter(allInhLinks), "", INHERITANCE_LINK, true);
            logger().debug("4) allInhLinks.size() = %d (vhIdx: %d)", allInhLinks.size(), vhIdx);
            //for (unsigned int x = 0; x < allInhLinks.size(); ++x) {
            //    logger().info("allInhLinks[x]: %s\n", allInhLinks[x]->to_string().c_str());
            //}
            // TS_ASSERT(allInhLinks.size() == 8);
            HandleSeq justInhLinks;
            get_handles_by_name(atomSpace, back_inserter(justInhLinks), "", INHERITANCE_LINK, false);
            logger().debug("5) justInhLinks.size() = %d (vhIdx: %d)", justInhLinks.size(), vhIdx);
            TS_ASSERT(justInhLinks.size() == 4);
            HandleSeq partOfLinks;
            get_handles_by_name(atomSpace, back_inserter(partOfLinks), "", SUBSET_LINK, true);
            logger().debug("6) partOfLinks.size() = %d", partOfLinks.size());
            TS_ASSERT(partOfLinks.size() == 4);
            partOfLinks.clear();
            get_handles_by_name(atomSpace, back_inserter(partOfLinks), "", SUBSET_LINK, false);
            logger().debug("7) partOfLinks.size() = %d", partOfLinks.size());
            TS_ASSERT(partOfLinks.size() == 4);
        }
        logger().info("\nEnd testMiscellaneous()");
    }

    /**
     * Method tested:
     *
     * Add a new node to the Atom Table
     * @param t     Type of the node
     * @param name  Name of the node
     *
     * virtual Handle add_node(Type t,const string& name)=0;
     *
     */
    void testAddNode()
    {
        logger().info("Begin testAddNode()");
        ValuePtr tv1 = createFloatValue(std::vector<double>({0.001, 0.00001}));
        ValuePtr tv2 = createFloatValue(std::vector<double>({0.001, 0.00001}));
        ValuePtr tv3 = createFloatValue(std::vector<double>({0.5, 0.99}));
        Handle h1 = atomSpace->add_node(PREDICATE_NODE, "barkingAt");
        h1->setValue(truth_key(), tv1);
        Handle h2 = atomSpace->add_node(CONCEPT_NODE, "dog1");
        h2->setValue(truth_key(), tv2);
        Handle h3 = atomSpace->add_node(CONCEPT_NODE, "tree");
        h3->setValue(truth_key(), tv3);

        tv1 = createFloatValue(std::vector<double>({0.002, 0.00002}));
        tv2 = createFloatValue(std::vector<double>({0.1, 0.0}));
        tv3 = createFloatValue(std::vector<double>({0.6, 0.90}));
        h1->setValue(truth_key(), tv1);
        h2->setValue(truth_key(), tv2);
        h3->setValue(truth_key(), tv3);
        Handle h1_ = atomSpace->add_node(PREDICATE_NODE, "barkingAt");
        Handle h2_ = atomSpace->add_node(CONCEPT_NODE, "dog1");
        Handle h3_ = atomSpace->add_node(CONCEPT_NODE, "tree");

        TS_ASSERT(h1 == h1_);
        FloatValuePtr h1tv = FloatValueCast(h1->getValue(truth_key()));
        logger().debug("h1tv = %s", h1tv->to_string().c_str());
        logger().debug("tv1 = %s", tv1->to_string().c_str());
        FloatValuePtr fv1 = FloatValueCast(tv1);
        TS_ASSERT(fabs(h1tv->value()[0] - fv1->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(h1tv->value()[1] - fv1->value()[1]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(h2 == h2_);
        FloatValuePtr h2tv = FloatValueCast(h2->getValue(truth_key()));
        FloatValuePtr fv2 = FloatValueCast(tv2);
        TS_ASSERT(fabs(h2tv->value()[0] - fv2->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(h2tv->value()[1] - fv2->value()[1]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(h3 == h3_);
        FloatValuePtr h3tv = FloatValueCast(h3->getValue(truth_key()));
        FloatValuePtr fv3 = FloatValueCast(tv3);
        TS_ASSERT(fabs(h3tv->value()[0] - fv3->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(h3tv->value()[1] - fv3->value()[1]) < FLOAT_ACCEPTABLE_ERROR);

        // test adding a node with no truth value
        Handle h4 = atomSpace->add_node(CONCEPT_NODE, "wood");
        TS_ASSERT(h4 != Handle::UNDEFINED);

        logger().info("End testAddNode()");
    }


    /**
     * Method tested:
     *
     * Add a new link to the Atom Table
     * @param t         Type of the link
     * @param outgoing  HandleSeq containing the outgoing set of the link
     *
     * virtual Handle add_link(Type t,const HandleSeq& outgoing)=0;
     */
    void testAddLink()
    {
        logger().info("Begin testAddLink()");
        Handle h1 = atomSpace->add_node(PREDICATE_NODE, "barkingAt");
        h1->setValue(truth_key(), createFloatValue(std::vector<double>({0.001, 0.00001})));
        Handle h2 = atomSpace->add_node(CONCEPT_NODE, "dog1");
        h2->setValue(truth_key(), createFloatValue(std::vector<double>({0.001, 0.00001})));
        Handle h3 = atomSpace->add_node(CONCEPT_NODE, "tree");
        h3->setValue(truth_key(), createFloatValue(std::vector<double>({0.5, 0.99})));
        ValuePtr tv1 = createFloatValue(std::vector<double>({0.001, 0.00001}));
        ValuePtr tv2 = createFloatValue(std::vector<double>({0.001, 0.00001}));
        HandleSeq outgoing1;
        outgoing1.push_back(h2);
        outgoing1.push_back(h3);
        Handle l1 = atomSpace->add_link(LIST_LINK,
                                        std::move(HandleSeq(outgoing1)));
        l1->setValue(truth_key(), tv1);
        HandleSeq outgoing2;
        outgoing2.push_back(h1);
        outgoing2.push_back(l1);
        Handle l2 = atomSpace->add_link(EVALUATION_LINK,
                                        std::move(HandleSeq(outgoing2)));
        l2->setValue(truth_key(), tv2);

        tv1 = createFloatValue(std::vector<double>({0.002, 0.00002}));
        l1->setValue(truth_key(), tv1);
        Handle l1_ = atomSpace->add_link(LIST_LINK,
                                         std::move(HandleSeq(outgoing1)));
        tv2 = createFloatValue(std::vector<double>({0.1, 0.0}));
        l2->setValue(truth_key(), tv2);
        Handle l2_ = atomSpace->add_link(EVALUATION_LINK,
                                         std::move(HandleSeq(outgoing2)));

        TS_ASSERT(l1 == l1_);
        FloatValuePtr l1tv = FloatValueCast(l1->getValue(truth_key()));
        logger().debug("l1tv = %s", l1tv->to_string().c_str());
        logger().debug("tv1 = %s", tv1->to_string().c_str());
        FloatValuePtr fv1 = FloatValueCast(tv1);
        TS_ASSERT(fabs(l1tv->value()[0] - fv1->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(l1tv->value()[1] - fv1->value()[1]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(l2 ==  l2_);
        FloatValuePtr l2tv = FloatValueCast(l2->getValue(truth_key()));
        FloatValuePtr fv2 = FloatValueCast(tv2);
        TS_ASSERT(fabs(l2tv->value()[0] - fv2->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(l2tv->value()[1] - fv2->value()[1]) < FLOAT_ACCEPTABLE_ERROR);

        logger().info("End testAddLink()");
    }

    /**
     * Values should get copied over, when a new version of the same atom is added.
     */
    void testTVUpdate()
    {
        logger().info("Begin testTVUpdate");

        Handle h1 = createNode(CONCEPT_NODE, "dog1");
        h1->setValue(truth_key(), createFloatValue(std::vector<double>({0.001, 0.00001})));
        Handle ash1 = atomSpace->add_atom(h1);
        ValuePtr tv1 = createFloatValue(std::vector<double>({0.001, 0.00001}));
        FloatValuePtr htv = FloatValueCast(ash1->getValue(truth_key()));
        logger().debug("ash1 = %s", ash1->to_string().c_str());
        logger().debug("htv = %s", htv->to_string().c_str());
        logger().debug("tv1 = %s", tv1->to_string().c_str());

        FloatValuePtr fv1 = FloatValueCast(tv1);
        TS_ASSERT(fabs(htv->value()[0] - fv1->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(htv->value()[1] - fv1->value()[1]) < FLOAT_ACCEPTABLE_ERROR);

        Handle h2 = createNode(CONCEPT_NODE, "dog1");
        h2->setValue(truth_key(), createFloatValue(std::vector<double>({0.02, 0.002})));
        Handle ash2 = atomSpace->add_atom(h2);
        ValuePtr tv2 = createFloatValue(std::vector<double>({0.02, 0.002}));
        logger().debug("ash2 = %s", ash2->to_string().c_str());

        TS_ASSERT(ash1 == ash2);
        htv = FloatValueCast(ash1->getValue(truth_key()));
        logger().debug("htv = %s", htv->to_string().c_str());
        logger().debug("tv2 = %s", tv2->to_string().c_str());
        FloatValuePtr fv2 = FloatValueCast(tv2);
        TS_ASSERT(fabs(htv->value()[0] - fv2->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(htv->value()[1] - fv2->value()[1]) < FLOAT_ACCEPTABLE_ERROR);

        Handle h3 = createNode(CONCEPT_NODE, "dog1");
        h3->setValue(truth_key(), createFloatValue(std::vector<double>({0.3, 0.03})));
        Handle ash3 = atomSpace->add_atom(h3);
        ValuePtr tv3 = createFloatValue(std::vector<double>({0.3, 0.03}));
        logger().debug("ash3 = %s", ash3->to_string().c_str());

        TS_ASSERT(ash1 == ash3);
        htv = FloatValueCast(ash1->getValue(truth_key()));
        logger().debug("htv = %s", htv->to_string().c_str());
        logger().debug("tv3 = %s", tv3->to_string().c_str());
        FloatValuePtr fv3 = FloatValueCast(tv3);
        TS_ASSERT(fabs(htv->value()[0] - fv3->value()[0]) < FLOAT_ACCEPTABLE_ERROR);
        TS_ASSERT(fabs(htv->value()[1] - fv3->value()[1]) < FLOAT_ACCEPTABLE_ERROR);

        logger().info("End testTVUpdate");
    }

    void testGetHandle_bugfix1()
    {
        HandleSeq emptyOutgoing;
        Handle result = atomSpace->xget_handle(LIST_LINK, emptyOutgoing);
        TS_ASSERT(result == Handle::UNDEFINED);
        atomSpace->add_link(LIST_LINK,
                            std::move(HandleSeq(emptyOutgoing)));
        result = atomSpace->xget_handle(LIST_LINK, emptyOutgoing);
        TS_ASSERT(result != Handle::UNDEFINED);
    }

    void testGetHandleSetByName()
    {
        Handle h1 = atomSpace->add_node(PREDICATE_NODE, "dog1");
        h1->setValue(truth_key(), createFloatValue(std::vector<double>({0.001, 0.00001})));
        Handle h2 = atomSpace->add_node(CONCEPT_NODE, "dog1");
        h2->setValue(truth_key(), createFloatValue(std::vector<double>({0.001, 0.00001})));
        Handle h3 = atomSpace->add_node(NODE, "dog1");
        h3->setValue(truth_key(), createFloatValue(std::vector<double>({0.5, 0.99})));

        HandleSeq namedAtoms;
        atomSpace->get_handles_by_type(namedAtoms, NODE, true);
        TS_ASSERT_EQUALS(namedAtoms.size(), 3);
    }
};