/* * 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 . */ #ifndef OGP_FORMULA_PARSER_H #define OGP_FORMULA_PARSER_H #include #include #include #include "tree.hh" namespace ogp { using std::vector; /** Pure virtual class defining a minimal interface for * representation of nulary terms within FormulaParser. */ class Atoms { public: Atoms() = default; virtual ~Atoms() = default; /** This returns previously assigned internal index to the * given atom, or returns -1 if the atom has not been assigned * yet. The method can raise an exception, if the Atoms * implementation is strict and the name is not among * prescribed possible values. */ virtual int check(const std::string &name) const = 0; /** This method assigns an internal index to the nulary term * described by the name. The internal index is allocated by * OperationTree class. */ virtual void assign(const std::string &name, int t) = 0; /** Returns a number of variables which will be used for * differentiations. */ virtual int nvar() const = 0; /** Returns a vector of variable's internal indices which will * be used for differentiations. */ virtual vector variables() const = 0; /** Debug print. */ virtual void print() const = 0; }; /** Pure virtual class defining interface for all classes able to * set nulary terms to evaluation tree EvalTree. The * implementations of this class will have to be connected with * Atoms to have knowledge about the atoms and their indices in * the tree, and will call EvalTree::set_nulary. */ class AtomValues { public: virtual ~AtomValues() = default; virtual void setValues(EvalTree &et) const = 0; }; class FormulaDerEvaluator; class FoldMultiIndex; /** For ordering FoldMultiIndex in the std::map. */ struct ltfmi { bool operator()(const FoldMultiIndex &i1, const FoldMultiIndex &i2) const; }; /** This class stores derivatives (tree indices) of one formula * for all orders upto a given one. It stores the derivatives as a * sequence (vector) of these tree indices and sequence of the * multidimensional indices of variables wrt which the derivatives * were taken. In order to speed up querying for a derivative * given the variables, we have a map mapping the multidimensional * index to the order of the derivative in the sequence. * * The only reason we do not have only this map is that the * iterators of the map do not survive the insertions to the map, * and implementation of the constructor has to be very difficult. */ class FormulaDerivatives { friend class FormulaDerEvaluator; protected: /** Vector of derivatives. This is a list of derivatives (tree * indices), the ordering is given by the algorithm used to * create it. Currently, it starts with zero-th derivative, * the formula itself and carries with first order, second, * etc. */ vector tder; /** Vector of multiindices corresponding to the vector of * derivatives. */ vector indices; /** For retrieving derivatives via a multiindex, we have a map * mapping a multiindex to a derivative in the tder * ordering. This means that indices[ind2der[index]] == index. */ using Tfmiintmap = map; Tfmiintmap ind2der; /** The number of variables. */ int nvar; /** The maximum order of derivatives. */ int order; public: /** The constructor allocates and fills the sequence of the * indices of derivatives for a formula. * @param otree the OperationTree for which all work is done * and to which the derivatives are added. * @param vars the vector of nulary terms in the tree; the * derivatives are taken with respect to these variables in * the ordering given by the vector. * @param f the index of the formula being differentiated. The * zero derivative is set to f. * @param max_order the maximum order of differentiation. */ FormulaDerivatives(OperationTree &otree, const vector &vars, int f, int max_order); /** Copy constructor. */ FormulaDerivatives(const FormulaDerivatives &fd); virtual ~FormulaDerivatives() = default; /** Random access to the derivatives via multiindex. */ int derivative(const FoldMultiIndex &mi) const; /** Return the order. */ int get_order() const { return order; } /** Debug print. */ void print(const OperationTree &otree) const; }; class FormulaEvaluator; /** This class is able to parse a number of formulas and * differentiate them. The life cycle of the object is as follows: * After it is created, a few calls to parse will add formulas * (zero derivatives) to the object. Then a method differentiate() * can be called and a vector of pointers to derivatives for each * formula is created. After this, no one should call other * parse() or differentiate(). A const reference of the object can * be used in constructors of FormulaEvaluator and * FormulaDerEvaluator in order to evaluate formulas (zero * derivatives) and higher derivatives resp. */ class FormulaParser { friend class FormulaCustomEvaluator; friend class FormulaDerEvaluator; protected: /** The OperationTree of all formulas, including derivatives. */ OperationTree otree; /** Reference to Atoms. The Atoms are filled with nulary terms * during execution of parse(). */ Atoms &atoms; /** Vector of formulas (zero derivatives) in the order as they * have been parsed. */ vector formulas; /** The vector to derivatives, each vector corresponds to a * formula in the vector formulas. */ vector> ders; public: /** Construct an empty formula parser. */ FormulaParser(Atoms &a) : atoms(a) { } FormulaParser(const FormulaParser &fp) = delete; /** Copy constructor using a different instance of Atoms. */ FormulaParser(const FormulaParser &fp, Atoms &a); virtual ~FormulaParser() = default; /** Requires an addition of the formula; called from the * parser. */ void add_formula(int t); /** Requires an addition of the binary operation; called from * the parser. */ int add_binary(code_t code, int t1, int t2); /** Requires an addition of the unary operation; called from * the parser. */ int add_unary(code_t code, int t); /** Requires an addition of the nulary operation given by the * string. The Atoms are consulted for uniquness and are given * an internal index generated by the OperationTree. This is * the channel through which the Atoms are filled. */ int add_nulary(const std::string &str); /** Adds a derivative to the tree. This just calls * OperationTree::add_derivative. */ int add_derivative(int t, int v) { return otree.add_derivative(t, v); } /** Adds a substitution. This just calls * OperationTree::add_substitution. */ int add_substitution(int t, const map &subst) { return otree.add_substitution(t, subst); } /** Add the substitution given by the map where left sides of * substitutions come from another parser. The right sides are * from this object. The given t is from the given parser fp. */ int add_substitution(int t, const map &subst, const FormulaParser &fp) { return otree.add_substitution(t, subst, fp.otree); } /** This adds formulas from the given parser with (possibly) * different atoms applying substitutions from the given map * mapping atoms from fp to atoms of the object. */ void add_subst_formulas(const map &subst, const FormulaParser &fp); /** Substitute formulas. For each i from 1 through all * formulas, it adds a substitution of the i-th formula and * make it to be i-th formula.*/ void substitute_formulas(const std::map &subst); /** This method turns the given term to nulary operation. It * should be used with caution, since this method does not * anything do with atoms, but usually some action is also * needed (at leat to assign the tree index t to some * atom). */ void nularify(int t) { otree.nularify(t); } /** Returns a set of nulary terms of the given term. Just * calls OperationTree::nulary_of_term. */ const unordered_set & nulary_of_term(int t) const { return otree.nulary_of_term(t); } /** Parse a given string containing one or more formulas. The * formulas are parsed and added to the OperationTree and to * the formulas vector. */ void parse(const std::string &stream); /** Processes a syntax error from bison. */ void error(std::string mes) const; /** Differentiate all the formulas up to the given order. The * variables with respect to which the derivatives are taken * are obtained by Atoms::variables(). If the derivates exist, * they are destroyed and created again (with possibly * different order). */ void differentiate(int max_order); /** Return i-th formula derivatives. */ const FormulaDerivatives &derivatives(int i) const; /** This returns a maximum index of zero derivative formulas * including all nulary terms. This is a mimumum length of the * tree for which it is safe to evaluate zero derivatives of * the formulas. */ int last_formula() const; /** This returns a tree index of the i-th formula in the * vector. */ int formula(int i) const { return formulas[i]; } /** This returns a tree index of the last formula and pops its * item from the formulas vector. The number of formulas is * then less by one. Returns -1 if there is no formula. If * there are derivatives of the last formula, they are * destroyed and the vector ders is popped from the back. */ int pop_last_formula(); /** This returns a number of formulas. */ int nformulas() const { return static_cast(formulas.size()); } /** This returns a reference to atoms. */ const Atoms & getAtoms() const { return atoms; } Atoms & getAtoms() { return atoms; } /** This returns the tree. */ const OperationTree & getTree() const { return otree; } OperationTree & getTree() { return otree; } /** Debug print. */ void print() const; }; /** This is a pure virtual class defining an interface for all * classes which will load the results of formula (zero * derivative) evaluations. A primitive implementation of this * class can be a vector of doubles. */ class FormulaEvalLoader { public: virtual ~FormulaEvalLoader() = default; /** Set the value res for the given formula. The formula is * identified by an index corresponding to the ordering in * which the formulas have been parsed (starting from * zero). */ virtual void load(int i, double res) = 0; }; /** This class evaluates a selected subset of terms of the * tree. In the protected constructor, one can constraint the * initialization of the evaluation tree to a given number of * terms in the beginning. Using this constructor, one has to make * sure, that the terms in the beginning do not refer to terms * behind the initial part. */ class FormulaCustomEvaluator { protected: /** The evaluation tree. */ EvalTree etree; /** The custom tree indices to be evaluated. */ vector terms; public: /** Construct from FormulaParser and given list of terms. */ FormulaCustomEvaluator(const FormulaParser &fp, vector ts) : etree(fp.otree), terms(std::move(ts)) { } /** Construct from OperationTree and given list of terms. */ FormulaCustomEvaluator(const OperationTree &ot, vector ts) : etree(ot), terms(std::move(ts)) { } /** Evaluate the terms using the given AtomValues and load the * results using the given loader. The loader is called for * each term in the order of the terms. */ void eval(const AtomValues &av, FormulaEvalLoader &loader); protected: FormulaCustomEvaluator(const FormulaParser &fp) : etree(fp.otree, fp.last_formula()), terms(fp.formulas) { } }; /** This class evaluates zero derivatives of the FormulaParser. */ class FormulaEvaluator : public FormulaCustomEvaluator { public: /** Construct from FormulaParser. */ FormulaEvaluator(const FormulaParser &fp) : FormulaCustomEvaluator(fp) { } }; /** This is a pure virtual class defining an interface for all * classes which will load the results of formula derivative * evaluations. */ class FormulaDerEvalLoader { public: virtual ~FormulaDerEvalLoader() = default; /** This loads the result of the derivative of the given * order. The semantics of i is the same as in * FormulaEvalLoader::load. The indices of variables with * respect to which the derivative was taken are stored in * memory pointed by vars. These are the tree indices of the * variables. */ virtual void load(int i, int order, const int *vars, double res) = 0; }; /** This class is a utility class representing the tensor * multindex. It can basically increment itself, and calculate * its offset in the folded tensor. */ class FoldMultiIndex { /** Number of variables. */ int nvar; /** Dimension. */ int ord; /** The multiindex. */ std::unique_ptr data; public: /** Initializes to the zero derivative. Order is 0, data is * empty. */ FoldMultiIndex(int nv); /** Initializes the multiindex to zeros or given i. */ FoldMultiIndex(int nv, int order, int i = 0); /** Makes a new multiindex of the same order applying a given * mapping to the indices. The mapping is supposed to be monotone. */ FoldMultiIndex(int nv, const FoldMultiIndex &mi, const vector &mp); /** Shifting constructor. This adds a given number of orders * to the end, copying the last item to the newly added items, * keeping the index ordered. If the index was empty (zero-th * dimension), then zeros are added. */ FoldMultiIndex(const FoldMultiIndex &fmi, int new_orders); /** Copy constructor. */ FoldMultiIndex(const FoldMultiIndex &fmi); /** Desctructor. */ virtual ~FoldMultiIndex() = default; /** Assignment operator. */ const FoldMultiIndex &operator=(const FoldMultiIndex &fmi); /** Operator < implementing lexicographic ordering within one * order, increasing order across orders. */ bool operator<(const FoldMultiIndex &fmi) const; bool operator==(const FoldMultiIndex &fmi) const; /** Increment the multiindex. */ void increment(); /** Return offset of the multiindex in the folded tensor. */ int offset() const; const int & operator[](int i) const { return data[i]; } /** Return order of the multiindex, i.e. dimension of the * tensor. */ int order() const { return ord; } /** Return the number of variables. */ int nv() const { return nvar; } /** Return the data. */ const int * ind() const { return data.get(); } /** Return true if the end of the tensor is reached. The * result of a subsequent increment should be considered * unpredictable. */ bool past_the_end() const { return (ord == 0) || (data[0] == nvar); } /** Prints the multiindex in the brackets. */ void print() const; private: static int offset_recurse(int *data, int len, int nv); }; /** This class evaluates derivatives of the FormulaParser. */ class FormulaDerEvaluator { /** Its own instance of EvalTree. */ EvalTree etree; /** The indices of derivatives for each formula. This is a * const copy FormulaParser::ders. We do not allocate nor * deallocate anything here. */ vector ders; /** A copy of tree indices corresponding to atoms to with * respect the derivatives were taken. */ vector der_atoms; public: /** Construct the object from FormulaParser. */ FormulaDerEvaluator(const FormulaParser &fp); /** Evaluate the derivatives from the FormulaParser wrt to all * atoms in variables vector at the given AtomValues. The * given loader is used for output. */ void eval(const AtomValues &av, FormulaDerEvalLoader &loader, int order); /** Evaluate the derivatives from the FormulaParser wrt to a * selection of atoms of the atoms in der_atoms vector at the * given AtomValues. The selection is given by a monotone * mapping to the indices (not values) of the der_atoms. */ void eval(const vector &mp, const AtomValues &av, FormulaDerEvalLoader &loader, int order); }; }; #endif