OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / atomspace / AtomSpace.h

575 lines · c ref:
/*
 * opencog/atomspace/AtomSpace.h
 *
 * Copyright (C) 2002-2007 Novamente LLC
 * Copyright (C) 2008-2011 OpenCog Foundation
 * Copyright (C) 2015-2021 Linas Vepstas
 * 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.
 */

#ifndef _OPENCOG_ATOMSPACE_H
#define _OPENCOG_ATOMSPACE_H

#include <opencog/util/async_method_caller.h>
#include <opencog/util/exceptions.h>

#include <opencog/atoms/atom_types/NameServer.h>
#include <opencog/atoms/base/Atom.h>
#include <opencog/atoms/base/Node.h>
#include <opencog/atoms/base/Link.h>

#include <opencog/atomspace/Frame.h>
#include <opencog/atomspace/TypeIndex.h>

class AtomTableUTest;

namespace opencog
{
/** \addtogroup grp_atomspace
 *  @{
 */
class AtomSpace;
typedef std::shared_ptr<AtomSpace> AtomSpacePtr;

/**
 * This class provides mechanisms to store atoms and keep indices for
 * efficient lookups. It implements the local storage data structure of
 * OpenCog. It contains methods to add and remove atoms, as well as to
 * retrieve specific sets according to different criteria.
 */
class AtomSpace : public Frame
{
    friend class StorageNode;     // Needs to call add() directly.
    template< class... Args >
    friend AtomSpacePtr createAtomSpace(Args&&...); // Needs to call install()

    // Debug tools
    static const bool EMIT_DIAGNOSTICS = true;
    static const bool DONT_EMIT_DIAGNOSTICS = false;
    static const bool CHECK_VALUES = true;
    static const bool DONT_CHECK_VALUES = false;

    bool _read_only;
    bool _copy_on_write;

    /**
     * Drop copy constructor and equals operator to
     * prevent accidental copying of large objects.
     */
    AtomSpace& operator=(const AtomSpace&) = delete;
    AtomSpace(const AtomSpace&) = delete;

    /// Base AtomSpaces wrapped by this space. Empty if top-level.
    /// This AtomSpace will behave like the set-union of the base
    /// atomspaces in the `_environ`: it exposes all Atoms in those
    /// bases, plus also anything in this AtomSpace.
    //
    // This _environ vector is similar to _outgoing but not the same.
    // The _outgoing vector can contain regular Atoms (not AtomSpaces)
    // that are executable, and, when executed, return AtomSpaces. The
    // result of this execution is stored in _environ; the original
    // Atoms are in _outgoing.
    std::vector<AtomSpacePtr> _environ;
    virtual void install(void) override;
    virtual void remove(void) override;

    void init();
    void clear_all_atoms();

#if USE_INCOME_INDEX
    // This is never used, and remains here for historical reference.
    // See IncomeIndex.h for an explanation.
    IncomeIndex incomeIndex;

public:
    bool have_inset_map(const Handle& h) const {
        return incomeIndex.haveInset(h); }
    InSetMap& get_inset_map(const Handle& h) {
        return incomeIndex.getInset(h); }
    void drop_inset_map(const Handle& h) {
        return incomeIndex.removeInset(h); }
private:
#endif

    // --------------------------------------------------
    //! Index of atoms.
    TypeIndex typeIndex;

    /** Find out about atom type additions in the NameServer. */
    NameServer& _nameserver;
    int addedTypeConnection;
    void typeAdded(Type);

    /**
     * Private: add an atom to the table. This skips the read-only
     * check.
     *
     * The `force` flag forces the addition of this atom into the
     * atomtable, even if it is already in a parent atomspace.
     *
     * The `recurse` flag suppresses the copying of values for
     * recursive additions. This is used to avodi accidental clobbers.
     *
     * The `absent` flag indicates that the atom is being added
     * so that it can hide others in lower layers.
     */
    Handle add(const Handle&, bool force=false,
               bool recurse=false, bool absent = false);
    Handle check(const Handle&, bool force=false);
    Handle lookupHide(const Handle&, bool hide=false) const;

    virtual ContentHash compute_hash() const;

    // Private helper function.
    void shadow_by_type(UnorderedHandleSet&,
                        Type type,
                        bool subclass,
                        bool parent,
                        const AtomSpace*) const;

    void get_absent_atoms(HandleSeq&) const;
    void get_atoms_in_frame(HandleSeq&) const;

public:
    /**
     * Constructor and destructor for this class.
     *
     * An AtomSpace can have one or more base AtomSpaces; the created
     * AtomSpace will behave like the union of the component AtomSpaces.
     * The created AtomSpace essentially "yokes together" the base
     * AtomSpaces. Atoms are NOT copied, unless the base spaces are
     * read-only, in which case a copy-on-write is performed.
     *
     * If 'transient' is true, then the resulting AtomSpace is operates
     * in a copy-on-write mode, suitable for holding temporary, scratch
     * results (e.g. for evaluation or inference.) Transient AtomSpaces
     * should have a parent which holds the actual Atoms being worked
     * with. See COW below.
     */
    AtomSpace(AtomSpace* base=nullptr, bool transient=false);
    AtomSpace(const AtomSpacePtr&);
    AtomSpace(const HandleSeq&);
    ~AtomSpace();

    bool is_node(void) const { return true; }
    bool is_link(void) const { return true; }

    /// Read-only (RO) atomspaces provide protection against update of the
    /// AtomSpace contents. Atoms in a read-only atomspace cannot be
    /// deleted, nor can their values (including truthvalues) be changed.
    /// New atoms cannot be added to a read-only atomspace.
    void set_read_only(void);
    void set_read_write(void);
    bool get_read_only(void) { return _read_only; }

    /// Copy-on-write (COW) atomspaces protect base atomspaces from
    /// being damaged by updates to this atomspace. COW only makes
    /// sense if this atomspace has underlying base atomspaces;
    /// otherwise its a no-op.
    ///
    /// When an atomspace is marked COW, it behaves as if the base is
    /// read-only, so that changes to any Values affect this atomspace
    /// only. This is used heavily in temporary atomspaces, wherein
    /// updates will not trash the base.
    /// Transient atomspaces are always COW.
    void set_copy_on_write(void) { _copy_on_write = true; }
    void clear_copy_on_write(void) { _copy_on_write = false; }
    bool get_copy_on_write(void) const { return _copy_on_write; }

    // -------------------------------------------------------

    /**
     * Return the depth of the Atom, relative to this AtomSpace.
     * The depth is zero, if the Atom is in this table; it is one
     * if it is in the parent, and so on. It is -1 if it is not
     * in the chain.
     */
    int depth(const Handle& atom) const;
    int depth(const AtomSpace*) const;

    /**
     * Return true if the atom is in this AtomSpace,
     * or if it is in the environment of this AtomSpace.
     */
    bool in_environ(const Handle&) const;
    bool in_environ(const AtomSpace*) const;

    /* AtomSpaces are Atoms; provide virtual methods of base class. */
    virtual const std::string& get_name() const;
    virtual size_t size() const { return _environ.size(); }
    virtual const HandleSeq& getOutgoingSet() const { return _outgoing; }
    virtual Handle getOutgoingAtom(Arity) const;

    const std::vector<AtomSpacePtr>& getEnviron() const { return _environ; }

    /* Restoring complex AtomSpace DAG's from storage requires the
     * ability to set the AtomSpace name. So we provide this.
     */
    void set_name(const std::string&);

    /**
     * AtomSpaces are identified by their name, and so behave like
     * Nodes when being maniupulated and inserted. Such insertion
     * needs operator==() and thus a string compare of the name is
     * done here. Use content_compare() below if you want that.
     */
    virtual bool operator==(const Atom&) const;

    /** Ordering operator for AtomSpaces. */
    virtual bool operator<(const Atom&) const;

    /**
     * Compare contents for equality. Useful during testing.
     */
    static bool content_compare(const AtomSpace& first,
                                const AtomSpace& second,
                                bool check_values=CHECK_VALUES,
                                bool emit_diagnostics=DONT_EMIT_DIAGNOSTICS);
    /**
     * Return the number of atoms contained in the space.
     */
    size_t get_size() const;
    size_t get_num_atoms_of_type(Type type, bool subclass=false) const;

    //! Clear the atomspace, extract all atoms.
    void clear();

    /**
     * Read-write synchronization barrier fence.  When called, this
     * will not return until all the atoms previously added to the
     * atomspace have been fully inserted.
     */
    void barrier(void);

    /**
     * Add an atom to the Atom Table.  If the atom already exists
     * then that is returned. If this AtomSpace is stacked on top
     * of others, then the shallowest copy of an existing Atom will
     * be returrned.
     */
    Handle add_atom(const Handle&);

    /**
     * Add an atom to the Atom Table.  If the atom already exists
     * then that is returned. It is force-added to this AtomSpace,
     * even if a version of this same Atom exists deeper in the
     * AtomSpace stack. This is conventionally used to guarantee
     * that execution happens in scratch AtomSpaces.
     */
    Handle localize(const Handle& h)
    {
        if (h->getAtomSpace() == this) return h;
        return add(h, true);  // force copy
    }

    /**
     * Add a node to the Atom Table.  If the atom already exists
     * then that is returned.
     *
     * \param t     Type of the node
     * \param name  Name of the node
     */
    inline Handle add_node(Type t, std::string&& name) {
        return add_atom(createNode(t, std::move(name)));
    }
    Handle xadd_node(Type t, std::string str) {
        return add_node(t, std::move(str));
    }

    /**
     * Add a link to the AtomSpace. If the atom already exists, then
     * that is returned.
     *
     * @param t         Type of the link
     * @param outgoing  a const reference to a HandleSeq containing
     *                  the outgoing set of the link
     */
    inline Handle add_link(Type t, HandleSeq&& outgoing) {
        return add_atom(createLink(std::move(outgoing), t));
    }
    inline Handle xadd_link(Type t, HandleSeq seq) {
        return add_link(t, std::move(seq));
    }

    inline Handle add_link(Type t)
    {
        return add_link(t, HandleSeq{});
    }

    inline Handle add_link(Type t, Handle h)
    {
	    return add_link(t, HandleSeq({h}));
    }

    inline Handle add_link(Type t, Handle ha, Handle hb)
    {
	    return add_link(t, {ha, hb});
    }

    inline Handle add_link(Type t, Handle ha, Handle hb, Handle hc)
    {
        return add_link(t, {ha, hb, hc});
    }

    inline Handle add_link(Type t, Handle ha, Handle hb, Handle hc, Handle hd)
    {
        return add_link(t, {ha, hb, hc, hd});
    }

    inline Handle add_link(Type t, Handle ha, Handle hb, Handle hc,
                           Handle hd, Handle he)
    {
	    return add_link(t, {ha, hb, hc, hd, he});
    }

    inline Handle add_link(Type t, Handle ha, Handle hb, Handle hc,
                           Handle hd, Handle he, Handle hf)
    {
	    return add_link(t, {ha, hb, hc, hd, he, hf});
    }

    inline Handle add_link(Type t, Handle ha, Handle hb, Handle hc,
                           Handle hd, Handle he, Handle hf, Handle hg)
    {
	    return add_link(t, {ha, hb, hc, hd, he, hf, hg});
    }

    inline Handle add_link(Type t, Handle ha, Handle hb, Handle hc,
                           Handle hd, Handle he, Handle hf, Handle hg,
                           Handle hh)
    {
	    return add_link(t, {ha, hb, hc, hd, he, hf, hg, hh});
    }

    inline Handle add_link(Type t, Handle ha, Handle hb, Handle hc,
                           Handle hd, Handle he, Handle hf, Handle hg,
                           Handle hh, Handle hi)
    {
	    return add_link(t, {ha, hb, hc, hd, he, hf, hg, hh, hi});
    }

    /**
     * Given a Value, find all of the Atoms inside of it, and add them
     * to the AtomSpace. Return an equivalent Value, with all Atoms
     * substituted by the ones that are in this AtomSpace.
     */
    ValuePtr add_atoms(const ValuePtr&);

    /**
     * Get an atom from the AtomSpace. If the atom is not there, then
     * return Handle::UNDEFINED.
     */
    inline Handle get_atom(const Handle& a) const {
        if (nullptr == a) return Handle::UNDEFINED;
        return lookupHandle(a);
    }

    /**
     * Extract an atom from the atomspace.  This only removes the atom
     * from the (local, in-RAM) AtomSpace (in this process); any copies
     * of the atom in persistent storage or in other address spaces are
     * unaffected.
     *
     * The atom itself remains valid as long as there are Handles
     * that reference it; the RAM associated with the atom is
     * freed only when the last reference goes away.
     *
     * Note that if the recursive flag is set to false, and the atom
     * appears in the incoming set of some other atom, then extraction
     * will fail.  Thus, it is generally recommended that extraction
     * be recursive, unless you can guarantee that the atom is not in
     * someone else's outgoing set.
     *
     * @param h The Handle of the atom to be removed.
     * @param recursive Recursive-removal flag. If the flag is set,
     *       then this atom, and *everything* that points to it will
     *       be removed from the atomspace.  This can cause a large
     *       cascade of removals!  If the flag is not set, then the
     *       atom will be removed only if its incoming set is empty.
     *       By default, recursion is disabled.
     * @return True if the Atom for the given Handle was successfully
     *         removed. False, otherwise.
     */
    bool extract_atom(const Handle&, bool recursive=false);

    bool remove_atom(const Handle& h, bool recursive=false) {
        return extract_atom(h, recursive);
    }

    /**
     * Set the Value on the atom, performing necessary permissions
     * checking. If this atomspace is read-only, then the setting
     * of values is prohibited.  If this atomspace has read-write
     * permissions, but the atom is in a parent atomspace that is
     * read-only, then the atom is copied into this atomspace, before
     * the value is changed. (Copy-on-write (COW) semantics).
     *
     * If the atom is copied, then the copy is returned.
     */
    Handle set_value(const Handle&, const Handle& key, const ValuePtr& value);

    /**
     * Increment the count on a FloatValue. The increment is performed
     * atomically, so that there are no races in the update. Atomspaces
     * that are read-only, COW, or those that are frames, are handled as
     * described above, for `set_value()`.
     *
     * If the atom is copied, then the copy is returned.
     */
    Handle increment_count(const Handle&, const Handle&, const std::vector<double>&);
    Handle increment_count(const Handle&, const Handle&, size_t, double);

    /**
     * Find an equivalent Atom that is exactly the same as the arg.
     * If such an atom is in the AtomSpace, or in any of it's parent
     * AtomSpaces, return that Atom. Return the shallowest such Atom.
     */
    Handle lookupHandle(const Handle& h) const
    { return lookupHide(h, true); }

    /**
     * Get a node from the AtomSpace, if it's in there. If the atom
     * can't be found, Handle::UNDEFINED will be returned.
     *
     * @param t     Type of the node
     * @param str   Name of the node
     */
    inline Handle get_node(Type t, std::string&& name) const {
        return lookupHandle(createNode(t, std::move(name)));
    }
    inline Handle xget_handle(Type t, std::string name) const {
        return get_node(t, std::move(name));
    }

    /**
     * Get a link from the AtomSpace, if it's in there. If the atom
     * can't be found, Handle::UNDEFINED will be returned.
     *
     * See also the get_atom() method.
     *
     * @param t        Type of the node
     * @param outgoing a reference to a HandleSeq containing
     *        the outgoing set of the link.
     */
    inline Handle get_link(Type t, HandleSeq&& outgoing) const {
        return lookupHandle(createLink(std::move(outgoing), t));
    }
    inline Handle xget_handle(Type t, HandleSeq outgoing) const {
        return get_link(t, std::move(outgoing));
    }

    /* Currently used by link-grammar, and best leave this here
     * until that gets revised. */
    inline Handle get_link(Type t, const Handle& ha, const Handle& hb) const {
        return get_link(t, {ha, hb});
    }

    /**
     * Return true if the handle points to an atom that is in some
     * (any) atomspace; else return false.
     */
    bool is_valid_handle(const Handle& h) const {
        return (nullptr != h) and (h->getAtomSpace() != nullptr);
    }

    /**
     * Gets a set of handles that matches with the given type
     * (subclasses optionally).
     *
     * @param hseq the HandleSeq into which to insert handles.
     * @param type The desired type.
     * @param subclass Whether type subclasses should be considered.
     *
     * Example of call to this method, which would return all ConceptNodes
     * in the AtomSpace:
     * @code
     *         HandleSeq atoms;
     *         atomSpace.get_handles_by_type(atoms, CONCEPT_NODE);
     * @endcode
     */
    void
    get_handles_by_type(HandleSeq&,
                        Type type,
                        bool subclass=false,
                        bool parent=true,
                        const AtomSpace* = nullptr) const;

    void
    get_handles_by_type(UnorderedHandleSet&,
                        Type type,
                        bool subclass=false,
                        bool parent=true,
                        const AtomSpace* = nullptr) const;

    /**
     * Gets a set of handles that matches with the given type,
     * but ONLY if they have an empty incoming set! 
     *
     * @param hset the HandleSeq into which to insert handles.
     * @param The desired type.
     * @param Whether type subclasses should be considered.
     *
     * Example of call to this method, which would return all ConceptNodes
     * in the AtomSpace:
     * @code
     *         HandleSeq atoms;
     *         atomSpacePtr->get_root_set_by_type(atoms, CONCEPT_NODE);
     * @endcode
     */
    void
    get_root_set_by_type(HandleSeq&,
                         Type type,
                         bool subclass=false,
                         bool parent=true,
                         const AtomSpace* = nullptr) const;

    /** Returns a string representation of the AtomSpace. */
    virtual std::string to_string(void) const;
    virtual std::string to_string(const std::string& indent) const;
    virtual std::string to_short_string(const std::string& indent) const;

    /* ----------------------------------------------------------- */
    // Not for public use! Only StorageNodes get to call this!
    // XXX FIXME Users should call StorageNode::add_nocheck() instead.
    Handle storage_add_nocheck(const Handle& h) { return add(h); }
};

static inline AtomSpacePtr AtomSpaceCast(const ValuePtr& a)
    { return std::dynamic_pointer_cast<AtomSpace>(a); }
static inline AtomSpacePtr AtomSpaceCast(AtomSpace* as)
    { return AtomSpaceCast(as->shared_from_this()); }
static inline Handle HandleCast(AtomSpace* as)
    { return HandleCast(as->shared_from_this()); }

template< class... Args >
AtomSpacePtr createAtomSpace( Args&&... args )
{
	AtomSpacePtr asp(std::make_shared<AtomSpace>(std::forward<Args>(args) ...));
	return asp;
}

/** @}*/
} // namespace opencog

namespace std {

/**
* Output the whole atomspace, actually all orphans as the others are
* output by of their parents (incoming sets).
*/
ostream& operator<<(ostream&, const opencog::AtomSpace&);

} //namespace std

#endif // _OPENCOG_ATOMSPACE_H