OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / atoms / reduct / PlusLink.cc

237 lines · cc ref:
/*
 * opencog/atoms/reduct/PlusLink.cc
 *
 * Copyright (C) 2015, 2018 Linas Vepstas
 * All Rights Reserved
 * SPDX-License-Identifier: AGPL-3.0-or-later
 */

#include <opencog/atoms/atom_types/atom_types.h>
#include <opencog/atoms/base/ClassServer.h>
#include <opencog/atoms/core/NumberNode.h>
#include "MinusLink.h"
#include "PlusLink.h"
#include "TimesLink.h"

using namespace opencog;

Handle PlusLink::zero;

PlusLink::PlusLink(const HandleSeq&& oset, Type t)
    : ArithmeticLink(std::move(oset), t)
{
	init();
}

PlusLink::PlusLink(const Handle& a, const Handle& b)
    : ArithmeticLink({a, b}, PLUS_LINK)
{
	init();
}

void PlusLink::init(void)
{
	if (nullptr == zero) zero = createNumberNode(0);

	Type tscope = get_type();
	if (not nameserver().isA(tscope, PLUS_LINK))
		throw InvalidParamException(TRACE_INFO, "Expecting a PlusLink");

	knil = zero;
	_commutative = true;
}

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

ValuePtr PlusLink::kons(AtomSpace* as, bool silent,
                        const ValuePtr& fi, const ValuePtr& fj) const
{
	if (fj == knil)
		return exec_for_value(as, silent, fi);

	// Try to yank out values, if possible.
	ValuePtr vi(exec_for_value(as, silent, fi));
	Type vitype = vi->get_type();

	ValuePtr vj(fj);
	Type vjtype = vj->get_type();

	// If adding zero, just drop the zero.
	if (NUMBER_NODE == vjtype and content_eq(HandleCast(vj), zero))
		return vi;

	// Are they numbers? If so, perform vector (pointwise) addition.
	// Always lower the strength: Number+Number->Number
	// but FloatValue+Number->FloatValue
	try
	{
		if (NUMBER_NODE == vitype and NUMBER_NODE == vjtype)
			return createNumberNode(plus(vi, vj, true));

		return plus(vi, vj, true);
	}
	catch (const SilentException& ex)
	{
		// If we are here, they were not simple numbers.
	}

	// Is vi a PlusLink? If so, then flatten.
	if (PLUS_LINK == vitype)
	{
		HandleSeq seq = HandleCast(vi)->getOutgoingSet();

		// flatten the right
		if (PLUS_LINK == vjtype)
		{
			for (const Handle& rhs: HandleCast(vj)->getOutgoingSet())
				seq.push_back(rhs);
		}
		else
		{
			seq.push_back(HandleCast(vj));
		}
		PlusLinkPtr ap = createPlusLink(std::move(seq));
		return ap->delta_reduce(as, silent);
	}

	if (PLUS_LINK == vjtype)
	{
		// Paste on one at a time; this avoids what would otherwise
		// be infinite recursion on `(Plus A B C)` where kons was
		// unable to reduce `(Plus B C)`. So we instead try to do
		// `(Plus (Plus A B) C)` which should work out...
		ValuePtr vsum = vi;
		for (const Handle& h : HandleCast(vj)->getOutgoingSet())
		{
			if (PLUS_LINK == vsum->get_type())
			{
				HandleSeq vout(HandleCast(vsum)->getOutgoingSet());
				vout.push_back(h);
				vsum = createPlusLink(std::move(vout));
			}
			else
			{
				vsum = kons(as, silent, vsum, h);
			}
		}
		return vsum;
	}

	// Is vi identical to vj? If so, then replace by 2*vi
	Handle hvi(HandleCast(vi));
	if (hvi and content_eq(hvi, HandleCast(vj)))
	{
		Handle two(createNumberNode("2"));
		return createTimesLink(hvi, two) -> execute(as, silent);
	}

	// Swap order, to make the Minus handling below easier.
	if (nameserver().isA(vitype, NUMBER_NODE))
	{
		std::swap(vi, vj);
		std::swap(vitype, vjtype);
	}

	// Collapse (3 + (5 - x)) and (13 + (x - 6))
	if (MINUS_LINK == vitype and NUMBER_NODE == vjtype)
	{
		Handle minuend(HandleCast(vi)->getOutgoingAtom(0));
		Handle subtrahend(HandleCast(vi)->getOutgoingAtom(1));
		if (NUMBER_NODE == minuend->get_type())
		{
			Handle hsum(createNumberNode(plus(vj, minuend)));
			return createMinusLink(hsum, subtrahend);
		}
		if (NUMBER_NODE == subtrahend->get_type())
		{
			Handle hdiff(createNumberNode(
				minus(vj, subtrahend)));
			if (content_eq(hdiff, zero))
				return minuend;
			return createPlusLink(minuend, hdiff);
		}
	}

	// If j is (TimesLink x a) and i is identical to x,
	// then create (TimesLink x (a+1))
	//
	// If j is (TimesLink x a) and i is (TimesLink x b)
	// then create (TimesLink x (a+b))
	//
	if (vjtype == TIMES_LINK)
	{
		bool do_add = false;
		HandleSeq rest;

		Handle exx = HandleCast(vj)->getOutgoingAtom(0);

		// Handle the (a+1) case described above.
		if (vi == exx)
		{
			Handle one(createNumberNode("1"));
			rest.push_back(one);
			do_add = true;
		}

		// Handle the (a+b) case described above.
		else if (vitype == TIMES_LINK and
		         hvi->getOutgoingAtom(0) == exx)
		{
			const HandleSeq& ilpo = hvi->getOutgoingSet();
			size_t ilpsz = ilpo.size();
			for (size_t k=1; k<ilpsz; k++)
				rest.push_back(ilpo[k]);
			do_add = true;
		}

		if (do_add)
		{
			const HandleSeq& jlpo = HandleCast(vj)->getOutgoingSet();
			size_t jlpsz = jlpo.size();
			for (size_t k=1; k<jlpsz; k++)
				rest.push_back(jlpo[k]);

			// a_plus is now (a+1) or (a+b) as described above.
			Handle foo(createLink(std::move(rest), PLUS_LINK));
			PlusLinkPtr ap = PlusLinkCast(foo);
			ValuePtr a_plus(ap->delta_reduce(as, silent));

			return createTimesLink(exx, HandleCast(a_plus));
		}
	}

	// Swap order, make things easier below.
	if (nameserver().isA(vitype, FLOAT_VALUE))
	{
		std::swap(vi, vj);
		std::swap(vitype, vjtype);
	}

	// vector ops...
	if (NUMBER_NODE == vitype and nameserver().isA(vjtype, FLOAT_VALUE))
	{
		return plus(NumberNodeCast(vi), FloatValueCast(vj));
	}

	// Vector plus vector
	if (nameserver().isA(vitype, FLOAT_VALUE) and nameserver().isA(vjtype, FLOAT_VALUE))
	{
		return plus(FloatValueCast(vi), FloatValueCast(vj));
	}

	Handle hi(HandleCast(vi));
	if (nullptr == hi) hi = HandleCast(fi);

	Handle hj(HandleCast(vj));
	if (nullptr == hj) hj = HandleCast(fj);

	// If we are here, we've been asked to add two things of the same
	// type, but they are not of a type that we know how to add.
	// For example, fi and fj might be two different VariableNodes.
	return createPlusLink(hi, hj);
}

DEFINE_LINK_FACTORY(PlusLink, PLUS_LINK);

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