From 4435a285dd74d3ae17d8b40beb5d615e89bbebd1 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Wed, 27 Feb 2019 20:59:54 -0600 Subject: [PATCH 01/17] Add new QueryLink --- opencog/atoms/atom_types/atom_types.script | 9 +- opencog/atoms/pattern/CMakeLists.txt | 2 + opencog/atoms/pattern/QueryLink.cc | 215 +++++++++++++++++++++ opencog/atoms/pattern/QueryLink.h | 71 +++++++ opencog/query/README.md | 4 +- 5 files changed, 296 insertions(+), 5 deletions(-) create mode 100644 opencog/atoms/pattern/QueryLink.cc create mode 100644 opencog/atoms/pattern/QueryLink.h diff --git a/opencog/atoms/atom_types/atom_types.script b/opencog/atoms/atom_types/atom_types.script index 68b883340c..cd0f0c27db 100644 --- a/opencog/atoms/atom_types/atom_types.script +++ b/opencog/atoms/atom_types/atom_types.script @@ -355,9 +355,9 @@ PATTERN_LINK <- PRENEX_LINK // executed. The distinction is made for the benefit of the C++ code, // so that it can dispatch appropriately, based on the base type. -// Finds all groundings, return TV +// Finds all groundings, return binary truth value (true/false) SATISFACTION_LINK <- PATTERN_LINK,EVALUATABLE_LINK -// Finds all groundings, return LinkValue +// Finds all groundings, returns generic Value SATISFYING_LINK <- PATTERN_LINK,EVALUATABLE_LINK // The GetLink is almost exactly the same thing as a SatsifyingSetLink, @@ -365,8 +365,11 @@ SATISFYING_LINK <- PATTERN_LINK,EVALUATABLE_LINK // declarative. Likewise, BindLink is exactly the same thing as an // ImplicationLink, except that its imperative, not declarative. // Both return SetLinks holding the results. +// QueryLink is identical to BindLink, except it returns a LinkValue +// holding the result, instead of a SetLink. (Less atomspace pollution). GET_LINK <- SATISFYING_LINK // Finds all groundings, returns them -BIND_LINK <- SATISFYING_LINK // Finds all groundings, substitutes. +QUERY_LINK <- SATISFYING_LINK // Finds all groundings, substitutes. +BIND_LINK <- QUERY_LINK // Finds all groundings, substitutes. // Adjoint to the GetLink. This is "adjoint" in the sense that the roles // of the pattern and the grounding are reversed: given a grounding, the diff --git a/opencog/atoms/pattern/CMakeLists.txt b/opencog/atoms/pattern/CMakeLists.txt index 094f3d0732..61dac4f281 100644 --- a/opencog/atoms/pattern/CMakeLists.txt +++ b/opencog/atoms/pattern/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_LIBRARY (lambda PatternTerm.cc PatternUtils.cc Pattern.cc + QueryLink.cc SatisfactionLink.cc ) @@ -35,6 +36,7 @@ INSTALL (FILES Pattern.h PatternTerm.h PatternUtils.h + QueryLink.h SatisfactionLink.h DESTINATION "include/opencog/atoms/pattern" ) diff --git a/opencog/atoms/pattern/QueryLink.cc b/opencog/atoms/pattern/QueryLink.cc new file mode 100644 index 0000000000..7139227f66 --- /dev/null +++ b/opencog/atoms/pattern/QueryLink.cc @@ -0,0 +1,215 @@ +/* + * QueryLink.cc + * + * Copyright (C) 2009, 2014, 2015 Linas Vepstas + * + * Author: Linas Vepstas January 2009 + * + * 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 +#include +#include +#include + +#include "QueryLink.h" + +using namespace opencog; + +void QueryLink::init(void) +{ + Type t = get_type(); + if (not nameserver().isA(t, QUERY_LINK)) + { + const std::string& tname = nameserver().getTypeName(t); + throw InvalidParamException(TRACE_INFO, + "Expecting a QueryLink, got %s", tname.c_str()); + } + + extract_variables(_outgoing); + unbundle_clauses(_body); + common_init(); + setup_components(); + _pat.redex_name = "anonymous QueryLink"; +} + +QueryLink::QueryLink(const Handle& vardecl, + const Handle& body, + const Handle& rewrite) + : QueryLink(HandleSeq{vardecl, body, rewrite}) +{} + +QueryLink::QueryLink(const Handle& body, const Handle& rewrite) + : QueryLink(HandleSeq{body, rewrite}) +{} + +QueryLink::QueryLink(const HandleSeq& hseq, Type t) + : PatternLink(hseq, t) +{ + init(); +} + +QueryLink::QueryLink(const Link &l) + : PatternLink(l) +{ + init(); +} + +/* ================================================================= */ +/// +/// Find and unpack variable declarations, if any; otherwise, just +/// find all free variables. +/// +/// On top of that initialize _body and _implicand with the +/// clauses and the rewrite rule. +/// +void QueryLink::extract_variables(const HandleSeq& oset) +{ + size_t sz = oset.size(); + if (sz < 2 or 3 < sz) + throw InvalidParamException(TRACE_INFO, + "Expecting an outgoing set size of at most two, got %d", sz); + + // If the outgoing set size is two, then there are no variable + // declarations; extract all free variables. + if (2 == sz) + { + _body = oset[0]; + _implicand = oset[1]; + _varlist.find_variables(oset[0]); + return; + } + + // If we are here, then the first outgoing set member should be + // a variable declaration. + _vardecl = oset[0]; + _body = oset[1]; + _implicand = oset[2]; + + // Initialize _varlist with the scoped variables + init_scoped_variables(oset[0]); +} + +/* ================================================================= */ +/* ================================================================= */ +/** + * Execute a QueryLink + * + * Given a QueryLink containing variable declarations, a predicate and + * an implicand, this method will "execute" the implication, matching + * the predicate, and creating a grounded implicand, assuming the + * predicate can be satisfied. Thus, for example, given the structure + * + * QueryLink + * VariableList + * VariableNode "$var0" + * VariableNode "$var1" + * AndList + * etc ... + * + * The whole point of the QueryLink is to do nothing more than + * to indicate the bindings of the variables, and (optionally) limit + * the types of acceptable groundings for the variables. + * + * Use the default implicator to find pattern-matches. Associated truth + * values are completely ignored during pattern matching; if a set of + * atoms that could be a ground are found in the atomspace, then they + * will be reported. + */ +HandleSet QueryLink::do_execute(AtomSpace* as, bool silent) +{ + if (nullptr == as) as = _atom_space; + + DefaultImplicator impl(as); + impl.implicand = this->get_implicand(); + + /* + * The `do_conn_check` flag stands for "do connectivity check"; if the + * flag is set, and the pattern is disconnected, then an error will be + * thrown. The URE explicitly allows disconnected graphs. + * + * Set the default to always allow disconnected graphs. This will + * get naive users into trouble, but there are legit uses, not just + * in the URE, for doing disconnected searches. + */ + bool do_conn_check=false; + if (do_conn_check and 0 == _virtual.size() and 1 < _components.size()) + throw InvalidParamException(TRACE_INFO, + "QueryLink consists of multiple " + "disconnected components!"); + + this->PatternLink::satisfy(impl); + + // If we got a non-empty answer, just return it. + if (0 < impl.get_result_set().size()) + { + // The result_set contains a list of the grounded expressions. + // (The order of the list has no significance, so it's really a set.) + return impl.get_result_set(); + } + + // If we are here, then there were zero matches. + // + // There are certain useful queries, where the goal of the query + // is to determine that some clause or set of clauses are absent + // from the AtomSpace. If the clauses are jointly not found, after + // a full and exhaustive search, then we want to run the implicator, + // and perform some action. Easier said than done, this code is + // currently a bit of a hack. It seems to work, per the AbsentUTest + // but is perhaps a bit fragile in its assumptions. + // + // Theoretical background: the atomspace can be thought of as a + // Kripke frame: it holds everything we know "right now". The + // AbsentLink is a check for what we don't know, right now. + const Pattern& pat = this->get_pattern(); + DefaultPatternMatchCB* intu = + dynamic_cast(&impl); + if (0 == pat.mandatory.size() and 0 < pat.optionals.size() + and not intu->optionals_present()) + { + HandleSet result; + result.insert(HandleCast(impl.inst.execute(impl.implicand, true))); + return result; + } + + return HandleSet(); +} + +ValuePtr QueryLink::execute(AtomSpace* as, bool silent) +{ + // The result_list 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. + Handle rewr(createUnorderedLink(do_execute(as, silent), SET_LINK)); + +#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. + rewr = as->add_atom(rewr); +#endif /* PLACE_RESULTS_IN_ATOMSPACE */ + return rewr; +} + +/* ================================================================= */ + +DEFINE_LINK_FACTORY(QueryLink, QUERY_LINK) + +/* ===================== END OF FILE ===================== */ diff --git a/opencog/atoms/pattern/QueryLink.h b/opencog/atoms/pattern/QueryLink.h new file mode 100644 index 0000000000..a8d2bc1c8b --- /dev/null +++ b/opencog/atoms/pattern/QueryLink.h @@ -0,0 +1,71 @@ +/* + * opencog/atoms/pattern/QueryLink.h + * + * Copyright (C) 2015 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. + */ +#ifndef _OPENCOG_QUERY_LINK_H +#define _OPENCOG_QUERY_LINK_H + +#include + +namespace opencog +{ +/** \addtogroup grp_atomspace + * @{ + */ +class QueryLink : public PatternLink +{ +protected: + void init(void); + + /// The rewrite term + Handle _implicand; + + // Overwrite PatternLink::extract_variables as QueryLink has one + // more outgoing for the rewrite rule. In addition this method + // will initialize the rewrite term _implicand. + void extract_variables(const HandleSeq& oset); + + virtual HandleSet do_execute(AtomSpace*, bool silent); + +public: + QueryLink(const HandleSeq&, Type=QUERY_LINK); + QueryLink(const Handle& vardecl, const Handle& body, const Handle& rewrite); + QueryLink(const Handle& body, const Handle& rewrite); + explicit QueryLink(const Link &l); + + const Handle& get_implicand(void) { return _implicand; } + + virtual ValuePtr execute(AtomSpace*, bool silent=false); + + static Handle factory(const Handle&); +}; + +typedef std::shared_ptr QueryLinkPtr; +static inline QueryLinkPtr QueryLinkCast(const Handle& h) + { AtomPtr a(h); return std::dynamic_pointer_cast(a); } +static inline QueryLinkPtr QueryLinkCast(AtomPtr a) + { return std::dynamic_pointer_cast(a); } + +#define createQueryLink std::make_shared + +/** @}*/ +} + +#endif // _OPENCOG_QUERY_LINK_H diff --git a/opencog/query/README.md b/opencog/query/README.md index be8b86976b..04ecb365cf 100644 --- a/opencog/query/README.md +++ b/opencog/query/README.md @@ -693,7 +693,7 @@ be faster? I suppose that depends on the actual query... Forward Chainer --------------- -The `PatternMatch::imply()` method implements a critical component for a +The `PatternLink::satisfy()` method implements a critical component for a forward chainer: it is able to accept a BindLink, containing a pattern and a rewriting rule, and basically implement a form of IF ... THEN ... statement, expressed as an AtomSpace hypergraph. @@ -706,7 +706,7 @@ variables are found, then a hypergraph is created based on the implicand, using the grounded values found. Because there may be more than one grounding, a SetLink of all grounded implicands is returned. -Thus, the `PatternbMatch::imply()` method can be used to implement a +Thus, the `PatternLink::satisfy()` method can be used to implement a simple forward-chainer. For example, one may create a collection of BindLinks. Then, calling each in turn, from a loop, will cause each to be evaluated. Thus, N iterations of the loop is equivalent to chaining From 6ee033ba5d4a32b2650ddad502dbb704ba84490d Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Wed, 27 Feb 2019 21:14:35 -0600 Subject: [PATCH 02/17] Make BindLink inherit from QueryLink --- opencog/atoms/pattern/BindLink.cc | 133 ++--------------------------- opencog/atoms/pattern/BindLink.h | 16 +--- opencog/atoms/pattern/QueryLink.cc | 26 +++--- 3 files changed, 17 insertions(+), 158 deletions(-) diff --git a/opencog/atoms/pattern/BindLink.cc b/opencog/atoms/pattern/BindLink.cc index 240a50270d..7afb224b95 100644 --- a/opencog/atoms/pattern/BindLink.cc +++ b/opencog/atoms/pattern/BindLink.cc @@ -24,9 +24,8 @@ */ #include -#include #include -#include +#include #include "BindLink.h" @@ -42,10 +41,6 @@ void BindLink::init(void) "Expecting a BindLink, got %s", tname.c_str()); } - extract_variables(_outgoing); - unbundle_clauses(_body); - common_init(); - setup_components(); _pat.redex_name = "anonymous BindLink"; } @@ -60,140 +55,24 @@ BindLink::BindLink(const Handle& body, const Handle& rewrite) {} BindLink::BindLink(const HandleSeq& hseq, Type t) - : PatternLink(hseq, t) + : QueryLink(hseq, t) { init(); } BindLink::BindLink(const Link &l) - : PatternLink(l) + : QueryLink(l) { init(); } -/* ================================================================= */ -/// -/// Find and unpack variable declarations, if any; otherwise, just -/// find all free variables. -/// -/// On top of that initialize _body and _implicand with the -/// clauses and the rewrite rule. -/// -void BindLink::extract_variables(const HandleSeq& oset) -{ - size_t sz = oset.size(); - if (sz < 2 or 3 < sz) - throw InvalidParamException(TRACE_INFO, - "Expecting an outgoing set size of at most two, got %d", sz); - - // If the outgoing set size is two, then there are no variable - // declarations; extract all free variables. - if (2 == sz) - { - _body = oset[0]; - _implicand = oset[1]; - _varlist.find_variables(oset[0]); - return; - } - - // If we are here, then the first outgoing set member should be - // a variable declaration. - _vardecl = oset[0]; - _body = oset[1]; - _implicand = oset[2]; - - // Initialize _varlist with the scoped variables - init_scoped_variables(oset[0]); -} - /* ================================================================= */ /* ================================================================= */ -/** - * Execute a BindLink - * - * Given a BindLink containing variable declarations, a predicate and - * an implicand, this method will "execute" the implication, matching - * the predicate, and creating a grounded implicand, assuming the - * predicate can be satisfied. Thus, for example, given the structure - * - * BindLink - * VariableList - * VariableNode "$var0" - * VariableNode "$var1" - * AndList - * etc ... - * - * The whole point of the BindLink is to do nothing more than - * to indicate the bindings of the variables, and (optionally) limit - * the types of acceptable groundings for the variables. - * - * Use the default implicator to find pattern-matches. Associated truth - * values are completely ignored during pattern matching; if a set of - * atoms that could be a ground are found in the atomspace, then they - * will be reported. - */ -HandleSet BindLink::do_execute(AtomSpace* as, bool silent) -{ - if (nullptr == as) as = _atom_space; - - DefaultImplicator impl(as); - impl.implicand = this->get_implicand(); - - /* - * The `do_conn_check` flag stands for "do connectivity check"; if the - * flag is set, and the pattern is disconnected, then an error will be - * thrown. The URE explicitly allows disconnected graphs. - * - * Set the default to always allow disconnected graphs. This will - * get naive users into trouble, but there are legit uses, not just - * in the URE, for doing disconnected searches. - */ - bool do_conn_check=false; - if (do_conn_check and 0 == _virtual.size() and 1 < _components.size()) - throw InvalidParamException(TRACE_INFO, - "BindLink consists of multiple " - "disconnected components!"); - - this->PatternLink::satisfy(impl); - - // If we got a non-empty answer, just return it. - if (0 < impl.get_result_set().size()) - { - // The result_set contains a list of the grounded expressions. - // (The order of the list has no significance, so it's really a set.) - return impl.get_result_set(); - } - - // If we are here, then there were zero matches. - // - // There are certain useful queries, where the goal of the query - // is to determine that some clause or set of clauses are absent - // from the AtomSpace. If the clauses are jointly not found, after - // a full and exhaustive search, then we want to run the implicator, - // and perform some action. Easier said than done, this code is - // currently a bit of a hack. It seems to work, per the AbsentUTest - // but is perhaps a bit fragile in its assumptions. - // - // Theoretical background: the atomspace can be thought of as a - // Kripke frame: it holds everything we know "right now". The - // AbsentLink is a check for what we don't know, right now. - const Pattern& pat = this->get_pattern(); - DefaultPatternMatchCB* intu = - dynamic_cast(&impl); - if (0 == pat.mandatory.size() and 0 < pat.optionals.size() - and not intu->optionals_present()) - { - HandleSet result; - result.insert(HandleCast(impl.inst.execute(impl.implicand, true))); - return result; - } - - return HandleSet(); -} +/** Wrap query results in a SetLink, place them in the AtomSpace. */ ValuePtr BindLink::execute(AtomSpace* as, bool silent) { - // The result_list contains a list of the grounded expressions. + // 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. Handle rewr(createUnorderedLink(do_execute(as, silent), SET_LINK)); @@ -208,8 +87,6 @@ ValuePtr BindLink::execute(AtomSpace* as, bool silent) return rewr; } -/* ================================================================= */ - DEFINE_LINK_FACTORY(BindLink, BIND_LINK) /* ===================== END OF FILE ===================== */ diff --git a/opencog/atoms/pattern/BindLink.h b/opencog/atoms/pattern/BindLink.h index 6798741206..faba783ad9 100644 --- a/opencog/atoms/pattern/BindLink.h +++ b/opencog/atoms/pattern/BindLink.h @@ -22,36 +22,24 @@ #ifndef _OPENCOG_BIND_LINK_H #define _OPENCOG_BIND_LINK_H -#include +#include namespace opencog { /** \addtogroup grp_atomspace * @{ */ -class BindLink : public PatternLink +class BindLink : public QueryLink { protected: void init(void); - /// The rewrite term - Handle _implicand; - - // Overwrite PatternLink::extract_variables as BindLink has one - // more outgoing for the rewrite rule. In addition this method - // will initialize the rewrite term _implicand. - void extract_variables(const HandleSeq& oset); - - virtual HandleSet do_execute(AtomSpace*, bool silent); - public: BindLink(const HandleSeq&, Type=BIND_LINK); BindLink(const Handle& vardecl, const Handle& body, const Handle& rewrite); BindLink(const Handle& body, const Handle& rewrite); explicit BindLink(const Link &l); - const Handle& get_implicand(void) { return _implicand; } - virtual ValuePtr execute(AtomSpace*, bool silent=false); static Handle factory(const Handle&); diff --git a/opencog/atoms/pattern/QueryLink.cc b/opencog/atoms/pattern/QueryLink.cc index 7139227f66..03f5261d28 100644 --- a/opencog/atoms/pattern/QueryLink.cc +++ b/opencog/atoms/pattern/QueryLink.cc @@ -24,9 +24,9 @@ */ #include -#include -#include #include +#include +#include #include "QueryLink.h" @@ -193,22 +193,16 @@ HandleSet QueryLink::do_execute(AtomSpace* as, bool silent) ValuePtr QueryLink::execute(AtomSpace* as, bool silent) { - // The result_list contains a list of the grounded expressions. + // 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. - Handle rewr(createUnorderedLink(do_execute(as, silent), SET_LINK)); - -#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. - rewr = as->add_atom(rewr); -#endif /* PLACE_RESULTS_IN_ATOMSPACE */ - return rewr; -} + HandleSet rewr = do_execute(as, silent); -/* ================================================================= */ + std::vector vali; + for (const Handle& h : rewr) + vali.push_back(h); + + return createLinkValue(vali); +} DEFINE_LINK_FACTORY(QueryLink, QUERY_LINK) From 171e6c6029b4fd80d450603caeca8e720ff87f84 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Wed, 27 Feb 2019 21:33:02 -0600 Subject: [PATCH 03/17] Start work on a demo of the new function. --- examples/pattern-matcher/query.scm | 62 ++++++++++++++++++++++++++++++ opencog/atoms/pattern/QueryLink.cc | 2 +- 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 examples/pattern-matcher/query.scm diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm new file mode 100644 index 0000000000..6a9ca8c4b1 --- /dev/null +++ b/examples/pattern-matcher/query.scm @@ -0,0 +1,62 @@ +; +; query.scm -- Parallel queries (parallel pattern matching) +; +; QueryLink usage example. +; +; The QueryLink and the BindLink are both very similar; both search +; the atomspace for groundings of the query pattern, and then perform +; a re-write, based on the results. The only difference between the two +; is that the BindLink returns a SetLink containing the results, whereas +; the QueryLink returns a LinkValue containing the results. This makes +; the QueryLink a bit nicer, because it does not pollute the AtomSpace +; with nearly-useless SetLinks. +; +; Although both can be run in parallel (i.e. run in different threads), +; there's almost no point to doing so for the BindLink, since you have +; to wait for it to complete, and drop the result SetLink on your +; doorstep. By contrast, they QueryLink can drop off results at a +; "well-known location" in the atomspace, as they appear, so that +; processing can happen in parallel: processing can start on some +; results, even while others are still being found. +; +; This example uses an AchorNode to establish a "well-known location", +; the ParallelLink to run multiple threads, and other trickery to create +; a parallel processing pipeline. +; + +(use-modules (opencog) (opencog exec)) + +; Define a very simple satisfaction link. +(define satlink + (Satisfaction + (Evaluation + (Predicate "foobar") + (List + (Concept "funny") + (Variable "$x"))))) + +; Create something that will satisfy the above. +(Evaluation + (Predicate "foobar") + (List + (Concept "funny") + (Concept "thing"))) + +; Actually run it - this should return TrueTV i.e. `(stv 1 1)` +; because the SatisfactionLink is satisfiable. +(cog-evaluate! satlink) + +; Print a list of all the keys attached to the SatisfactionLink. +(cog-keys satlink) + +; The one and only key printed above should be the below. +(define pgk (Predicate "*-PatternGroundingsKey-*")) + +; Get the value associated with this key. +; Ah Ha! It should print `(Concept "thing")` which is the +; value that grounded the `(Variable "$x")`. +; +; This value is cached indefinitely - until the next time that +; the SatisfactionLink is evaluated. If it evaluates to false, +; the old cached value is NOT cleared! +(cog-value satlink pgk) diff --git a/opencog/atoms/pattern/QueryLink.cc b/opencog/atoms/pattern/QueryLink.cc index 03f5261d28..12e886691f 100644 --- a/opencog/atoms/pattern/QueryLink.cc +++ b/opencog/atoms/pattern/QueryLink.cc @@ -1,7 +1,7 @@ /* * QueryLink.cc * - * Copyright (C) 2009, 2014, 2015 Linas Vepstas + * Copyright (C) 2009, 2014, 2015, 2019 Linas Vepstas * * Author: Linas Vepstas January 2009 * From 7a0dc4691ca1f83be056df76e1ef64c80667f13d Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Wed, 27 Feb 2019 21:54:31 -0600 Subject: [PATCH 04/17] continue developing the example of QueryLink --- examples/pattern-matcher/query.scm | 31 ++++++++++++++++------------ opencog/atoms/pattern/PatternLink.cc | 8 +++---- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index 6a9ca8c4b1..d002d2b05b 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -26,21 +26,26 @@ (use-modules (opencog) (opencog exec)) -; Define a very simple satisfaction link. -(define satlink - (Satisfaction +; Create three bits of "knowledge". +(Evaluation + (Predicate "foobar") (List (Concept "funny") (Concept "thing"))) +(Evaluation + (Predicate "foobar") (List (Concept "funny") (Concept "story"))) +(Evaluation + (Predicate "foobar") (List (Concept "funny") (Concept "joke"))) + +; Define a simple query. It looks for the funny stuff, and attaches +; the result to an AnchorNode +(define query + (Query + (TypedVariable (Variable "$x") (Type 'ConceptNode)) (Evaluation (Predicate "foobar") - (List - (Concept "funny") - (Variable "$x"))))) - -; Create something that will satisfy the above. -(Evaluation - (Predicate "foobar") - (List - (Concept "funny") - (Concept "thing"))) + (List (Concept "funny") (Variable "$x"))) + (ListLink + (Anchor "*-query reults -*") + (Implication (Variable "$x") (Concept "laughable"))) + )) ; Actually run it - this should return TrueTV i.e. `(stv 1 1)` ; because the SatisfactionLink is satisfiable. diff --git a/opencog/atoms/pattern/PatternLink.cc b/opencog/atoms/pattern/PatternLink.cc index 3b30d22652..2dab339619 100644 --- a/opencog/atoms/pattern/PatternLink.cc +++ b/opencog/atoms/pattern/PatternLink.cc @@ -301,8 +301,8 @@ PatternLink::PatternLink(const HandleSeq& hseq, Type t) "Expecting a PatternLink, got %s", tname.c_str()); } - // BindLink uses a different initialization sequence. - if (BIND_LINK == t) return; + // QueryLink, BindLink use a different initialization sequence. + if (nameserver().isA(t, QUERY_LINK)) return; if (DUAL_LINK == t) return; init(); } @@ -319,8 +319,8 @@ PatternLink::PatternLink(const Link& l) "Expecting a PatternLink, got %s", tname.c_str()); } - // BindLink uses a different initialization sequence. - if (BIND_LINK == tscope) return; + // QueryLink, BindLink use a different initialization sequence. + if (nameserver().isA(tscope, QUERY_LINK)) return; if (DUAL_LINK == tscope) return; init(); } From 45048df576ff6435d82143378388c27f184091fa Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Wed, 27 Feb 2019 22:29:22 -0600 Subject: [PATCH 05/17] Get QueryLink to actually work. --- examples/pattern-matcher/query.scm | 21 +++------------------ opencog/atoms/execution/Instantiator.cc | 11 +++++++++-- opencog/atoms/execution/Instantiator.h | 6 +++--- opencog/atoms/value/LinkValue.h | 2 +- opencog/atoms/value/StringValue.h | 2 +- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index d002d2b05b..dc2e3e6c68 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -47,21 +47,6 @@ (Implication (Variable "$x") (Concept "laughable"))) )) -; Actually run it - this should return TrueTV i.e. `(stv 1 1)` -; because the SatisfactionLink is satisfiable. -(cog-evaluate! satlink) - -; Print a list of all the keys attached to the SatisfactionLink. -(cog-keys satlink) - -; The one and only key printed above should be the below. -(define pgk (Predicate "*-PatternGroundingsKey-*")) - -; Get the value associated with this key. -; Ah Ha! It should print `(Concept "thing")` which is the -; value that grounded the `(Variable "$x")`. -; -; This value is cached indefinitely - until the next time that -; the SatisfactionLink is evaluated. If it evaluates to false, -; the old cached value is NOT cleared! -(cog-value satlink pgk) +; Actually run it - this should return all of the results, wrapped +; in a LinkValue. +(cog-execute! query) diff --git a/opencog/atoms/execution/Instantiator.cc b/opencog/atoms/execution/Instantiator.cc index 60af64a7ba..9a31d4b9e5 100644 --- a/opencog/atoms/execution/Instantiator.cc +++ b/opencog/atoms/execution/Instantiator.cc @@ -536,8 +536,8 @@ bool Instantiator::not_self_match(Type t) * added to the atomspace, and its handle is returned. */ ValuePtr Instantiator::instantiate(const Handle& expr, - const HandleMap &vars, - bool silent) + const HandleMap &vars, + bool silent) { // throw, not assert, because this is a user error ... if (nullptr == expr) @@ -592,6 +592,13 @@ ValuePtr Instantiator::instantiate(const Handle& expr, return pap; } + // If there is a SatisfyingLink, we have to perform it + // and return the satisfying set. + if (nameserver().isA(t, SATISFYING_LINK)) + { + return expr->execute(_as, silent); + } + // Instantiate. Handle grounded(walk_tree(expr, silent)); diff --git a/opencog/atoms/execution/Instantiator.h b/opencog/atoms/execution/Instantiator.h index 93051c49d9..106c175ed2 100644 --- a/opencog/atoms/execution/Instantiator.h +++ b/opencog/atoms/execution/Instantiator.h @@ -135,12 +135,12 @@ class Instantiator } ValuePtr instantiate(const Handle& expr, - const HandleMap& vars, - bool silent=false); + const HandleMap& vars, + bool silent=false); ValuePtr execute(const Handle& expr, bool silent=false) { // If no actual instantiation is involved, then do not consume - // quotations, as it might change the semantics. (??) + // quotations, as it might change the semantics. (Huh ??) _consume_quotations = false; return instantiate(expr, HandleMap(), silent); } diff --git a/opencog/atoms/value/LinkValue.h b/opencog/atoms/value/LinkValue.h index 3b2a815eee..8253e10513 100644 --- a/opencog/atoms/value/LinkValue.h +++ b/opencog/atoms/value/LinkValue.h @@ -53,7 +53,7 @@ class LinkValue const std::vector& value() const { return _value; } /** Returns a string representation of the value. */ - virtual std::string to_string(const std::string& indent) const; + virtual std::string to_string(const std::string& indent = "") const; /** Returns true if the two atoms are equal, else false. */ virtual bool operator==(const Value&) const; diff --git a/opencog/atoms/value/StringValue.h b/opencog/atoms/value/StringValue.h index eb9b678e50..bef2042b85 100644 --- a/opencog/atoms/value/StringValue.h +++ b/opencog/atoms/value/StringValue.h @@ -55,7 +55,7 @@ class StringValue const std::vector& value() const { return _value; } /** Returns a string representation of the value. */ - virtual std::string to_string(const std::string& indent) const; + virtual std::string to_string(const std::string& indent = "") const; /** Returns true if the two atoms are equal. */ virtual bool operator==(const Value&) const; From ea2e4ab78469b17bba96b6b3e22ad2c8d5745b4e Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Wed, 27 Feb 2019 22:50:37 -0600 Subject: [PATCH 06/17] Continue developing the example. --- examples/pattern-matcher/query.scm | 50 ++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index dc2e3e6c68..97532c169c 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -20,8 +20,9 @@ ; results, even while others are still being found. ; ; This example uses an AchorNode to establish a "well-known location", -; the ParallelLink to run multiple threads, and other trickery to create -; a parallel processing pipeline. +; the ParallelLink to run multiple threads, and a DeleteLink to dettach +; results from the AnchorLink. The result is a toy parallel processing +; pipeline. ; (use-modules (opencog) (opencog exec)) @@ -43,10 +44,53 @@ (Predicate "foobar") (List (Concept "funny") (Variable "$x"))) (ListLink - (Anchor "*-query reults -*") + (Anchor "*-query results -*") (Implication (Variable "$x") (Concept "laughable"))) )) ; Actually run it - this should return all of the results, wrapped ; in a LinkValue. (cog-execute! query) + +; Define a second stage to the processing pipeline +(define absurd + (Query + (TypedVariable (Variable "$x") (Type 'ConceptNode)) + (ListLink + (Anchor "*-query results -*") + (Implication (Variable "$x") (Concept "laughable"))) + + ; After matching the above, perform the below. + (SequentialAnd + ; First, delete the attachment to the anchor + (True (Delete + (ListLink + (Anchor "*-query results -*") + (Implication (Variable "$x") (Concept "laughable"))))) + + ; Next, create an attachment to the second stage + (True + (ListLink + (Anchor "*-risible results -*") + (Implication (Variable "$x") (Concept "ludicrous"))))) + )) + +(define absurd + (Query + (TypedVariable (Variable "$x") (Type 'ConceptNode)) + (And + (Present (ListLink + (Anchor "*-query results -*") + (Implication (Variable "$x") (Concept "laughable")))) + (True (Delete (ListLink + (Anchor "*-query results -*") + (Implication (Variable "$x") (Concept "laughable")))))) + + ; After matching the above, perform the below. + ; Next, create an attachment to the second stage + (ListLink + (Anchor "*-risible results -*") + (Implication (Variable "$x") (Concept "ludicrous"))) + )) + +(cog-execute! absurd) From 06e746f843ec56c1a9f99105ea4b3f356e09f6c5 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Wed, 27 Feb 2019 22:57:06 -0600 Subject: [PATCH 07/17] Develop the example farther --- examples/pattern-matcher/query.scm | 49 +++++++++++++----------------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index 97532c169c..3689e5b1dc 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -44,7 +44,7 @@ (Predicate "foobar") (List (Concept "funny") (Variable "$x"))) (ListLink - (Anchor "*-query results -*") + (Anchor "*-query results-*") (Implication (Variable "$x") (Concept "laughable"))) )) @@ -52,45 +52,38 @@ ; in a LinkValue. (cog-execute! query) -; Define a second stage to the processing pipeline -(define absurd - (Query - (TypedVariable (Variable "$x") (Type 'ConceptNode)) - (ListLink - (Anchor "*-query results -*") - (Implication (Variable "$x") (Concept "laughable"))) - - ; After matching the above, perform the below. - (SequentialAnd - ; First, delete the attachment to the anchor - (True (Delete - (ListLink - (Anchor "*-query results -*") - (Implication (Variable "$x") (Concept "laughable"))))) - - ; Next, create an attachment to the second stage - (True - (ListLink - (Anchor "*-risible results -*") - (Implication (Variable "$x") (Concept "ludicrous"))))) - )) +; Take a look at the incoming set of the anchor point, and verify +; that the expected content is there. +(cog-incoming-set (Anchor "*-query results-*")) +; Define a second stage to the processing pipeline (define absurd (Query (TypedVariable (Variable "$x") (Type 'ConceptNode)) (And + ; Search at the earlier anchor point. (Present (ListLink - (Anchor "*-query results -*") + (Anchor "*-query results-*") (Implication (Variable "$x") (Concept "laughable")))) + + ; Immediately dettach from that anchor, by deleting the + ; ListLink that couples the two together. (True (Delete (ListLink - (Anchor "*-query results -*") + (Anchor "*-query results-*") (Implication (Variable "$x") (Concept "laughable")))))) - ; After matching the above, perform the below. - ; Next, create an attachment to the second stage + ; After matching the above, create an attachment to the + ; second stage anchor point. (ListLink - (Anchor "*-risible results -*") + (Anchor "*-risible results-*") (Implication (Variable "$x") (Concept "ludicrous"))) )) +; Run the query. See what happens. (cog-execute! absurd) + +; Verify that the old anchor point has been vacated, as expected. +(cog-incoming-set (Anchor "*-query results-*")) + +; Verify that the results are now at the new anchor +(cog-incoming-set (Anchor "*-risible results-*")) From 5b5083733d5a5d12500c97f45f58eba09f787ac3 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Thu, 28 Feb 2019 15:31:42 -0600 Subject: [PATCH 08/17] Continue developing the example. --- examples/pattern-matcher/query.scm | 48 ++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index 3689e5b1dc..58f4444184 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -27,6 +27,7 @@ (use-modules (opencog) (opencog exec)) +; ------------- ; Create three bits of "knowledge". (Evaluation (Predicate "foobar") (List (Concept "funny") (Concept "thing"))) @@ -35,6 +36,7 @@ (Evaluation (Predicate "foobar") (List (Concept "funny") (Concept "joke"))) +; ------------- ; Define a simple query. It looks for the funny stuff, and attaches ; the result to an AnchorNode (define query @@ -56,6 +58,7 @@ ; that the expected content is there. (cog-incoming-set (Anchor "*-query results-*")) +; ------------- ; Define a second stage to the processing pipeline (define absurd (Query @@ -87,3 +90,48 @@ ; Verify that the results are now at the new anchor (cog-incoming-set (Anchor "*-risible results-*")) + +; ------------- +; Now define a third stage of processing. This will generate output, +; i.e. it will print something to stdout. +; +(define (report-stuff NODE-A NODE-B) + (format #t "I think that ~A is ~A\n" + (cog-name NODE-A) (cog-name NODE-B)) + (SimpleTruthValue 1 1)) + +(define output + (Query + (VariableList + (TypedVariable (Variable "$x") (Type 'ConceptNode)) + (TypedVariable (Variable "$y") (Type 'ConceptNode))) + (And + ; Search at the earlier anchor point. + (Present (ListLink + (Anchor "*-risible results-*") + (Implication (Variable "$x") (Variable "$y")))) + + ; Immediately dettach from that anchor, by deleting the + ; ListLink that couples the two together. + (True (Delete (ListLink + (Anchor "*-risible results-*") + (Implication (Variable "$x") (Variable "$y")))))) + + ; After matching the above, print a report. + (ExecutionOutput + (GroundedSchema "scm:report-stuff") + (ListLink (Variable "$x") (Variable "$y"))) + )) + +; Run it. Verify that it works. +(cog-execute! output) + +; Run it a second time, verify that all inputs have been consumed. +(cog-execute! output) + +; Double-check that inputs have been consumed, by looking at the atnchor +; point. +(cog-incoming-set (AnchorNode "*-risible results-*")) + +; ------------- +; Now, assemble an automated processing pipeline. From e1dd0effbc612c4cb6c0abdf7b206f0ecd1a24b4 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Thu, 28 Feb 2019 23:04:13 -0600 Subject: [PATCH 09/17] Continue work on the example. --- examples/pattern-matcher/query.scm | 33 ++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index 58f4444184..f89c9b70b9 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -96,8 +96,10 @@ ; i.e. it will print something to stdout. ; (define (report-stuff NODE-A NODE-B) - (format #t "I think that ~A is ~A\n" - (cog-name NODE-A) (cog-name NODE-B)) + (format #t "I think that ~A is ~A. -- ~A\n" + (cog-name NODE-A) (cog-name NODE-B) + (strftime "%c" (localtime (current-time))) + ) (SimpleTruthValue 1 1)) (define output @@ -135,3 +137,30 @@ ; ------------- ; Now, assemble an automated processing pipeline. + +(define threads + (Parallel + (SequentialAnd + (True query) + (True (Sleep (Number 4))) + (True query) + (True (Sleep (Number 4))) + (True query)) + + (SequentialAnd + (True (Sleep (Number 1))) + (True absurd) + (True (Sleep (Number 4))) + (True absurd) + (True (Sleep (Number 4))) + (True absurd)) + + (SequentialAnd + (True (Sleep (Number 2))) + (True output) + (True (Sleep (Number 4))) + (True output) + (True (Sleep (Number 4))) + (True output)) + )) + From 59d405a29b907cea3cc838cf19ce8f27e4075540 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Thu, 28 Feb 2019 23:18:23 -0600 Subject: [PATCH 10/17] Add debug-prints to threads --- examples/pattern-matcher/query.scm | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index f89c9b70b9..f1a21cd38c 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -138,29 +138,55 @@ ; ------------- ; Now, assemble an automated processing pipeline. +; Print the current time +(define (prti N) + (format #t "Thread ~A. -- ~A\n" (cog-name N) + (strftime "%c" (localtime (current-time)))) + (SimpleTruthValue 1 1)) + +; Atom to print the current time +(define (prtime STR) + (Evaluation + (GroundedPredicate "scm:prti") + (Concept STR))) + +; When executed, this launches three threads, and returns to the +; caller without any delay. The threads run to conclusion, and +; then quietly exit. (define threads (Parallel (SequentialAnd + (prtime "step one A") (True query) (True (Sleep (Number 4))) + (prtime "step one B") (True query) (True (Sleep (Number 4))) + (prtime "step one C") (True query)) (SequentialAnd (True (Sleep (Number 1))) + (prtime "step two A") (True absurd) (True (Sleep (Number 4))) + (prtime "step two B") (True absurd) (True (Sleep (Number 4))) + (prtime "step two C") (True absurd)) (SequentialAnd (True (Sleep (Number 2))) + (prtime "step three A") (True output) (True (Sleep (Number 4))) + (prtime "step three B") (True output) (True (Sleep (Number 4))) + (prtime "step three C") (True output)) )) +; Run the multi-threaded pipeline +; (cog-execute! threads) From 72969e8eb366af7e1bcb7a5b3aa77c4a9400604f Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Thu, 28 Feb 2019 23:52:08 -0600 Subject: [PATCH 11/17] Make parallelLink executable, too --- opencog/atoms/execution/Instantiator.cc | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/opencog/atoms/execution/Instantiator.cc b/opencog/atoms/execution/Instantiator.cc index 9a31d4b9e5..b5a14b4625 100644 --- a/opencog/atoms/execution/Instantiator.cc +++ b/opencog/atoms/execution/Instantiator.cc @@ -599,6 +599,12 @@ ValuePtr Instantiator::instantiate(const Handle& expr, return expr->execute(_as, silent); } + // The thread-links are ambiguously executable/evaluatable. + if (nameserver().isA(t, PARALLEL_LINK)) + { + return ValueCast(EvaluationLink::do_evaluate(_as, expr, silent)); + } + // Instantiate. Handle grounded(walk_tree(expr, silent)); From a254fae05009aa504e326810bf21cf767c2e1ec7 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Fri, 1 Mar 2019 00:06:00 -0600 Subject: [PATCH 12/17] Atom hierarchy bug-fix --- opencog/atoms/atom_types/atom_types.script | 2 +- opencog/atoms/execution/EvaluationLink.cc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/opencog/atoms/atom_types/atom_types.script b/opencog/atoms/atom_types/atom_types.script index cd0f0c27db..938f9be5bb 100644 --- a/opencog/atoms/atom_types/atom_types.script +++ b/opencog/atoms/atom_types/atom_types.script @@ -358,7 +358,7 @@ PATTERN_LINK <- PRENEX_LINK // Finds all groundings, return binary truth value (true/false) SATISFACTION_LINK <- PATTERN_LINK,EVALUATABLE_LINK // Finds all groundings, returns generic Value -SATISFYING_LINK <- PATTERN_LINK,EVALUATABLE_LINK +SATISFYING_LINK <- PATTERN_LINK // The GetLink is almost exactly the same thing as a SatsifyingSetLink, // except that GetLink is imperative, while SatisfyingSetLink is diff --git a/opencog/atoms/execution/EvaluationLink.cc b/opencog/atoms/execution/EvaluationLink.cc index d480efae75..8579aa9a80 100644 --- a/opencog/atoms/execution/EvaluationLink.cc +++ b/opencog/atoms/execution/EvaluationLink.cc @@ -586,7 +586,7 @@ TruthValuePtr EvaluationLink::do_eval_scratch(AtomSpace* as, { Instantiator inst(as); Handle result(HandleCast(inst.execute(term, silent))); - scratch->add_atom(result); + if (result) scratch->add_atom(result); } } if (TRUE_LINK == t) From 808194f2138bbafb1433cf421ad73bcb7eac507a Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Fri, 1 Mar 2019 00:11:30 -0600 Subject: [PATCH 13/17] Fix up the demo --- examples/pattern-matcher/query.scm | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index f1a21cd38c..0efd5b89c2 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -13,16 +13,18 @@ ; ; Although both can be run in parallel (i.e. run in different threads), ; there's almost no point to doing so for the BindLink, since you have -; to wait for it to complete, and drop the result SetLink on your -; doorstep. By contrast, they QueryLink can drop off results at a -; "well-known location" in the atomspace, as they appear, so that +; to wait for it to complete, and provide you with the resulting +; SetLink. By contrast, the QueryLink can drop off results at a +; "well-known location" in the AtomSpace, as they appear, so that ; processing can happen in parallel: processing can start on some ; results, even while others are still being found. ; ; This example uses an AchorNode to establish a "well-known location", -; the ParallelLink to run multiple threads, and a DeleteLink to dettach -; results from the AnchorLink. The result is a toy parallel processing -; pipeline. +; a QueryLink to attache them there, and a DeleteLink to dettach results +; from the AnchorLink. The ParallelLink is used to run multiple threads, +; and SleepLink to slow it down enough to see what is happening. +; +; Taken as a whole, it demos a toy parallel processing pipeline. ; (use-modules (opencog) (opencog exec)) @@ -188,5 +190,7 @@ (True output)) )) -; Run the multi-threaded pipeline +; Run the multi-threaded pipeline. This should print some fairly verbose +; messages, which hopefully makes clear what is going on. +; ; (cog-execute! threads) From d51a74a36488b4b11a92cdb8f21469d3dfbf69f7 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Fri, 1 Mar 2019 00:13:10 -0600 Subject: [PATCH 14/17] spell-check --- examples/pattern-matcher/query.scm | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/pattern-matcher/query.scm b/examples/pattern-matcher/query.scm index 0efd5b89c2..12cdaf9a1a 100644 --- a/examples/pattern-matcher/query.scm +++ b/examples/pattern-matcher/query.scm @@ -4,7 +4,7 @@ ; QueryLink usage example. ; ; The QueryLink and the BindLink are both very similar; both search -; the atomspace for groundings of the query pattern, and then perform +; the AtomSpace for groundings of the query pattern, and then perform ; a re-write, based on the results. The only difference between the two ; is that the BindLink returns a SetLink containing the results, whereas ; the QueryLink returns a LinkValue containing the results. This makes @@ -19,9 +19,9 @@ ; processing can happen in parallel: processing can start on some ; results, even while others are still being found. ; -; This example uses an AchorNode to establish a "well-known location", -; a QueryLink to attache them there, and a DeleteLink to dettach results -; from the AnchorLink. The ParallelLink is used to run multiple threads, +; This example uses an AnchorNode to establish a "well-known location", +; a QueryLink to attache them there, and a DeleteLink to detach results +; from the AnchorNode. The ParallelLink is used to run multiple threads, ; and SleepLink to slow it down enough to see what is happening. ; ; Taken as a whole, it demos a toy parallel processing pipeline. @@ -71,7 +71,7 @@ (Anchor "*-query results-*") (Implication (Variable "$x") (Concept "laughable")))) - ; Immediately dettach from that anchor, by deleting the + ; Immediately detach from that anchor, by deleting the ; ListLink that couples the two together. (True (Delete (ListLink (Anchor "*-query results-*") @@ -115,7 +115,7 @@ (Anchor "*-risible results-*") (Implication (Variable "$x") (Variable "$y")))) - ; Immediately dettach from that anchor, by deleting the + ; Immediately detach from that anchor, by deleting the ; ListLink that couples the two together. (True (Delete (ListLink (Anchor "*-risible results-*") @@ -133,7 +133,7 @@ ; Run it a second time, verify that all inputs have been consumed. (cog-execute! output) -; Double-check that inputs have been consumed, by looking at the atnchor +; Double-check that inputs have been consumed, by looking at the anchor ; point. (cog-incoming-set (AnchorNode "*-risible results-*")) From 69969dcf38072b144de08d595af0e421ead36745 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Fri, 1 Mar 2019 00:14:34 -0600 Subject: [PATCH 15/17] Advertize the new example. --- examples/pattern-matcher/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/pattern-matcher/README.md b/examples/pattern-matcher/README.md index eb37523b0a..bf0fb0f30a 100644 --- a/examples/pattern-matcher/README.md +++ b/examples/pattern-matcher/README.md @@ -110,6 +110,7 @@ are the logics of theorem-proving, in general.) * `presence.scm` -- Testing for the presence of an Atom. * `absent.scm` -- Using the AbsentLink. * `value-of.scm` -- Looking for high or low TruthValues. +* `query.scm` -- Running queries in parallel. Pattern Recognition From 18e6329d5149180908c9e6acf7246b350dad0638 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Fri, 1 Mar 2019 11:48:24 -0600 Subject: [PATCH 16/17] Add QueryLink unit test --- tests/query/CMakeLists.txt | 1 + tests/query/QueryUTest.cxxtest | 101 +++++++++++++++++++++++++++++++++ tests/query/query.scm | 29 ++++++++++ 3 files changed, 131 insertions(+) create mode 100644 tests/query/QueryUTest.cxxtest create mode 100644 tests/query/query.scm diff --git a/tests/query/CMakeLists.txt b/tests/query/CMakeLists.txt index 064719a3ec..405fd21e6c 100644 --- a/tests/query/CMakeLists.txt +++ b/tests/query/CMakeLists.txt @@ -40,6 +40,7 @@ IF (HAVE_GUILE) ADD_CXXTEST(PatternCrashUTest) ADD_CXXTEST(DisconnectedUTest) ADD_CXXTEST(ImplicationUTest) + ADD_CXXTEST(QueryUTest) ADD_CXXTEST(ExecutionOutputUTest) ADD_CXXTEST(BindTVUTest) ADD_CXXTEST(EvalLinkDefaultTVUTest) diff --git a/tests/query/QueryUTest.cxxtest b/tests/query/QueryUTest.cxxtest new file mode 100644 index 0000000000..2faece067b --- /dev/null +++ b/tests/query/QueryUTest.cxxtest @@ -0,0 +1,101 @@ +/* + * tests/query/QueryUTest.cxxtest + * + * Copyright (C) 2017 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 + +#include +#include +#include +#include +#include + +using namespace opencog; + +#define al as->add_link +#define an as->add_node + +class QueryUTest: public CxxTest::TestSuite +{ +private: + AtomSpace* as; + +public: + QueryUTest(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 "\")"); + + } + + ~QueryUTest() + { + 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_basic(void); +}; + +void QueryUTest::tearDown(void) +{ +} + +void QueryUTest::setUp(void) +{ +} + +/* + * Basic QueryLink unit test. + */ +void QueryUTest::test_basic(void) +{ + logger().debug("BEGIN TEST: %s", __FUNCTION__); + + SchemeEval* eval = SchemeEval::get_evaluator(as); + std::string rc = eval->eval("(load-from-path \"tests/query/query.scm\")"); +printf("duuude its %s<<<\n", rc.c_str()); + TS_ASSERT_EQUALS(false, eval->eval_error()); + + ValuePtr v = eval->eval_v("(cog-execute! \"query\")"); + + TS_ASSERT_EQUALS(v->get_type(), LINK_VALUE); + + logger().debug("END TEST: %s", __FUNCTION__); +} + + +#undef al +#undef an diff --git a/tests/query/query.scm b/tests/query/query.scm new file mode 100644 index 0000000000..b05db96629 --- /dev/null +++ b/tests/query/query.scm @@ -0,0 +1,29 @@ +; +; query.scm -- QueryLink usage example. +; +; The QueryLink and the BindLink are both very similar ... + +(use-modules (opencog) (opencog exec)) + +; ------------- +; Create three bits of "knowledge". +(Evaluation + (Predicate "foobar") (List (Concept "funny") (Concept "thing"))) +(Evaluation + (Predicate "foobar") (List (Concept "funny") (Concept "story"))) +(Evaluation + (Predicate "foobar") (List (Concept "funny") (Concept "joke"))) + +; ------------- +; Define a simple query. It looks for the funny stuff, and attaches +; the result to an AnchorNode +(define query + (Query + (TypedVariable (Variable "$x") (Type 'ConceptNode)) + (Evaluation + (Predicate "foobar") + (List (Concept "funny") (Variable "$x"))) + (ListLink + (Anchor "*-query results-*") + (Implication (Variable "$x") (Concept "laughable"))) + )) From 1b7ead680066ccc185db1978f3eb5d458d5c2724 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Fri, 1 Mar 2019 12:04:09 -0600 Subject: [PATCH 17/17] Expand the new unit test --- tests/query/QueryUTest.cxxtest | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/tests/query/QueryUTest.cxxtest b/tests/query/QueryUTest.cxxtest index 2faece067b..25b0dbc496 100644 --- a/tests/query/QueryUTest.cxxtest +++ b/tests/query/QueryUTest.cxxtest @@ -63,9 +63,6 @@ public: void setUp(void); void tearDown(void); - void Setter(void); - void Getter(void); - void test_basic(void); }; @@ -85,13 +82,22 @@ void QueryUTest::test_basic(void) logger().debug("BEGIN TEST: %s", __FUNCTION__); SchemeEval* eval = SchemeEval::get_evaluator(as); - std::string rc = eval->eval("(load-from-path \"tests/query/query.scm\")"); -printf("duuude its %s<<<\n", rc.c_str()); + eval->eval("(load-from-path \"tests/query/query.scm\")"); TS_ASSERT_EQUALS(false, eval->eval_error()); - ValuePtr v = eval->eval_v("(cog-execute! \"query\")"); + // Can we create it? + Handle h = eval->eval_h("(Query (List (Variable \"x\")) (Variable \"x\"))"); + TS_ASSERT_EQUALS(h->get_type(), QUERY_LINK); + + // Can we execute it? + ValuePtr sv = h->execute(as); + TS_ASSERT_EQUALS(sv->get_type(), LINK_VALUE); + TS_ASSERT_EQUALS(LinkValueCast(sv)->value().size(), 0); + // A more complex query + ValuePtr v = eval->eval_v("(cog-execute! query)"); TS_ASSERT_EQUALS(v->get_type(), LINK_VALUE); + TS_ASSERT_EQUALS(LinkValueCast(v)->value().size(), 3); logger().debug("END TEST: %s", __FUNCTION__); }