diff --git a/src/DynamicModel.cc b/src/DynamicModel.cc index 20630879bd2617f92a54b6f03b67b8f01779d14d..ef0f9f064efa313613c6d1323aedfcc8873cce83 100644 --- a/src/DynamicModel.cc +++ b/src/DynamicModel.cc @@ -5768,6 +5768,65 @@ DynamicModel::transformPredeterminedVariables() // No need to handle static_only_equations, since there are no leads/lags there } +void +DynamicModel::substituteLogTransform() +{ + for (int symb_id : symbol_table.getVariablesWithLogTransform()) + { + expr_t aux_def = AddLog(AddVariable(symb_id)); + int aux_symb_id = symbol_table.addLogTransformAuxiliaryVar(symb_id, 0, aux_def); + + for (auto &[id, definition] : local_variables_table) + definition = definition->substituteLogTransform(symb_id, aux_symb_id); + + for (auto &equation : equations) + equation = dynamic_cast<BinaryOpNode *>(equation->substituteLogTransform(symb_id, aux_symb_id)); + + for (auto &equation : static_only_equations) + equation = dynamic_cast<BinaryOpNode *>(equation->substituteLogTransform(symb_id, aux_symb_id)); + + /* + We add the following new equations: + + X=exp(log_X) to the model + + log_X=log(X) to the list of auxiliary equations + + In this way: + + statements like X=1 in initval/endval blocks will be correctly + handled (i.e. log_X will be initialized to 0 in this case), through + the set_auxiliary_variables.m and dynamic_set_auxiliary_series.m files + + computation of X in perfect foresight simulations will be done by + simple evaluation when using block decomposition (X will belong to an + block of type “evaluate”, or maybe even the epilogue) + */ + addAuxEquation(AddEqual(AddVariable(aux_symb_id), aux_def)); + addEquation(AddEqual(AddVariable(symb_id), AddExp(AddVariable(aux_symb_id))), + -1, {}); + } +} + +void +DynamicModel::checkNoWithLogTransform(const set<int> &eqnumbers) +{ + set<int> endos; + for (int eq : eqnumbers) + equations[eq]->collectVariables(SymbolType::endogenous, endos); + + const set<int> &with_log_transform = symbol_table.getVariablesWithLogTransform(); + + vector<int> intersect; + set_intersection(endos.begin(), endos.end(), + with_log_transform.begin(), with_log_transform.end(), + back_inserter(intersect)); + if (!intersect.empty()) + { + cerr << "ERROR: the following variables are declared with var(log) and therefore cannot appear in a VAR/TCM/PAC equation: "; + for (int symb_id : intersect) + cerr << symbol_table.getName(symb_id) << " "; + cerr << endl; + exit(EXIT_FAILURE); + } +} + void DynamicModel::detrendEquations() { diff --git a/src/DynamicModel.hh b/src/DynamicModel.hh index 420dbe2a1d8493676014af05dc6b1ccd7c83438b..d858b739e98807e6fb46b4d9fd27c3ce84188c8a 100644 --- a/src/DynamicModel.hh +++ b/src/DynamicModel.hh @@ -499,6 +499,12 @@ public: //! Transforms the model by decreasing the lead/lag of predetermined variables in model equations by one void transformPredeterminedVariables(); + // Performs the transformations associated to variables declared with “var(log)” + void substituteLogTransform(); + + // Check that no variable was declared with “var(log)” in the given equations + void checkNoWithLogTransform(const set<int> &eqnumbers); + //! Transforms the model by removing trends specified by the user void detrendEquations(); diff --git a/src/DynareBison.yy b/src/DynareBison.yy index 81829b9b0625b315e9b4b56fecac4ca27d714022..a89a6567c08e241d0fdb6519b54735e886a71eb0 100644 --- a/src/DynareBison.yy +++ b/src/DynareBison.yy @@ -491,11 +491,17 @@ log_trend_var : LOG_TREND_VAR '(' LOG_GROWTH_FACTOR EQUAL { driver.begin_model() ; var : VAR symbol_list_with_tex_and_partition ';' - { driver.var($2); } + { driver.var($2, false); } + | VAR '(' LOG ')' symbol_list_with_tex_and_partition ';' + { driver.var($5, true); } | VAR '(' DEFLATOR EQUAL { driver.begin_model(); } hand_side ')' symbol_list_with_tex_and_partition ';' - { driver.end_nonstationary_var(false, $6, $8); } + { driver.end_nonstationary_var(false, $6, $8, false); } + | VAR '(' LOG COMMA DEFLATOR EQUAL { driver.begin_model(); } hand_side ')' symbol_list_with_tex_and_partition ';' + { driver.end_nonstationary_var(false, $8, $10, true); } | VAR '(' LOG_DEFLATOR EQUAL { driver.begin_model(); } hand_side ')' symbol_list_with_tex_and_partition ';' - { driver.end_nonstationary_var(true, $6, $8); } + { driver.end_nonstationary_var(true, $6, $8, false); } + /* The case LOG + LOG_DEFLATOR is omitted, because it does not make much sense + from an economic point of view (amounts to taking the log two times) */ ; var_remove : VAR_REMOVE symbol_list ';' { driver.var_remove($2); }; diff --git a/src/ExprNode.cc b/src/ExprNode.cc index 1c11d7acd844d0a3abf23a0cc7687424b1002f3e..c6f3997edc1ed3c6a5bce210319b85d502745526 100644 --- a/src/ExprNode.cc +++ b/src/ExprNode.cc @@ -748,6 +748,12 @@ NumConstNode::replaceVarsInEquation(map<VariableNode *, NumConstNode *> &table) return const_cast<NumConstNode *>(this); } +expr_t +NumConstNode::substituteLogTransform(int orig_symb_id, int aux_symb_id) const +{ + return const_cast<NumConstNode *>(this); +} + VariableNode::VariableNode(DataTree &datatree_arg, int idx_arg, int symb_id_arg, int lag_arg) : ExprNode{datatree_arg, idx_arg}, symb_id{symb_id_arg}, @@ -1990,6 +1996,18 @@ VariableNode::matchMatchedMoment(vector<int> &symb_ids, vector<int> &lags, vecto powers.push_back(1); } +expr_t +VariableNode::substituteLogTransform(int orig_symb_id, int aux_symb_id) const +{ + if (get_type() == SymbolType::modelLocalVariable) + return datatree.getLocalVariable(symb_id)->substituteLogTransform(orig_symb_id, aux_symb_id); + + if (symb_id == orig_symb_id) + return datatree.AddExp(datatree.AddVariable(aux_symb_id, lag)); + else + return const_cast<VariableNode *>(this); +} + UnaryOpNode::UnaryOpNode(DataTree &datatree_arg, int idx_arg, UnaryOpcode op_code_arg, const expr_t arg_arg, int expectation_information_set_arg, int param1_symb_id_arg, int param2_symb_id_arg, string adl_param_name_arg, vector<int> adl_lags_arg) : ExprNode{datatree_arg, idx_arg}, arg{arg_arg}, @@ -3768,6 +3786,13 @@ UnaryOpNode::replaceVarsInEquation(map<VariableNode *, NumConstNode *> &table) c return buildSimilarUnaryOpNode(argsubst, datatree); } +expr_t +UnaryOpNode::substituteLogTransform(int orig_symb_id, int aux_symb_id) const +{ + expr_t argsubst = arg->substituteLogTransform(orig_symb_id, aux_symb_id); + return buildSimilarUnaryOpNode(argsubst, datatree); +} + BinaryOpNode::BinaryOpNode(DataTree &datatree_arg, int idx_arg, const expr_t arg1_arg, BinaryOpcode op_code_arg, const expr_t arg2_arg, int powerDerivOrder_arg) : ExprNode{datatree_arg, idx_arg}, @@ -5620,6 +5645,13 @@ BinaryOpNode::matchMatchedMoment(vector<int> &symb_ids, vector<int> &lags, vecto throw MatchFailureException("Unsupported binary operator"); } +expr_t +BinaryOpNode::substituteLogTransform(int orig_symb_id, int aux_symb_id) const +{ + expr_t arg1subst = arg1->substituteLogTransform(orig_symb_id, aux_symb_id); + expr_t arg2subst = arg2->substituteLogTransform(orig_symb_id, aux_symb_id); + return buildSimilarBinaryOpNode(arg1subst, arg2subst, datatree); +} TrinaryOpNode::TrinaryOpNode(DataTree &datatree_arg, int idx_arg, const expr_t arg1_arg, TrinaryOpcode op_code_arg, const expr_t arg2_arg, const expr_t arg3_arg) : @@ -6499,6 +6531,15 @@ TrinaryOpNode::replaceVarsInEquation(map<VariableNode *, NumConstNode *> &table) return buildSimilarTrinaryOpNode(arg1subst, arg2subst, arg3subst, datatree); } +expr_t +TrinaryOpNode::substituteLogTransform(int orig_symb_id, int aux_symb_id) const +{ + expr_t arg1subst = arg1->substituteLogTransform(orig_symb_id, aux_symb_id); + expr_t arg2subst = arg2->substituteLogTransform(orig_symb_id, aux_symb_id); + expr_t arg3subst = arg3->substituteLogTransform(orig_symb_id, aux_symb_id); + return buildSimilarTrinaryOpNode(arg1subst, arg2subst, arg3subst, datatree); +} + AbstractExternalFunctionNode::AbstractExternalFunctionNode(DataTree &datatree_arg, int idx_arg, int symb_id_arg, @@ -7079,6 +7120,15 @@ ExternalFunctionNode::ExternalFunctionNode(DataTree &datatree_arg, { } +expr_t +AbstractExternalFunctionNode::substituteLogTransform(int orig_symb_id, int aux_symb_id) const +{ + vector<expr_t> arguments_subst; + for (auto argument : arguments) + arguments_subst.push_back(argument->substituteLogTransform(orig_symb_id, aux_symb_id)); + return buildSimilarExternalFunctionNode(arguments_subst, datatree); +} + expr_t ExternalFunctionNode::composeDerivatives(const vector<expr_t> &dargs) { @@ -8371,6 +8421,12 @@ SubModelNode::removeTrendLeadLag(const map<int, expr_t> &trend_symbols_map) cons exit(EXIT_FAILURE); } +expr_t +SubModelNode::substituteLogTransform(int orig_symb_id, int aux_symb_id) const +{ + return const_cast<SubModelNode *>(this); +} + VarExpectationNode::VarExpectationNode(DataTree &datatree_arg, int idx_arg, string model_name_arg) : diff --git a/src/ExprNode.hh b/src/ExprNode.hh index b484318b94a297df07ee0c93ebccc85f2f57d862..35fdc45c10f923106e5c67f3feed3d64905f7843 100644 --- a/src/ExprNode.hh +++ b/src/ExprNode.hh @@ -744,6 +744,9 @@ public: // Returns true if the expression contains an exogenous or an exogenous deterministic bool hasExogenous() const; + + // Substitutes orig_symb_id(±l) with exp(aux_symb_id(±l)) (used for “var(log)”) + virtual expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const = 0; }; //! Object used to compare two nodes (using their indexes) @@ -826,6 +829,7 @@ public: bool containsPacTargetNonstationary(const string &pac_model_name = "") const override; bool isParamTimesEndogExpr() const override; expr_t substituteStaticAuxiliaryVariable() const override; + expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const override; }; //! Symbol or variable node @@ -901,6 +905,7 @@ public: expr_t substituteStaticAuxiliaryVariable() const override; void matchMatchedMoment(vector<int> &symb_ids, vector<int> &lags, vector<int> &powers) const override; pair<int, expr_t> matchEndogenousTimesConstant() const override; + expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const override; }; //! Unary operator node @@ -1004,6 +1009,7 @@ public: //! Substitute auxiliary variables by their expression in static model expr_t substituteStaticAuxiliaryVariable() const override; void decomposeAdditiveTerms(vector<pair<expr_t, int>> &terms, int current_sign) const override; + expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const override; }; //! Binary operator node @@ -1148,6 +1154,7 @@ public: void decomposeMultiplicativeFactors(vector<pair<expr_t, int>> &factors, int current_exponent = 1) const override; void matchMatchedMoment(vector<int> &symb_ids, vector<int> &lags, vector<int> &powers) const override; pair<int, expr_t> matchEndogenousTimesConstant() const override; + expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const override; }; //! Trinary operator node @@ -1244,6 +1251,7 @@ public: bool isParamTimesEndogExpr() const override; //! Substitute auxiliary variables by their expression in static model expr_t substituteStaticAuxiliaryVariable() const override; + expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const override; }; //! External function node @@ -1355,6 +1363,7 @@ public: bool isParamTimesEndogExpr() const override; //! Substitute auxiliary variables by their expression in static model expr_t substituteStaticAuxiliaryVariable() const override; + expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const override; }; class ExternalFunctionNode : public AbstractExternalFunctionNode @@ -1531,6 +1540,7 @@ public: expr_t replaceTrendVar() const override; expr_t detrend(int symb_id, bool log_trend, expr_t trend) const override; expr_t removeTrendLeadLag(const map<int, expr_t> &trend_symbols_map) const override; + expr_t substituteLogTransform(int orig_symb_id, int aux_symb_id) const override; }; class VarExpectationNode : public SubModelNode diff --git a/src/ModFile.cc b/src/ModFile.cc index e41cd2f6355f149063cc837e0925b118fee80b7b..ceac9f32867de4c970ca4b85476d3b242f300f6b 100644 --- a/src/ModFile.cc +++ b/src/ModFile.cc @@ -428,6 +428,9 @@ ModFile::transformPass(bool nostrict, bool stochastic, bool compute_xrefs, bool set<int> unary_ops_eqs = dynamic_model.getEquationNumbersFromTags(var_tcm_eqtags); unary_ops_eqs.merge(dynamic_model.findPacExpectationEquationNumbers()); + // Check that no variable in VAR/TCM/PAC equations was declared with “var(log)” + dynamic_model.checkNoWithLogTransform(unary_ops_eqs); + // Create auxiliary variables and equations for unary ops lag_equivalence_table_t unary_ops_nodes; ExprNode::subst_table_t unary_ops_subst_table; @@ -502,6 +505,9 @@ ModFile::transformPass(bool nostrict, bool stochastic, bool compute_xrefs, bool dynamic_model.createVariableMapping(); + // Must come after detrending of variables and Ramsey policy transformation + dynamic_model.substituteLogTransform(); + /* Create auxiliary vars for leads and lags greater than 2, on both endos and exos. The transformation is not exactly the same on stochastic and deterministic models, because there is no need to take into account the diff --git a/src/ParsingDriver.cc b/src/ParsingDriver.cc index ef4b6f664110e6e6af0afffd3712adebc6078388..48ba4537a5ae25a11cb9621f780ffe0014e1715e 100644 --- a/src/ParsingDriver.cc +++ b/src/ParsingDriver.cc @@ -205,10 +205,15 @@ ParsingDriver::declare_endogenous(const string &name, const string &tex_name, co } void -ParsingDriver::var(const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list) +ParsingDriver::var(const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list, + bool log_option) { for (auto &[name, tex_name, partition] : symbol_list) - declare_endogenous(name, tex_name, partition); + { + int symb_id = declare_endogenous(name, tex_name, partition); + if (log_option) + mod_file->symbol_table.markWithLogTransform(symb_id); + } } int @@ -469,7 +474,7 @@ ParsingDriver::add_expression_variable(const string &name) } void -ParsingDriver::end_nonstationary_var(bool log_deflator, expr_t deflator, const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list) +ParsingDriver::end_nonstationary_var(bool log_deflator, expr_t deflator, const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list, bool log_option) { mod_file->nonstationary_variables = true; @@ -478,6 +483,8 @@ ParsingDriver::end_nonstationary_var(bool log_deflator, expr_t deflator, const v { int symb_id = declare_endogenous(name, tex_name, partition); declared_nonstationary_vars.push_back(symb_id); + if (log_option) + mod_file->symbol_table.markWithLogTransform(symb_id); } try diff --git a/src/ParsingDriver.hh b/src/ParsingDriver.hh index 2758dbcb0126c014cfd2f15a6c9b0c2f702ccaa6..a421bc5ea21bf88b92d9c587b5a20cfb11acea69 100644 --- a/src/ParsingDriver.hh +++ b/src/ParsingDriver.hh @@ -354,8 +354,8 @@ public: void initval_file(); //! Declares an endogenous variable (and returns its symbol ID) int declare_endogenous(const string &name, const string &tex_name = "", const vector<pair<string, string>> &partition_value = {}); - // Handles a “var” statement (without “deflator” or “log_deflator” options) - void var(const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list); + // Handles a “var” or “var(log)” statement (without “deflator” or “log_deflator” options) + void var(const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list, bool log_option); //! Declares an exogenous variable (and returns its symbol ID) int declare_exogenous(const string &name, const string &tex_name = "", const vector<pair<string, string>> &partition_value = {}); // Handles a “varexo” statement @@ -844,8 +844,8 @@ public: void add_steady_state_model_equal_multiple(const vector<string> &symbol_list, expr_t expr); //! Ends declaration of trend variable void end_trend_var(bool log_trend, expr_t growth_factor, const vector<pair<string, string>> &symbol_list); - //! Ends declaration of nonstationary variable - void end_nonstationary_var(bool log_deflator, expr_t deflator, const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list); + //! Handles a “var(deflator=…)”, “var(log, deflator=…)” or “var(log_deflator=…)” statement + void end_nonstationary_var(bool log_deflator, expr_t deflator, const vector<tuple<string, string, vector<pair<string, string>>>> &symbol_list, bool log_option); //! Add a graph format to the list of formats requested void add_graph_format(string name); //! Add the graph_format option to the OptionsList structure diff --git a/src/SymbolTable.cc b/src/SymbolTable.cc index 62a5307ff75ae05f10b279088bd3c20dc4e77000..c78d009913b70ead17ed6f3edfb104554f20b469 100644 --- a/src/SymbolTable.cc +++ b/src/SymbolTable.cc @@ -363,6 +363,7 @@ SymbolTable::writeOutput(ostream &output) const noexcept(false) break; case AuxVarType::endoLag: case AuxVarType::exoLag: + case AuxVarType::logTransform: case AuxVarType::diffLag: case AuxVarType::diffLead: case AuxVarType::diffForward: @@ -525,6 +526,26 @@ SymbolTable::addExpectationAuxiliaryVar(int information_set, int index, expr_t e return symb_id; } +int +SymbolTable::addLogTransformAuxiliaryVar(int orig_symb_id, int orig_lead_lag, expr_t expr_arg) noexcept(false) +{ + string varname = "LOG_" + getName(orig_symb_id); + int symb_id; + try + { + symb_id = addSymbol(varname, SymbolType::endogenous); + } + catch (AlreadyDeclaredException &e) + { + cerr << "ERROR: you should rename your variable called " << varname << ", it conflicts with the auxiliary variable created for representing the log of " << getName(orig_symb_id) << endl; + exit(EXIT_FAILURE); + } + + aux_vars.emplace_back(symb_id, AuxVarType::logTransform, orig_symb_id, orig_lead_lag, 0, 0, expr_arg, ""); + + return symb_id; +} + int SymbolTable::addDiffLagAuxiliaryVar(int index, expr_t expr_arg, int orig_symb_id, int orig_lag) noexcept(false) { @@ -763,6 +784,19 @@ SymbolTable::markPredetermined(int symb_id) noexcept(false) predetermined_variables.insert(symb_id); } +void +SymbolTable::markWithLogTransform(int symb_id) noexcept(false) +{ + validateSymbID(symb_id); + + if (frozen) + throw FrozenException(); + + assert(getType(symb_id) == SymbolType::endogenous); + + with_log_transform.insert(symb_id); +} + bool SymbolTable::isPredetermined(int symb_id) const noexcept(false) { @@ -992,6 +1026,7 @@ SymbolTable::writeJsonOutput(ostream &output) const break; case AuxVarType::endoLag: case AuxVarType::exoLag: + case AuxVarType::logTransform: case AuxVarType::diffLag: case AuxVarType::diffLead: case AuxVarType::diffForward: @@ -1064,3 +1099,9 @@ SymbolTable::getEquationNumberForMultiplier(int symb_id) const return aux_var.get_equation_number_for_multiplier(); return -1; } + +const set<int> & +SymbolTable::getVariablesWithLogTransform() const +{ + return with_log_transform; +} diff --git a/src/SymbolTable.hh b/src/SymbolTable.hh index f888eb2fe614cc04949a6570e69f8a4d1deaa9ff..0972173a4fa4a59dbfb585de731b80f4bf7fd585 100644 --- a/src/SymbolTable.hh +++ b/src/SymbolTable.hh @@ -46,9 +46,7 @@ enum class AuxVarType for the differentiate_forward_vars option. N.B.: nothing to do with the diff() operator! */ multiplier = 6, //!< Multipliers for FOC of Ramsey Problem - - // Value 7 is unused for the time being (but could be reused) - + logTransform = 7, //!< Log-transformation of a variable declared with “var(log)” diff = 8, //!< Variable for Diff operator diffLag = 9, //!< Variable for timing between Diff operators (lag) unaryOp = 10, //!< Variable for allowing the undiff operator to work when diff was taken of unary op, eg diff(log(x)) @@ -65,7 +63,7 @@ private: AuxVarType type; //!< Its type int orig_symb_id; /* Symbol ID of the (only) endo that appears on the RHS of the definition of this auxvar. - Used by endoLag, exoLag, diffForward, diff, diffLag, + Used by endoLag, exoLag, diffForward, logTransform, diff, diffLag, diffLead and unaryOp. For diff and unaryOp, if the argument expression is more complex than than a simple variable, this value is equal to -1. */ @@ -182,6 +180,9 @@ private: //! Stores the list of observed exogenous variables vector<int> varexobs; + //! Stores the endogenous variables declared with “var(log)” + set<int> with_log_transform; + public: //! Thrown when trying to access an unknown symbol (by name) class UnknownSymbolNameException @@ -308,6 +309,13 @@ public: \return the symbol ID of the new symbol */ int addMultiplierAuxiliaryVar(int index) noexcept(false); + /* Adds an auxiliary variable associated to an endogenous declared with + “var(log)”. + – orig_symb_id is the symbol ID of the original variable + – orig_lead_lag is typically 0 + – expr_arg is typically log(orig_symb_id) + */ + int addLogTransformAuxiliaryVar(int orig_symb_id, int orig_lead_lag, expr_t expr_arg) noexcept(false); //! Adds an auxiliary variable for the (time) differentiate of a forward var /*! \param[in] orig_symb_id The symb_id of the forward variable @@ -409,6 +417,8 @@ public: void writeJsonOutput(ostream &output) const; //! Mark a symbol as predetermined variable void markPredetermined(int symb_id) noexcept(false); + //! Mark an endogenous as having been declared with “var(log)” + void markWithLogTransform(int symb_id) noexcept(false); //! Test if a given symbol is a predetermined variable bool isPredetermined(int symb_id) const noexcept(false); //! Return the number of predetermined variables @@ -456,6 +466,8 @@ public: /* Return all the information about a given auxiliary variable. Throws UnknownSymbolIDException if it is not an aux var */ const AuxVarInfo &getAuxVarInfo(int symb_id) const; + // Returns the set of all endogenous declared with “var(log)” + const set<int> &getVariablesWithLogTransform() const; }; inline void