diff --git a/src/DynamicModel.cc b/src/DynamicModel.cc
index fa4cfb86dc3af2e01ba25bb7d84bd0a136aa2d2d..7beab03505ff96d0af8df881113eca6a250ea350 100644
--- a/src/DynamicModel.cc
+++ b/src/DynamicModel.cc
@@ -3402,9 +3402,6 @@ DynamicModel::fillVarModelTable() const
   var_model_table.setLhs(lhsr);
   var_model_table.setRhs(rhsr);
   var_model_table.setLhsExprT(lhs_expr_tr);
-
-  // Fill AR Matrix
-  var_model_table.setAR(computeAutoregressiveMatrices(true));
 }
 
 void
@@ -3424,12 +3421,19 @@ DynamicModel::fillVarModelTableFromOrigModel() const
           set<pair<int, int>> rhs_endo_set, rhs_exo_set;
           equations[eqn]->arg2->collectDynamicVariables(SymbolType::endogenous, rhs_endo_set);
           for (const auto &[symb_id, lag] : rhs_endo_set)
-            if (lag >= 0)
+            if (lag > 0)
+              {
+                cerr << "ERROR: in Equation " << eqtag
+                     << ". A VAR model may not have leaded endogenous variables on the RHS. " << endl;
+                exit(EXIT_FAILURE);
+              }
+            else if (!var_model_table.getStructural().at(model_name) && lag == 0)
               {
                 cerr << "ERROR: in Equation " << eqtag
-                     << ". A VAR model may not have leaded or contemporaneous endogenous variables on the RHS. " << endl;
+                     << ". A non-structural VAR model may not have contemporaneous endogenous variables on the RHS. " << endl;
                 exit(EXIT_FAILURE);
               }
+
           equations[eqn]->arg2->collectDynamicVariables(SymbolType::exogenous, rhs_exo_set);
           for (const auto &[symb_id, lag] : rhs_exo_set)
             if (lag != 0)
@@ -3487,18 +3491,122 @@ DynamicModel::fillVarModelTableFromOrigModel() const
   var_model_table.setOrigDiffVar(orig_diff_var);
 }
 
+vector<int>
+DynamicModel::getVARDerivIDs(int lhs_symb_id, int lead_lag) const
+{
+  vector<int> deriv_ids;
+
+  // First directly look for the variable itself
+  if (auto it = deriv_id_table.find({ lhs_symb_id, lead_lag });
+      it != deriv_id_table.end())
+    deriv_ids.push_back(it->second);
+
+  // Then go through auxiliary variables
+  for (auto &[key, deriv_id2] : deriv_id_table)
+    {
+      auto [symb_id2, lead_lag2] = key;
+      const AuxVarInfo *avi;
+      try
+        {
+          avi = &symbol_table.getAuxVarInfo(symb_id2);
+        }
+      catch (SymbolTable::UnknownSymbolIDException)
+        {
+          continue;
+        }
+
+      if (avi->get_type() == AuxVarType::endoLag && avi->get_orig_symb_id() == lhs_symb_id
+          && avi->get_orig_lead_lag() + lead_lag2 == lead_lag)
+        deriv_ids.push_back(deriv_id2);
+
+      // Handle diff lag auxvar, possibly nested several times
+      int diff_lag_depth = 0;
+      while (avi->get_type() == AuxVarType::diffLag)
+        {
+          diff_lag_depth++;
+          if (avi->get_orig_symb_id() == lhs_symb_id && lead_lag2 - diff_lag_depth == lead_lag)
+            {
+              deriv_ids.push_back(deriv_id2);
+              break;
+            }
+          try
+            {
+              avi = &symbol_table.getAuxVarInfo(avi->get_orig_symb_id());
+            }
+          catch (SymbolTable::UnknownSymbolIDException)
+            {
+              break;
+            }
+        }
+    }
+
+  return deriv_ids;
+}
+
+void
+DynamicModel::fillVarModelTableMatrices()
+{
+  map<string, map<tuple<int, int, int>, expr_t>> AR;
+  map<string, map<tuple<int, int>, expr_t>> A0;
+  for (const auto &[model_name, eqns] : var_model_table.getEqNums())
+    {
+      const vector<int> &lhs = var_model_table.getLhs(model_name);
+      int max_lag = var_model_table.getMaxLag(model_name);
+      for (auto lhs_symb_id : lhs)
+        {
+          // Fill autoregressive matrix (AR)
+          for (int lag = 1; lag <= max_lag; lag++)
+            {
+              vector<int> deriv_ids = getVARDerivIDs(lhs_symb_id, -lag);;
+              for (size_t i = 0; i < eqns.size(); i++)
+                {
+                  expr_t d = Zero;
+                  for (int deriv_id : deriv_ids)
+                    d = AddPlus(d, equations[eqns[i]]->getDerivative(deriv_id));
+                  if (d != Zero)
+                    {
+                      if (!d->isConstant())
+                        {
+                          cerr << "ERROR: Equation '" << equation_tags.getTagValueByEqnAndKey(eqns[i], "name") << "' is not linear" << endl;
+                          exit(EXIT_FAILURE);
+                        }
+
+                      AR[model_name][{ i, lag, lhs_symb_id }] = AddUMinus(d);
+                    }
+                }
+            }
+
+          // Fill A0 matrix (for contemporaneous variables)
+          int lhs_deriv_id = getDerivID(lhs_symb_id, 0);
+          for (size_t i = 0; i < eqns.size(); i++)
+            {
+              expr_t d = equations[eqns[i]]->getDerivative(lhs_deriv_id);
+              if (d != Zero)
+                {
+                  if (!d->isConstant())
+                    {
+                      cerr << "ERROR: Equation '" << equation_tags.getTagValueByEqnAndKey(eqns[i], "name") << "' is not linear" << endl;
+                      exit(EXIT_FAILURE);
+                    }
+
+                  A0[model_name][{ i, lhs_symb_id }] = d;
+                }
+            }
+        }
+    }
+  var_model_table.setAR(AR);
+  var_model_table.setA0(A0);
+}
+
 map<string, map<tuple<int, int, int>, expr_t>>
-DynamicModel::computeAutoregressiveMatrices(bool is_var) const
+DynamicModel::computeAutoregressiveMatrices() const
 {
   map<string, map<tuple<int, int, int>, expr_t>> ARr;
-  const auto &all_eqnums = is_var ?
-    var_model_table.getEqNums() : trend_component_model_table.getNonTargetEqNums();
-  for (const auto &[model_name, eqns] : all_eqnums)
+  for (const auto &[model_name, eqns] : trend_component_model_table.getNonTargetEqNums())
     {
       int i = 0;
       map<tuple<int, int, int>, expr_t> AR;
-      const vector<int> &lhs = is_var ? var_model_table.getLhsOrigIds(model_name)
-        : trend_component_model_table.getNonTargetLhs(model_name);
+      const vector<int> &lhs = trend_component_model_table.getNonTargetLhs(model_name);
       for (auto eqn : eqns)
         {
           auto bopn = dynamic_cast<BinaryOpNode *>(equations[eqn]->arg2);
@@ -3710,7 +3818,7 @@ DynamicModel::fillTrendComponentModelTableFromOrigModel() const
 void
 DynamicModel::fillTrendComponentModelTableAREC(const ExprNode::subst_table_t &diff_subst_table) const
 {
-  auto ARr = computeAutoregressiveMatrices(false);
+  auto ARr = computeAutoregressiveMatrices();
   trend_component_model_table.setAR(ARr);
   auto [A0r, A0starr] = computeErrorComponentMatrices(diff_subst_table);
   trend_component_model_table.setA0(A0r, A0starr);
diff --git a/src/DynamicModel.hh b/src/DynamicModel.hh
index 84fad95ed2819155ff5b5fd58a573c1a02550a9f..a2ad30bf2a3ec81053a3806bb4e905ea833a84df 100644
--- a/src/DynamicModel.hh
+++ b/src/DynamicModel.hh
@@ -271,13 +271,23 @@ private:
     str.erase(str.find_last_not_of("\t\n\v\f\r ") + 1);
   }
 
-  //! Compute autoregressive matrices of VAR or trend component models
-  map<string, map<tuple<int, int, int>, expr_t>> computeAutoregressiveMatrices(bool is_var) const;
+  //! Compute autoregressive matrices of trend component models
+  /* The algorithm uses matching rules over expression trees. It cannot handle
+     arbitrarily-written expressions. */
+  map<string, map<tuple<int, int, int>, expr_t>> computeAutoregressiveMatrices() const;
 
   //! Compute error component matrices of trend component_models
   /*! Returns a pair (A0r, A0starr) */
   pair<map<string, map<tuple<int, int, int>, expr_t>>, map<string, map<tuple<int, int, int>, expr_t>>> computeErrorComponentMatrices(const ExprNode::subst_table_t &diff_subst_table) const;
 
+  /* For a VAR model, given the symbol ID of a LHS variable, and a (negative)
+     lag, returns all the corresponding deriv_ids (by properly dealing with two
+     types of auxiliary variables: endo lags and diff lags). It returns a
+     vector because in some cases there may be sereval corresponding deriv_ids
+     (for example, in the deriv_id table, AUX_DIFF_nn(-1) may appear as itself
+     (with a lag), and also as a contemporaneous diff lag auxvar). */
+  vector<int> getVARDerivIDs(int lhs_symb_id, int lead_lag) const;
+
 public:
   DynamicModel(SymbolTable &symbol_table_arg,
                NumericalConstants &num_constants_arg,
@@ -363,9 +373,13 @@ public:
   void fillTrendComponentModelTableAREC(const ExprNode::subst_table_t &diff_subst_table) const;
 
   //! Fill the VAR model table with information available from the transformed model
+  // NB: Does not fill the AR and A0 matrices
   void fillVarModelTable() const;
   //! Fill the VAR model table with information available from the original model
   void fillVarModelTableFromOrigModel() const;
+  //! Fill the AR and A0 matrices of the VAR model table
+  // Uses derivatives, hence must be called after computingPass()
+  void fillVarModelTableMatrices();
 
   //! Update the rhs references in the var model and trend component tables
   //! after substitution of auxiliary variables and find the trend variables
diff --git a/src/DynareBison.yy b/src/DynareBison.yy
index cdade2eb6d7798fd5afcd3c74816ce5b798059fd..f4ce61ac70bc7b2f3377107870184044ef5d154e 100644
--- a/src/DynareBison.yy
+++ b/src/DynareBison.yy
@@ -166,7 +166,7 @@ class ParsingDriver;
 %token PARAMETER_CONVERGENCE_CRITERION NUMBER_OF_LARGE_PERTURBATIONS NUMBER_OF_SMALL_PERTURBATIONS
 %token NUMBER_OF_POSTERIOR_DRAWS_AFTER_PERTURBATION MAX_NUMBER_OF_STAGES
 %token RANDOM_FUNCTION_CONVERGENCE_CRITERION RANDOM_PARAMETER_CONVERGENCE_CRITERION NO_INIT_ESTIMATION_CHECK_FIRST_OBS
-%token HETEROSKEDASTIC_FILTER TIME_SHIFT
+%token HETEROSKEDASTIC_FILTER TIME_SHIFT STRUCTURAL
 /* Method of Moments */
 %token METHOD_OF_MOMENTS MOM_METHOD
 %token BARTLETT_KERNEL_LAG WEIGHTING_MATRIX WEIGHTING_MATRIX_SCALING_FACTOR ANALYTIC_STANDARD_ERRORS ANALYTIC_JACOBIAN PENALIZED_ESTIMATOR VERBOSE 
@@ -386,6 +386,7 @@ var_model_options_list : var_model_options_list COMMA var_model_options
 
 var_model_options : o_var_name
                   | o_var_eq_tags
+                  | o_var_structural
                   ;
 
 trend_component_model : TREND_COMPONENT_MODEL '('  trend_component_model_options_list ')' ';' { driver.trend_component_model(); }
@@ -3230,6 +3231,7 @@ o_series : SERIES EQUAL symbol { driver.option_str("series", $3); };
 o_datafile : DATAFILE EQUAL filename { driver.option_str("datafile", $3); };
 o_filename : FILENAME EQUAL filename { driver.option_str("filename", $3); };
 o_var_eq_tags : EQTAGS EQUAL vec_str { driver.option_vec_str("var.eqtags", $3); }
+o_var_structural : STRUCTURAL { driver.option_num("var.structural", "true"); }
 o_dirname : DIRNAME EQUAL filename { driver.option_str("dirname", $3); };
 o_huge_number : HUGE_NUMBER EQUAL non_negative_number { driver.option_num("huge_number", $3); };
 o_nobs : NOBS EQUAL vec_int
diff --git a/src/DynareFlex.ll b/src/DynareFlex.ll
index 4347d9947f40dc9d31201bcd1a86e55cc890bc0e..180b691973982e5b4b46c0f50c65fc184a6ea3e7 100644
--- a/src/DynareFlex.ll
+++ b/src/DynareFlex.ll
@@ -815,6 +815,7 @@ DATE -?[0-9]+([ya]|m([1-9]|1[0-2])|q[1-4])
 <DYNARE_STATEMENT>variances {return token::VARIANCES;}
 <DYNARE_STATEMENT>equations {return token::EQUATIONS;}
 <DYNARE_STATEMENT>time_shift {return token::TIME_SHIFT;}
+<DYNARE_STATEMENT>structural {return token::STRUCTURAL;}
 <DYNARE_STATEMENT>true {
   yylval->build<string>(yytext);
   return token::TRUE;
diff --git a/src/ExprNode.cc b/src/ExprNode.cc
index 9982fcddc606963dbbed0f218315aa794d3dba7d..b1f77541b48f1786399b5ef6c30704a0b5ed67ba 100644
--- a/src/ExprNode.cc
+++ b/src/ExprNode.cc
@@ -379,6 +379,16 @@ ExprNode::matchMatchedMoment(vector<int> &symb_ids, vector<int> &lags, vector<in
   throw MatchFailureException{"Unsupported expression"};
 }
 
+bool
+ExprNode::isConstant() const
+{
+  set<pair<int, int>> symbs_lags;
+  collectDynamicVariables(SymbolType::endogenous, symbs_lags);
+  collectDynamicVariables(SymbolType::exogenous, symbs_lags);
+  collectDynamicVariables(SymbolType::exogenousDet, symbs_lags);
+  return symbs_lags.empty();
+}
+
 
 NumConstNode::NumConstNode(DataTree &datatree_arg, int idx_arg, int id_arg) :
   ExprNode{datatree_arg, idx_arg},
diff --git a/src/ExprNode.hh b/src/ExprNode.hh
index 53446c2f9f9f139b6c4d5654e6c59d66831563aa..766f00cfae873118b122ff38e5566f6d8f9901bd 100644
--- a/src/ExprNode.hh
+++ b/src/ExprNode.hh
@@ -703,6 +703,10 @@ public:
      the first, lag in the second, exponent in the third).
      Throws a MatchFailureException if not of the right form. */
   virtual void matchMatchedMoment(vector<int> &symb_ids, vector<int> &lags, vector<int> &powers) const;
+
+  /* Returns true if the expression contains no endogenous, no exogenous and no
+     exogenous deterministic */
+  bool isConstant() const;
 };
 
 //! Object used to compare two nodes (using their indexes)
diff --git a/src/ModFile.cc b/src/ModFile.cc
index bad563f31935522dcc08677bcea9fe8c84c69785..af3e4fcb9834ed8a0d49b6e3efa2a9f39302d2c4 100644
--- a/src/ModFile.cc
+++ b/src/ModFile.cc
@@ -837,6 +837,9 @@ ModFile::computingPass(bool no_tmp_terms, OutputType output, int params_derivs_o
         }
     }
 
+  // Those matrices can only be filled here, because we use derivatives
+  dynamic_model.fillVarModelTableMatrices();
+
   for (auto &statement : statements)
     statement->computingPass(mod_file_struct);
 
diff --git a/src/ParsingDriver.cc b/src/ParsingDriver.cc
index 77302c81a5f0adceeb48e4b3504f6a066b9d48d8..bd156cd2304d27e1f6bcd0c98092bf427e594d3a 100644
--- a/src/ParsingDriver.cc
+++ b/src/ParsingDriver.cc
@@ -1391,7 +1391,12 @@ ParsingDriver::var_model()
     error("You must pass the eqtags option to the var_model statement.");
   auto eqtags = itvs->second;
 
-  mod_file->var_model_table.addVarModel(name, eqtags);
+  bool structural = false;
+  if (auto itn = options_list.num_options.find("var.structural");
+      itn != options_list.num_options.end() && itn->second == "true")
+    structural = true;
+
+  mod_file->var_model_table.addVarModel(name, structural, eqtags);
   options_list.clear();
 }
 
diff --git a/src/SubModel.cc b/src/SubModel.cc
index c18bc17e3e7f74effa6ed9c05239ad3a960435d4..ec1f26615b7b80616e6c871e1b380078a0ba5959 100644
--- a/src/SubModel.cc
+++ b/src/SubModel.cc
@@ -428,7 +428,7 @@ VarModelTable::VarModelTable(SymbolTable &symbol_table_arg) :
 }
 
 void
-VarModelTable::addVarModel(string name_arg, vector<string> eqtags_arg)
+VarModelTable::addVarModel(string name_arg, bool structural_arg, vector<string> eqtags_arg)
 {
   if (isExistingVarModelName(name_arg))
     {
@@ -436,6 +436,7 @@ VarModelTable::addVarModel(string name_arg, vector<string> eqtags_arg)
       exit(EXIT_FAILURE);
     }
 
+  structural[name_arg] = structural_arg;
   eqtags[name_arg] = move(eqtags_arg);
   names.insert(move(name_arg));
 }
@@ -454,14 +455,15 @@ VarModelTable::writeOutput(const string &basename, ostream &output) const
       cerr << "Error: Can't open file " << filename << " for writing" << endl;
       exit(EXIT_FAILURE);
     }
-  ar_output << "function ar = var_ar(model_name, params)" << endl
-            << "%function ar = var_ar(model_name, params)" << endl
+  ar_output << "function [ar, a0] = var_ar(model_name, params)" << endl
+            << "%function [ar, a0] = var_ar(model_name, params)" << endl
             << "% File automatically generated by the Dynare preprocessor" << endl << endl;
 
   for (const auto &name : names)
     {
-      output << "M_.var." << name << ".model_name = '" << name << "';" << endl;
-      output << "M_.var." << name << ".eqtags = {";
+      output << "M_.var." << name << ".model_name = '" << name << "';" << endl
+             << "M_.var." << name << ".structural = " << (structural.at(name) ? "true" : "false") << ";" << endl
+             << "M_.var." << name << ".eqtags = {";
       for (const auto &it : eqtags.at(name))
         output << "'" << it << "'; ";
       output << "};" << endl
@@ -512,7 +514,18 @@ VarModelTable::writeOutput(const string &basename, ostream &output) const
           expr->writeOutput(ar_output, ExprNodeOutputType::matlabDynamicModel);
           ar_output << ";" << endl;
         }
-      ar_output << "    return" << endl
+      ar_output << "    if nargout > 1" << endl
+                << "      a0 = zeros(" << lhs.size() << ", " << lhs.size() << ");" << endl;
+      for (const auto &[key, expr] : A0.at(name))
+        {
+          auto [eqn, lhs_symb_id] = key;
+          int colidx = static_cast<int>(distance(lhs.begin(), find(lhs.begin(), lhs.end(), lhs_symb_id)));
+          ar_output << "      a0(" << eqn + 1 << ", " << colidx + 1 << ") = ";
+          expr->writeOutput(ar_output, ExprNodeOutputType::matlabDynamicModel);
+          ar_output << ";" << endl;
+        }
+      ar_output << "    end" << endl
+                << "    return" << endl
                 << "end" << endl << endl;
     }
   ar_output << "error([model_name ' is not a valid var_model name'])" << endl
@@ -540,6 +553,12 @@ VarModelTable::writeJsonOutput(ostream &output) const
     }
 }
 
+const map<string, bool> &
+VarModelTable::getStructural() const
+{
+  return structural;
+}
+
 const map<string, vector<string>> &
 VarModelTable::getEqTags() const
 {
@@ -652,6 +671,12 @@ VarModelTable::setAR(map<string, map<tuple<int, int, int>, expr_t>> AR_arg)
   AR = move(AR_arg);
 }
 
+void
+VarModelTable::setA0(map<string, map<tuple<int, int>, expr_t>> A0_arg)
+{
+  A0 = move(A0_arg);
+}
+
 const vector<int> &
 VarModelTable::getMaxLags(const string &name_arg) const
 {
diff --git a/src/SubModel.hh b/src/SubModel.hh
index 627dbec6b829efe475c5893a1ab9f21b996c5995..37c35da73d46ad38aa83136bd16c871aac93293a 100644
--- a/src/SubModel.hh
+++ b/src/SubModel.hh
@@ -47,6 +47,8 @@ private:
   map<string, vector<expr_t>> lhs_expr_t;
   map<string, vector<int>> target_vars;
   map<string, map<tuple<int, int, int>, expr_t>> AR; // name -> (eqn, lag, lhs_symb_id) -> expr_t
+  /* Note that A0 in the trend-component model context is not the same thing as
+     in the structural VAR context. */
   map<string, map<tuple<int, int, int>, expr_t>> A0, A0star; // name -> (eqn, lag, col) -> expr_t
 public:
   explicit TrendComponentModelTable(SymbolTable &symbol_table_arg);
@@ -116,21 +118,28 @@ class VarModelTable
 private:
   SymbolTable &symbol_table;
   set<string> names;
+  map<string, bool> structural; // Whether VARs are structural or reduced-form
   map<string, vector<string>> eqtags;
   map<string, vector<int>> eqnums, max_lags, lhs, lhs_orig_symb_ids, orig_diff_var;
   map<string, vector<set<pair<int, int>>>> rhs; // name -> for each equation: set of pairs (var, lag)
   map<string, vector<bool>> diff;
   map<string, vector<expr_t>> lhs_expr_t;
   map<string, map<tuple<int, int, int>, expr_t>> AR; // name -> (eqn, lag, lhs_symb_id) -> param_expr_t
+  /* The A0 matrix is mainly for structural VARs. For reduced-form VARs, it
+     will be equal to the identity matrix. Also note that A0 in the structural
+     VAR context is not the same thing as in the trend-component model
+     context. */
+  map<string, map<tuple<int, int>, expr_t>> A0; // name -> (eqn, lhs_symb_id) -> param_expr_t
 public:
   explicit VarModelTable(SymbolTable &symbol_table_arg);
 
   //! Add a VAR model
-  void addVarModel(string name, vector<string> eqtags);
+  void addVarModel(string name, bool structural_arg, vector<string> eqtags);
 
   inline bool isExistingVarModelName(const string &name_arg) const;
   inline bool empty() const;
 
+  const map<string, bool> &getStructural() const;
   const map<string, vector<string>> &getEqTags() const;
   const vector<string> &getEqTags(const string &name_arg) const;
   const map<string, vector<int>> &getEqNums() const;
@@ -151,6 +160,7 @@ public:
   void setMaxLags(map<string, vector<int>> max_lags_arg);
   void setOrigDiffVar(map<string, vector<int>> orig_diff_var_arg);
   void setAR(map<string, map<tuple<int, int, int>, expr_t>> AR_arg);
+  void setA0(map<string, map<tuple<int, int>, expr_t>> A0_arg);
 
   //! Write output of this class
   void writeOutput(const string &basename, ostream &output) const;