OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-generate / opencog / guile / modules / GenerateSCM.cc

239 lines · cc ref:
/*
 * GenerateSCM.cc
 *
 * Copyright (C) 2020 Linas Vepstas
 *
 * 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/atoms/core/NumberNode.h>
#include <opencog/atoms/grant/StateLink.h>
#include <opencog/guile/SchemeModule.h>
#include <opencog/guile/SchemePrimitive.h>

#include <opencog/generate/Aggregate.h>
#include <opencog/generate/Dictionary.h>
#include <opencog/generate/BasicParameters.h>
#include <opencog/generate/RandomCallback.h>
#include <opencog/generate/SimpleCallback.h>

using namespace opencog;
namespace opencog {

/**
 * Scheme wrapper for the generation code. Quick Hack.
 * Mediocre, ugly-ish API.  XXX FIXME.
 */

class GenerateSCM : public ModuleWrap
{
protected:
	virtual void init();

	Handle do_random_aggregate(Handle, Handle, Handle, Handle, Handle);
	Handle do_simple_aggregate(Handle, Handle, Handle, Handle);

public:
	GenerateSCM();
};

// ----------------------------------------------------------------
/// Decode parameters. A bit ad-hoc, right now.
///
/// The expected encoding for a paramter is
///    (StateLink
///       (MemberLink (PredicateNode "param") (ConceptNode "class"))
///       (Atom "value"))
/// where "param" is a well-known parameter, "class" is the particular
/// grouping of paramters we care about, and `(Atom "value")` is the
/// value for that parameter.
///
void decode_param(const Handle& membli,
                  GenerateCallback& cb,
                  BasicParameters& basic)
{
	Handle statli = StateLink::get_link(membli);
	if (nullptr == statli) return;

	const Handle& pname = membli->getOutgoingAtom(0);
	const Handle& pval = statli->getOutgoingAtom(1);

	// We expect the parameter name in a PredicateNode. Ah heck,
	// any node will do ...
	if (not pname->is_node())
		throw InvalidParamException(TRACE_INFO,
			"Expecting a parameter name, got %s",
			pname->to_short_string());
	const std::string& sname = pname->get_name();

	// We expect a node. Well, any Atom, so we don't check.
	if (0 == sname.compare("*-point-set-anchor-*"))
	{
		cb.point_set = pval;
		return;
	}

	// All parameters below here expect a NumberNode
	if (not nameserver().isA(pval->get_type(), NUMBER_NODE))
		throw InvalidParamException(TRACE_INFO,
			"Expecting a numerical value, got %s",
			pval->to_short_string());
	double dval = NumberNodeCast(pval)->get_value();

	if (0 == sname.compare("*-max-solutions-*"))
		cb.max_solutions = dval;

	else if (0 == sname.compare("*-max-steps-*"))
		cb.max_steps = dval;

	else if (0 == sname.compare("*-max-depth-*"))
		cb.max_depth = dval;

	else if(0 == sname.compare("*-max-network-size-*"))
		cb.max_network_size = dval;

	else if (0 == sname.compare("*-close-fraction-*"))
		basic.close_fraction = dval;
}

/// Decode all parameters attached to an anchor point.
/// See `decode_param()` above. This is just a loop.
void decode_params(const Handle& param_anchor,
                   GenerateCallback& cb,
                   BasicParameters& basic)
{
	// Decode the parameters. One at a time.
	HandleSeq memps = param_anchor->getIncomingSetByType(MEMBER_LINK);
	for (const Handle& membli : memps)
	{
		if (*membli->getOutgoingAtom(1) != *param_anchor) continue;
		decode_param(membli, cb, basic);
	}
}

// ----------------------------------------------------------------
/// Pull the lexis out of the atomspace.
Dictionary decode_lexis(AtomSpace* as, Handle poles, Handle lexis)
{
	Dictionary dict(as);

	// Add the poles to the dictionary.
	HandleSeq poleset = poles->getIncomingSetByType(MEMBER_LINK);
	for (const Handle& membli : poleset)
	{
		if (*membli->getOutgoingAtom(1) != *poles) continue;

		const Handle& pole_pair = membli->getOutgoingAtom(0);
		const Handle& p0 = pole_pair->getOutgoingAtom(0);
		const Handle& p1 = pole_pair->getOutgoingAtom(1);
		dict.add_pole_pair(p0, p1);

		if (nameserver().isA(pole_pair->get_type(), UNORDERED_LINK)
		    and *p0 != *p1)
		{
			dict.add_pole_pair(p1, p0);
		}
	}

	// Add the sections to the dictionary.
	HandleSeq sects = lexis->getIncomingSetByType(MEMBER_LINK);
	for (const Handle& membli : sects)
	{
		if (*membli->getOutgoingAtom(1) != *lexis) continue;
		dict.add_to_lexis(membli->getOutgoingAtom(0));
	}
	return dict;
}

// ----------------------------------------------------------------
/// C++ implementation of the scheme function.
Handle GenerateSCM::do_random_aggregate(Handle poles,
                                        Handle lexis,
                                        Handle weight,
                                        Handle params,
                                        Handle root)
{
	AtomSpacePtr asp = SchemeSmob::ss_get_env_as("cog-random-aggregate");
	AtomSpace* as = asp.get();

	Dictionary dict(decode_lexis(as, poles, lexis));

	BasicParameters basic;
	RandomCallback cb(as, dict, basic);
	cb.set_weight_key(weight);

	// Decode the parameters.
	decode_params(params, cb, basic);

	Aggregate ag(as);
	ag.aggregate({root}, cb);

	Handle result = cb.get_solutions();
	result = as->add_atom(result);
	return result;
}

// ----------------------------------------------------------------
/// C++ implementation of the scheme function.
Handle GenerateSCM::do_simple_aggregate(Handle poles,
                                        Handle lexis,
                                        Handle params,
                                        Handle root)
{
	AtomSpacePtr asp = SchemeSmob::ss_get_env_as("cog-simple-aggregate");
	AtomSpace* as = asp.get();

	Dictionary dict(decode_lexis(as, poles, lexis));

	BasicParameters basic;
	SimpleCallback cb(as, dict);
	decode_params(params, cb, basic);

	Aggregate ag(as);
	ag.aggregate({root}, cb);

	Handle result = cb.get_solutions();
	result = as->add_atom(result);
	return result;
}

// ----------------------------------------------------------------
} /*end of namespace opencog*/

GenerateSCM::GenerateSCM() : ModuleWrap("opencog generate") {}

/// This is called while (opencog generate) is the current module.
/// Thus, all the definitions below happen in that module.
void GenerateSCM::init(void)
{
	define_scheme_primitive("cog-random-aggregate",
		&GenerateSCM::do_random_aggregate, this, "generate");
	define_scheme_primitive("cog-simple-aggregate",
		&GenerateSCM::do_simple_aggregate, this, "generate");
}

extern "C" {
void opencog_generate_init(void);
};

void opencog_generate_init(void)
{
	 static GenerateSCM generate_scm;
	 generate_scm.module_init();
}

// --------------------- END OF FILE -----------------------