Skip to content

Commit 46dea8e

Browse files
authored
Merge pull request opencog#2500 from linas/anchor-link
Post search results to an AnchorLink
2 parents 5ec84bb + c5be58d commit 46dea8e

File tree

11 files changed

+308
-21
lines changed

11 files changed

+308
-21
lines changed

examples/pattern-matcher/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ Basic Examples
8484
The first four examples provide a basic introduction to basic
8585
pattern matching.
8686

87+
* `anchor.scm` -- Obtaining results incrementally.
8788
* `satisfaction.scm` -- Determining satisfiability of a query.
8889
* `glob.scm` -- Matching multiple atoms at once.
8990
* `choice.scm` -- Using the ChoiceLink to explore alternatives.

examples/pattern-matcher/anchor.scm

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
;
2+
; anchor.scm - Obtaining search results incrementally.
3+
;
4+
; Both GetLink and BindLink return all search results wrapped in
5+
; a SetLink. This can be inconvenient in several ways. First,
6+
; in most typical uses, the contents of the SetLink are examined,
7+
; and the set itself is promptly discarded. If it is not discarded,
8+
; then one risks that it just hangs out in the AtomSpace, using up
9+
; storage, but otherwise forgetten and useless. Another downside
10+
; is that it is impossible to report any results, until all of them
11+
; are found. This can be a problem for extremely long-running searches.
12+
;
13+
; To avoid both of the above issues, one can specify a "drop-off
14+
; location" for the searches; as results arrive, they are attached
15+
; to these drop-off points with MemberLinks, and other parts of the
16+
; system can then grab results from there, and continue. (There are
17+
; also proposals for a promise/future-type mechanis in the same vein,
18+
; but it has not been implemented yet.)
19+
;
20+
; This example shows how to declare a drop-off point. Its actually
21+
; almost trivial.
22+
;
23+
(use-modules (opencog) (opencog exec))
24+
25+
; Some data that we will query over.
26+
(Evaluation (Predicate "foo") (List (Concept "A") (Concept "alpha")))
27+
(Evaluation (Predicate "foo") (List (Concept "B") (Concept "alpha")))
28+
(Evaluation (Predicate "foo") (List (Concept "C") (Concept "alpha")))
29+
30+
; ----------------------------------
31+
; Define a search query. Just an ordinary GetLink - with one twist:
32+
; there is an AnchorNode in the variable declaration. This AnchorNode
33+
; will be used as the drop-off point.
34+
(define get-link
35+
(Get
36+
(VariableList
37+
(TypedVariable (Variable "$x") (Type 'ConceptNode))
38+
(Anchor "get-results"))
39+
(Present
40+
(Evaluation (Predicate "foo")
41+
(List (Variable "$x") (Concept "alpha"))))))
42+
43+
; Perform the query. This will return the Anchor, instead of a SetLink.
44+
; (cog-execute! get-link)
45+
46+
; Verify that the expected results showed up. They will be attached
47+
; to the AnchorNode, with MemberLinks.
48+
; (cog-incoming-by-type (AnchorNode "get-results") 'MemberLink)
49+
; ----------------------------------
50+
51+
; Very nearly identical to the above, this shows that the BindLink
52+
; can be used in a similar fashion.
53+
(define bind-link
54+
(Bind
55+
(VariableList
56+
(TypedVariable (Variable "$z") (Type 'ConceptNode))
57+
(Anchor "bind-results"))
58+
(Present
59+
(Evaluation (Predicate "foo")
60+
(List (Variable "$z") (Concept "alpha"))))
61+
(Inheritance (Variable "$z") (Concept "letters"))))
62+
63+
; As above: perform the query, and verify that the results showed up.
64+
; (cog-execute! bind-link)
65+
; (cog-incoming-by-type (AnchorNode "bind-results") 'MemberLink)
66+
; ----------------------------------

opencog/atoms/core/Variables.cc

+15-1
Original file line numberDiff line numberDiff line change
@@ -1197,6 +1197,16 @@ bool Variables::is_upper_bound(const Handle &glob, size_t n) const
11971197
return (n <= intervals.second or intervals.second < 0);
11981198
}
11991199

1200+
static const GlobInterval& default_interval(Type t)
1201+
{
1202+
static const GlobInterval var_def_interval =
1203+
GlobInterval(1, 1);
1204+
static const GlobInterval glob_def_interval =
1205+
GlobInterval(1, SIZE_MAX);
1206+
return t == GLOB_NODE ? glob_def_interval :
1207+
var_def_interval;
1208+
}
1209+
12001210
const GlobInterval& Variables::get_interval(const Handle& var) const
12011211
{
12021212
const auto interval = _glob_intervalmap.find(var);
@@ -1485,10 +1495,14 @@ void Variables::validate_vardecl(const HandleSeq& oset)
14851495
{
14861496
get_vartype(h);
14871497
}
1498+
else if (ANCHOR_NODE == t)
1499+
{
1500+
_anchor = h;
1501+
}
14881502
else
14891503
{
14901504
throw InvalidParamException(TRACE_INFO,
1491-
"Expected a VariableNode or a TypedVariableLink, got: %s"
1505+
"Expected a Variable or TypedVariable or Anchor, got: %s"
14921506
"\nVariableList is %s",
14931507
nameserver().getTypeName(t).c_str(),
14941508
to_string().c_str());

opencog/atoms/core/Variables.h

+3-12
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,9 @@ struct Variables : public FreeVariables,
224224
/// GlobNodes in the pattern.
225225
GlobIntervalMap _glob_intervalmap;
226226

227+
/// Anchor, if present, else undefined.
228+
Handle _anchor;
229+
227230
// See VariableList.cc for comments
228231
void get_vartype(const Handle&);
229232

@@ -340,18 +343,6 @@ struct Variables : public FreeVariables,
340343
const Handle&) const;
341344

342345
void extend_interval(const Handle &h, const Variables &vset);
343-
344-
private:
345-
inline const GlobInterval &default_interval(Type t) const
346-
{
347-
static const GlobInterval var_def_interval =
348-
GlobInterval(1, 1);
349-
static const GlobInterval glob_def_interval =
350-
GlobInterval(1, SIZE_MAX);
351-
return t == GLOB_NODE ? glob_def_interval :
352-
var_def_interval;
353-
}
354-
355346
};
356347

357348
// Debugging helpers see

opencog/atoms/pattern/BindLink.cc

+12-1
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,21 @@ BindLink::BindLink(const HandleSeq& hseq, Type t)
6666
/** Wrap query results in a SetLink, place them in the AtomSpace. */
6767
ValuePtr BindLink::execute(AtomSpace* as, bool silent)
6868
{
69+
ValueSet rslt(do_execute(as, silent));
70+
71+
// If there is an anchor, then attach results to the anchor.
72+
// Otherwise, create a SetLink and return that.
73+
if (_variables._anchor and as)
74+
{
75+
for (const ValuePtr& v: rslt)
76+
as->add_link(MEMBER_LINK, HandleCast(v), _variables._anchor);
77+
78+
return _variables._anchor;
79+
}
80+
6981
// The result_set contains a list of the grounded expressions.
7082
// (The order of the list has no significance, so it's really a set.)
7183
// Put the set into a SetLink, cache it, and return that.
72-
ValueSet rslt(do_execute(as, silent));
7384
HandleSeq hlist;
7485
for (const ValuePtr& v: rslt) hlist.emplace_back(HandleCast(v));
7586
Handle rewr(createUnorderedLink(hlist, SET_LINK));

opencog/atoms/pattern/DualLink.cc

+15
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ ValuePtr DualLink::execute(AtomSpace* as, bool silent)
7373
if (nullptr == as) as = _atom_space;
7474
Recognizer reco(as);
7575
satisfy(reco);
76+
77+
// If there is an anchor, then attach results to the anchor.
78+
// Otherwise, create a SetLink and return that.
79+
// XXX FIXME ... at this time, there is no documented way of
80+
// squeezing an AnchorLink into a DualLink. So the below
81+
// if-statement is never taken. Some additional design work
82+
// is needed.
83+
if (_variables._anchor and as)
84+
{
85+
for (const Handle& h : reco._rules)
86+
as->add_link(MEMBER_LINK, h, _variables._anchor);
87+
88+
return _variables._anchor;
89+
}
90+
7691
return as->add_atom(createUnorderedLink(reco._rules, SET_LINK));
7792
}
7893

opencog/atoms/pattern/GetLink.cc

+11
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ HandleSet GetLink::do_execute(AtomSpace* as, bool silent)
6060

6161
ValuePtr GetLink::execute(AtomSpace* as, bool silent)
6262
{
63+
// If there is an anchor, then attach results to the anchor.
64+
// Otherwise, create a SetLink and return that.
65+
if (_variables._anchor and as)
66+
{
67+
HandleSet hs(do_execute(as, silent));
68+
for (const Handle& h : hs)
69+
as->add_link(MEMBER_LINK, h, _variables._anchor);
70+
71+
return _variables._anchor;
72+
}
73+
6374
// Create the satisfying set, and cache it.
6475
Handle satset(createUnorderedLink(do_execute(as, silent), SET_LINK));
6576

opencog/atoms/pattern/SatisfactionLink.cc

+6-7
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,12 @@ TruthValuePtr SatisfactionLink::evaluate(AtomSpace* as, bool silent)
5252
Satisfier sater(as);
5353
satisfy(sater);
5454

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

6362
return sater._result;
6463
}

tests/query/AnchorUTest.cxxtest

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* tests/query/AnchorUTest.cxxtest
3+
*
4+
* Copyright (C) 2020 Linas Vepstas
5+
* All Rights Reserved
6+
*
7+
* This program is free software; you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License v3 as
9+
* published by the Free Software Foundation and including the exceptions
10+
* at http://opencog.org/wiki/Licenses
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program; if not, write to:
19+
* Free Software Foundation, Inc.,
20+
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21+
*/
22+
23+
#include <thread>
24+
25+
#include <opencog/guile/SchemeEval.h>
26+
#include <opencog/atoms/truthvalue/SimpleTruthValue.h>
27+
#include <opencog/atomspace/AtomSpace.h>
28+
#include <opencog/util/Logger.h>
29+
#include <cxxtest/TestSuite.h>
30+
31+
using namespace opencog;
32+
33+
#define al as->add_link
34+
#define an as->add_node
35+
36+
class AnchorUTest: public CxxTest::TestSuite
37+
{
38+
private:
39+
AtomSpace* as;
40+
41+
public:
42+
AnchorUTest(void)
43+
{
44+
// logger().set_level(Logger::FINE);
45+
logger().set_print_to_stdout_flag(true);
46+
logger().set_timestamp_flag(false);
47+
48+
as = new AtomSpace();
49+
SchemeEval* eval = SchemeEval::get_evaluator(as);
50+
eval->eval("(use-modules (opencog exec))");
51+
eval->eval("(add-to-load-path \"" PROJECT_SOURCE_DIR "\")");
52+
}
53+
54+
~AnchorUTest()
55+
{
56+
delete as;
57+
// Erase the log file if no assertions failed.
58+
if (!CxxTest::TestTracker::tracker().suiteFailed())
59+
std::remove(logger().get_filename().c_str());
60+
}
61+
62+
void setUp(void);
63+
void tearDown(void);
64+
65+
void Setter(void);
66+
void Getter(void);
67+
68+
void test_get(void);
69+
void test_bind(void);
70+
void xtest_dual(void);
71+
};
72+
73+
void AnchorUTest::tearDown(void)
74+
{
75+
}
76+
77+
void AnchorUTest::setUp(void)
78+
{
79+
}
80+
81+
/*
82+
* GetLink unit test.
83+
*/
84+
void AnchorUTest::test_get(void)
85+
{
86+
logger().debug("BEGIN TEST: %s", __FUNCTION__);
87+
88+
SchemeEval* eval = SchemeEval::get_evaluator(as);
89+
eval->eval("(load-from-path \"tests/query/anchor.scm\")");
90+
91+
Handle anchor = eval->eval_h("(cog-execute! getli)");
92+
93+
IncomingSet results = anchor->getIncomingSetByType(MEMBER_LINK);
94+
95+
TSM_ASSERT_EQUALS("Expecting three answers", results.size(), 3);
96+
97+
logger().debug("END TEST: %s", __FUNCTION__);
98+
}
99+
100+
/*
101+
* BindLink unit test.
102+
*/
103+
void AnchorUTest::test_bind(void)
104+
{
105+
logger().debug("BEGIN TEST: %s", __FUNCTION__);
106+
107+
SchemeEval* eval = SchemeEval::get_evaluator(as);
108+
eval->eval("(load-from-path \"tests/query/anchor.scm\")");
109+
110+
Handle anchor = eval->eval_h("(cog-execute! bindli)");
111+
112+
IncomingSet results = anchor->getIncomingSetByType(MEMBER_LINK);
113+
114+
TSM_ASSERT_EQUALS("Expecting three answers", results.size(), 3);
115+
116+
logger().debug("END TEST: %s", __FUNCTION__);
117+
}
118+
119+
/*
120+
* DualLink unit test.
121+
* XXX FIXME -- there is currently no documented way of squeezing
122+
* an AnchorNode into an SRAI search, and so the unit test below
123+
* canot possibly pass until that design issue is fixed...
124+
*/
125+
void AnchorUTest::xtest_dual(void)
126+
{
127+
logger().debug("BEGIN TEST: %s", __FUNCTION__);
128+
129+
SchemeEval* eval = SchemeEval::get_evaluator(as);
130+
eval->eval("(load-from-path \"tests/query/anchor.scm\")");
131+
132+
Handle anchor = eval->eval_h("(cog-execute! (Dual srai))");
133+
134+
IncomingSet results = anchor->getIncomingSetByType(MEMBER_LINK);
135+
136+
TSM_ASSERT_EQUALS("Expecting two answers", results.size(), 2);
137+
138+
logger().debug("END TEST: %s", __FUNCTION__);
139+
}

tests/query/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ IF (HAVE_GUILE)
5858
ADD_CXXTEST(ArcanaUTest)
5959
ADD_CXXTEST(SubstitutionUTest)
6060
ADD_CXXTEST(GetLinkUTest)
61+
ADD_CXXTEST(AnchorUTest)
6162
ADD_CXXTEST(NotLinkUTest)
6263
ADD_CXXTEST(GetStateUTest)
6364
ADD_CXXTEST(DeepTypeUTest)

0 commit comments

Comments
 (0)