Skip to content

Commit

Permalink
Merge pull request #2500 from linas/anchor-link
Browse files Browse the repository at this point in the history
Post search results to an AnchorLink
  • Loading branch information
linas authored Feb 10, 2020
2 parents 5ec84bb + c5be58d commit 46dea8e
Show file tree
Hide file tree
Showing 11 changed files with 308 additions and 21 deletions.
1 change: 1 addition & 0 deletions examples/pattern-matcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ Basic Examples
The first four examples provide a basic introduction to basic
pattern matching.

* `anchor.scm` -- Obtaining results incrementally.
* `satisfaction.scm` -- Determining satisfiability of a query.
* `glob.scm` -- Matching multiple atoms at once.
* `choice.scm` -- Using the ChoiceLink to explore alternatives.
Expand Down
66 changes: 66 additions & 0 deletions examples/pattern-matcher/anchor.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
;
; anchor.scm - Obtaining search results incrementally.
;
; Both GetLink and BindLink return all search results wrapped in
; a SetLink. This can be inconvenient in several ways. First,
; in most typical uses, the contents of the SetLink are examined,
; and the set itself is promptly discarded. If it is not discarded,
; then one risks that it just hangs out in the AtomSpace, using up
; storage, but otherwise forgetten and useless. Another downside
; is that it is impossible to report any results, until all of them
; are found. This can be a problem for extremely long-running searches.
;
; To avoid both of the above issues, one can specify a "drop-off
; location" for the searches; as results arrive, they are attached
; to these drop-off points with MemberLinks, and other parts of the
; system can then grab results from there, and continue. (There are
; also proposals for a promise/future-type mechanis in the same vein,
; but it has not been implemented yet.)
;
; This example shows how to declare a drop-off point. Its actually
; almost trivial.
;
(use-modules (opencog) (opencog exec))

; Some data that we will query over.
(Evaluation (Predicate "foo") (List (Concept "A") (Concept "alpha")))
(Evaluation (Predicate "foo") (List (Concept "B") (Concept "alpha")))
(Evaluation (Predicate "foo") (List (Concept "C") (Concept "alpha")))

; ----------------------------------
; Define a search query. Just an ordinary GetLink - with one twist:
; there is an AnchorNode in the variable declaration. This AnchorNode
; will be used as the drop-off point.
(define get-link
(Get
(VariableList
(TypedVariable (Variable "$x") (Type 'ConceptNode))
(Anchor "get-results"))
(Present
(Evaluation (Predicate "foo")
(List (Variable "$x") (Concept "alpha"))))))

; Perform the query. This will return the Anchor, instead of a SetLink.
; (cog-execute! get-link)

; Verify that the expected results showed up. They will be attached
; to the AnchorNode, with MemberLinks.
; (cog-incoming-by-type (AnchorNode "get-results") 'MemberLink)
; ----------------------------------

; Very nearly identical to the above, this shows that the BindLink
; can be used in a similar fashion.
(define bind-link
(Bind
(VariableList
(TypedVariable (Variable "$z") (Type 'ConceptNode))
(Anchor "bind-results"))
(Present
(Evaluation (Predicate "foo")
(List (Variable "$z") (Concept "alpha"))))
(Inheritance (Variable "$z") (Concept "letters"))))

; As above: perform the query, and verify that the results showed up.
; (cog-execute! bind-link)
; (cog-incoming-by-type (AnchorNode "bind-results") 'MemberLink)
; ----------------------------------
16 changes: 15 additions & 1 deletion opencog/atoms/core/Variables.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1197,6 +1197,16 @@ bool Variables::is_upper_bound(const Handle &glob, size_t n) const
return (n <= intervals.second or intervals.second < 0);
}

static const GlobInterval& default_interval(Type t)
{
static const GlobInterval var_def_interval =
GlobInterval(1, 1);
static const GlobInterval glob_def_interval =
GlobInterval(1, SIZE_MAX);
return t == GLOB_NODE ? glob_def_interval :
var_def_interval;
}

const GlobInterval& Variables::get_interval(const Handle& var) const
{
const auto interval = _glob_intervalmap.find(var);
Expand Down Expand Up @@ -1485,10 +1495,14 @@ void Variables::validate_vardecl(const HandleSeq& oset)
{
get_vartype(h);
}
else if (ANCHOR_NODE == t)
{
_anchor = h;
}
else
{
throw InvalidParamException(TRACE_INFO,
"Expected a VariableNode or a TypedVariableLink, got: %s"
"Expected a Variable or TypedVariable or Anchor, got: %s"
"\nVariableList is %s",
nameserver().getTypeName(t).c_str(),
to_string().c_str());
Expand Down
15 changes: 3 additions & 12 deletions opencog/atoms/core/Variables.h
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ struct Variables : public FreeVariables,
/// GlobNodes in the pattern.
GlobIntervalMap _glob_intervalmap;

/// Anchor, if present, else undefined.
Handle _anchor;

// See VariableList.cc for comments
void get_vartype(const Handle&);

Expand Down Expand Up @@ -340,18 +343,6 @@ struct Variables : public FreeVariables,
const Handle&) const;

void extend_interval(const Handle &h, const Variables &vset);

private:
inline const GlobInterval &default_interval(Type t) const
{
static const GlobInterval var_def_interval =
GlobInterval(1, 1);
static const GlobInterval glob_def_interval =
GlobInterval(1, SIZE_MAX);
return t == GLOB_NODE ? glob_def_interval :
var_def_interval;
}

};

// Debugging helpers see
Expand Down
13 changes: 12 additions & 1 deletion opencog/atoms/pattern/BindLink.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,21 @@ BindLink::BindLink(const HandleSeq& hseq, Type t)
/** Wrap query results in a SetLink, place them in the AtomSpace. */
ValuePtr BindLink::execute(AtomSpace* as, bool silent)
{
ValueSet rslt(do_execute(as, silent));

// If there is an anchor, then attach results to the anchor.
// Otherwise, create a SetLink and return that.
if (_variables._anchor and as)
{
for (const ValuePtr& v: rslt)
as->add_link(MEMBER_LINK, HandleCast(v), _variables._anchor);

return _variables._anchor;
}

// The result_set contains a list of the grounded expressions.
// (The order of the list has no significance, so it's really a set.)
// Put the set into a SetLink, cache it, and return that.
ValueSet rslt(do_execute(as, silent));
HandleSeq hlist;
for (const ValuePtr& v: rslt) hlist.emplace_back(HandleCast(v));
Handle rewr(createUnorderedLink(hlist, SET_LINK));
Expand Down
15 changes: 15 additions & 0 deletions opencog/atoms/pattern/DualLink.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ ValuePtr DualLink::execute(AtomSpace* as, bool silent)
if (nullptr == as) as = _atom_space;
Recognizer reco(as);
satisfy(reco);

// If there is an anchor, then attach results to the anchor.
// Otherwise, create a SetLink and return that.
// XXX FIXME ... at this time, there is no documented way of
// squeezing an AnchorLink into a DualLink. So the below
// if-statement is never taken. Some additional design work
// is needed.
if (_variables._anchor and as)
{
for (const Handle& h : reco._rules)
as->add_link(MEMBER_LINK, h, _variables._anchor);

return _variables._anchor;
}

return as->add_atom(createUnorderedLink(reco._rules, SET_LINK));
}

Expand Down
11 changes: 11 additions & 0 deletions opencog/atoms/pattern/GetLink.cc
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ HandleSet GetLink::do_execute(AtomSpace* as, bool silent)

ValuePtr GetLink::execute(AtomSpace* as, bool silent)
{
// If there is an anchor, then attach results to the anchor.
// Otherwise, create a SetLink and return that.
if (_variables._anchor and as)
{
HandleSet hs(do_execute(as, silent));
for (const Handle& h : hs)
as->add_link(MEMBER_LINK, h, _variables._anchor);

return _variables._anchor;
}

// Create the satisfying set, and cache it.
Handle satset(createUnorderedLink(do_execute(as, silent), SET_LINK));

Expand Down
13 changes: 6 additions & 7 deletions opencog/atoms/pattern/SatisfactionLink.cc
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,12 @@ TruthValuePtr SatisfactionLink::evaluate(AtomSpace* as, bool silent)
Satisfier sater(as);
satisfy(sater);

#define PLACE_RESULTS_IN_ATOMSPACE
#ifdef PLACE_RESULTS_IN_ATOMSPACE
// Shoot. XXX FIXME. Most of the unit tests require that the atom
// that we return is in the atomspace. But it would be nice if we
// could defer this indefinitely, until its really needed.
Handle satgrd = as->add_atom(sater._ground);
#endif /* PLACE_RESULTS_IN_ATOMSPACE */
// If there is an anchor, then attach results to the anchor.
if (_variables._anchor and as)
{
for (const Handle& h : sater._ground->getOutgoingSet())
as->add_link(MEMBER_LINK, h, _variables._anchor);
}

return sater._result;
}
Expand Down
139 changes: 139 additions & 0 deletions tests/query/AnchorUTest.cxxtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* tests/query/AnchorUTest.cxxtest
*
* Copyright (C) 2020 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.
*
* 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 <thread>

#include <opencog/guile/SchemeEval.h>
#include <opencog/atoms/truthvalue/SimpleTruthValue.h>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/util/Logger.h>
#include <cxxtest/TestSuite.h>

using namespace opencog;

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

class AnchorUTest: public CxxTest::TestSuite
{
private:
AtomSpace* as;

public:
AnchorUTest(void)
{
// logger().set_level(Logger::FINE);
logger().set_print_to_stdout_flag(true);
logger().set_timestamp_flag(false);

as = new AtomSpace();
SchemeEval* eval = SchemeEval::get_evaluator(as);
eval->eval("(use-modules (opencog exec))");
eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")");
}

~AnchorUTest()
{
delete as;
// 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 Setter(void);
void Getter(void);

void test_get(void);
void test_bind(void);
void xtest_dual(void);
};

void AnchorUTest::tearDown(void)
{
}

void AnchorUTest::setUp(void)
{
}

/*
* GetLink unit test.
*/
void AnchorUTest::test_get(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);

SchemeEval* eval = SchemeEval::get_evaluator(as);
eval->eval("(load-from-path \"tests/query/anchor.scm\")");

Handle anchor = eval->eval_h("(cog-execute! getli)");

IncomingSet results = anchor->getIncomingSetByType(MEMBER_LINK);

TSM_ASSERT_EQUALS("Expecting three answers", results.size(), 3);

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

/*
* BindLink unit test.
*/
void AnchorUTest::test_bind(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);

SchemeEval* eval = SchemeEval::get_evaluator(as);
eval->eval("(load-from-path \"tests/query/anchor.scm\")");

Handle anchor = eval->eval_h("(cog-execute! bindli)");

IncomingSet results = anchor->getIncomingSetByType(MEMBER_LINK);

TSM_ASSERT_EQUALS("Expecting three answers", results.size(), 3);

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

/*
* DualLink unit test.
* XXX FIXME -- there is currently no documented way of squeezing
* an AnchorNode into an SRAI search, and so the unit test below
* canot possibly pass until that design issue is fixed...
*/
void AnchorUTest::xtest_dual(void)
{
logger().debug("BEGIN TEST: %s", __FUNCTION__);

SchemeEval* eval = SchemeEval::get_evaluator(as);
eval->eval("(load-from-path \"tests/query/anchor.scm\")");

Handle anchor = eval->eval_h("(cog-execute! (Dual srai))");

IncomingSet results = anchor->getIncomingSetByType(MEMBER_LINK);

TSM_ASSERT_EQUALS("Expecting two answers", results.size(), 2);

logger().debug("END TEST: %s", __FUNCTION__);
}
1 change: 1 addition & 0 deletions tests/query/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ IF (HAVE_GUILE)
ADD_CXXTEST(ArcanaUTest)
ADD_CXXTEST(SubstitutionUTest)
ADD_CXXTEST(GetLinkUTest)
ADD_CXXTEST(AnchorUTest)
ADD_CXXTEST(NotLinkUTest)
ADD_CXXTEST(GetStateUTest)
ADD_CXXTEST(DeepTypeUTest)
Expand Down
Loading

0 comments on commit 46dea8e

Please sign in to comment.