OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / query / RewriteMixin.cc

226 lines · cc ref:
/*
 * RewriteMixin.cc
 *
 * Copyright (C) 2009, 2014, 2025 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.
 */

#include <opencog/atoms/atom_types/atom_types.h>
#include <opencog/atoms/grant/DefineLink.h>
#include <opencog/atoms/free/Replacement.h>
#include <opencog/atomspace/AtomSpace.h>

#include "RewriteMixin.h"

using namespace opencog;

RewriteMixin::RewriteMixin(AtomSpace* as, ContainerValuePtr& qvp)
	: _as(as), _result_queue(qvp),
	_num_results(0), max_results(SIZE_MAX)
{
}

void RewriteMixin::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});
	}

	// Record the implicands, too
	for (const Handle& himp: _implicand)
	{
		ValuePtr vp(_plp->getValue(himp));
		ContainerValuePtr cvp(ContainerValueCast(vp));
		if (nullptr == cvp) continue;
		if (cvp->is_closed())
		{
			cvp->clear();
			cvp->open();
		}
		_implicand_grnds.insert({himp, cvp});
	}
}

void RewriteMixin::record_marginals(const GroundingMap& var_soln)
{
	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);
		}
		catch (...) {}
	}
}

/**
 * instantiate -- create a grounded expression from an ungrounded one,
 * and then execute it.  That is, beta-reduce, then execute.
 *
 * Given a handle to an ungrounded expression, and a set of groundings,
 * this will create a grounded expression.
 *
 * The set of groundings is to be passed in with the map 'vars', which
 * maps variable names to their groundings -- it maps variable names to
 * atoms that already exist in the atomspace.  This method will then go
 * through all of the variables in the expression, and substitute them
 * with their grounding, creating a new expression. The new expression
 * is then executed in the provided AtomSpace.
 */
static ValuePtr instantiate(AtomSpace* as,
                            const GroundingMap& varmap,
                            const Handle& expr,
                            bool silent)
{
	// throw, not assert, because this is a user error ...
	if (nullptr == expr)
		throw InvalidParamException(TRACE_INFO,
			"Asked to ground a null expression");

	// Beta-reduce, respecting quotes
	Handle grounded(Replacement::replace_nocheck(expr, varmap));

	// Fire executable links.
	// We currently exclude EVALUATABLE_LINK here; the evaluatables
	// will have (true == grounded->is_executable()) but currently
	// a dozen unit tests fail if we execute them. They fail for two
	// reasons: (a) Some of the unit tests use EvaluationLinks that
	// aren't actually evaluatable, and (b) AndLink, OrLink, MemberLink
	// etc. can all appear here, but executing them will give us
	// undesirable results.
	if (grounded->is_executable() and
	    not grounded->is_type(EVALUATABLE_LINK))
		return grounded->execute(as, silent);

	return grounded;
}

/**
 * This callback takes the reported grounding, runs it through the
 * instantiator, to create the implicand, and then records the result
 * in the `result_set`. Repeated solutions are skipped. If the number
 * of unique results so far is less than `max_results`, it then returns
 * false, to search for more groundings.  (The engine will halt its
 * search for a grounding once an acceptable one has been found; so,
 * to continue hunting for more, we return `false` here. We want to
 * find all possible groundings.)
 */
bool RewriteMixin::propose_grounding(const GroundingMap& var_soln,
                                     const GroundingMap& term_soln)
{
	LOCK_PE_MUTEX;
	// PatternMatchEngine::print_solution(var_soln, term_soln);

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

	_num_results ++;

	// Record marginals for variables.
	record_marginals(var_soln);

	if (1 == _implicand.size())
	{
		ValuePtr v(instantiate(_as, var_soln, _implicand[0], true));
		// AbsentLinks can result in nullptr v's
		if (nullptr != v)
		{
			if (v->is_atom())
				v = _as->add_atom(HandleCast(v));
			auto it = _implicand_grnds.find(_implicand[0]);
			if (_implicand_grnds.end() != it)
				(*it).second->add(v);
			insert_result(v);
		}
	}
	else
	{
		ValueSeq vs;
		for (const Handle& himp: _implicand)
		{
			ValuePtr v(instantiate(_as, var_soln, himp, true));
			if (nullptr != v)
			{
				if (v->is_atom())
					v = _as->add_atom(HandleCast(v));
				auto it = _implicand_grnds.find(himp);
				if (_implicand_grnds.end() != it)
					(*it).second->add(v);
				vs.emplace_back(v);
			}
		}
		insert_result(createLinkValue(std::move(vs)));
	}

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

void RewriteMixin::insert_result(ValuePtr v)
{
	if (_result_set.end() != _result_set.find(v)) return;

	// Insert atom into the atomspace immediately. This avoids having
	// the atom appear twice, once unassigned to any AS, and the other
	// in the AS.
	if (v->is_atom())
		v = _as->add_atom(HandleCast(v));

	if (_result_set.end() != _result_set.find(v)) return;

	_result_set.insert(v);
	_result_queue->add(std::move(v));
}

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

bool RewriteMixin::search_finished(bool done)
{
	for (auto& mgs : _var_marginals)
		mgs.second->close();

	for (auto& igs : _implicand_grnds)
		igs.second->close();

	_result_queue->close();
	return done;
}

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