OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-ure / opencog / ure / backwardchainer / BIT.cc

899 lines · cc ref:
/*
 * BIT.cc
 *
 * Copyright (C) 2016-2017 OpenCog Foundation
 * Author: Nil Geisweiller
 *
 * 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 <boost/range/algorithm/binary_search.hpp>
#include <boost/range/algorithm/lower_bound.hpp>
#include <boost/range/algorithm/reverse.hpp>
#include <boost/range/algorithm/unique.hpp>
#include <boost/range/algorithm/find.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/algorithm/cxx11/all_of.hpp>

#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/join.hpp>

#include <opencog/util/random.h>
#include <opencog/util/algorithm.h>
#include <opencog/atoms/core/FindUtils.h>
#include <opencog/atoms/core/TypeUtils.h>
#include <opencog/atoms/grounded/LibraryManager.h>
#include <opencog/atoms/pattern/PatternUtils.h>

#include "BIT.h"
#include "../URELogger.h"

namespace opencog {

/////////////
// BITNode //
/////////////

BITNode::BITNode(const Handle& bd, const BITNodeFitness& fi)
	: body(bd), fitness(fi), exhausted(false) {
	complexity = -std::log(operator()());
}

double BITNode::operator()() const
{
	// The probably estimate is anti-proportional to the fitness. The
	// assumption used here is that, if the fitness is already high,
	// expanding the BIT-Node is less likely to increase it. This
	// assumption is perfectly right when the fitness equals its upper
	// bound, but isn't generally right otherwise.
	return (exhausted ? 0.0 : 1.0) *
		(fitness.upper - fitness(*this)) / (fitness.upper - fitness.lower);
}

std::string	BITNode::to_string(const std::string& indent) const
{
	std::stringstream ss;
	ss << indent << "body:" << std::endl
	   << oc_to_string(body, indent + OC_TO_STRING_INDENT) << std::endl
	   << indent << "exhausted: " << exhausted << std::endl
	   << indent << "rules:" << std::endl
	   << (indent + oc_to_string_indent) << "size = " << rules.size();
	std::string rule_indent = indent + oc_to_string_indent + oc_to_string_indent;
	size_t i = 0;
	for (const auto& rule : rules)
		ss << std::endl << (indent + oc_to_string_indent)
		   << "rule[" << i++ << "]:" << std::endl
		   << rule.first.to_short_string(rule_indent);
	return ss.str();
}

////////////
// AndBIT //
////////////

AndBIT::AndBIT() : complexity(0), exhausted(false), queried_as(nullptr) {}

AndBIT::AndBIT(AtomSpace& bit_as, const Handle& target, Handle vardecl,
               const BITNodeFitness& fitness, const AtomSpace* qas)
	: exhausted(false), queried_as(qas)
{
	// in case it is undefined
	if (nullptr == vardecl)
	{
		HandleSet vars = get_free_variables(target);
		vardecl = HandleCast(createVariableSet(HandleSeq(vars.begin(), vars.end())));
	}

	// Create initial FCS
	Handle body =
		Unify::remove_constant_clauses(vardecl, target, queried_as);
	HandleSeq bl{body, target};
	vardecl = filter_vardecl(vardecl, body); // remove useless vardecl
	if (vardecl)
		bl.insert(bl.begin(), vardecl);
	fcs = bit_as.add_link(BIND_LINK, std::move(bl));

	// Insert the initial BITNode and initialize the AndBIT complexity
	auto it = insert_bitnode(target, fitness);
	complexity = it->second.complexity;
}

AndBIT::AndBIT(const Handle& f, double cpx, const AtomSpace* qas)
	: fcs(f), complexity(cpx), exhausted(false), queried_as(qas)
{
	set_leaf2bitnode();         // TODO: might differ till needed to optimize
}

AndBIT::~AndBIT() {}

AndBIT AndBIT::expand(const Handle& leaf,
                      const RuleTypedSubstitutionPair& rule,
                      double prob) const
{
	Handle new_fcs = expand_fcs(leaf, rule);
	double new_cpx = expand_complexity(leaf, prob);

	// Only consider expansions that actually expands
	if (content_eq(fcs, new_fcs)) {
		ure_logger().warn() << "The new FCS is equal to the old one. "
		                    << "There is probably a bug. This expansion has "
		                    << "been cancelled.";
		return AndBIT();
	}

	// Discard expansion with cycle
	if (has_cycle(BindLinkCast(new_fcs)->get_implicand()[0])) {
		ure_logger().debug() << "The new FCS has some cycle (some conclusion "
		                     << "has itself has premise, directly or "
		                     << "indirectly). This expansion has been cancelled.";
		return AndBIT();
	}

	return AndBIT(new_fcs, new_cpx, queried_as);
}

BITNode* AndBIT::select_leaf()
{
	// Generate the distribution over target leaves according to the
	// BIT-node fitnesses. The higher the fitness the lower the chance
	// of being selected as it is already fit.
	std::vector<double> weights;
	bool all_weights_null = true;
	for (const auto& lb : leaf2bitnode) {
		double p = lb.second();
		weights.push_back(p);
		if (p > 0) all_weights_null = false;
	}

	// Check that the distribution is well defined
	if (all_weights_null)
		return nullptr;

	// If well defined then sample according to it
	LeafDistribution dist(weights.begin(), weights.end());
	return &rand_element(leaf2bitnode, dist).second;
}

void AndBIT::reset_exhausted()
{
	for (auto& el : leaf2bitnode)
		el.second.exhausted = false;
	exhausted = false;
}

bool AndBIT::has_cycle() const
{
	return has_cycle(BindLinkCast(fcs)->get_implicand()[0]);
}

bool AndBIT::has_cycle(const Handle& h, HandleSet ancestors) const
{
	if (h->get_type() == EXECUTION_OUTPUT_LINK) {
		Handle arg = h->getOutgoingAtom(1);
		if (arg->get_type() == LIST_LINK) {
			Handle conclusion = arg->getOutgoingAtom(0);
			if (contains(ancestors, conclusion))
				return true;

			ancestors.insert(conclusion);
			Arity arity = arg->get_arity();
			if (1 < arity) {
				bool unordered_premises =
					arg->getOutgoingAtom(1)->get_type() == SET_LINK;
				if (unordered_premises) {
					OC_ASSERT(arity == 2,
					          "Mixture of ordered and unordered"
					          " premises not implemented!");
					arg = arg->getOutgoingAtom(1);
					for (const Handle& ph : arg->getOutgoingSet())
						if (has_cycle(ph, ancestors))
							return true;
					return false;
				} else {
					for (Arity i = 1; i < arity; ++i) {
						Handle ph = arg->getOutgoingAtom(i);
						if (has_cycle(ph, ancestors))
							return true;
						return false;
					}
				}
			}
			return false;
		} else {
			return contains(ancestors, arg);
		}
	} else {
		return contains(ancestors, h);
	}
}

bool AndBIT::operator==(const AndBIT& andbit) const
{
	return fcs == andbit.fcs;
}

bool AndBIT::operator<(const AndBIT& andbit) const
{
	// Sort by complexity to so that simpler and-BITs come first. Then
	// by content. Makes it easier to prune by complexity. It should
	// also make sampling a bit faster. And finally the user probabably
	// want that.
	return (complexity < andbit.complexity)
		or (complexity == andbit.complexity
		    and content_based_handle_less()(fcs, andbit.fcs));
}

std::string AndBIT::to_string(const std::string& indent) const
{
	return oc_to_string(fcs, indent);
}

std::string AndBIT::fcs_to_ascii_art(const Handle& nfcs) const
{
	return fcs_rewrite_to_ascii_art(BindLinkCast(nfcs)->get_implicand()[0]);
}

std::string AndBIT::fcs_rewrite_to_ascii_art(const Handle& h) const
{
	if (h->get_type() == EXECUTION_OUTPUT_LINK) {
		Handle gsn = h->getOutgoingAtom(0);
		Handle arg = h->getOutgoingAtom(1);
		if (arg->get_type() == LIST_LINK) {
			// Render the conclusion
			Handle conclusion = arg->getOutgoingAtom(0);
			std::string conclusion_aa = fcs_rewrite_to_ascii_art(conclusion);

			// Render the premises
			Arity arity = arg->get_arity();
			if (1 < arity) {
				std::vector<std::string> premises_aas;
				bool unordered_premises =
					arg->getOutgoingAtom(1)->get_type() == SET_LINK;
				if (unordered_premises) {
					OC_ASSERT(arity == 2,
					          "Mixture of ordered and unordered"
					          " premises not implemented!");
					arg = arg->getOutgoingAtom(1);
					for (const Handle& ph : arg->getOutgoingSet())
						premises_aas.push_back(fcs_rewrite_to_ascii_art(ph));
				} else {
					for (Arity i = 1; i < arity; ++i) {
						Handle ph = arg->getOutgoingAtom(i);
						premises_aas.push_back(fcs_rewrite_to_ascii_art(ph));
					}
				}
				// Merge horizontally the ascii arts of all premises
				std::string premises_merged_aa = ascii_art_hmerge(premises_aas);

				// Put a line over the head of the conclusion, with
				// the premises over that line.
				std::string ul(line_separator(premises_merged_aa, conclusion_aa,
				                              gsn, unordered_premises));
				unsigned ulls = leading_spaces(ul);
				unsigned ullls = ul.size() + ulls;
				unsigned conclusion_offset = ullls < conclusion_aa.size() ? 0 :
					(ullls - conclusion_aa.size()) / 2;
				std::string conclusion_indent(conclusion_offset, ' ');
				return premises_merged_aa + "\n" + ul + "\n"
					+ conclusion_indent + conclusion_aa;
			} else {
				// No premises, just put a line over the head of the
				// conclusion
				std::string line_str(line_separator("", conclusion_aa, gsn));
				return line_str + "\n" + conclusion_aa;
			}
		} else {
			// No premise, put a line over the head of the conclusion
			std::string conclusion_aa = fcs_rewrite_to_ascii_art(arg);
			std::string line_str(line_separator("", conclusion_aa, gsn));
			return line_str + "\n" + conclusion_aa;
		}
	} else return h->id_to_string();
}

double AndBIT::expand_complexity(const Handle& leaf, double prob) const
{
	// Calculate the complexity of the expanded and-BIT. Sum up the
	// complexity of the parent and-BIT with the complexity of the
	// expanded BIT-node and the complexity of the rule (1 - log(prob))
	return complexity
		+ leaf2bitnode.find(leaf)->second.complexity
		+ 1 - log(prob);
}

Handle AndBIT::expand_fcs(const Handle& leaf,
                          const RuleTypedSubstitutionPair& rule) const
{
	// Unify the rule conclusion with the leaf, and substitute any
	// variables in it by the associated term.
	Handle nfcs = substitute_unified_variables(leaf, rule.second);

	BindLinkPtr nfcs_bl(BindLinkCast(nfcs));
	Handle nfcs_vardecl = nfcs_bl->get_vardecl();
	Handle nfcs_pattern = nfcs_bl->get_body();
	Handle nfcs_rewrite = nfcs_bl->get_implicand()[0]; // assume that there is only one
	Handle rule_vardecl = rule.first.get_vardecl();

	// Generate new pattern term
	Handle npattern = expand_fcs_pattern(nfcs_pattern, rule.first);

	// Generate new rewrite term
	Handle nrewrite = expand_fcs_rewrite(nfcs_rewrite, rule.first);

	// Generate new vardecl
	// TODO: is this merging necessary?
	Handle merged_vardecl = merge_vardecl(nfcs_vardecl, rule_vardecl);
	Handle nvardecl = filter_vardecl(merged_vardecl, {npattern, nrewrite});

	// Remove constant clauses from npattern
	npattern = Unify::remove_constant_clauses(nvardecl, npattern, queried_as);

	// Generate new atomese forward chaining s trategy
	HandleSeq noutgoings({npattern, nrewrite});
	if (nvardecl)
		noutgoings.insert(noutgoings.begin(), nvardecl);
	nfcs = fcs->getAtomSpace()->add_link(BIND_LINK, std::move(noutgoings));

	// Log expansion
	LAZY_URE_LOG_DEBUG << "Expanded forward chainer strategy:" << std::endl
	                   << nfcs->to_string();
	LAZY_URE_LOG_DEBUG << "With inference tree:" << std::endl << std::endl
	                   << fcs_to_ascii_art(nfcs) << std::endl;

	return nfcs;
}

void AndBIT::set_leaf2bitnode()
{
	// For each leaf of fcs, associate a corresponding BITNode
	for (const Handle& leaf : get_leaves())
		insert_bitnode(leaf, BITNodeFitness());
}

AndBIT::HandleBITNodeMap::iterator
AndBIT::insert_bitnode(Handle leaf, const BITNodeFitness& fitness)
{
	if (not leaf)
		return leaf2bitnode.end();

	HandleBITNodeMap::iterator it = leaf2bitnode.find(leaf);
	if (it == leaf2bitnode.end())
		return leaf2bitnode.emplace(leaf, BITNode(leaf, fitness)).first;
	return it;
}

HandleSet AndBIT::get_leaves() const
{
	return get_leaves(fcs);
}

HandleSet AndBIT::get_leaves(const Handle& h) const
{
	Type t = h->get_type();
	if (t == BIND_LINK) {
		BindLinkPtr hsc = BindLinkCast(h);
		// Assume there is only one rewrite.
		Handle rewrite = hsc->get_implicand()[0];
		return get_leaves(rewrite);
	} else if (t == EXECUTION_OUTPUT_LINK) {
		// All arguments except the first one are potential target leaves
		Handle args = h->getOutgoingAtom(1);
		HandleSet leaves;
		if (args->get_type() == LIST_LINK) {
			OC_ASSERT(args->get_arity() > 0);
			for (Arity i = 1; i < args->get_arity(); i++) {
				HandleSet aleaves = get_leaves(args->getOutgoingAtom(i));
				leaves.insert(aleaves.begin(), aleaves.end());
			}
		}
		return leaves;
	} else if (t == SET_LINK) {
		// All atoms wrapped in a SetLink are potential target leaves
		HandleSet leaves;
		for (const Handle& el : h->getOutgoingSet()) {
			HandleSet el_leaves = get_leaves(el);
			leaves.insert(el_leaves.begin(), el_leaves.end());
		}
		return leaves;
	} else if (contains_atomtype(h, EXECUTION_OUTPUT_LINK)) {
		// If it contains an unquoted ExecutionOutputLink then it is
		// not a leaf (maybe it could but it would over complicate the
		// rest and bring no benefit since we can always expand a
		// parent and-BIT that has no such ExecutionOutputLink).
		return HandleSet{};
	}

	// Here it must be a leaf so return it
	return HandleSet{h};
}

Handle AndBIT::substitute_unified_variables(const Handle& leaf,
                                            const Unify::TypedSubstitution& ts) const
{
	BindLinkPtr fcs_bl(BindLinkCast(fcs));
	return Handle(Unify::substitute(fcs_bl, ts, queried_as));
}

Handle AndBIT::expand_fcs_pattern(const Handle& fcs_pattern,
                                  const Rule& rule) const
{
	Handle conclusion = rule.get_conclusion();
	HandleSeq prs_clauses = get_present_clauses(rule.get_implicant());
	HandleSeq virt_clauses = get_virtual_clauses(rule.get_implicant());
	HandleSeq prs_fcs_clauses = get_present_clauses(fcs_pattern);
	HandleSeq virt_fcs_clauses = get_virtual_clauses(fcs_pattern);

	// Remove any present fcs clauses that is equal to the conclusion
	auto eq_to_conclusion = [&](const Handle& h) {
		                        return content_eq(conclusion, h); };
	boost::remove_erase_if(prs_fcs_clauses, eq_to_conclusion);

	// Remove any virtual fcs clause that:
	// 1. is equal to the conclusion.
	// 2. is a precondition that uses that conclusion as argument.
	auto to_remove = [&](const Handle& h) {
		                 return eq_to_conclusion(h)
			                 or is_argument_of(h, conclusion); };
	boost::remove_erase_if(virt_fcs_clauses, to_remove);

	// Add present rule clauses
	prs_fcs_clauses.insert(prs_fcs_clauses.end(),
	                       prs_clauses.begin(), prs_clauses.end());

	// Add virtual rule clauses
	virt_fcs_clauses.insert(virt_fcs_clauses.end(),
	                        virt_clauses.begin(), virt_clauses.end());

	// Assemble the body
	return mk_pattern(prs_fcs_clauses, virt_fcs_clauses);
}

Handle AndBIT::expand_fcs_rewrite(const Handle& fcs_rewrite,
                                  const Rule& rule) const
{
	HandlePairSeq conclusions = rule.get_conclusions();
	OC_ASSERT(conclusions.size() == 1);
	Handle conclusion = conclusions[0].second;

	// Base cases

	// Replace the fcs rewrite atoms by the rule rewrite if equal to
	// the rule conclusion
	if (content_eq(fcs_rewrite, conclusion))
		return rule.get_implicand();

	// Recursive cases

	AtomSpace& as = *fcs->getAtomSpace();
	Type t = fcs_rewrite->get_type();

	if (t == EXECUTION_OUTPUT_LINK) {
		// If it is an ExecutionOutput then skip the first input
		// argument as it is a conclusion already.
		Handle gsn = fcs_rewrite->getOutgoingAtom(0);
		Handle arg = fcs_rewrite->getOutgoingAtom(1);
		if (arg->get_type() == LIST_LINK) {
			HandleSeq args = arg->getOutgoingSet();
			for (size_t i = 1; i < args.size(); i++)
				args[i] = expand_fcs_rewrite(args[i], rule);
			arg = as.add_link(LIST_LINK, std::move(args));
		}
		return as.add_link(EXECUTION_OUTPUT_LINK, {gsn, arg});
	} else if (t == SET_LINK) {
		// If a SetLink then treat its arguments as (unordered)
		// premises.
		HandleSeq args = fcs_rewrite->getOutgoingSet();
		for (size_t i = 0; i < args.size(); i++)
			args[i] = expand_fcs_rewrite(args[i], rule);
		return as.add_link(SET_LINK, std::move(args));
	} else
		// If none of the conditions apply just leave alone. Indeed,
		// assuming that the pattern matcher is executing the rewrite
		// term eagerly then it is garantied that all premises TVs
		// will be updated before running a rule, so we don't need to
		// substitute parts of a term containing the conclusion by the
		// application rule(premises).
		return fcs_rewrite;
}

bool AndBIT::is_argument_of(const Handle& eval, const Handle& atom) const
{
	if (eval->get_type() == EVALUATION_LINK) {
		Handle args = eval->getOutgoingAtom(1);
		if (content_eq(args, atom))
			return true;
		if (args->get_type() == LIST_LINK)
			for (Arity i = 0; i < args->get_arity(); i++)
				if (content_eq(args->getOutgoingAtom(i), atom))
					return true;
	}
	return false;
}

bool AndBIT::is_locally_quoted_eq(const Handle& lhs, const Handle& rhs) const
{
	if (content_eq(lhs, rhs))
		return true;
	Type lhs_t = lhs->get_type();
	Type rhs_t = rhs->get_type();
	if (lhs_t == LOCAL_QUOTE_LINK and rhs_t != LOCAL_QUOTE_LINK)
		return content_eq(lhs->getOutgoingAtom(0), rhs);
	if (lhs_t != LOCAL_QUOTE_LINK and rhs_t == LOCAL_QUOTE_LINK)
		return content_eq(lhs, rhs->getOutgoingAtom(0));
	return false;
}

Handle AndBIT::mk_pattern(HandleSeq prs_clauses, HandleSeq virt_clauses) const
{
	// Remove redundant clauses
	remove_redundant(prs_clauses);
	remove_redundant(virt_clauses);

	// Assemble the body
	AtomSpace& as = *fcs->getAtomSpace();
	if (not prs_clauses.empty())
		virt_clauses.push_back(as.add_link(PRESENT_LINK, std::move(prs_clauses)));
	return virt_clauses.empty() ? Handle::UNDEFINED
		: (virt_clauses.size() == 1 ? virt_clauses.front()
		   : as.add_link(AND_LINK, std::move(virt_clauses)));
}

void AndBIT::remove_redundant(HandleSeq& hs)
{
	boost::sort(hs);
	boost::erase(hs, boost::unique<boost::return_found_end>(hs));
}

HandleSeq AndBIT::get_present_clauses(const Handle& pattern)
{
	if (pattern->get_type() == AND_LINK)
		return get_present_clauses(pattern->getOutgoingSet());
	else if (pattern->get_type() == PRESENT_LINK)
		return pattern->getOutgoingSet();
	else
		return {};
}

HandleSeq AndBIT::get_present_clauses(const HandleSeq& clauses)
{
	HandleSeq prs_clauses;
	for (const Handle& clause : clauses) {
		if (clause->get_type() == PRESENT_LINK) {
			const HandleSeq& oset = clause->getOutgoingSet();
			prs_clauses.insert(prs_clauses.end(), oset.begin(), oset.end());
		}
	}
	return prs_clauses;
}

HandleSeq AndBIT::get_virtual_clauses(const Handle& pattern)
{
	if (pattern->get_type() == AND_LINK)
		return get_virtual_clauses(pattern->getOutgoingSet());
	else if (pattern->get_type() == PRESENT_LINK)
		return {};
	else
		return {pattern};
}

HandleSeq AndBIT::get_virtual_clauses(const HandleSeq& clauses)
{
	HandleSeq virt_clauses;
	for (const Handle& clause : clauses)
		if (clause->get_type() != PRESENT_LINK)
			virt_clauses.push_back(clause);
	return virt_clauses;
}

std::vector<std::string>
AndBIT::ascii_art_hmerge(const std::vector<std::string>& laa,
                         const std::vector<std::string>& raa,
                         unsigned dst)
{
	if (laa.empty())
		return raa;
	if (raa.empty())
		return laa;

	// Max line size of laa (in common with raa)
	int left_max = 0;

	// Min leading spaces of raa (in common with laa)
	int right_min = std::numeric_limits<int>::max();

	// Calculate the merging offset of origin of the right image
	// relative to the origin of the left one.
	size_t ls = laa.size();
	size_t rs = raa.size();
	for (size_t i = 0; i < std::min(ls, rs); ++i) {
		left_max = std::max(left_max, (int)laa[i].size());
		right_min = std::min(right_min, (int)leading_spaces(raa[i]));
	}

	// Only add dst if there is an actual border to not collide with.
	if (left_max) left_max += dst;

	// Where to paste the raa
	size_t offset = std::max(0, left_max - right_min);

	// Perform merging
	std::vector<std::string> result;
	for (size_t i = 0; i < std::max(ls, rs); ++i) {
		std::string line;
		if (i < ls)
			line = laa[i];
		if (i < rs) {
			// Fill with spaces
			while (line.size() < offset)
				line += " ";
			// Remove unused leading spaces
			line += i < ls ? raa[i].substr(line.size() - offset) : raa[i];
		}
		result.push_back(line);
	}
	return result;
}

std::string AndBIT::ascii_art_hmerge(const std::string& laa,
                                     const std::string& raa, unsigned dst)
{
	// Split laa into lines, from bottom to top
	std::vector<std::string> laa_lines = reverse_split(laa);
	// Split raa into lines, from bottom to top
	std::vector<std::string> raa_lines = reverse_split(raa);
	// Produce the merge and join it back into a string
	std::vector<std::string> res_lines =
		ascii_art_hmerge(laa_lines, raa_lines, dst);
	boost::reverse(res_lines);
	return boost::join(res_lines, "\n");
}

std::string AndBIT::ascii_art_hmerge(const std::vector<std::string>& aas,
                                     unsigned dst)
{
	std::string result;
	for (const std::string& aa : aas)
		result = ascii_art_hmerge(result, aa, dst);
	return result;
}

std::vector<std::string> AndBIT::reverse_split(const std::string& aa)
{
	std::vector<std::string> result;
	boost::split(result, aa, boost::is_any_of("\n"));
	boost::reverse(result);
	return result;
}

std::string AndBIT::bottom_line(const std::string& aa)
{
	std::vector<std::string> aa_lines = reverse_split(aa);
	return aa_lines.front();
}

unsigned AndBIT::leading_spaces(const std::string& line)
{
	std::string left_trimmed_line = boost::trim_left_copy(line);
	return line.size() - left_trimmed_line.size();
}

std::string AndBIT::remove_vowels(std::string str, size_t tg_size)
{
	std::string::size_type pos;

	// Remove lower-case vowels, starting at the end
	while (str.size() > tg_size) {
		if ((pos = str.find_last_of("aeiou")) == std::string::npos
			 or // Never remove the first character
			 pos == 0)
			break;
		str.replace (pos, 1, "");
	}
	return str;
}

std::string AndBIT::remove_consonants(std::string str, size_t tg_size)
{
	std::string::size_type pos;

	// Remove lower-case consonants, starting at the end
	while (str.size() > tg_size) {
		if ((pos = str.find_last_of("bcdfghjklmnpqrtvwxyz")) == std::string::npos
			 or // Never remove the first character
			 pos == 0)
			break;
		str.replace (pos, 1, "");
	}
	return str;
}

std::string AndBIT::abbreviate(std::string str, size_t tg_size)
{
	// We remove characters bit by bit in each word of the rule name,
	// starting from the end.
	std::vector<std::string> words;
	boost::split(words, str, boost::is_any_of("-"));

	for (int min_wrd_size = 3; 0 < min_wrd_size; min_wrd_size--) {
		for (std::string& word : boost::adaptors::reverse(words)) {
			int wrd_size = word.size();
			int rm_size = (int)str.size() - (int)tg_size;
			size_t wrd_tg_size = std::max(min_wrd_size, wrd_size - rm_size);
			word = remove_vowels(word, wrd_tg_size);
			word = remove_consonants(word, wrd_tg_size);
			str = boost::join(words, "-");
			if (str.size() == tg_size)
				return str;
		}
	}

	return str;
}

std::string AndBIT::line_separator(const std::string& up_aa,
                                   const std::string& low_aa,
                                   const Handle& gsn,
                                   bool unordered_premises)
{
	// Calculate the leading space and line separator sizes. We assume
	// that low_aa has no leading space.
	size_t lead_sp_size = 0;                // Leading space size
	size_t line_sep_size = low_aa.size();   // Line separator size
	if (not up_aa.empty()) {
		std::string up_bl = bottom_line(up_aa);
		size_t up_bls = up_bl.size();
		lead_sp_size = leading_spaces(up_bl);
		line_sep_size = std::max(line_sep_size, up_bls - lead_sp_size);
	}

	// Get formula string
	std::string lang, lib, fun;
	LibraryManager::parse_schema(gsn->get_name(), lang, lib, fun);
	std::string frml_str = fun;

	// Abbreviate formula string to fit inside the line separator
	size_t frml_str_max_size = line_sep_size;
	if (2 < frml_str_max_size)
		frml_str_max_size -= 2;
	std::string abbr_frml_str = abbreviate(frml_str, frml_str_max_size);
	size_t abbr_frml_str_size = abbr_frml_str.size();

	// Overlay the formula string on top of the line
	std::string line_str;
	size_t offset = (line_sep_size - abbr_frml_str_size) / 2;
	for (size_t i = 0; i < line_sep_size; ++i)
		if (i < offset or offset + abbr_frml_str_size <= i)
			line_str.push_back(unordered_premises ? '=' : '-');
		else
			line_str.push_back(abbr_frml_str[i - offset]);

	// Append leading space in front of the line
	return std::string(lead_sp_size, ' ') + line_str;
}

/////////
// BIT //
/////////

BIT::BIT() : _as(nullptr) {}

BIT::BIT(AtomSpace& as,
         const Handle& target,
         const Handle& vardecl,
         const BITNodeFitness& fitness)
	: bit_as(&as), // child atomspace of as
	  _as(&as), _init_target(target), _init_vardecl(vardecl),
	  _init_fitness(fitness)
{
	bit_as.clear_copy_on_write();
}

BIT::~BIT() {}

bool BIT::empty() const
{
	return andbits.empty();
}

size_t BIT::size() const
{
	return andbits.size();
}

AndBIT* BIT::init()
{
	andbits.emplace_back(bit_as, _init_target,
	                     _init_vardecl, _init_fitness, _as);

	LAZY_URE_LOG_DEBUG << "Initialize BIT with:" << std::endl
	                   << andbits.begin()->to_string();

	return &*andbits.begin();
}

AndBIT* BIT::expand(AndBIT& andbit, BITNode& bitleaf,
                    const RuleTypedSubstitutionPair& rule, double prob)
{
	// Make sure that the rule is not already an or-child of bitleaf.
	if (contains(bitleaf, rule)) {
		ure_logger().debug() << "An equivalent rule has already expanded "
		                     << "that BIT-node, abort expansion";
		return nullptr;
	}

	// Insert the rule as or-branch of this bitleaf
	bitleaf.rules.insert(rule);

	// Expand the and-BIT and insert it in the BIT, if the expansion
	// was successful
	AndBIT new_andbit = andbit.expand(bitleaf.body, rule, prob);
	return (bool)new_andbit.fcs ? insert(new_andbit) : nullptr;
}

AndBIT* BIT::insert(AndBIT& andbit)
{
	// Check that it isn't already in the BIT
	if (boost::find(andbits, andbit) != andbits.end()) {
		LAZY_URE_LOG_DEBUG << "The following and-BIT is already in the BIT: "
		                   << andbit.fcs->id_to_string();
		return nullptr;
	}
	// Insert while keeping the order
	auto it = andbits.insert(boost::lower_bound(andbits, andbit), andbit);

	// Return andbit pointer
	return &*it;
}

void BIT::reset_exhausted_flags()
{
	for (AndBIT& andbit : andbits)
		andbit.reset_exhausted();
}

bool BIT::andbits_exhausted() const
{
	return boost::algorithm::all_of(andbits, [](const AndBIT& andbit) {
			return andbit.exhausted; });
}

bool BIT::contains(const BITNode& bitnode,
                   const RuleTypedSubstitutionPair& rule) const
{
	return bitnode.rules.find(rule.first) != bitnode.rules.end();
}

std::string oc_to_string(const BITNode& bitnode, const std::string& indent)
{
	return bitnode.to_string(indent);
}

std::string oc_to_string(const AndBIT& andbit, const std::string& indent)
{
	return andbit.to_string(indent);
}

// std::string oc_to_string(const BIT& bit)
// {
// 	return bit.to_string();
// }

} // ~namespace opencog