OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-atomspace / opencog / atoms / columnvec / TransposeColumn.cc

346 lines · cc ref:
/*
 * TransposeColumn.cc
 *
 * Copyright (C) 2015, 2022, 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.
 *
 * 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/value/FloatValue.h>
#include <opencog/atoms/value/LinkValue.h>
#include <opencog/atoms/value/StringValue.h>

#include "TransposeColumn.h"

using namespace opencog;

TransposeColumn::TransposeColumn(const HandleSeq&& oset, Type t)
	: Link(std::move(oset), t)
{
	if (not nameserver().isA(t, TRANSPOSE_COLUMN))
	{
		const std::string& tname = nameserver().getTypeName(t);
		throw InvalidParamException(TRACE_INFO,
			"Expecting a TransposeColumn, got %s", tname.c_str());
	}
}

// ---------------------------------------------------------------

/// Return a FloatValue vector.
ValuePtr TransposeColumn::do_handle_loop(AtomSpace* as, bool silent,
                                         const HandleSeq& hrows)
{
	ValueSeq vrows;
	vrows.reserve(hrows.size());
	for (const Handle& h : hrows)
		vrows.push_back(h);

	return do_value_loop(as, silent, vrows);
}

// ---------------------------------------------------------------

// Rows of length zero is a common end-of-stream indicator.
// For example, the length of VoidValue is zero.
#define CHKSZ(ROW) \
	if (0 == ROW.size()) break; \
	if (ROW.size() < ncols) \
		throw RuntimeException(TRACE_INFO, \
			"Short row! Got %lu want %lu\n", ROW.size(), ncols);

/// Return a FloatValue vector.
ValuePtr TransposeColumn::do_value_loop(AtomSpace* as, bool silent,
                                        const ValueSeq& vrows)
{
	if (0 == vrows.size())
		return createLinkValue();

	// On transposition, rows become columns. The number of rows becomes
	// the number of columns, and the type of the row becomes the type of
	// the column. ... except when the columns are packaged into either a
	// LinkValue or a Link, in which case, the columns show up packed
	// individually in rows. These are two distinct cases; each gets
	// different unpacking.
	Type rtype = vrows[0]->get_type();
	for (ValuePtr vp: vrows)
	{
		if (vp->get_type() != rtype)
			return do_direct_loop(as, silent, vrows);
	}

	// If we are here, then the first LinkValue row holds the columns
	// that we will be extracting. That is, the first row provides all
	// the columns and column types.
	size_t ncols = 0;
	ValueSeq vcols;
	for (ValuePtr vp: vrows)
	{
		if (vp->is_atom())
			vp = HandleCast(vp)->execute(as, silent);

		if (0 == ncols)
		{
			ncols = vp->size();
			vcols.reserve(ncols);

			// The if-statemens below are ordered in the sequence
			// of most-likely to least likely. I think transposing
			// FloatValues will be the most common case, and then
			// the LinkValues...
			if (vp->is_type(FLOAT_VALUE))
			{
				const std::vector<double>& vals = FloatValueCast(vp)->value();
				for (double d : vals)
					vcols.emplace_back(createFloatValue(d));
			}
			else if (vp->is_type(LINK_VALUE))
			{
				const ValueSeq& vrow = LinkValueCast(vp)->value();
				for (const ValuePtr& v : vrow)
				{
					if (1 == v->size())
					{
						if (v->is_type(FLOAT_VALUE))
						{
							double d = FloatValueCast(v)->value()[0];
							vcols.emplace_back(createFloatValue(d));
						}
						else if (v->is_type(STRING_VALUE))
						{
							const std::string& s = StringValueCast(v)->value()[0];
							vcols.emplace_back(createStringValue(s));
						}
						else
							vcols.emplace_back(createLinkValue(v));
					}
					else
						vcols.emplace_back(createLinkValue(v));
				}
			}
			else if (vp->is_type(NUMBER_NODE))
			{
				const std::vector<double>& vals = NumberNodeCast(vp)->value();
				for (double d : vals)
					vcols.emplace_back(createFloatValue(d));
			}
			else if (vp->is_type(STRING_VALUE))
			{
				const std::vector<std::string>& vals = StringValueCast(vp)->value();
				for (const std::string& s : vals)
					vcols.emplace_back(createStringValue(s));
			}
			else if (vp->is_link())
			{
				const HandleSeq& hrow = HandleCast(vp)->getOutgoingSet();
				for (const Handle& h : hrow)
					vcols.emplace_back(createLinkValue(h));
			}
			else
				throw RuntimeException(TRACE_INFO,
					"I don't know what to do with %s\n", vp->to_string().c_str());

			continue;
		}

		// If we are here, this is not the first row, and we know how
		// many columns there are, and what their types should be.
		if (vp->is_type(FLOAT_VALUE))
		{
			const std::vector<double>& vals = FloatValueCast(vp)->value();
			CHKSZ(vals);
			for (size_t i=0; i<ncols; i++)
				FloatValueCast(vcols[i]) -> _value.push_back(vals[i]);
		}
		else if (vp->is_type(LINK_VALUE))
		{
			const ValueSeq& vrow = LinkValueCast(vp)->value();
			CHKSZ(vrow);
			for (size_t i=0; i< ncols; i++)
			{
				const ValuePtr& v(vrow[i]);
				if (1 == v->size())
				{
					if (v->is_type(FLOAT_VALUE))
					{
						double d = FloatValueCast(v)->value()[0];
						FloatValueCast(vcols[i]) -> _value.emplace_back(d);
					}
					else if (v->is_type(STRING_VALUE))
					{
						const std::string& s = StringValueCast(v)->value()[0];
						StringValueCast(vcols[i]) -> _value.emplace_back(s);
					}
					else
						LinkValueCast(vcols[i]) -> _value.push_back(vrow[i]);
				}
				else
					LinkValueCast(vcols[i]) -> _value.push_back(vrow[i]);
			}
		}
		else if (vp->is_type(NUMBER_NODE))
		{
			const std::vector<double>& vals = NumberNodeCast(vp)->value();
			CHKSZ(vals);
			for (size_t i=0; i< ncols; i++)
				FloatValueCast(vcols[i]) -> _value.push_back(vals[i]);
		}
		else if (vp->is_type(STRING_VALUE))
		{
			const std::vector<std::string>& vals = StringValueCast(vp)->value();
			CHKSZ(vals);
			for (size_t i=0; i< ncols; i++)
				StringValueCast(vcols[i]) -> _value.push_back(vals[i]);
		}
		else if (vp->is_link())
		{
			const HandleSeq& hrow = HandleCast(vp)->getOutgoingSet();
			CHKSZ(hrow);
			for (size_t i=0; i< ncols; i++)
				LinkValueCast(vcols[i]) -> _value.push_back(hrow[i]);
		}
	}

	return createLinkValue(std::move(vcols));
}

// ---------------------------------------------------------------

/// Return a FloatValue vector.
//
// XXX FIXME. This is not correct, in two different ways. First,
// consider the `formulas.scm` example, of having one formula per
// row, and then executing that formula to get a FloatValue of
// length one. We want to concatenate all these floats for make
// just one vector. This doesn't do it ...
//   a) It fails to even ttry to execute each (singleton) row
//   b) It doesn't realize they are *all* Floats.
//      (And thus should return a FloatValue, not a LinkValue)
//
// Same complaint as above, but the formulas contain variables
// and thus might be "delta reducible" (constant terms combined)
// without being fully executable. In this case, we probably don't
// want a LinkValue, but a TransposeColumn as before, with the
// reductions in it. For example, the 41 and the 1 can be added:
//
//     (cog-execute!
//        (TransposeColumn
//           (Plus (Number 41)
//              (Minus
//                 (Number 1)
//                 (Times
//                    (strength-of (Variable "$VA"))
//                    (strength-of (Variable "$VB")))))
//              (Times
//                 (confidence-of (Variable "$VA"))
//                 (confidence-of (Variable "$VB")))))
//
ValuePtr TransposeColumn::do_direct_loop(AtomSpace* as, bool silent,
                                         const ValueSeq& vrows)
{
	// Convert rows to columns. The number of columns output will
	// equal the length of the first row (and all rows should have
	// this same length.) The type of the column will be the type
	// of the corresponding row.
	size_t ncols = vrows[0]->size();
	ValueSeq vcols;
	vcols.reserve(ncols);
	for (size_t i=0; i<ncols; i++)
		vcols.emplace_back(createLinkValue());

	for (ValuePtr vp: vrows)
	{
		if (vp->is_type(FLOAT_VALUE))
		{
			const std::vector<double>& vals = FloatValueCast(vp)->value();
			CHKSZ(vals);
			for (size_t i=0; i< ncols; i++)
				LinkValueCast(vcols[i]) -> _value.emplace_back(
					createFloatValue(vals[i]));
		}
		else if (vp->is_type(STRING_VALUE))
		{
			const std::vector<std::string>& vals = StringValueCast(vp)->value();
			CHKSZ(vals);
			for (size_t i=0; i< ncols; i++)
				LinkValueCast(vcols[i]) -> _value.emplace_back(
					createStringValue(vals[i]));
		}
		else if (vp->is_type(NUMBER_NODE))
		{
			const std::vector<double>& vals = NumberNodeCast(vp)->value();
			CHKSZ(vals);
			for (size_t i=0; i< ncols; i++)
				LinkValueCast(vcols[i]) -> _value.emplace_back(
					createFloatValue(vals[i]));
		}
		else if (vp->is_link())
		{
			const HandleSeq& vals = HandleCast(vp)->getOutgoingSet();
			CHKSZ(vals);
			for (size_t i=0; i< ncols; i++)
				LinkValueCast(vcols[i]) -> _value.push_back(vals[i]);
		}
	}
	return createLinkValue(std::move(vcols));
}

// ---------------------------------------------------------------

/// Return a FloatValue vector.
ValuePtr TransposeColumn::do_execute(AtomSpace* as, bool silent)
{
	// If the given Atom is executable, then execute it.
	Handle base(_outgoing[0]);
	if (base->is_executable())
	{
		ValuePtr vpe(base->execute(as, silent));
		if (vpe->is_atom())
			base = HandleCast(vpe);
		else
		{
			if (vpe->is_type(LINK_VALUE))
				return do_value_loop(as, silent, LinkValueCast(vpe)->value());

			ValueSeq vsq;
			vsq.emplace_back(vpe);
			return do_value_loop(as, silent, vsq);
		}
	}

	// If we are here, then base is a link. Expect
	// it to contain things that evaluate to a double
	return do_handle_loop(as, silent, base->getOutgoingSet());
}

// ---------------------------------------------------------------

/// Return a FloatValue vector.
ValuePtr TransposeColumn::execute(AtomSpace* as, bool silent)
{
	if (1 == _outgoing.size())
		return do_execute(as, silent);

	return do_handle_loop(as, silent, _outgoing);
}

DEFINE_LINK_FACTORY(TransposeColumn, TRANSPOSE_COLUMN)

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