Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement AlwaysLink (for-all link in pattern matcher) #2288

Merged
merged 11 commits into from
Jul 24, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions examples/pattern-matcher/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,9 @@ pattern matcher implements a form of "intuitionistic logic", or
"constructivist logic" under the covers. Perhaps not a surprise: these
are the logics of theorem-proving, in general.)

* `presence.scm` -- Testing for the presence of an Atom.
* `absent.scm` -- Using the AbsentLink.
* `presence.scm` -- Testing for presence of an Atom ("there-exists").
* `absent.scm` -- Using the AbsentLink ("there-does-not-exist").
* `always.scm` -- Testing if a clause always holds ("for-all").
* `value-of.scm` -- Looking for high or low TruthValues.
* `query.scm` -- Running queries in parallel.

Expand Down
65 changes: 65 additions & 0 deletions examples/pattern-matcher/always.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
;
; always.scm -- Demo illustrating AlwaysLink ("for-all")
;
; The most basic kinds of pattern searches are to see if some
; particular pattern can be found. These are "exists" searches;
; does the pattern exist? Sometimes one wants to have "for-all"
; searches: does **every** instance of the pattern **always**
; satisfy some predicate or term? Such searches can be implemented
; with the AlwaysLink.
;
; In this example, there are three baskets holding balls. Some
; hold balls of several different colors, some hold only balls
; that are all of the same color. The example sets up these baskets,
; and then issues a query to find the basket that holds only red
; balls, and no others.
;
(use-modules (opencog) (opencog exec))

; Three baskets holding balls
(Inheritance (Concept "reds basket") (Concept "basket"))
(Inheritance (Concept "reds&greens basket") (Concept "basket"))
(Inheritance (Concept "yellows basket") (Concept "basket"))

; Balls placed into baskets
(Member (Concept "red ball") (Concept "reds basket"))
(Member (Concept "red ball too") (Concept "reds basket"))
(Member (Concept "red ball also") (Concept "reds basket"))
(Member (Concept "red ball") (Concept "reds&greens basket"))
(Member (Concept "red ball too") (Concept "reds&greens basket"))
(Member (Concept "green ball") (Concept "reds&greens basket"))
(Member (Concept "yellow ball") (Concept "yellows basket"))

; Predicate that tests the colors of the balls
(Evaluation (Predicate "is red") (Concept "red ball"))
(Evaluation (Predicate "is red") (Concept "red ball too"))
(Evaluation (Predicate "is red") (Concept "red ball also"))

(Evaluation (Predicate "is green") (Concept "green ball"))
(Evaluation (Predicate "is yellow") (Concept "yellow ball"))

; The search that we will perform.
(define get-baskets-with-only-red-balls
(Bind
(VariableList
(TypedVariable (Variable "basket") (Type 'ConceptNode))
(TypedVariable (Variable "ball") (Type 'ConceptNode))
)
(And
; Look at every basket ...
(Inheritance (Variable "basket") (Concept "basket"))

; Look at the balls in the basket ...
(Member (Variable "ball") (Variable "basket"))

; Always means that *every* ball in the basket MUST
; be red! Any single failure to satisfy this invalidates
; the entire search.
(Always (Evaluation (Predicate "is red") (Variable "ball")))
)

; Report the basket which has only red balls in it.
(Variable "basket"))
)

(cog-execute! get-baskets-with-only-red-balls)
22 changes: 21 additions & 1 deletion opencog/atoms/atom_types/atom_types.script
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,17 @@ PRESENT_LINK <- SET_LINK,CRISP_OUTPUT_LINK
// PresentLink is used to accomplish this.
ABSENT_LINK <- PRESENT_LINK

// The other adjoint.
// Exists == left adjoint to the pullback functor of a relation.
// where PresentLink is `Exists` for the pattern matcher.
// ForAll == right adjoint to the pullback functor of a relation.
// We invent AlwaysLink, since `ForAll` is already taken
// up by PLN.
// This is used in the pattern matcher, to implement an "always the
// case" condition during matching. It's a kind-of `ForAll`, just like
// Present is a kind-of `ThereExists`.
ALWAYS_LINK <- PRESENT_LINK

// Performance scripting links.
// ParallelLink launches multiple threads, but does not wait for any of
// them to return. JoinLink launches multiple threads, and waits for
Expand Down Expand Up @@ -704,9 +715,18 @@ INTENSIONAL_SIMILARITY_LINK <- SIMILARITY_LINK
// Should these inherit from ReWriteLink or PrenexLink instead ???
// I think PLN uses these; otherwise they are not yet properly defined.
//
// Lojban uses ExistsLink, but incorrectly (it is unaware that Exists
// is scoped!)
//
// Exists == left adjoint to the pullback functor of a relation.
// Note that PRESENT_LINK below is the unscoped variant
// of this, used in the pattern matcher.
// ForAll == right adjoint to the pullback functor of a relation.
// XXX Currently not implemented (viz don't work with virtual links).
// Note that ALWAYS_LINK below is the unscoped variant
// of this, used in the pattern matcher.
//
// XXX These are currently not implemented (viz they don't actually
// work as virtual links).
FORALL_LINK <- SCOPE_LINK,CRISP_OUTPUT_LINK "ForAllLink"
EXISTS_LINK <- SCOPE_LINK,CRISP_OUTPUT_LINK

Expand Down
4 changes: 4 additions & 0 deletions opencog/atoms/pattern/Pattern.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ struct Pattern
/// grounded, they might be rejected (depending on the callback).
HandleSeq optionals; // Optional clauses

/// The always (for-all) clauses have to always be the same way.
/// Any grounding failure at all invalidates all other groundings.
HandleSeq always; // ForAll clauses

/// Black-box clauses. These are clauses that contain GPN's. These
/// have to drop into scheme or python to get evaluated, which means
/// that they will be slow. So, we leave these for last, so that the
Expand Down
35 changes: 35 additions & 0 deletions opencog/atoms/pattern/PatternLink.cc
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,17 @@ bool PatternLink::record_literal(const Handle& h, bool reverse)
return true;
}

// Pull clauses out of an AlwaysLink
if (not reverse and ALWAYS_LINK == typ)
// or (reverse and NEVER_LINK == typ))
{
for (const Handle& ah: h->getOutgoingSet())
{
_pat.always.emplace_back(ah);
}
return true;
}

return false;
}

Expand Down Expand Up @@ -814,6 +825,11 @@ void PatternLink::make_term_trees()
PatternTermPtr root_term(std::make_shared<PatternTerm>());
make_term_tree_recursive(clause, clause, root_term);
}
for (const Handle& clause : _pat.always)
{
PatternTermPtr root_term(std::make_shared<PatternTerm>());
make_term_tree_recursive(clause, clause, root_term);
}
}

void PatternLink::make_term_tree_recursive(const Handle& root,
Expand Down Expand Up @@ -912,6 +928,25 @@ void PatternLink::debug_log(void) const
else
logger().fine("No optional clauses");

if (0 < _pat.always.size())
{
logger().fine("Predicate includes the following for-all clauses:");
cl = 0;
for (const Handle& h : _pat.always)
{
std::stringstream ss;
ss << "Always clause " << cl << ":";
if (_pat.evaluatable_holders.find(h) != _pat.evaluatable_holders.end())
ss << " (evaluatable)";
ss << std::endl;
ss << h->to_short_string();
logger().fine() << ss.str();
cl++;
}
}
else
logger().fine("No always clauses");

// Print out the bound variables in the predicate.
for (const Handle& h : _varlist.varset)
{
Expand Down
37 changes: 37 additions & 0 deletions opencog/query/DefaultPatternMatchCB.cc
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,43 @@ bool DefaultPatternMatchCB::optional_clause_match(const Handle& ptrn,

/* ======================================================== */

/* This implements AlwaysLink (the non-scoped vesion of ForAllLink
* used by the pattern matcher.) The AlwaysLink must always be
* satsifed, every time it is called, from the begining of the
* search to the end. The AlwaysLinks is satsified whenever
* ptrn==grnd, and otherwise, if fails. That is, if ptrn==nullptr
* then there is some grounding of (all of) the other clauses of
* the pattern, with AlwaysLink failing to be satisfied. Reject
* this case, now and forever. (viz, this is stateful.)
*/
bool DefaultPatternMatchCB::always_clause_match(const Handle& ptrn,
const Handle& grnd,
const HandleMap& term_gnds)
{
// No grounding was found, reject it.
if (not grnd) _forall_state = false;

// If we failed once, we will always fail.
return _forall_state;
}

void DefaultPatternMatchCB::push(void)
{
_stack_depth++;
}

void DefaultPatternMatchCB::pop(void)
{
_stack_depth--;

// Reset the for-all state at the very start of a new search.
// If might be more elegant if iniate_search told us when
// it was starting again, but whatever. This works for now.
if (0 == _stack_depth) _forall_state = true;
}

/* ======================================================== */

IncomingSet DefaultPatternMatchCB::get_incoming_set(const Handle& h)
{
return h->getIncomingSet(_as);
Expand Down
15 changes: 12 additions & 3 deletions opencog/query/DefaultPatternMatchCB.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,20 @@ class DefaultPatternMatchCB : public virtual PatternMatchCallback

virtual bool clause_match(const Handle&, const Handle&,
const HandleMap&);
/**
* Typically called for AbsentLink
*/

/** Called for AbsentLink */
virtual bool optional_clause_match(const Handle& pattrn,
const Handle& grnd,
const HandleMap&);

/** Called for AlawaysLink */
virtual bool always_clause_match(const Handle& pattrn,
const Handle& grnd,
const HandleMap&);

virtual void push(void);
virtual void pop(void);

virtual IncomingSet get_incoming_set(const Handle&);

/**
Expand Down Expand Up @@ -132,6 +139,8 @@ class DefaultPatternMatchCB : public virtual PatternMatchCallback
bool eval_sentence(const Handle& pat, const HandleMap& gnds);

bool _optionals_present = false;
bool _forall_state = true;
size_t _stack_depth = 0;
AtomSpace* _as;
};

Expand Down
6 changes: 6 additions & 0 deletions opencog/query/PatternLinkRuntime.cc
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ class PMCGroundings : public PatternMatchCallback
{
return _cb.optional_clause_match(pattrn, grnd, term_gnds);
}
bool always_clause_match(const Handle& pattrn,
const Handle& grnd,
const HandleMap& term_gnds)
{
return _cb.always_clause_match(pattrn, grnd, term_gnds);
}
IncomingSet get_incoming_set(const Handle& h) {
return _cb.get_incoming_set(h);
}
Expand Down
21 changes: 21 additions & 0 deletions opencog/query/PatternMatchCallback.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,27 @@ class PatternMatchCallback
const Handle& grnd,
const HandleMap& term_gnds) = 0;

/**
* Called when the search for a top-level for-all clause
* has been completed. The clause may or may not have been
* grounded as a result of the search. If it has been grounded,
* then grnd will be non-null.
*
* Return false to terminate further searches from this point
* on; the result of termination will be backtracking to search
* for other possible groundings of the required clauses.
* Return true to examine the next for-all clause (if any).
*
* Note that all required clauses will have been grounded,
* and all optional clauses will have been examined before
* the for-all clauses are tested. This guarantees that the
* groundings for all variables are "final", and this is a
* last-chance test.
*/
virtual bool always_clause_match(const Handle& pattrn,
const Handle& grnd,
const HandleMap& term_gnds) = 0;

/**
* Called when a complete grounding for all clauses is found.
* Should return false to search for more solutions; or return
Expand Down
51 changes: 39 additions & 12 deletions opencog/query/PatternMatchEngine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1621,6 +1621,13 @@ bool PatternMatchEngine::clause_accept(const Handle& clause_root,
DO_LOG({logger().fine("optional clause match callback match=%d", match);})
}
else
if (is_always(clause_root))
{
clause_accepted = true;
match = _pmc.always_clause_match(clause_root, hg, var_grounding);
DO_LOG({logger().fine("for-all clause match callback match=%d", match);})
}
else
{
match = _pmc.clause_match(clause_root, hg, var_grounding);
DO_LOG({logger().fine("clause match callback match=%d", match);})
Expand Down Expand Up @@ -1783,24 +1790,36 @@ void PatternMatchEngine::get_next_untried_clause(void)
}
}

// If there are no optional clauses, we are done.
if (_pat->optionals.empty())
// Try again, this time, considering the optional clauses.
if (not _pat->optionals.empty())
{
// There are no more ungrounded clauses to consider. We are done.
next_clause = Handle::UNDEFINED;
next_joint = Handle::UNDEFINED;
return;
if (get_next_thinnest_clause(false, false, true)) return;
if (not _pat->evaluatable_holders.empty())
{
if (get_next_thinnest_clause(true, false, true)) return;
if (not _pat->black.empty())
{
if (get_next_thinnest_clause(true, true, true)) return;
}
}
}

// Try again, this time, considering the optional clauses.
if (get_next_thinnest_clause(false, false, true)) return;
if (not _pat->evaluatable_holders.empty())
// Now loop over all for-all clauses.
// I think that all variables will be grounded at this point, right?
for (const Handle& root : _pat->always)
{
if (get_next_thinnest_clause(true, false, true)) return;
if (not _pat->black.empty())
if (issued.end() != issued.find(root)) continue;
issued.insert(root);
next_clause = root;
for (const Handle &v : _varlist->varset)
{
if (get_next_thinnest_clause(true, true, true)) return;
if (is_free_in_tree(root, v))
{
next_joint = v;
return;
}
}
throw RuntimeException(TRACE_INFO, "BUG! Somethings wrong!!");
}

// If we are here, there are no more unsolved clauses to consider.
Expand Down Expand Up @@ -2189,6 +2208,14 @@ bool PatternMatchEngine::explore_clause(const Handle& term,
found = explore_term_branches(term, grnd, clause);
}

// AlwaysLink clauses must always be satisfied. Report the
// failure to satisfy to the callback.
if (is_always(clause))
{
Handle empty;
_pmc.always_clause_match(clause, empty, var_grounding);
}

// If found is false, then there's no solution here.
// Bail out, return false to try again with the next candidate.
return found;
Expand Down
4 changes: 4 additions & 0 deletions opencog/query/PatternMatchEngine.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ class PatternMatchEngine
const HandleSeq& o(_pat->optionals);
return o.end() != std::find(o.begin(), o.end(), h); }

bool is_always(const Handle& h) {
const HandleSeq& o(_pat->always);
return o.end() != std::find(o.begin(), o.end(), h); }

bool is_evaluatable(const Handle& h) {
return (_pat->evaluatable_holders.count(h) != 0); }

Expand Down
Loading