preprocessor/src/ModelTree.hh

535 lines
25 KiB
C++
Raw Normal View History

/*
* Copyright © 2003-2020 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 <http://www.gnu.org/licenses/>.
*/
#ifndef _MODELTREE_HH
#define _MODELTREE_HH
using namespace std;
#include <string>
#include <vector>
#include <deque>
#include <map>
#include <ostream>
#include <array>
#include <filesystem>
#include "DataTree.hh"
#include "EquationTags.hh"
#include "ExtendedPreprocessorTypes.hh"
// Helper to convert a vector into a tuple
2019-12-20 16:59:30 +01:00
template<typename T, size_t... Indices>
auto
vectorToTupleHelper(const vector<T> &v, index_sequence<Indices...>)
{
return tuple(v[Indices] ...);
}
2019-12-20 16:59:30 +01:00
template<size_t N, typename T>
auto
vectorToTuple(const vector<T> &v)
{
assert(v.size() >= N);
return vectorToTupleHelper(v, make_index_sequence<N>());
}
//! Vector describing equations: BlockSimulationType, if BlockSimulationType == EVALUATE_s then a expr_t on the new normalized equation
using equation_type_and_normalized_equation_t = vector<pair<EquationType, expr_t>>;
//! Vector describing variables: max_lag in the block, max_lead in the block
using lag_lead_vector_t = vector<pair<int, int>>;
//! Shared code for static and dynamic models
class ModelTree : public DataTree
{
friend class DynamicModel;
friend class StaticModel;
public:
// Set via the `compiler` command
string user_set_add_flags, user_set_subst_flags, user_set_add_libs, user_set_subst_libs, user_set_compiler;
protected:
2019-10-29 12:56:53 +01:00
/*
* ************** BEGIN **************
* The following structures keep track of the model equations and must all be updated
* when adding or removing an equation. Hence, if a new parallel structure is added
* in the future, it must be maintained whereever these structures are updated
* See in particular methods clearEquations(), replaceMyEquations() and
* computeRamseyPolicyFOCs() of DynamicModel class.
2019-10-29 12:56:53 +01:00
* NB: This message added with the introduction of the `exclude_eqs` option, hence
* that's a place to update future structures.
*/
//! Stores declared and generated auxiliary equations
vector<BinaryOpNode *> equations;
//! Stores line numbers of declared equations; -1 means undefined
vector<int> equations_lineno;
2019-10-29 12:56:53 +01:00
//! Stores equation tags
EquationTags equation_tags;
2019-10-29 12:56:53 +01:00
/*
* ************** END **************
*/
//! Only stores generated auxiliary equations, in an order meaningful for evaluation
/*! These equations only contain the definition of auxiliary variables, and
may diverge from those in the main model (equations), if other model
transformations applied subsequently. This is not a problem, since
aux_equations is only used for regenerating the values of auxiliaries
given the others.
For example, such a divergence appears when there is an expectation
operator in a ramsey model, see
tests/optimal_policy/nk_ramsey_expectation.mod */
vector<BinaryOpNode *> aux_equations;
//! Maximum order at which (endogenous) derivatives have been computed
int computed_derivs_order{0};
//! Stores derivatives
/*! Index 0 is not used, index 1 contains first derivatives, ...
For each derivation order, stores a map whose key is a vector of integer: the
first integer is the equation index, the remaining ones are the derivation
IDs of variables (in non-decreasing order, to avoid storing symmetric
elements several times) */
vector<map<vector<int>, expr_t>> derivatives;
//! Number of non-zero derivatives
/*! Index 0 is not used, index 1 contains number of non-zero first
derivatives, ... */
vector<int> NNZDerivatives;
//! Derivatives with respect to parameters
/*! The key of the outer map is a pair (derivation order w.r.t. endogenous,
derivation order w.r.t. parameters). For e.g., { 1, 2 } corresponds to the jacobian
differentiated twice w.r.t. to parameters.
In inner maps, the vector of integers consists of: the equation index, then
the derivation IDs of endogenous (in non-decreasing order),
then the IDs of parameters (in non-decreasing order)*/
map<pair<int,int>, map<vector<int>, expr_t>> params_derivatives;
//! Storage for temporary terms in block/bytecode mode
temporary_terms_t temporary_terms;
//! Used model local variables, that will be treated as temporary terms
/*! See the comments in ModelTree::computeTemporaryTerms() */
2018-05-28 15:50:29 +02:00
map<expr_t, expr_t, ExprNodeLess> temporary_terms_mlv;
//! Temporary terms for residuals and derivatives
/*! Index 0 is temp. terms of residuals, index 1 for first derivatives, ... */
vector<temporary_terms_t> temporary_terms_derivatives;
//! Stores, for each temporary term, its index in the MATLAB/Julia vector
2018-03-27 17:14:30 +02:00
temporary_terms_idxs_t temporary_terms_idxs;
//! Temporary terms for block decomposed models
vector<vector<temporary_terms_t>> v_temporary_terms;
vector<temporary_terms_inuse_t> v_temporary_terms_inuse;
//! Temporary terms for parameter derivatives, under a disaggregated form
/*! The pair of integers is to be interpreted as in param_derivatives */
2019-12-20 16:59:30 +01:00
map<pair<int, int>, temporary_terms_t> params_derivs_temporary_terms;
//! Stores, for each temporary term in param. derivs, its index in the MATLAB/Julia vector
temporary_terms_idxs_t params_derivs_temporary_terms_idxs;
//! Trend variables and their growth factors
map<int, expr_t> trend_symbols_map;
//! for all trends; the boolean is true if this is a log-trend, false otherwise
using nonstationary_symbols_map_t = map<int, pair<bool, expr_t>>;
//! Nonstationary variables and their deflators
nonstationary_symbols_map_t nonstationary_symbols_map;
/* Maps indices of equations in the block-decomposition order into original
equation IDs */
vector<int> eq_idx_block2orig;
/* Maps indices of (endogenous) variables in the block-decomposition order into original
type-specific endogenous IDs */
vector<int> endo_idx_block2orig;
/* Maps original variable and equation indices into the block-decomposition order.
Set by updateReverseVariableEquationOrderings() */
vector<int> eq_idx_orig2block, endo_idx_orig2block;
map_idx_t map_idx;
//! Vector describing equations: BlockSimulationType, if BlockSimulationType == EVALUATE_s then a expr_t on the new normalized equation
equation_type_and_normalized_equation_t equation_type_and_normalized_equation;
/* Stores derivatives of each block.
The tuple is: equation number (inside the block), variable number (inside
the block), lead/lag */
vector<map<tuple<int, int, int>, expr_t>> blocks_derivatives;
//! Map the derivatives for a block tuple<lag, eq, var>
using derivative_t = map<tuple<int, int, int>, expr_t>;
//! Vector of derivative for each blocks
vector<derivative_t> derivative_other_endo, derivative_exo, derivative_exo_det;
class BlockInfo
{
public:
BlockSimulationType simulation_type;
int first_equation; // Stores a block-ordered equation ID
int size{0};
int mfs_size{0}; // Size of the minimal feedback set
bool linear{true}; // Whether the block is linear in endogenous variable
int n_static{0}, n_forward{0}, n_backward{0}, n_mixed{0};
int max_endo_lag{0}, max_endo_lead{0}; // Maximum lag/lead on endos that appear in and *that belong to* the block
int max_other_endo_lag{0}, max_other_endo_lead{0}; // Maximum lag/lead on endos that appear in but do not belong to the block
int max_exo_lag{0}, max_exo_lead{0};
int max_exo_det_lag{0}, max_exo_det_lead{0};
int max_lag{0}, max_lead{0}; // The max over all endo/exo variables
inline int getRecursiveSize() const { return size - mfs_size; };
};
// Stores various informations on the blocks
vector<BlockInfo> blocks;
// Maps endogenous type-specific IDs to the block number to which it belongs
vector<int> endo2block;
/* Maps (original) equation number to the block number to which it belongs.
It verifies: i, eq2block[endo2eq[i]] = endo2block[i] */
vector<int> eq2block;
//! the file containing the model and the derivatives code
ofstream code_file;
2019-12-20 16:59:30 +01:00
//! Vector indicating if the equation is linear in endogenous variable (true) or not (false)
vector<bool> is_equation_linear;
//! Computes derivatives
/*! \param order the derivation order
\param vars the derivation IDs w.r.t. which compute the derivatives */
void computeDerivatives(int order, const set<int> &vars);
//! Computes derivatives of the Jacobian and Hessian w.r. to parameters
void computeParamsDerivatives(int paramsDerivsOrder);
//! Write derivative of an equation w.r. to a variable
void writeDerivative(ostream &output, int eq, int symb_id, int lag, ExprNodeOutputType output_type, const temporary_terms_t &temporary_terms) const;
//! Computes temporary terms (for all equations and derivatives)
void computeTemporaryTerms(bool is_matlab, bool no_tmp_terms);
//! Computes temporary terms for the file containing parameters derivatives
void computeParamsDerivativesTemporaryTerms();
2017-06-14 07:01:31 +02:00
//! Writes temporary terms
void writeTemporaryTerms(const temporary_terms_t &tt, temporary_terms_t &temp_term_union, const temporary_terms_idxs_t &tt_idxs, ostream &output, ExprNodeOutputType output_type, deriv_node_temp_terms_t &tef_terms) const;
void writeJsonTemporaryTerms(const temporary_terms_t &tt, temporary_terms_t &temp_term_union, ostream &output, deriv_node_temp_terms_t &tef_terms, const string &concat) const;
//! Compiles temporary terms
void compileTemporaryTerms(ostream &code_file, unsigned int &instruction_number, const temporary_terms_t &tt, map_idx_t map_idx, bool dynamic, bool steady_dynamic) const;
//! Adds informations for simulation in a binary file
void Write_Inf_To_Bin_File(const string &filename, int &u_count_int, bool &file_open, bool is_two_boundaries, int block_mfs) const;
//! Fixes output when there are more than 32 nested parens, Issue #1201
void fixNestedParenthesis(ostringstream &output, map<string, string> &tmp_paren_vars, bool &message_printed) const;
//! Tests if string contains more than 32 nested parens, Issue #1201
bool testNestedParenthesis(const string &str) const;
void writeModelLocalVariableTemporaryTerms(temporary_terms_t &temp_term_union,
const temporary_terms_idxs_t &tt_idxs,
2018-03-27 17:14:30 +02:00
ostream &output, ExprNodeOutputType output_type,
deriv_node_temp_terms_t &tef_terms) const;
//! Writes model equations
void writeModelEquations(ostream &output, ExprNodeOutputType output_type) const;
2018-03-27 17:14:30 +02:00
void writeModelEquations(ostream &output, ExprNodeOutputType output_type,
const temporary_terms_t &temporary_terms) const;
2017-02-02 15:09:43 +01:00
//! Writes JSON model equations
//! if residuals = true, we are writing the dynamic/static model.
//! Otherwise, just the model equations (with line numbers, no tmp terms)
void writeJsonModelEquations(ostream &output, bool residuals) const;
void writeJsonModelLocalVariables(ostream &output, deriv_node_temp_terms_t &tef_terms) const;
//! Compiles model equations
void compileModelEquations(ostream &code_file, unsigned int &instruction_number, const temporary_terms_t &tt, const map_idx_t &map_idx, bool dynamic, bool steady_dynamic) const;
//! Writes LaTeX model file
void writeLatexModelFile(const string &mod_basename, const string &latex_basename, ExprNodeOutputType output_type, bool write_equation_tags) const;
//! Sparse matrix of double to store the values of the static Jacobian
/*! First index is equation number, second index is endogenous type specific ID */
using jacob_map_t = map<pair<int, int>, double>;
//! Normalization of equations, as computed by computeNonSingularNormalization()
/*! Maps endogenous type specific IDs to equation numbers */
vector<int> endo2eq;
//! number of equation in the prologue and in the epilogue
int epilogue, prologue;
/* Compute a pseudo-Jacobian whose all elements are either zero or one,
depending on whether the variable symbolically appears in the equation */
jacob_map_t computeSymbolicJacobian() const;
// Compute {var,eq}_idx_orig2block from {var,eq}_idx_block2orig
void updateReverseVariableEquationOrderings();
//! Compute the matching between endogenous and variable using the jacobian contemporaneous_jacobian
/*!
\param contemporaneous_jacobian Jacobian used as an incidence matrix: all elements declared in the map (even if they are zero), are used as vertices of the incidence matrix
\return True if a complete normalization has been achieved
*/
bool computeNormalization(const jacob_map_t &contemporaneous_jacobian, bool verbose);
//! Try to compute the matching between endogenous and variable using a decreasing cutoff
/*!
Applied to the jacobian contemporaneous_jacobian and stop when a matching is found.
If no matching is found using a strictly positive cutoff, then a zero cutoff is applied (i.e. use a symbolic normalization); in that case, the method adds zeros in the jacobian matrices to reflect all the edges in the symbolic incidence matrix.
If no matching is found with a zero cutoff, an error message is printed.
The resulting normalization is stored in endo2eq.
*/
void computeNonSingularNormalization(const jacob_map_t &contemporaneous_jacobian, double cutoff, const jacob_map_t &static_jacobian);
//! Try to find a natural normalization if all equations are matched to an endogenous variable on the LHS
bool computeNaturalNormalization();
2020-03-20 18:42:59 +01:00
//! Evaluate the jacobian (w.r.t. endogenous) and suppress all the elements below the cutoff
/*! Returns a pair (contemporaneous_jacobian, static_jacobian).
Elements below the cutoff are discarded. External functions are evaluated to 1. */
pair<jacob_map_t, jacob_map_t> evaluateAndReduceJacobian(const eval_context_t &eval_context, double cutoff, bool verbose) const;
//! Select and reorder the non linear equations of the model
void select_non_linear_equations_and_variables();
//! Search the equations and variables belonging to the prologue and the epilogue of the model
void computePrologueAndEpilogue();
//! Determine the type of each equation of model and try to normalize the unnormalized equation
void equationTypeDetermination(const map<tuple<int, int, int>, expr_t> &first_order_endo_derivatives, int mfs);
/* Fills the max lags/leads and n_{static,mixed,forward,backward} fields of a
given block.
Needs the fields size and first_equation. */
void computeDynamicStructureOfBlock(int blk);
/* Compute the block decomposition and for a non-recusive block find the minimum feedback set
Initializes the blocks structure, and fills the following fields: size, first_equation,
mfs_size, n_static, n_forward, n_backward, n_mixed, maximum lags/leads.
Also initializes the endo2block and eq2block structures. */
void computeBlockDecompositionAndFeedbackVariablesForEachBlock();
/* Reduce the number of block by merging the same type of equations in the
prologue and the epilogue, and determine the type of each block.
Fills the simulation_type field of the blocks structure. */
void reduceBlocksAndTypeDetermination();
/* The 1st output gives, for each equation (in original order) the (max_lag,
max_lead) across all endogenous that appear in the equation and that
belong to the same block (i.e. those endogenous are solved in the same
block).
The 2nd output gives, for each type-specific endo IDs, its (max_lag,
max_lead) across all its occurences inside the equations of the block to
which it belongs. */
pair<lag_lead_vector_t, lag_lead_vector_t> getVariableLeadLagByBlock() const;
//! For each equation determine if it is linear or not
void equationLinear(const map<tuple<int, int, int>, expr_t> &first_order_endo_derivatives);
//! Print an abstract of the block structure of the model
void printBlockDecomposition() const;
//! Determine for each block if it is linear or not
void determineLinearBlocks();
//! Remove equations specified by exclude_eqs
vector<int> includeExcludeEquations(set<pair<string, string>> &eqs, bool exclude_eqs,
vector<BinaryOpNode *> &equations, vector<int> &equations_lineno,
EquationTags &equation_tags, bool static_equations) const;
//! Return the number of exogenous variable in the block block_number
virtual int getBlockExoSize(int block_number) const = 0;
//! Return the number of colums in the jacobian matrix for exogenous variable in the block block_number
virtual int getBlockExoColSize(int block_number) const = 0;
//! Return the type of equation belonging to the block
EquationType
getBlockEquationType(int blk, int eq) const
{
return equation_type_and_normalized_equation[eq_idx_block2orig[blocks[blk].first_equation+eq]].first;
};
//! Return true if the equation has been normalized
bool
isBlockEquationRenormalized(int blk, int eq) const
{
return equation_type_and_normalized_equation[eq_idx_block2orig[blocks[blk].first_equation + eq]].first == EquationType::evaluate_s;
};
//! Return the expr_t of equation belonging to the block
expr_t
getBlockEquationExpr(int blk, int eq) const
{
return equations[eq_idx_block2orig[blocks[blk].first_equation + eq]];
};
//! Return the expr_t of renormalized equation belonging to the block
expr_t
getBlockEquationRenormalizedExpr(int blk, int eq) const
{
return equation_type_and_normalized_equation[eq_idx_block2orig[blocks[blk].first_equation + eq]].second;
};
//! Return the original number of equation belonging to the block
int
getBlockEquationID(int blk, int eq) const
{
return eq_idx_block2orig[blocks[blk].first_equation + eq];
};
//! Return the original number of variable belonging to the block
int
getBlockVariableID(int blk, int var) const
{
return endo_idx_block2orig[blocks[blk].first_equation + var];
};
//! Return the original number of the exogenous variable varexo_number belonging to the block block_number
virtual int getBlockVariableExoID(int block_number, int variable_number) const = 0;
//! Return the position of an equation (given by its original index) inside its block
int
getBlockInitialEquationID(int blk, int eq) const
{
return eq_idx_orig2block[eq] - blocks[blk].first_equation;
};
//! Return the position of a variable (given by its original index) inside its block
int
getBlockInitialVariableID(int blk, int var) const
{
return endo_idx_orig2block[var] - blocks[blk].first_equation;
};
//! Return the position of variable_number in the block number belonging to the block block_number
virtual int getBlockInitialExogenousID(int block_number, int variable_number) const = 0;
//! Return the position of the deterministic exogenous variable_number in the block number belonging to the block block_number
virtual int getBlockInitialDetExogenousID(int block_number, int variable_number) const = 0;
//! Return the position of the other endogenous variable_number in the block number belonging to the block block_number
virtual int getBlockInitialOtherEndogenousID(int block_number, int variable_number) const = 0;
//! Initialize equation_reordered & variable_reordered
void initializeVariablesAndEquations();
//! Returns the 1st derivatives w.r.t. endogenous in a different format
/*! Returns a map (equation, type-specific ID, lag) → derivative.
Assumes that derivatives have already been computed. */
map<tuple<int, int, int>, expr_t> collectFirstOrderDerivativesEndogenous();
private:
//! Internal helper for the copy constructor and assignment operator
/*! Copies all the structures that contain ExprNode*, by the converting the
pointers into their equivalent in the new tree */
void copyHelper(const ModelTree &m);
//! Returns the name of the MATLAB architecture given the extension used for MEX files
static string matlab_arch(const string &mexext);
//! Compiles the MEX file
void compileDll(const string &basename, const string &static_or_dynamic, const string &mexext, const filesystem::path &matlabroot, const filesystem::path &dynareroot) const;
public:
2018-08-14 14:23:21 +02:00
ModelTree(SymbolTable &symbol_table_arg,
NumericalConstants &num_constants_arg,
ExternalFunctionsTable &external_functions_table_arg,
bool is_dynamic_arg = false);
ModelTree(const ModelTree &m);
ModelTree(ModelTree &&) = delete;
2019-12-20 16:59:30 +01:00
ModelTree &operator=(const ModelTree &m);
ModelTree &operator=(ModelTree &&) = delete;
//! Absolute value under which a number is considered to be zero
double cutoff{1e-15};
//! Compute the minimum feedback set
/*! 0 : all endogenous variables are considered as feedback variables
1 : the variables belonging to non normalized equation are considered as feedback variables
2 : the variables belonging to a non linear equation are considered as feedback variables
3 : the variables belonging to a non normalizable non linear equation are considered as feedback variables
default value = 0 */
int mfs{0};
//! Declare a node as an equation of the model; also give its line number
void addEquation(expr_t eq, int lineno);
//! Declare a node as an equation of the model, also giving its tags
void addEquation(expr_t eq, int lineno, const map<string, string> &eq_tags);
//! Declare a node as an auxiliary equation of the model, adding it at the end of the list of auxiliary equations
void addAuxEquation(expr_t eq);
//! Returns the number of equations in the model
int equation_number() const;
//! Adds a trend variable with its growth factor
void addTrendVariables(const vector<int> &trend_vars, expr_t growth_factor) noexcept(false);
//! Adds a nonstationary variables with their (common) deflator
void addNonstationaryVariables(const vector<int> &nonstationary_vars, bool log_deflator, expr_t deflator) noexcept(false);
//! Is a given variable non-stationary?
bool isNonstationary(int symb_id) const;
void set_cutoff_to_zero();
//! Simplify model equations: if a variable is equal to a constant, replace that variable elsewhere in the model
/*! Equations with MCP tags are excluded, see dynare#1697 */
void simplifyEquations();
/*! Reorder auxiliary variables so that they appear in recursive order in
set_auxiliary_variables.m and dynamic_set_auxiliary_series.m */
void reorderAuxiliaryEquations();
//! Find equations of the form “variable=constant”, excluding equations with “mcp” tag (see dynare#1697)
void findConstantEquationsWithoutMcpTag(map<VariableNode *, NumConstNode *> &subst_table) const;
2019-10-10 15:31:03 +02:00
//! Helper for writing the Jacobian elements in MATLAB and C
/*! Writes either (i+1,j+1) or [i+j*no_eq] */
void jacobianHelper(ostream &output, int eq_nb, int col_nb, ExprNodeOutputType output_type) const;
//! Helper for writing the sparse Hessian or third derivatives in MATLAB and C
/*! If order=2, writes either v2(i+1,j+1) or v2[i+j*NNZDerivatives[2]]
If order=3, writes either v3(i+1,j+1) or v3[i+j*NNZDerivatives[3]] */
void sparseHelper(int order, ostream &output, int row_nb, int col_nb, ExprNodeOutputType output_type) const;
//! Returns all the equation tags associated to an equation
2019-12-20 16:59:30 +01:00
inline map<string, string>
getEquationTags(int eq) const
{
return equation_tags.getTagsByEqn(eq);
}
inline static string
c_Equation_Type(EquationType type)
{
switch (type)
{
case EquationType::evaluate:
return "EVALUATE ";
case EquationType::evaluate_s:
return "EVALUATE_S";
case EquationType::solve:
return "SOLVE ";
default:
return "UNKNOWN ";
}
}
inline static string
BlockType0(BlockType type)
{
switch (type)
{
case BlockType::simultans:
return "SIMULTANEOUS TIME SEPARABLE ";
case BlockType::prologue:
return "PROLOGUE ";
case BlockType::epilogue:
return "EPILOGUE ";
case BlockType::simultan:
return "SIMULTANEOUS TIME UNSEPARABLE";
default:
return "UNKNOWN ";
}
}
inline static string
BlockSim(BlockSimulationType type)
{
switch (type)
{
case BlockSimulationType::evaluateForward:
return "EVALUATE FORWARD ";
case BlockSimulationType::evaluateBackward:
return "EVALUATE BACKWARD ";
case BlockSimulationType::solveForwardSimple:
return "SOLVE FORWARD SIMPLE ";
case BlockSimulationType::solveBackwardSimple:
return "SOLVE BACKWARD SIMPLE ";
case BlockSimulationType::solveTwoBoundariesSimple:
return "SOLVE TWO BOUNDARIES SIMPLE ";
case BlockSimulationType::solveForwardComplete:
return "SOLVE FORWARD COMPLETE ";
case BlockSimulationType::solveBackwardComplete:
return "SOLVE BACKWARD COMPLETE ";
case BlockSimulationType::solveTwoBoundariesComplete:
return "SOLVE TWO BOUNDARIES COMPLETE";
default:
return "UNKNOWN ";
}
}
};
#endif