New model_remove statement and model_replace block

Factorize the implementation with the existing code for the include_eqs and
exclude_eqs options (which has been refactored by the way).

Ref. #19
pac-components
Sébastien Villemot 2021-12-14 17:24:14 +01:00
parent a73b0d911a
commit 1f004584e9
No known key found for this signature in database
GPG Key ID: 2CECE9350ECEBE4A
10 changed files with 262 additions and 163 deletions

View File

@ -137,7 +137,7 @@ enum class SymbolType
// Value 17 is unused for the time being (but could be reused)
epilogue = 18, //!< Variables created in epilogue block
excludedVariable = 19 //!< Type to use when an equation is excluded via include/exclude_eqs and the LHS variable is not used elsewhere in the model
excludedVariable = 19 //!< Variable excluded via model_remove/include_eqs/exclude_eqs
};
enum class ExpressionType

View File

@ -2356,18 +2356,23 @@ DynamicModel::writeDynamicJacobianNonZeroElts(const string &basename) const
output.close();
}
void
DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_eq_tags,
set<pair<string, string>> &eq_tag_set, bool exclude_eqs)
vector<pair<string, string>>
DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs)
{
auto removeLeadingTrailingWhitespace = [](string &str)
{
str.erase(0, str.find_first_not_of("\t\n\v\f\r "));
str.erase(str.find_last_not_of("\t\n\v\f\r ") + 1);
};
string tags;
if (filesystem::exists(inc_exc_eq_tags))
if (filesystem::exists(inc_exc_option_value))
{
ifstream exclude_file;
exclude_file.open(inc_exc_eq_tags, ifstream::in);
exclude_file.open(inc_exc_option_value, ifstream::in);
if (!exclude_file.is_open())
{
cerr << "ERROR: Could not open " << inc_exc_eq_tags << endl;
cerr << "ERROR: Could not open " << inc_exc_option_value << endl;
exit(EXIT_FAILURE);
}
@ -2397,12 +2402,12 @@ DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_eq_tags,
}
}
else
tags = inc_exc_eq_tags;
tags = inc_exc_option_value;
removeLeadingTrailingWhitespace(tags);
if (tags.front() == '[' && tags.back() != ']')
{
cerr << "Error: " << (exclude_eqs ? "exclude_eqs" : "include_eqs")
cerr << "ERROR: " << (exclude_eqs ? "exclude_eqs" : "include_eqs")
<< ": if the first character is '[' the last must be ']'" << endl;
exit(EXIT_FAILURE);
}
@ -2433,11 +2438,12 @@ DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_eq_tags,
regex r(R"((\s*)" + quote_regex + "|" + non_quote_regex + R"(\s*)(,\s*()" + quote_regex + "|" + non_quote_regex + R"()\s*)*)");
if (!regex_match(tags, r))
{
cerr << "Error: " << (exclude_eqs ? "exclude_eqs" : "include_eqs")
cerr << "ERROR: " << (exclude_eqs ? "exclude_eqs" : "include_eqs")
<< ": argument is of incorrect format." << endl;
exit(EXIT_FAILURE);
}
vector<pair<string, string>> eq_tag_set;
regex s(quote_regex + "|" + non_quote_regex);
for (auto it = sregex_iterator(tags.begin(), tags.end(), s);
it != sregex_iterator(); ++it)
@ -2445,67 +2451,172 @@ DynamicModel::parseIncludeExcludeEquations(const string &inc_exc_eq_tags,
auto str = it->str();
if (str[0] == '\'' && str[str.size()-1] == '\'')
str = str.substr(1, str.size()-2);
eq_tag_set.insert({tagname, str});
eq_tag_set.emplace_back(tagname, str);
}
return eq_tag_set;
}
vector<int>
DynamicModel::removeEquationsHelper(set<pair<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
bool excluded_vars_change_type,
vector<BinaryOpNode *> &all_equations,
vector<int> &all_equations_lineno,
EquationTags &all_equation_tags, bool static_equations) const
{
if (all_equations.empty())
return {};
/* Try to convert the list of equations by tags into a list of equation
numbers.
The tag pairs that match an equation are removed from the list, so that
the caller knows which tag pairs have not been handled. */
set<int> listed_eqs_by_number;
for (auto it = listed_eqs_by_tag.begin(); it != listed_eqs_by_tag.end();)
if (auto tmp = all_equation_tags.getEqnsByTag(it->first, it->second);
!tmp.empty())
{
listed_eqs_by_number.insert(tmp.begin(), tmp.end());
it = listed_eqs_by_tag.erase(it);
}
else
++it;
// Compute the indices of equations to be actually deleted
set<int> eqs_to_delete_by_number;
if (exclude_eqs)
eqs_to_delete_by_number = listed_eqs_by_number;
else
for (size_t i = 0; i < all_equations.size(); i++)
if (listed_eqs_by_number.find(i) == listed_eqs_by_number.end())
eqs_to_delete_by_number.insert(i);
// remove from equations, equations_lineno, equation_tags
vector<BinaryOpNode *> new_equations;
vector<int> new_equations_lineno;
map<int, int> old_eqn_num_2_new;
vector<int> excluded_vars;
for (size_t i = 0; i < all_equations.size(); i++)
if (eqs_to_delete_by_number.find(i) != eqs_to_delete_by_number.end())
{
if (excluded_vars_change_type)
{
if (auto tmp = all_equation_tags.getTagValueByEqnAndKey(i, "endogenous"); !tmp.empty())
excluded_vars.push_back(symbol_table.getID(tmp));
else
{
set<int> result;
all_equations[i]->arg1->collectVariables(SymbolType::endogenous, result);
if (result.size() == 1)
excluded_vars.push_back(*result.begin());
else
{
cerr << "ERROR: Equation " << i+1
<< " has been excluded but it does not have a single variable on its left-hand side or an `endogenous` tag" << endl;
exit(EXIT_FAILURE);
}
}
}
}
else
{
new_equations.emplace_back(all_equations[i]);
old_eqn_num_2_new[i] = new_equations.size() - 1;
new_equations_lineno.emplace_back(all_equations_lineno[i]);
}
int n_excl = all_equations.size() - new_equations.size();
all_equations = new_equations;
all_equations_lineno = new_equations_lineno;
all_equation_tags.erase(eqs_to_delete_by_number, old_eqn_num_2_new);
if (!static_equations)
for (size_t i = 0; i < excluded_vars.size(); i++)
for (size_t j = i+1; j < excluded_vars.size(); j++)
if (excluded_vars[i] == excluded_vars[j])
{
cerr << "ERROR: Variable " << symbol_table.getName(i) << " was excluded twice"
<< " via a model_remove or model_replace statement, or via the include_eqs or exclude_eqs option" << endl;
exit(EXIT_FAILURE);
}
cout << "Excluded " << n_excl << (static_equations ? " static " : " dynamic ")
<< "equation" << (n_excl > 1 ? "s" : "") << " via model_remove or model_replace statement, or via include_eqs or exclude_eqs option" << endl;
return excluded_vars;
}
void
DynamicModel::removeEquations(const vector<pair<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
bool excluded_vars_change_type)
{
/* Convert the const vector to a (mutable) set */
set<pair<string, string>> listed_eqs_by_tag2;
copy(listed_eqs_by_tag.begin(), listed_eqs_by_tag.end(), inserter(listed_eqs_by_tag2, listed_eqs_by_tag2.end()));
vector<int> excluded_vars = removeEquationsHelper(listed_eqs_by_tag2, exclude_eqs,
excluded_vars_change_type,
equations, equations_lineno,
equation_tags, false);
// Ignore output because variables are not excluded when equations marked 'static' are excluded
removeEquationsHelper(listed_eqs_by_tag2, exclude_eqs, excluded_vars_change_type,
static_only_equations, static_only_equations_lineno,
static_only_equations_equation_tags, true);
if (!listed_eqs_by_tag2.empty())
{
cerr << "ERROR: model_remove/model_replace/exclude_eqs/include_eqs: The equations specified by" << endl;
for (const auto &[tagname, tagvalue] : listed_eqs_by_tag)
cerr << " " << tagname << "=" << tagvalue << endl;
cerr << "were not found." << endl;
exit(EXIT_FAILURE);
}
if (excluded_vars_change_type)
{
// Collect list of used variables in updated list of equations
set<int> eqn_vars;
for (auto eqn : equations)
eqn->collectVariables(SymbolType::endogenous, eqn_vars);
for (auto eqn : static_only_equations)
eqn->collectVariables(SymbolType::endogenous, eqn_vars);
/* Change type of endogenous variables determined by excluded equations.
They become exogenous if they are still used somewhere, otherwise they are
completely excluded from the model. */
for (auto ev : excluded_vars)
if (eqn_vars.find(ev) != eqn_vars.end())
{
symbol_table.changeType(ev, SymbolType::exogenous);
cerr << "Variable '" << symbol_table.getName(ev) << "' turned into an exogenous, as its defining equation has been removed (but it still appears in an equation)" << endl;
}
else
{
symbol_table.changeType(ev, SymbolType::excludedVariable);
cerr << "Variable '" << symbol_table.getName(ev) << "' has been excluded from the model, as its defining equation has been removed and it appears nowhere else" << endl;
}
}
}
void
DynamicModel::includeExcludeEquations(const string &eqs, bool exclude_eqs)
DynamicModel::includeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs)
{
if (eqs.empty())
if (inc_exc_option_value.empty())
return;
set<pair<string, string>> eq_tag_set;
parseIncludeExcludeEquations(eqs, eq_tag_set, exclude_eqs);
auto listed_eqs_by_tag = parseIncludeExcludeEquations(inc_exc_option_value, exclude_eqs);
vector<int> excluded_vars
= ModelTree::includeExcludeEquations(eq_tag_set, exclude_eqs,
equations, equations_lineno,
equation_tags, false);
// Ignore output because variables are not excluded when equations marked 'static' are excluded
ModelTree::includeExcludeEquations(eq_tag_set, exclude_eqs,
static_only_equations, static_only_equations_lineno,
static_only_equations_equation_tags, true);
if (!eq_tag_set.empty())
{
cerr << "ERROR: " << (exclude_eqs ? "exclude_eqs" : "include_eqs") << ": The equations specified by `";
cerr << eq_tag_set.begin()->first << "= ";
for (auto &it : eq_tag_set)
cerr << it.second << ", ";
cerr << "` were not found." << endl;
exit(EXIT_FAILURE);
}
removeEquations(listed_eqs_by_tag, exclude_eqs, true);
/* There is already a check about #static and #dynamic in
ModFile::checkPass(), but the present method is called from
ModFile::transformPass(), so we must do the check again */
if (staticOnlyEquationsNbr() != dynamicOnlyEquationsNbr())
{
cerr << "ERROR: " << (exclude_eqs ? "exclude_eqs" : "include_eqs")
<< ": You must remove the same number of equations marked `static` as equations marked `dynamic`." << endl;
cerr << "ERROR: exclude_eqs/include_eqs: You must remove the same number of equations marked `static` as equations marked `dynamic`." << endl;
exit(EXIT_FAILURE);
}
// Collect list of used variables in updated list of equations
set<pair<int, int>> eqn_vars;
for (const auto &eqn : equations)
eqn->collectDynamicVariables(SymbolType::endogenous, eqn_vars);
for (const auto &eqn : static_only_equations)
eqn->collectDynamicVariables(SymbolType::endogenous, eqn_vars);
// Change LHS variable type of excluded equation if it is used in an eqution that has been kept
for (auto ev : excluded_vars)
{
bool found = false;
for (const auto &it : eqn_vars)
if (it.first == ev)
{
symbol_table.changeType(ev, SymbolType::exogenous);
found = true;
break;
}
if (!found)
symbol_table.changeType(ev, SymbolType::excludedVariable);
}
}
void

View File

@ -244,32 +244,52 @@ private:
pointers into their equivalent in the new tree */
void copyHelper(const DynamicModel &m);
// Internal helper functions for includeExcludeEquations()
/*! Handles parsing of argument passed to exclude_eqs/include_eqs*/
/*
Expects command line arguments of the form:
/* Handles parsing of argument passed to exclude_eqs/include_eqs.
The argument inc_exc_option_value should be of one of the following forms:
* filename.txt
* eq1
* ['eq 1', 'eq 2']
* [tagname='eq 1']
* [tagname=('eq 1', 'eq 2')]
If argument is a file, the file should be formatted as:
If argument is a filename, the file should be formatted as:
eq 1
eq 2
OR
tagname=
X
Y
*/
void parseIncludeExcludeEquations(const string &inc_exc_eq_tags, set<pair<string, string>> &eq_tag_set, bool exclude_eqs);
// General function that removes leading/trailing whitespace from a string
inline void
removeLeadingTrailingWhitespace(string &str)
{
str.erase(0, str.find_first_not_of("\t\n\v\f\r "));
str.erase(str.find_last_not_of("\t\n\v\f\r ") + 1);
}
The boolean exclude_eqs should be true if we are in the exclude_eqs case,
false in the include_eqs case (this only affects error messages).
Returns a set of pairs (tag name, tag value) corresponding to the set of
equations to be included or excluded.
*/
static vector<pair<string, string>> parseIncludeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs);
/* Helper for the removeEquations() method.
listed_eqs_by_tag is the list of (tag name, tag value) pairs corresponding
to the option value, exclude_eqs is a boolean indicating whether were
excluding or including, and excluded_vars_change_type is a boolean
indicating whether to compute variables to be excluded.
The all_equations* arguments will be modified by the routine by excluding
equations. They are either the main structures for storing equations in
ModelTree, or their counterpart for static-only equations. The
static_equations boolean indicates when we are in the latter case.
The listed_eqs_by_tag structure will be updated by removing those tag
pairs that have been matched with equations in the all_equations*
argument*.
Returns a list of excluded variables (empty if
excluded_vars_change_type=false) */
vector<int> removeEquationsHelper(set<pair<string, string>> &listed_eqs_by_tag,
bool exclude_eqs, bool excluded_vars_change_type,
vector<BinaryOpNode *> &all_equations,
vector<int> &all_equations_lineno,
EquationTags &all_equation_tags,
bool static_equations) const;
//! Compute autoregressive matrices of trend component models
/* The algorithm uses matching rules over expression trees. It cannot handle
@ -409,8 +429,14 @@ public:
//! Set the max leads/lags of the original model
void setLeadsLagsOrig();
//! Removes equations from the model according to name tags
void includeExcludeEquations(const string &eqs, bool exclude_eqs);
//! Implements the include_eqs/exclude_eqs options
void includeExcludeEquations(const string &inc_exc_option_value, bool exclude_eqs);
/* Removes equations from the model (identified by their name tags).
Used for include_eqs/exclude_eqs options and for model_remove and
model_replace blocks */
void removeEquations(const vector<pair<string, string>> &listed_eqs_by_tag, bool exclude_eqs,
bool excluded_vars_change_type);
//! Replaces model equations with derivatives of Lagrangian w.r.t. endogenous
void computeRamseyPolicyFOCs(const StaticModel &static_model);

View File

@ -183,7 +183,7 @@ class ParsingDriver;
%token NO_IDENTIFICATION_STRENGTH NO_IDENTIFICATION_REDUCEDFORM NO_IDENTIFICATION_MOMENTS
%token NO_IDENTIFICATION_MINIMAL NO_IDENTIFICATION_SPECTRUM NORMALIZE_JACOBIANS GRID_NBR
%token TOL_RANK TOL_DERIV TOL_SV CHECKS_VIA_SUBSETS MAX_DIM_SUBSETS_GROUPS ZERO_MOMENTS_TOLERANCE
%token MAX_NROWS SQUEEZE_SHOCK_DECOMPOSITION WITH_EPILOGUE
%token MAX_NROWS SQUEEZE_SHOCK_DECOMPOSITION WITH_EPILOGUE MODEL_REMOVE MODEL_REPLACE
%token <vector<string>> SYMBOL_VEC
@ -205,7 +205,7 @@ class ParsingDriver;
%type <PriorDistributions> prior_pdf prior_distribution
%type <pair<expr_t,expr_t>> calibration_range
%type <pair<string,string>> named_var_elem subsamples_eq_opt integer_range_w_inf
%type <vector<pair<string,string>>> named_var named_var_1
%type <vector<pair<string,string>>> named_var named_var_1 tag_pair_list_for_selection
%type <tuple<string,string,string,string>> prior_eq_opt options_eq_opt
%type <vector<pair<int, int>>> period_list
%type <vector<expr_t>> matched_moments_list value_list
@ -342,6 +342,8 @@ statement : parameters
| compilation_setup
| matched_moments
| occbin_constraints
| model_remove
| model_replace
;
dsample : DSAMPLE INT_NUMBER ';'
@ -1109,6 +1111,30 @@ comma_hand_side : hand_side
pound_expression: '#' symbol EQUAL hand_side ';'
{ driver.declare_and_init_model_local_variable($2, $4); };
model_remove : MODEL_REMOVE '(' tag_pair_list_for_selection ')' ';'
{ driver.model_remove($3); };
model_replace : MODEL_REPLACE '(' tag_pair_list_for_selection ')' ';'
{ driver.begin_model_replace($3); }
equation_list END ';'
{ driver.end_model(); };
tag_pair_list_for_selection : QUOTED_STRING
{ $$ = { { "name", $1 } }; }
| symbol EQUAL QUOTED_STRING
{ $$ = { { $1, $3 } }; }
| tag_pair_list_for_selection COMMA QUOTED_STRING
{
$$ = $1;
$$.emplace_back("name", $3);
}
| tag_pair_list_for_selection COMMA symbol EQUAL QUOTED_STRING
{
$$ = $1;
$$.emplace_back($3, $5);
}
;
shocks : SHOCKS ';' shock_list END ';' { driver.end_shocks(false); }
| SHOCKS '(' OVERWRITE ')' ';' shock_list END ';' { driver.end_shocks(true); }
| SHOCKS '(' OVERWRITE ')' ';' END ';' { driver.end_shocks(true); }

View File

@ -192,6 +192,7 @@ DATE -?[0-9]+([ya]|m([1-9]|1[0-2])|q[1-4])
<INITIAL>perfect_foresight_with_expectation_errors_setup {BEGIN DYNARE_STATEMENT; return token::PERFECT_FORESIGHT_WITH_EXPECTATION_ERRORS_SETUP;}
<INITIAL>perfect_foresight_with_expectation_errors_solver {BEGIN DYNARE_STATEMENT; return token::PERFECT_FORESIGHT_WITH_EXPECTATION_ERRORS_SOLVER;}
<INITIAL>compilation_setup {BEGIN DYNARE_STATEMENT; return token::COMPILATION_SETUP;}
<INITIAL>model_remove {BEGIN DYNARE_STATEMENT; return token::MODEL_REMOVE;}
<DYNARE_STATEMENT>; {
if (!sigma_e)
@ -231,6 +232,7 @@ DATE -?[0-9]+([ya]|m([1-9]|1[0-2])|q[1-4])
<INITIAL>generate_irfs {BEGIN DYNARE_BLOCK; return token::GENERATE_IRFS;}
<INITIAL>matched_moments {BEGIN DYNARE_BLOCK; return token::MATCHED_MOMENTS;}
<INITIAL>occbin_constraints {BEGIN DYNARE_BLOCK; return token::OCCBIN_CONSTRAINTS;}
<INITIAL>model_replace {BEGIN DYNARE_BLOCK; return token::MODEL_REPLACE;}
/* For the semicolon after an "end" keyword */
<INITIAL>; {return Dynare::parser::token_type (yytext[0]);}

View File

@ -782,7 +782,7 @@ VariableNode::prepareForDerivation()
exit(EXIT_FAILURE);
case SymbolType::excludedVariable:
cerr << "VariableNode::prepareForDerivation: impossible case: "
<< "You are trying to derive a variable that has been excluded via include_eqs/exclude_eqs: "
<< "You are trying to derive a variable that has been excluded via model_remove/include_eqs/exclude_eqs: "
<< datatree.symbol_table.getName(symb_id) << endl;
exit(EXIT_FAILURE);
}

View File

@ -1545,87 +1545,6 @@ ModelTree::addEquation(expr_t eq, int lineno)
equations_lineno.push_back(lineno);
}
vector<int>
ModelTree::includeExcludeEquations(set<pair<string, string>> &eqs, bool exclude_eqs,
vector<BinaryOpNode *> &equations, vector<int> &equations_lineno,
EquationTags &equation_tags, bool static_equations) const
{
vector<int> excluded_vars;
if (equations.empty())
return excluded_vars;
// Get equation numbers of tags
set<int> tag_eqns;
for (auto it = eqs.begin(); it != eqs.end();)
if (auto tmp = equation_tags.getEqnsByTag(it->first, it->second);
!tmp.empty())
{
tag_eqns.insert(tmp.begin(), tmp.end());
it = eqs.erase(it);
}
else
++it;
set<int> eqns;
if (exclude_eqs)
eqns = tag_eqns;
else
for (size_t i = 0; i < equations.size(); i++)
if (tag_eqns.find(i) == tag_eqns.end())
eqns.insert(i);
// remove from equations, equations_lineno, equation_tags
vector<BinaryOpNode *> new_eqns;
vector<int> new_equations_lineno;
map<int, int> old_eqn_num_2_new;
for (size_t i = 0; i < equations.size(); i++)
if (eqns.find(i) != eqns.end())
{
if (auto tmp = equation_tags.getTagValueByEqnAndKey(i, "endogenous"); !tmp.empty())
excluded_vars.push_back(symbol_table.getID(tmp));
else
{
set<pair<int, int>> result;
equations[i]->arg1->collectDynamicVariables(SymbolType::endogenous, result);
if (result.size() == 1)
excluded_vars.push_back(result.begin()->first);
else
{
cerr << "ERROR: Equation " << i
<< " has been excluded but does not have a single variable on LHS or `endogenous` tag" << endl;
exit(EXIT_FAILURE);
}
}
}
else
{
new_eqns.emplace_back(equations[i]);
old_eqn_num_2_new[i] = new_eqns.size() - 1;
new_equations_lineno.emplace_back(equations_lineno[i]);
}
int n_excl = equations.size() - new_eqns.size();
equations = new_eqns;
equations_lineno = new_equations_lineno;
equation_tags.erase(eqns, old_eqn_num_2_new);
if (!static_equations)
for (size_t i = 0; i < excluded_vars.size(); i++)
for (size_t j = i+1; j < excluded_vars.size(); j++)
if (excluded_vars[i] == excluded_vars[j])
{
cerr << "Error: Variable " << symbol_table.getName(i) << " was excluded twice"
<< " via in/exclude_eqs option" << endl;
exit(EXIT_FAILURE);
}
cout << "Excluded " << n_excl << (static_equations ? " static " : " dynamic ")
<< "equation" << (n_excl > 1 ? "s" : "") << " via in/exclude_eqs option" << endl;
return excluded_vars;
}
void
ModelTree::findConstantEquationsWithoutMcpTag(map<VariableNode *, NumConstNode *> &subst_table) const
{

View File

@ -331,10 +331,6 @@ protected:
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 type of equation belonging to the block
EquationType

View File

@ -318,6 +318,8 @@ ParsingDriver::add_model_variable(const string &name)
try
{
symb_id = mod_file->symbol_table.getID(name);
if (mod_file->symbol_table.getType(symb_id) == SymbolType::excludedVariable)
error("Variable '" + name + "' can no longer be used since it has been excluded by a previous 'model_remove' statement");
}
catch (SymbolTable::UnknownSymbolNameException &e)
{
@ -3529,3 +3531,16 @@ ParsingDriver::isSymbolIdentifier(const string &str)
return false;
return true;
}
void
ParsingDriver::model_remove(const vector<pair<string, string>> &listed_eqs_by_tags)
{
mod_file->dynamic_model.removeEquations(listed_eqs_by_tags, true, true);
}
void
ParsingDriver::begin_model_replace(const vector<pair<string, string>> &listed_eqs_by_tags)
{
mod_file->dynamic_model.removeEquations(listed_eqs_by_tags, true, false);
set_current_data_tree(&mod_file->dynamic_model);
}

View File

@ -430,7 +430,7 @@ public:
void add_epilogue_equal(const string &varname, expr_t expr);
//! Begin a model block
void begin_model();
//! End a model block, printing errors that were encountered in parsing
//! End a model or model_replace block, printing errors that were encountered in parsing
void end_model();
//! Writes a shocks statement
void end_shocks(bool overwrite);
@ -889,6 +889,10 @@ public:
void begin_occbin_constraints();
//! Add an occbin_constraints block
void end_occbin_constraints(const vector<tuple<string, BinaryOpNode *, BinaryOpNode *, expr_t, expr_t>> &constraints);
// Process a model_remove statement
void model_remove(const vector<pair<string, string>> &listed_eqs_by_tags);
// Begin a model_replace statement
void begin_model_replace(const vector<pair<string, string>> &listed_eqs_by_tags);
// Equivalent of MATLABs strsplit. Returns an empty vector given an empty string.
static vector<string> strsplit(const string &str, char delim);
// Returns true iff the string is a legal symbol identifier (see NAME token in lexer)