503 lines
18 KiB
C++
503 lines
18 KiB
C++
/*
|
||
* Copyright © 2005-2011 Ondra Kamenik
|
||
* Copyright © 2019 Dynare Team
|
||
*
|
||
* This file is part of Dynare.
|
||
*
|
||
* Dynare is free software: you can redistribute it and/or modify
|
||
* it under the terms of the GNU General Public License as published by
|
||
* the Free Software Foundation, either version 3 of the License, or
|
||
* (at your option) any later version.
|
||
*
|
||
* Dynare 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 General Public License
|
||
* along with Dynare. If not, see <https://www.gnu.org/licenses/>.
|
||
*/
|
||
|
||
#ifndef OGP_TREE_H
|
||
#define OGP_TREE_H
|
||
|
||
#include <vector>
|
||
#include <set>
|
||
#include <map>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#include <ostream>
|
||
#include <memory>
|
||
|
||
namespace ogp
|
||
{
|
||
using std::unordered_set;
|
||
using std::unordered_map;
|
||
using std::vector;
|
||
using std::set;
|
||
using std::map;
|
||
|
||
/** Enumerator representing nulary, unary and binary operation
|
||
* codes. For nulary, 'none' is used. When one is adding a new
|
||
* codes, he should update the code of #OperationTree::add_unary,
|
||
* #OperationTree::add_binary, and of course
|
||
* #OperationTree::add_derivative. */
|
||
enum class code_t { NONE, UMINUS, LOG, EXP, SIN, COS, TAN, SQRT, ERF,
|
||
ERFC, PLUS, MINUS, TIMES, DIVIDE, POWER };
|
||
|
||
/** Class representing a nulary, unary, or binary operation. */
|
||
class Operation
|
||
{
|
||
protected:
|
||
/** Code of the operation. */
|
||
code_t code{code_t::NONE};
|
||
/** First operand. If none, then it is -1. */
|
||
int op1{-1};
|
||
/** Second operand. If none, then it is -1. */
|
||
int op2{-1};
|
||
|
||
public:
|
||
/** Constructs a binary operation. */
|
||
Operation(code_t cd, int oper1, int oper2)
|
||
: code(cd), op1(oper1), op2(oper2)
|
||
{
|
||
}
|
||
/** Constructs a unary operation. */
|
||
Operation(code_t cd, int oper1)
|
||
: code(cd), op1(oper1)
|
||
{
|
||
}
|
||
/** Constructs a nulary operation. */
|
||
Operation() = default;
|
||
/** A copy constructor. */
|
||
Operation(const Operation &op) = default;
|
||
|
||
/** Operator =. */
|
||
Operation &operator=(const Operation &op) = default;
|
||
/** Operator ==. */
|
||
bool
|
||
operator==(const Operation &op) const
|
||
{
|
||
return code == op.code && op1 == op.op1 && op2 == op.op2;
|
||
}
|
||
/** Operator < implementing lexicographic ordering. */
|
||
bool
|
||
operator<(const Operation &op) const
|
||
{
|
||
return (code < op.code
|
||
|| code == op.code
|
||
&& (op1 < op.op1 || op1 == op.op1 && op2 < op.op2));
|
||
}
|
||
/** Returns a number of operands. */
|
||
int
|
||
nary() const
|
||
{
|
||
return (op2 == -1) ? ((op1 == -1) ? 0 : 1) : 2;
|
||
}
|
||
/** Returns a hash value of the operation. */
|
||
size_t
|
||
hashval() const
|
||
{
|
||
return op2+1 + (op1+1)^15 + static_cast<int>(code)^30;
|
||
}
|
||
|
||
code_t
|
||
getCode() const
|
||
{
|
||
return code;
|
||
}
|
||
int
|
||
getOp1() const
|
||
{
|
||
return op1;
|
||
}
|
||
int
|
||
getOp2() const
|
||
{
|
||
return op2;
|
||
}
|
||
};
|
||
|
||
/** This struct is a predicate for ordering of the operations in
|
||
* OperationTree class. now obsolete */
|
||
struct ltoper
|
||
{
|
||
bool
|
||
operator()(const Operation &oper1, const Operation &oper2) const
|
||
{
|
||
return oper1 < oper2;
|
||
}
|
||
};
|
||
|
||
/** Hash function object for Operation. */
|
||
struct ophash
|
||
{
|
||
size_t
|
||
operator()(const Operation &op) const
|
||
{
|
||
return op.hashval();
|
||
}
|
||
};
|
||
|
||
/** This struct is a function object selecting some
|
||
* operations. The operation is given by a tree index. */
|
||
struct opselector
|
||
{
|
||
virtual bool operator()(int t) const = 0;
|
||
virtual ~opselector() = default;
|
||
};
|
||
|
||
/** Forward declaration of OperationFormatter. */
|
||
class OperationFormatter;
|
||
class DefaultOperationFormatter;
|
||
|
||
/** Forward declaration of EvalTree to make it friend of OperationTree. */
|
||
class EvalTree;
|
||
|
||
/** Class representing a set of trees for terms. Each term is
|
||
* given a unique non-negative integer. The terms are basically
|
||
* operations whose (integer) operands point to another terms in
|
||
* the tree. The terms are stored in the vector. Equivalent unary
|
||
* and binary terms are stored only once. This class guarantees
|
||
* the uniqueness. The uniqueness of nulary terms is guaranteed by
|
||
* the caller, since at this level of Operation abstraction, one
|
||
* cannot discriminate between different nulary operations
|
||
* (constants, variables). The uniqueness is enforced by the
|
||
* unordered_map whose keys are operations and values are integers
|
||
* (indices of the terms).
|
||
|
||
* This class can also make derivatives of a given term with
|
||
* respect to a given nulary term. I order to be able to quickly
|
||
* recognize zero derivativates, we maintain a list of nulary
|
||
* terms contained in the term. A possible zero derivative is then quickly
|
||
* recognized by looking at the list. The list is implemented as a
|
||
* unordered_set of integers.
|
||
*
|
||
* In addition, many term can be differentiated multiple times wrt
|
||
* one variable since they can be referenced multiple times. To
|
||
* avoid this, for each term we maintain a map mapping variables
|
||
* to the derivatives of the term. As the caller will
|
||
* differentiate wrt more and more variables, these maps will
|
||
* become richer and richer.
|
||
*/
|
||
class OperationTree
|
||
{
|
||
friend class EvalTree;
|
||
friend class DefaultOperationFormatter;
|
||
protected:
|
||
/** This is the vector of the terms. An index to this vector
|
||
* uniquelly determines the term. */
|
||
vector<Operation> terms;
|
||
|
||
/** This defines a type for a map mapping the unary and binary
|
||
* operations to their indices. */
|
||
using _Topmap = unordered_map<Operation, int, ophash>;
|
||
|
||
/** This is the map mapping the unary and binary operations to
|
||
* the indices of the terms.*/
|
||
_Topmap opmap;
|
||
|
||
/** This is a type for a set of integers. */
|
||
using _Tintset = unordered_set<int>;
|
||
/** This is a vector of integer sets corresponding to the
|
||
* nulary terms contained in the term. */
|
||
vector<_Tintset> nul_incidence;
|
||
|
||
/** This is a type of the map from variables (nulary terms) to
|
||
* the terms. */
|
||
using _Tderivmap = unordered_map<int, int>;
|
||
/** This is a vector of derivative mappings. For each term, it
|
||
* maps variables to the derivatives of the term with respect
|
||
* to the variables. */
|
||
vector<_Tderivmap> derivatives;
|
||
|
||
/** The tree index of the last nulary term. */
|
||
int last_nulary;
|
||
public:
|
||
/** Enumeration for special terms. We need zero, one, nan and
|
||
* 2/pi. These will be always first four terms having indices
|
||
* zero, one and two, three. If adding anything to this
|
||
* enumeration, make sure ‘num_constants’ remains the last one.*/
|
||
enum { zero, one, nan, two_over_pi, num_constants };
|
||
|
||
/** The unique constructor which initializes the object to
|
||
* contain only zero, one and nan and two_over_pi.*/
|
||
OperationTree();
|
||
|
||
/** Copy constructor. */
|
||
OperationTree(const OperationTree &ot) = default;
|
||
|
||
/** Add a nulary operation. The caller is responsible for not
|
||
* inserting two semantically equivalent nulary operations.
|
||
* @return newly allocated index
|
||
*/
|
||
int add_nulary();
|
||
|
||
/** Add a unary operation. The uniqness is checked, if it
|
||
* already exists, then it is not added.
|
||
* @param code the code of the unary operation
|
||
* @param op the index of the operand
|
||
* @return the index of the operation
|
||
*/
|
||
int add_unary(code_t code, int op);
|
||
|
||
/** Add a binary operation. The uniqueness is checked, if it
|
||
* already exists, then it is not added.
|
||
* @param code the code of the binary operation
|
||
* @param op1 the index of the first operand
|
||
* @param op2 the index of the second operand
|
||
* @return the index of the operation
|
||
*/
|
||
int add_binary(code_t code, int op1, int op2);
|
||
|
||
/** Add the derivative of the given term with respect to the
|
||
* given nulary operation.
|
||
* @param t the index of the operation being differentiated
|
||
* @param v the index of the nulary operation
|
||
* @return the index of the derivative
|
||
*/
|
||
int add_derivative(int t, int v);
|
||
|
||
/** Add the substitution given by the map. This adds a new
|
||
* term which is equal to the given term with applied
|
||
* substitutions given by the map replacing each term on the
|
||
* left by a term on the right. We do not check that the terms
|
||
* on the left are not subterms of the terms on the right. If
|
||
* so, the substituted terms are not subject of further
|
||
* substitution. */
|
||
int add_substitution(int t, const map<int, int> &subst);
|
||
|
||
/** Add the substitution given by the map where left sides of
|
||
* substitutions come from another tree. The right sides are
|
||
* from this tree. The given t is from the given otree. */
|
||
int add_substitution(int t, const map<int, int> &subst,
|
||
const OperationTree &otree);
|
||
|
||
/** This method turns the given term to a nulary
|
||
* operation. This is an only method, which changes already
|
||
* existing term (all other methods add something new). User
|
||
* should use this with caution and must make sure that
|
||
* something similar has happened for atoms. In addition, it
|
||
* does not do anything with derivatives, so it should not be
|
||
* used after some derivatives were created, and derivatives
|
||
* already created and saved in derivatives mappings should be
|
||
* forgotten with forget_derivative_maps. */
|
||
void nularify(int t);
|
||
|
||
/** Return the set of nulary terms of the given term. */
|
||
const unordered_set<int> &
|
||
nulary_of_term(int t) const
|
||
{
|
||
return nul_incidence[t];
|
||
}
|
||
|
||
/** Select subterms of the given term according a given
|
||
* operation selector and return the set of terms that
|
||
* correspond to the compounded operations. The given term is
|
||
* a compound function of the returned subterms and the
|
||
* function consists only from operations which yield false in
|
||
* the selector. */
|
||
unordered_set<int> select_terms(int t, const opselector &sel) const;
|
||
|
||
/** Select subterms of the given term according a given
|
||
* operation selector and return the set of terms that
|
||
* correspond to the compounded operations. The given term is
|
||
* a compound function of the returned subterms and the
|
||
* subterms are maximal subterms consisting from operations
|
||
* yielding true in the selector. */
|
||
unordered_set<int> select_terms_inv(int t, const opselector &sel) const;
|
||
|
||
/** This forgets all the derivative mappings. It is used after
|
||
* a term has been nularified, and then the derivative
|
||
* mappings carry wrong information. Note that the derivatives
|
||
* mappings serve only as a tool for quick returns in
|
||
* add_derivative. Resseting the mappings is harmless, all the
|
||
* information is rebuilt in add_derivative without any
|
||
* additional nodes (trees). */
|
||
void forget_derivative_maps();
|
||
|
||
/** This returns an operation of a given term. */
|
||
const Operation &
|
||
operation(int t) const
|
||
{
|
||
return terms[t];
|
||
}
|
||
|
||
/** This outputs the operation to the given file descriptor
|
||
* using the given OperationFormatter. */
|
||
void print_operation_tree(int t, std::ostream &os, OperationFormatter &f) const;
|
||
|
||
/** Debug print of a given operation: */
|
||
void print_operation(int t) const;
|
||
|
||
/** Return the last tree index of a nulary term. */
|
||
int
|
||
get_last_nulary() const
|
||
{
|
||
return last_nulary;
|
||
}
|
||
|
||
/** Get the number of all operations. */
|
||
int
|
||
get_num_op() const
|
||
{
|
||
return static_cast<int>(terms.size());
|
||
}
|
||
private:
|
||
/** This registers a calculated derivative of the term in the
|
||
* #derivatives vector.
|
||
* @param t the index of the term for which we register the derivative
|
||
* @param v the index of the nulary term (variable) to which
|
||
* respect the derivative was taken
|
||
* @param tder the index of the resulting derivative
|
||
*/
|
||
void register_derivative(int t, int v, int tder);
|
||
/** This does the same job as select_terms with the only
|
||
* difference, that it adds the terms to the given set and
|
||
* hence can be used recursivelly. */
|
||
void select_terms(int t, const opselector &sel, unordered_set<int> &subterms) const;
|
||
/** This does the same job as select_terms_inv with the only
|
||
* difference, that it adds the terms to the given set and
|
||
* hence can be used recursivelly and returns true if the term
|
||
* was selected. */
|
||
bool select_terms_inv(int t, const opselector &sel, unordered_set<int> &subterms) const;
|
||
/** This updates nul_incidence information after the term t
|
||
* was turned to a nulary term in all terms. It goes through
|
||
* the tree from simplest terms to teh more complex ones and
|
||
* changes the nul_incidence information where necesary. It
|
||
* maintains a set where the changes have been made.*/
|
||
void update_nul_incidence_after_nularify(int t);
|
||
};
|
||
|
||
/** EvalTree class allows for an evaluation of the given tree for
|
||
* a given values of nulary terms. For each term in the
|
||
* OperationTree the class maintains a resulting value and a flag
|
||
* if the value has been calculated or set. The life cycle of the
|
||
* class is the following: After it is initialized, the user must
|
||
* set values for necessary nulary terms. Then the object can be
|
||
* requested to evaluate particular terms. During this process,
|
||
* the number of evaluated terms is increasing. Then the user can
|
||
* request overall reset of evaluation flags, set the nulary terms
|
||
* to new values and evaluate a number of terms.
|
||
*
|
||
* Note that currently the user cannot request a reset of
|
||
* evaluation flags only for those terms depending on a given
|
||
* nulary term. This might be added in future and handeled by a
|
||
* subclasses of OperationTree and EvalTree, since we need a
|
||
* support for this in OperationTree.
|
||
*/
|
||
class EvalTree
|
||
{
|
||
protected:
|
||
/** Reference to the OperationTree over which all evaluations
|
||
* are done. */
|
||
const OperationTree &otree;
|
||
/** The array of values. */
|
||
const std::unique_ptr<double[]> values;
|
||
/** The array of evaluation flags. */
|
||
const std::unique_ptr<bool[]> flags;
|
||
/** The index of last operation in the EvalTree. Length of
|
||
* values and flags will be then last_operation+1. */
|
||
int last_operation;
|
||
public:
|
||
/** Initializes the evaluation tree for the given operation
|
||
* tree. If last is greater than -1, that the evaluation tree
|
||
* will contain only formulas up to the given last index
|
||
* (included). */
|
||
EvalTree(const OperationTree &otree, int last = -1);
|
||
EvalTree(const EvalTree &) = delete;
|
||
virtual ~EvalTree() = default;
|
||
/** Set evaluation flag to all terms (besides the first
|
||
* special terms) to false. */
|
||
void reset_all();
|
||
/** Set value for a given nulary term. */
|
||
void set_nulary(int t, double val);
|
||
/** Evaluate the given term with nulary terms set so far. */
|
||
double eval(int t);
|
||
/** Debug print. */
|
||
void print() const;
|
||
/* Return the operation tree. */
|
||
const OperationTree &
|
||
getOperationTree() const
|
||
{
|
||
return otree;
|
||
}
|
||
};
|
||
|
||
/** This is an interface describing how a given operation is
|
||
* formatted for output. */
|
||
class OperationFormatter
|
||
{
|
||
public:
|
||
/** Empty virtual destructor. */
|
||
virtual ~OperationFormatter() = default;
|
||
/** Print the formatted operation op with a given tree index t
|
||
* to a given descriptor. (See class OperationTree to know
|
||
* what is a tree index.) This prints all the tree. This
|
||
* always writes equation, left hand side is a string
|
||
* represenation (a variable, temporary, whatever) of the
|
||
* term, the right hand side is a string representation of the
|
||
* operation (which will refer to other string representation
|
||
* of subterms). */
|
||
virtual void format(const Operation &op, int t, std::ostream &os) = 0;
|
||
};
|
||
|
||
/** The default formatter formats the formulas with a usual syntax
|
||
* (for example Matlab). A formatting of atoms and terms might be
|
||
* reimplemented by a subclass. In addition, during its life, the
|
||
* object maintains a set of tree indices which have been output
|
||
* and they are not output any more. */
|
||
class DefaultOperationFormatter : public OperationFormatter
|
||
{
|
||
protected:
|
||
const OperationTree &otree;
|
||
set<int> stop_set;
|
||
public:
|
||
DefaultOperationFormatter(const OperationTree &ot)
|
||
: otree(ot)
|
||
{
|
||
}
|
||
/** Format the operation with the default syntax. */
|
||
void format(const Operation &op, int t, std::ostream &os) override;
|
||
/** This prints a string represenation of the given term, for
|
||
* example 'tmp10' for term 10. In this implementation it
|
||
* prints $10. */
|
||
virtual void format_term(int t, std::ostream &os) const;
|
||
/** Print a string representation of the nulary term. */
|
||
virtual void format_nulary(int t, std::ostream &os) const;
|
||
/** Print a delimiter between two statements. By default it is
|
||
* "\n". */
|
||
virtual void print_delim(std::ostream &os) const;
|
||
};
|
||
|
||
class NularyStringConvertor
|
||
{
|
||
public:
|
||
virtual ~NularyStringConvertor() = default;
|
||
/** Return the string representation of the atom with the tree
|
||
* index t. */
|
||
virtual std::string convert(int t) const = 0;
|
||
};
|
||
|
||
/** This class converts the given term to its mathematical string representation. */
|
||
class OperationStringConvertor
|
||
{
|
||
protected:
|
||
const NularyStringConvertor &nulsc;
|
||
const OperationTree &otree;
|
||
public:
|
||
OperationStringConvertor(const NularyStringConvertor &nsc, const OperationTree &ot)
|
||
: nulsc(nsc), otree(ot)
|
||
{
|
||
}
|
||
/** Empty virtual destructor. */
|
||
virtual ~OperationStringConvertor() = default;
|
||
/** Convert the operation to the string mathematical
|
||
* representation. This does not write any equation, just
|
||
* returns a string representation of the formula. */
|
||
std::string convert(const Operation &op, int t) const;
|
||
};
|
||
};
|
||
|
||
#endif
|