OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / query / Satisfier.cc

231 lines · cc ref:
/*
 * Satisfier.cc
 *
 * Copyright (C) 2015 Linas Vepstas
 *
 * Author: Linas Vepstas <linasvepstas@gmail.com>  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 <opencog/util/oc_assert.h>

#include <opencog/atomspace/AtomSpace.h>
#include <opencog/atoms/core/UnorderedLink.h>
#include <opencog/atoms/pattern/PatternLink.h>

#include "Satisfier.h"

using namespace opencog;

bool Satisfier::propose_grounding(const GroundingMap &var_soln,
                                  const GroundingMap &term_soln)
{
	LOCK_PE_MUTEX;
	// PatternMatchEngine::print_solution(var_soln, term_soln);
	_result = true;

	// XXX Temp hack alert. When Continuations finally terminate, they
	// supply us with empty groundings. This probably needs to be fixed
	// someday. For now, for the simple examples, its good enough.
	if (0 == var_soln.size()) return true;

	// Record the grounding; we cache this later.
	if (1 == _varseq.size())
	{
		_ground = var_soln.at(_varseq[0]);
	}
	else
	{
		// If more than one variable, encapsulate in sequential order,
		// in a ListLink.
		HandleSeq vargnds;
		for (const Handle& hv : _varseq)
		{
			// Optional clauses (e.g. AbsentLink) may have
			// variables in them that are not grounded.
			// Those variables won't have a grounding;
			// this will cause std::map::at to throw.
			try
			{
				vargnds.push_back(var_soln.at(hv));
			}
			catch (...)
			{
				vargnds.push_back(hv);
			}
		}
		_ground = createLink(std::move(vargnds), LIST_LINK);
	}

	// No need to look for more groundings as _result isn't going to change
	// and opencog::satisfaction_link only needs the value of _result.
	return true;
}

/// This method handles the case of SequentialAnd, SequentialOr with
/// embedded AbsentLinks, NotLink-PresentLink and some weird
/// combinations of NotLink-ChoiceLink, and so-on.  The idea here is
/// that, if the pattern matcher ran to exhaustion, and NO groundings
/// at all were found, then pattern evaluation may still need to
/// trigger evaluatable clauses that evaluate only when exhaustive
/// search fails.  So, indeed, we do that here.
///
/// Of course, if the pattern had no variables (e.g. a SequenceLink or
/// FallbackLink with only evaluatables), then there cannot be a
/// grounding failure, by definition.  And if there was a grounding,
/// there can be no grounding failure, either. So we only process the
/// case where there are variables, and grounding failed.
bool Satisfier::search_finished(bool done)
{
	if (done) return done;

	// If there were no variables to be grounded, we have nothing to do.
	if (not _have_variables) return done;

	// If there was a grounding, then don't re-run; we're here
	// only to handle the no-groundings case.
	if (_result) return done;

	// _optionals_present will be set to true if some optional clause
	// was grounded. Ergo, its not the no-grounding case.
	if (_optionals_present) return done;

	// Multi-component patterns will not have distinct bodies.
	// A failure to match one of the components is benign, and is
	// treated appropriately upstream. Just return.
	if (nullptr == _pattern_body) return done;

	// Evaluating the pattern body only makes sense if it is sequential
	// (ordered) -- if the body is an unordered AndLink, or if its a
	// ChoiceLink, etc, this makes no sense.
	Type btype = _pattern_body->get_type();
	if (SEQUENTIAL_AND_LINK != btype and SEQUENTIAL_OR_LINK != btype)
		return done;

	GroundingMap empty;
	bool rc = eval_sentence(_pattern_body, empty);
	if (rc)
		_result = true;

	return rc;
}

// ===========================================================

void SatisfyingSet::setup_marginals(void)
{
	// Grab the places where we'll record the marginals.
	for (const Handle& var: _varseq)
	{
		ValuePtr vp(_plp->getValue(var));
		ContainerValuePtr cvp(ContainerValueCast(vp));
		if (nullptr == cvp) continue;
		if (cvp->is_closed())
		{
			cvp->clear();
			cvp->open();
		}
		_var_marginals.insert({var, cvp});
	}
}

ValuePtr SatisfyingSet::wrap_result(const GroundingMap& var_soln)
{
	_num_results ++;

	if (1 == _varseq.size())
	{
		// std::map::at() can throw. Rethrow for easier deubugging.
		try
		{
			ValuePtr gvp(var_soln.at(_varseq[0]));
			auto it = _var_marginals.find(_varseq[0]);
			if (_var_marginals.end() != it)
				(*it).second->add(gvp);
			return gvp;
		}
		catch (...)
		{
			OC_ASSERT(false,
				"Internal error: ungrounded variable %s\n",
				_varseq[0]->to_string().c_str());
		}
	}

	// If more than one variable, encapsulate in sequential order,
	// in a ListLink.
	std::vector<ValuePtr> vargnds;
	for (const Handle& hv : _varseq)
	{
		// Optional clauses (e.g. AbsentLink) may have variables
		// in them that are not grounded. Those variables won't
		// have a grounding; this will cause std::map::at to throw.
		try
		{
			ValuePtr gvp(var_soln.at(hv));
			auto it = _var_marginals.find(hv);
			if (_var_marginals.end() != it)
				(*it).second->add(gvp);
			vargnds.push_back(gvp);
		}
		catch (...)
		{
			vargnds.push_back(hv);
		}
	}
	return createLinkValue(std::move(vargnds));
}

// MeetLink and GetLink groundings go through here.
bool SatisfyingSet::propose_grounding(const GroundingMap& var_soln,
                                      const GroundingMap& term_soln)
{
	LOCK_PE_MUTEX;
	// PatternMatchEngine::log_solution(var_soln, term_soln);

	// Do not accept new solution if maximum number has been already reached
	if (_num_results >= max_results)
		return true;

	_result_queue->add(std::move(wrap_result(var_soln)));

	// If we found as many as we want, then stop looking for more.
	return (_num_results >= max_results);
}

bool SatisfyingSet::start_search(void)
{
	if (_result_queue->is_closed())
	{
		_result_queue->clear();
		_result_queue->open();
	}
	return false;
}

bool SatisfyingSet::search_finished(bool done)
{
	// Close all queues
	for (auto& mgs : _var_marginals)
		mgs.second->close();

	_result_queue->close();
	return done;
}

/* ===================== END OF FILE ===================== */