From 8e3cdd94643b32e885a19ae4c79441ed952a5e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Villemot?= <sebastien@dynare.org> Date: Thu, 7 Nov 2024 18:09:24 +0100 Subject: [PATCH] =?UTF-8?q?Allow=20dates=20in=20=E2=80=9Cperiods=E2=80=9D?= =?UTF-8?q?=20keyword?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DynareBison.yy | 72 +++++++++++++++++------------------ src/DynareFlex.ll | 5 ++- src/ParsingDriver.cc | 89 ++++++++++++++++++++++++++++--------------- src/ParsingDriver.hh | 6 ++- src/Shocks.cc | 90 ++++++++++++++++++++++++++++++-------------- src/Shocks.hh | 12 ++++-- src/Statement.hh | 7 ++-- 7 files changed, 174 insertions(+), 107 deletions(-) diff --git a/src/DynareBison.yy b/src/DynareBison.yy index 2c501b5a..0048713f 100644 --- a/src/DynareBison.yy +++ b/src/DynareBison.yy @@ -39,6 +39,7 @@ #include "CommonEnums.hh" #include "ExprNode.hh" +#include "Shocks.hh" class ParsingDriver; } @@ -244,7 +245,8 @@ str_tolower(string s) %type <vector<map<string, string>>> tag_pair_list_for_selection %type <map<string, string>> tag_pair_list %type <tuple<string,string,string,string>> prior_eq_opt options_eq_opt -%type <vector<pair<int, int>>> period_list +%type <AbstractShocksStatement::period_range_t> period_range +%type <vector<AbstractShocksStatement::period_range_t>> period_list %type <vector<expr_t>> matched_moments_list value_list ramsey_constraints_list %type <tuple<string, BinaryOpNode*, BinaryOpNode*, expr_t, expr_t>> occbin_constraints_regime %type <vector<tuple<string, BinaryOpNode*, BinaryOpNode*, expr_t, expr_t>>> occbin_constraints_regimes_list @@ -1401,48 +1403,38 @@ mshock_list : mshock_list det_shock_elem | det_shock_elem ; -period_list : period_list COMMA INT_NUMBER - { - $$ = $1; - int p = stoi($3); - $$.emplace_back(p, p); - } - | period_list INT_NUMBER - { - $$ = $1; - int p = stoi($2); - $$.emplace_back(p, p); - } - | period_list COMMA INT_NUMBER ':' INT_NUMBER +period_list : period_range + { $$ = { $1 }; } + | period_list period_range { $$ = $1; - int p1 = stoi($3), p2 = stoi($5); - if (p1 > p2) - driver.error("Can't have first period index greater than second index in range specification"); - $$.emplace_back(p1, p2); + $$.emplace_back($2); } - | period_list INT_NUMBER ':' INT_NUMBER + | period_list COMMA period_range { $$ = $1; - int p1 = stoi($2), p2 = stoi($4); - if (p1 > p2) - driver.error("Can't have first period index greater than second index in range specification"); - $$.emplace_back(p1, p2); - } - | INT_NUMBER ':' INT_NUMBER - { - int p1 = stoi($1), p2 = stoi($3); - if (p1 > p2) - driver.error("Can't have first period index greater than second index in range specification"); - $$ = {{p1, p2}}; - } - | INT_NUMBER - { - int p = stoi($1); - $$ = {{p, p}}; + $$.emplace_back($3); } ; +period_range : INT_NUMBER + { + int p = stoi($1); + $$.emplace<pair<int, int>>(p, p); + } + | INT_NUMBER ':' INT_NUMBER + { + int p1 = stoi($1), p2 = stoi($3); + if (p1 > p2) + driver.error("Can't have first period index greater than second index in range specification"); + $$.emplace<pair<int, int>>(p1, p2); + } + | date_expr + { $$.emplace<pair<string, string>>($1, $1); } + | date_expr ':' date_expr + { $$.emplace<pair<string, string>>($1, $3); } + ; + value_list : value_list COMMA '(' expression ')' { $$ = $1; @@ -3627,7 +3619,15 @@ matched_irfs_elem : matched_irfs_elem_var_varexo vector<tuple<int, int, expr_t, expr_t>> v; v.reserve($3.size()); for (size_t i {0}; i < $3.size(); i++) - v.emplace_back($3[i].first, $3[i].second, $5.first[i], $5.second[i]); + try + { + auto [p1, p2] = get<pair<int, int>>($3[i]); + v.emplace_back(p1, p2, $5.first[i], $5.second[i]); + } + catch (bad_variant_access&) + { + driver.error("matched_irfs: dates are not allowed in the 'periods' keyword"); + } $$ = {$1, v}; } ; diff --git a/src/DynareFlex.ll b/src/DynareFlex.ll index 15023c8a..e10dcf31 100644 --- a/src/DynareFlex.ll +++ b/src/DynareFlex.ll @@ -256,11 +256,12 @@ DATE -?[0-9]+([ya]|m([1-9]|1[0-2])|q[1-4]|[sh][12]) <INITIAL>prior_function {BEGIN DYNARE_STATEMENT; return token::PRIOR_FUNCTION;} <INITIAL>posterior_function {BEGIN DYNARE_STATEMENT; return token::POSTERIOR_FUNCTION;} - /* Inside of a Dynare statement */ -<DYNARE_STATEMENT>{DATE} { +<DYNARE_STATEMENT,DYNARE_BLOCK>{DATE} { yylval->emplace<string>(yytext); return token::DATE; } + + /* Inside a Dynare statement */ <DYNARE_STATEMENT>file {return token::FILE;} <DYNARE_STATEMENT>datafile {return token::DATAFILE;} <DYNARE_STATEMENT>dirname {return token::DIRNAME;} diff --git a/src/ParsingDriver.cc b/src/ParsingDriver.cc index 5afacaf7..8852d319 100644 --- a/src/ParsingDriver.cc +++ b/src/ParsingDriver.cc @@ -948,6 +948,11 @@ ParsingDriver::end_mshocks(bool overwrite, bool relative_to_initval) void ParsingDriver::end_shocks_surprise(bool overwrite) { + if (ranges::any_of(views::values(det_shocks), [](auto& v) { + return ranges::any_of(views::keys(v), + [](auto& p) { return !holds_alternative<pair<int, int>>(p); }); + })) + error("shocks(surprise): dates are not allowed in the 'periods' keyword"); mod_file->addStatement( make_unique<ShocksSurpriseStatement>(overwrite, move(det_shocks), mod_file->symbol_table)); det_shocks.clear(); @@ -970,35 +975,41 @@ ParsingDriver::end_shocks_learnt_in(const string& learnt_in_period, bool overwri } for (auto& storage : {det_shocks, learnt_shocks_add, learnt_shocks_multiply}) for (auto& [symb_id, vals] : storage) - for (auto [period1, period2, expr] : vals) - if (period1 < learnt_in_period_int) - error("shocks: for variable " + mod_file->symbol_table.getName(symb_id) - + ", shock period (" + to_string(period1) - + ") is earlier than the period in which the shock is learnt (" + learnt_in_period - + ")"); + for (const auto& [period_range, expr] : vals) + if (holds_alternative<pair<int, int>>(period_range)) + if (int period1 = get<pair<int, int>>(period_range).first; period1 < learnt_in_period_int) + error("shocks: for variable " + mod_file->symbol_table.getName(symb_id) + + ", shock period (" + to_string(period1) + + ") is earlier than the period in which the shock is learnt (" + learnt_in_period + + ")"); // Aggregate the three types of shocks ShocksLearntInStatement::learnt_shocks_t learnt_shocks; for (const auto& [id, v] : det_shocks) { - vector<tuple<ShocksLearntInStatement::LearntShockType, int, int, expr_t>> v2; - for (auto [period1, period2, value] : v) - v2.emplace_back(ShocksLearntInStatement::LearntShockType::level, period1, period2, value); + vector<tuple<ShocksLearntInStatement::LearntShockType, + AbstractShocksStatement::period_range_t, expr_t>> + v2; + for (const auto& [period_range, value] : v) + v2.emplace_back(ShocksLearntInStatement::LearntShockType::level, period_range, value); learnt_shocks[id] = v2; } for (const auto& [id, v] : learnt_shocks_add) { - vector<tuple<ShocksLearntInStatement::LearntShockType, int, int, expr_t>> v2; - for (auto [period1, period2, value] : v) - v2.emplace_back(ShocksLearntInStatement::LearntShockType::add, period1, period2, value); + vector<tuple<ShocksLearntInStatement::LearntShockType, + AbstractShocksStatement::period_range_t, expr_t>> + v2; + for (const auto& [period_range, value] : v) + v2.emplace_back(ShocksLearntInStatement::LearntShockType::add, period_range, value); learnt_shocks[id] = v2; } for (const auto& [id, v] : learnt_shocks_multiply) { - vector<tuple<ShocksLearntInStatement::LearntShockType, int, int, expr_t>> v2; - for (auto [period1, period2, value] : v) - v2.emplace_back(ShocksLearntInStatement::LearntShockType::multiply, period1, period2, - value); + vector<tuple<ShocksLearntInStatement::LearntShockType, + AbstractShocksStatement::period_range_t, expr_t>> + v2; + for (const auto& [period_range, value] : v) + v2.emplace_back(ShocksLearntInStatement::LearntShockType::multiply, period_range, value); learnt_shocks[id] = v2; } @@ -1023,12 +1034,13 @@ ParsingDriver::end_mshocks_learnt_in(const string& learnt_in_period, bool overwr } for (auto& [symb_id, vals] : det_shocks) - for (auto [period1, period2, expr] : vals) - if (period1 < learnt_in_period_int) - error("mshocks: for variable " + mod_file->symbol_table.getName(symb_id) - + ", shock period (" + to_string(period1) - + ") is earlier than the period in which the shock is learnt (" + learnt_in_period - + ")"); + for (const auto& [period_range, expr] : vals) + if (holds_alternative<pair<int, int>>(period_range)) + if (int period1 = get<pair<int, int>>(period_range).first; period1 < learnt_in_period_int) + error("mshocks: for variable " + mod_file->symbol_table.getName(symb_id) + + ", shock period (" + to_string(period1) + + ") is earlier than the period in which the shock is learnt (" + learnt_in_period + + ")"); ShocksLearntInStatement::learnt_shocks_t learnt_shocks; const auto type {relative_to_initval @@ -1036,9 +1048,11 @@ ParsingDriver::end_mshocks_learnt_in(const string& learnt_in_period, bool overwr : ShocksLearntInStatement::LearntShockType::multiplySteadyState}; for (const auto& [id, v] : det_shocks) { - vector<tuple<ShocksLearntInStatement::LearntShockType, int, int, expr_t>> v2; - for (auto [period1, period2, value] : v) - v2.emplace_back(type, period1, period2, value); + vector<tuple<ShocksLearntInStatement::LearntShockType, + AbstractShocksStatement::period_range_t, expr_t>> + v2; + for (const auto& [period_range, value] : v) + v2.emplace_back(type, period_range, value); learnt_shocks[id] = v2; } @@ -1062,7 +1076,8 @@ ParsingDriver::end_heteroskedastic_shocks(bool overwrite) } void -ParsingDriver::add_det_shock(const string& var, const vector<pair<int, int>>& periods, +ParsingDriver::add_det_shock(const string& var, + const vector<AbstractShocksStatement::period_range_t>& periods, const vector<expr_t>& values, DetShockType type) { switch (type) @@ -1090,10 +1105,10 @@ ParsingDriver::add_det_shock(const string& var, const vector<pair<int, int>>& pe error("shocks/conditional_forecast_paths: variable " + var + ": number of periods is different from number of shock values"); - vector<tuple<int, int, expr_t>> v; + vector<pair<AbstractShocksStatement::period_range_t, expr_t>> v; for (size_t i = 0; i < periods.size(); i++) - v.emplace_back(periods[i].first, periods[i].second, values[i]); + v.emplace_back(periods[i], values[i]); switch (type) { @@ -1111,11 +1126,15 @@ ParsingDriver::add_det_shock(const string& var, const vector<pair<int, int>>& pe } void -ParsingDriver::add_heteroskedastic_shock(const string& var, const vector<pair<int, int>>& periods, - const vector<expr_t>& values, bool scales) +ParsingDriver::add_heteroskedastic_shock( + const string& var, const vector<AbstractShocksStatement::period_range_t>& periods, + const vector<expr_t>& values, bool scales) { check_symbol_is_exogenous(var, false); + if (ranges::any_of(periods, [](auto& p) { return !holds_alternative<pair<int, int>>(p); })) + error("heteroskedastic_shocks: dates are not allowed in the 'periods' keyword"); + int symb_id = mod_file->symbol_table.getID(var); if ((!scales && heteroskedastic_shocks_values.contains(symb_id)) @@ -1128,7 +1147,10 @@ ParsingDriver::add_heteroskedastic_shock(const string& var, const vector<pair<in vector<tuple<int, int, expr_t>> v; for (size_t i = 0; i < periods.size(); i++) - v.emplace_back(periods[i].first, periods[i].second, values[i]); + { + auto [period1, period2] = get<pair<int, int>>(periods[i]); + v.emplace_back(period1, period2, values[i]); + } if (scales) heteroskedastic_shocks_scales[symb_id] = v; @@ -2629,6 +2651,11 @@ ParsingDriver::plot_conditional_forecast(const optional<string>& periods, void ParsingDriver::conditional_forecast_paths() { + if (ranges::any_of(views::values(det_shocks), [](auto& v) { + return ranges::any_of(views::keys(v), + [](auto& p) { return !holds_alternative<pair<int, int>>(p); }); + })) + error("conditional_forecast_paths: dates are not allowed in the 'periods' keyword"); mod_file->addStatement( make_unique<ConditionalForecastPathsStatement>(move(det_shocks), mod_file->symbol_table)); det_shocks.clear(); diff --git a/src/ParsingDriver.hh b/src/ParsingDriver.hh index 132dee58..fffa1a39 100644 --- a/src/ParsingDriver.hh +++ b/src/ParsingDriver.hh @@ -481,10 +481,12 @@ public: multiply, // for “multiply†in “shocks(learnt_in)†conditional_forecast }; - void add_det_shock(const string& var, const vector<pair<int, int>>& periods, + void add_det_shock(const string& var, + const vector<AbstractShocksStatement::period_range_t>& periods, const vector<expr_t>& values, DetShockType type); //! Adds a heteroskedastic shock (either values or scales) - void add_heteroskedastic_shock(const string& var, const vector<pair<int, int>>& periods, + void add_heteroskedastic_shock(const string& var, + const vector<AbstractShocksStatement::period_range_t>& periods, const vector<expr_t>& values, bool scales); //! Adds a std error shock void add_stderr_shock(const string& var, expr_t value); diff --git a/src/Shocks.cc b/src/Shocks.cc index 1bb84966..644b2b06 100644 --- a/src/Shocks.cc +++ b/src/Shocks.cc @@ -19,11 +19,32 @@ #include <cassert> #include <cstdlib> +#include <functional> #include <iostream> #include <utility> #include "Shocks.hh" +static auto print_matlab_period_range = []<class T>(ostream& output, const T& arg) { + if constexpr (is_same_v<T, pair<int, int>>) + output << arg.first << ":" << arg.second; + else if constexpr (is_same_v<T, pair<string, string>>) + output << "(" << arg.first << "):(" << arg.second << ")"; + else + static_assert(always_false_v<T>, "Non-exhaustive visitor!"); +}; + +static auto print_json_period_range = []<class T>(ostream& output, const T& arg) { + if constexpr (is_same_v<T, pair<int, int>>) + output << R"("period1": )" << arg.first << ", " + << R"("period2": )" << arg.second; + else if constexpr (is_same_v<T, pair<string, string>>) + output << R"("period1": ")" << arg.first << R"(", )" + << R"("period2": ")" << arg.second << '"'; + else + static_assert(always_false_v<T>, "Non-exhaustive visitor!"); +}; + AbstractShocksStatement::AbstractShocksStatement(bool overwrite_arg, ShockType type_arg, det_shocks_t det_shocks_arg, const SymbolTable& symbol_table_arg) : @@ -39,12 +60,14 @@ AbstractShocksStatement::writeDetShocks(ostream& output) const { for (const auto& [id, shock_vec] : det_shocks) for (bool exo_det = (symbol_table.getType(id) == SymbolType::exogenousDet); - const auto& [period1, period2, value] : shock_vec) + const auto& [period_range, value] : shock_vec) { output << "M_.det_shocks = [ M_.det_shocks;" << endl << boolalpha << "struct('exo_det'," << exo_det << ",'exo_id'," << symbol_table.getTypeSpecificID(id) + 1 << ",'type','" << typeToString(type) << "'" - << ",'periods'," << period1 << ":" << period2 << ",'value',"; + << ",'periods',"; + visit(bind(print_matlab_period_range, ref(output), placeholders::_1), period_range); + output << ",'value',"; value->writeOutput(output); output << ") ];" << endl; } @@ -60,13 +83,13 @@ AbstractShocksStatement::writeJsonDetShocks(ostream& output) const output << ", "; output << R"({"var": ")" << symbol_table.getName(id) << R"(", )" << R"("values": [)"; - for (bool printed_something2 {false}; const auto& [period1, period2, value] : shock_vec) + for (bool printed_something2 {false}; const auto& [period_range, value] : shock_vec) { if (exchange(printed_something2, true)) output << ", "; - output << R"({"period1": )" << period1 << ", " - << R"("period2": )" << period2 << ", " - << R"("value": ")"; + output << "{"; + visit(bind(print_json_period_range, ref(output), placeholders::_1), period_range); + output << R"(, "value": ")"; value->writeJsonOutput(output, {}, {}); output << R"("})"; } @@ -461,8 +484,9 @@ ShocksSurpriseStatement::writeOutput(ostream& output, [[maybe_unused]] const str else output << "M_.surprise_shocks = [ M_.surprise_shocks;" << endl; for (const auto& [id, shock_vec] : surprise_shocks) - for (const auto& [period1, period2, value] : shock_vec) + for (const auto& [period_range, value] : shock_vec) { + auto [period1, period2] = get<pair<int, int>>(period_range); output << "struct('exo_id'," << symbol_table.getTypeSpecificID(id) + 1 << ",'periods'," << period1 << ":" << period2 << ",'value',"; value->writeOutput(output); @@ -483,8 +507,9 @@ ShocksSurpriseStatement::writeJsonOutput(ostream& output) const output << ", "; output << R"({"var": ")" << symbol_table.getName(id) << R"(", )" << R"("values": [)"; - for (bool printed_something2 {false}; const auto& [period1, period2, value] : shock_vec) + for (bool printed_something2 {false}; const auto& [period_range, value] : shock_vec) { + auto [period1, period2] = get<pair<int, int>>(period_range); if (exchange(printed_something2, true)) output << ", "; output << R"({"period1": )" << period1 << ", " @@ -546,11 +571,12 @@ ShocksLearntInStatement::writeOutput(ostream& output, [[maybe_unused]] const str output << "M_.learnt_shocks = [ M_.learnt_shocks;" << endl; for (const auto& [id, shock_vec] : learnt_shocks) - for (const auto& [type, period1, period2, value] : shock_vec) + for (const auto& [type, period_range, value] : shock_vec) { output << "struct('learnt_in'," << learnt_in_period << ",'exo_id'," - << symbol_table.getTypeSpecificID(id) + 1 << ",'periods'," << period1 << ":" - << period2 << ",'type','" << typeToString(type) << "'" + << symbol_table.getTypeSpecificID(id) + 1 << ",'periods',"; + visit(bind(print_matlab_period_range, ref(output), placeholders::_1), period_range); + output << ",'type','" << typeToString(type) << "'" << ",'value',"; value->writeOutput(output); output << ");" << endl; @@ -570,13 +596,13 @@ ShocksLearntInStatement::writeJsonOutput(ostream& output) const output << ", "; output << R"({"var": ")" << symbol_table.getName(id) << R"(", )" << R"("values": [)"; - for (bool printed_something2 {false}; const auto& [type, period1, period2, value] : shock_vec) + for (bool printed_something2 {false}; const auto& [type, period_range, value] : shock_vec) { if (exchange(printed_something2, true)) output << ", "; - output << R"({"period1": )" << period1 << ", " - << R"("period2": )" << period2 << ", " - << R"("type": ")" << typeToString(type) << R"(", )" + output << "{"; + visit(bind(print_json_period_range, ref(output), placeholders::_1), period_range); + output << R"(, "type": ")" << typeToString(type) << R"(", )" << R"("value": ")"; value->writeJsonOutput(output, {}, {}); output << R"("})"; @@ -805,9 +831,12 @@ ConditionalForecastPathsStatement::computePathLength( { int length {0}; for (const auto& [ignore, elems] : paths) - for (auto& [period1, period2, value] : elems) - // Period1 < Period2, as enforced in ParsingDriver::add_period() - length = max(length, period2); + for (auto& [period_range, value] : elems) + { + auto [period1, period2] = get<pair<int, int>>(period_range); + // Period1 < Period2, as enforced in ParsingDriver::add_period() + length = max(length, period2); + } return length; } @@ -827,13 +856,16 @@ ConditionalForecastPathsStatement::writeOutput(ostream& output, else output << "constrained_vars_ = [constrained_vars_; " << symbol_table.getTypeSpecificID(id) + 1 << "];" << endl; - for (const auto& [period1, period2, value] : elems) - for (int j = period1; j <= period2; j++) - { - output << "constrained_paths_(" << k << "," << j << ")="; - value->writeOutput(output); - output << ";" << endl; - } + for (const auto& [period_range, value] : elems) + { + auto [period1, period2] = get<pair<int, int>>(period_range); + for (int j = period1; j <= period2; j++) + { + output << "constrained_paths_(" << k << "," << j << ")="; + value->writeOutput(output); + output << ";" << endl; + } + } k++; } } @@ -849,13 +881,13 @@ ConditionalForecastPathsStatement::writeJsonOutput(ostream& output) const output << ", "; output << R"({"var": ")" << symbol_table.getName(id) << R"(", )" << R"("values": [)"; - for (bool printed_something2 {false}; const auto& [period1, period2, value] : elems) + for (bool printed_something2 {false}; const auto& [period_range, value] : elems) { if (exchange(printed_something2, true)) output << ", "; - output << R"({"period1": )" << period1 << ", " - << R"("period2": )" << period2 << ", " - << R"("value": ")"; + output << "{"; + visit(bind(print_json_period_range, ref(output), placeholders::_1), period_range); + output << R"(, "value": ")"; value->writeJsonOutput(output, {}, {}); output << R"("})"; } diff --git a/src/Shocks.hh b/src/Shocks.hh index a92ef733..2f8d335e 100644 --- a/src/Shocks.hh +++ b/src/Shocks.hh @@ -22,6 +22,7 @@ #include <map> #include <string> +#include <variant> #include <vector> #include "ExprNode.hh" @@ -34,8 +35,10 @@ using namespace std; class AbstractShocksStatement : public Statement { public: - // The tuple is (period1, period2, value) - using det_shocks_t = map<int, vector<tuple<int, int, expr_t>>>; + // A period range is either two indices (1-based), or two dates (from dseries) + using period_range_t = variant<pair<int, int>, pair<string, string>>; + // The pair is (period range, value) + using det_shocks_t = map<int, vector<pair<period_range_t, expr_t>>>; enum class ShockType { level, // The value is the level of the exogenous (“values†statement in “shocksâ€) @@ -140,8 +143,9 @@ public: // state as anticipated in the same informational period (“values†// statement in “mshocks(learnt_in=…, relative_to_initval)â€) }; - // The tuple is (type, period1, period2, value) - using learnt_shocks_t = map<int, vector<tuple<LearntShockType, int, int, expr_t>>>; + // The tuple is (type, period range, value) + using learnt_shocks_t + = map<int, vector<tuple<LearntShockType, AbstractShocksStatement::period_range_t, expr_t>>>; const learnt_shocks_t learnt_shocks; private: diff --git a/src/Statement.hh b/src/Statement.hh index e542e799..6716f8d5 100644 --- a/src/Statement.hh +++ b/src/Statement.hh @@ -30,6 +30,10 @@ #include "SymbolList.hh" #include "WarningConsolidation.hh" +// Helper constant for visitors +template<class> +inline constexpr bool always_false_v {false}; + struct ModFileStructure { //! Whether check is present @@ -333,9 +337,6 @@ public: private: map<string, OptionValue> options; void writeOutputCommon(ostream& output, const string& option_group) const; - // Helper constant for visitors - template<class> - static constexpr bool always_false_v {false}; }; #endif -- GitLab