OverviewFilesCommitsIssuesDocsReleasesPeers

opencog-ure / opencog / ure / Rule.h

413 lines · c ref:
/*
 * Rule.h
 *
 * Copyright (C) 2015 OpenCog Foundation
 *
 * Authors: Misgana Bayetta <misgana.bayetta@gmail.com>  2015
 *          Nil Geisweiller 2015-2016
 *          Shujing Ke 2018
 *
 * 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.
 */

#ifndef _OPENCOG_RULE_H_
#define _OPENCOG_RULE_H_

#include <boost/operators.hpp>
#include <opencog/atomspace/AtomSpace.h>
#include <opencog/atoms/core/ScopeLink.h>
#include <opencog/atoms/core/VariableList.h>
#include <opencog/atoms/core/Variables.h>
#include <opencog/atoms/pattern/BindLink.h>
#include <opencog/unify/Unify.h>
#include <opencog/util/empty_string.h>

namespace opencog {

class Rule;
typedef std::shared_ptr<Rule> RulePtr;
#define createRule std::make_shared<Rule>
struct rule_ptr_less
{
	bool operator()(const RulePtr& l, const RulePtr& r) const;
};

/**
 * The rule set is in fact a sorted vector, as to be able to change
 * some features of the rules such as their exhausted flag without
 * having to resort or rebuild the set. We use rule pointers to make
 * sure that insertion does not deallocate the rule since its pointer
 * is passed around.
 */
class RuleSet : public std::vector<RulePtr>,
                public boost::totally_ordered<RuleSet>
{
	typedef std::vector<RulePtr> super;

public:
	/**
	 * Run all meta rules over as and insert the resulting rules back
	 * in the rule set.
	 */
	void expand_meta_rules(AtomSpace& as);

	/**
	 * Return the set of rule aliases, as aliases of inference rules
	 * are used in control rules.
	 */
	HandleSet aliases() const;

	/**
	 * Insert rule in the rule set if no other alpha-equivalent rule is
	 * in it.
	 *
	 * Return a pair (iterator, true) iff rule has been successfully inserted.
	 */
	std::pair<iterator, bool> insert(RulePtr rule);

	/**
	 * Insert a range of rules
	 */
	template<typename It>
	void insert(It from, It to)
	{
		for (; from != to; ++from)
			insert(*from);
	}

	/**
	 * Content based comparison.
	 */
	bool operator==(const RuleSet& other) const;
	bool operator<(const RuleSet& other) const;

	/**
	 * Log-time search
	 */
	iterator find(const RulePtr& rule);
	const_iterator find(const RulePtr& rule) const;

	/**
	 * Get all rule truth values, ordered according to the rules. This
	 * can be useful to build distributions for subsequential
	 * selection.
	 */
	TruthValueSeq get_tvs() const;

	std::string to_string(const std::string& indent=empty_string) const;
	std::string to_short_string(const std::string& indent=empty_string) const;
};

typedef std::map<Rule, Unify::TypedSubstitution> RuleTypedSubstitutionMap;
typedef RuleTypedSubstitutionMap::value_type RuleTypedSubstitutionPair;

/**
 * Class for managing rules in the URE.
 *
 * A URE rule have one of the following format
 *
 *     <premise-1>
 *     ...
 *     <premise-1>
 *     |-
 *     <conclusion>
 *
 * represented either as
 *
 *     BindLink
 *        <variables>
 *        AndLink
 *           <premise-1>
 *           ...
 *           <premise-n>
 *        <conclusion>
 *
 * if there is ExecutionOutputLink in the BindLink rewrite term. Or
 * 
 *     BindLink
 *        <variables>
 *        AndLink
 *           <clauses-1>
 *           ...
 *           <clauses-n>
 *        ExecutionOutputLink
 *           <formula>
 *           ListLink
 *              <conclusion>
 *              <premises-1>
 *              ...
 *              <premises-m>
 *
 * Also, unordered premises can be wrapped in SetLink as this may
 * speed up a bit the Backward Chainer.
 */
class Rule : public boost::totally_ordered<Rule>
{
public:
	/**
	 * The rule argument has the format
	 *
	 * MemberLink <TV>
	 *    <rule alias>
	 *    <rbs>
	 *
	 * where `<rule alias>` is a `DefinedSchemaNode`, defined elsewhere,
	 * with a `DefineLink`.
	 */
	Rule();
	explicit Rule(const Handle& rule);
	Rule(const Rule& rule);
	Rule(const Handle& rule_alias, const Handle& rbs);
	Rule(const Handle& rule_alias, const Handle& rule, const Handle& rbs);

	void init(const Handle& rule_member);
	void init(const Handle& rule_alias, const Handle& rbs);
	void init(const Handle& rule_alias, const Handle& rule, const Handle& rbs);

	/**
	 * Verify if this rule is defined in the required format.
	 * The main purpose is to give user the corresponding warnings and info.
	 * Only verify a normal rule, do not use this function to verify a meta rule.
	 * Current verify items:
	 * 1.If there are multiple conslusions, they also need to be wrapped with one
	 *   single ExecutionOutPutLink, otherwise it won't work for bc.
	 */
	bool verify_rule();
	
	// Content-based (including alpha-equivalence) comparison
	bool operator==(const Rule& r) const;
	bool operator<(const Rule& r) const;

	// Assignment
	Rule& operator=(const Rule& r);

	// Modifiers
	void set_rule(const Handle&);
	void set_name(const std::string&);
	void set_category(const std::string&);

	// Access
	std::string& get_name();
	const std::string& get_name() const;
	Handle get_rule() const;
	Handle get_alias() const;
	Handle get_definition() const;
	Handle get_rbs() const;

	/**
	 * Add the rule in AtomSpace as.
	 *
	 * Warning: this will only add the pattern and rewrite terms, not
	 * the scope links themselves. This is a hack to work around the
	 * alpha conversion that the atomspace may perform when inserting
	 * scope links, so that alpha-equivalent scope links are not
	 * redundantly added to an atomspace, which turns out to be
	 * inconvenient for combining multiple scope links, in the way
	 * that the backward chainer does when building forward chaining
	 * strategies.
	 *
	 * TODO: support backward rule form.
	 */
	void add(AtomSpace&);

	/**
	 * Return the variable declaration of the rule.
	 */
	Handle get_vardecl() const;
	const Variables& get_variables() const;
	Handle get_implicant() const;
	Handle get_implicand() const;

	// Properties
	bool is_valid() const;      // is it a proper BindLink?
	bool is_meta() const;       // does that rule produces a rule?
	bool has_cycle() const;     // is some premise equal to conclusion?

	/**
	 * Return clauses. Warning: it doesn't not necessarily return all
	 * clauses, will avoid non virtual clauses in the case PresentLink
	 * is, as the intend of this function is to be used by
	 * get_premises().
	 */
	HandleSeq get_clauses() const;

	/**
	 * Return the rule premises, that is the last arguments of the
	 * rewrite term's ExecutionOutputLink. If the last arguments are
	 * SetLinks, then return their outgoings as well. That is because
	 * SetLink is used to represent unordered arguments.
	 */
	HandleSeq get_premises() const;

	/**
	 * Return the rule conclusion. That is the first argument of the
	 * rewrite term's ExecutionOutputLink.
	 */
	Handle get_conclusion() const;

	/**
	 * Return the list of conclusion patterns. Each pattern is a pair
	 * of Handles (variable declaration, body). Used for finding out
	 * is the rule matches a certain target.
	 *
	 * TODO: probably obsolete, should be removed
	 */
	HandlePairSeq get_conclusions() const;

	/**
	 * Get the TruthValue associated with the rule.
	 */
	TruthValuePtr get_tv() const;

	/**
	 * Used by the forward chainer to select rules. Given a source,
	 * generate all rule variations that may be applied over a given
	 * source. The variables in the rules are renamed to almost
	 * certainly avoid name collision.
	 *
	 * TODO: we probably want to support a vector of sources for rules
	 * with multiple premises.
	 *
	 * TODO: we probably want to return only typed substitutions.
	 * However due to the unifier not supporting well same variables
	 * one different sides, we need to perform alpha conversion to
	 * avoid troubles, thus having to return the rules along side the
	 * typed substitutions.
	 *
	 * TODO: it's not clear the forward chainer needs the
	 * TypedSubtitution at all. Maybe only the rules are enough. For
	 * now we return both.
	 */
	RuleTypedSubstitutionMap unify_source(const Handle& source,
	                                      const Handle& vardecl=Handle::UNDEFINED,
	                                      const AtomSpace* queried_as=nullptr) const;

	/**
	 * Used by the backward chainer. Given a target, generate all rule
	 * variations that may infer this target. The variables in the
	 * rules are renamed to almost certainly avoid name collision.
	 *
	 * TODO: we probably want to return only typed substitutions.
	 * However due to the unifier not supporting well same variables
	 * one different sides, we need to perform alpha conversion to
	 * avoid troubles, thus having to return the rules along side the
	 * typed substitutions.
	 */
	 RuleTypedSubstitutionMap unify_target(const Handle& target,
	                                       const Handle& vardecl=Handle::UNDEFINED,
	                                       const AtomSpace* queried_as=nullptr) const;

	/**
	 * Remove the typed substitutions from the rule typed substitution
	 * map and generate the resulting RuleSet.
	 */
	static RuleSet strip_typed_substitution(const RuleTypedSubstitutionMap& rules);

	/**
	 * Apply rule (in a forward way) over atomspace as.
	 */
	Handle apply(AtomSpace& as) const;

	/**
	 * Set exhausted flag to true.
	 */
	void set_exhausted();

	/**
	 * Set exhausted flag to false.
	 */
	void reset_exhausted();

	/**
	 * Get exhausted flag.
	 */
	bool is_exhausted() const;

	std::string to_string(const std::string& indent=empty_string) const;
	std::string to_short_string(const std::string& indent=empty_string) const;

	// This flag allows to only sonsider the Rule clauses as
	// premises. This is for backward compatibility with some rule
	// base like R2L.
	mutable bool premises_as_clauses;

private:
	// Rule
	BindLinkPtr _rule;

	// Rule alias: (DefineLink _rule_alias _rule_handle)
	Handle _rule_alias;

	// Rule name, the name of the node referring to the rule body
	std::string _name;

	// Rule-based system name
	Handle _rbs;

	// TruthValue associated to the rule (the TV of the membership of
	// the rule to the RBS). The semantics of the TV is the
	// probability of participating in building an inference (forward
	// or backward) fulfilling the objective.
	//
	// It is best to have this TV with a confidence lower than 1,
	// otherwise, if given the choice between several valid rules, the
	// URE will always choose the one with the highest confidence.
	TruthValuePtr _tv;

	// True if the rule has already been applied.
	bool _exhausted;

	// TODO: subdivide in smaller and shared mutexes
	mutable std::mutex _mutex;

	// Return a copy of the rule with the variables alpha-converted
	// into random variable names.
	Rule rand_alpha_converted() const;

	// Return the conclusion patterns of the rule. There are several
	// of them because the conclusions can be wrapped in the
	// ListLink. In case each conclusion is an ExecutionOutputLink
	// then return the first argument of that ExecutionOutputLink.
	HandleSeq get_conclusion_patterns() const;
	Handle get_conclusion_pattern(const Handle& h) const;

	// Given an ExecutionOutputLink return its first argument
	Handle get_execution_output_first_argument(const Handle& h) const;

	// Given a typed substitution obtained from typed_substitutions
	// unify function, generate a new partially substituted rule.
	Rule substituted(const Unify::TypedSubstitution& ts,
	                 const AtomSpace* queried_as=nullptr) const;
};

// Debugging helpers see
// http://wiki.opencog.org/w/Development_standards#Print_OpenCog_Objects
// The reason indent is not an optional argument with default is
// because gdb doesn't support that, see
// http://stackoverflow.com/questions/16734783 for more explanation.
std::string oc_to_string(const Rule& rule,
                         const std::string& indent=empty_string);
std::string oc_to_string(const RuleSet& rules,
                         const std::string& indent=empty_string);
std::string oc_to_string(const RuleTypedSubstitutionPair& rule_ts_pair,
                         const std::string& indent=empty_string);
std::string oc_to_string(const RuleTypedSubstitutionMap& rule_ts_map,
                         const std::string& indent=empty_string);

} // ~namespace opencog

#endif /* _OPENCOG_RULE_H_ */