From fe1ba750eb0b61bfc1ff54c4184eabdc01b289b3 Mon Sep 17 00:00:00 2001
From: Marco Ratto <marco.ratto@ec.europa.eu>
Date: Wed, 19 Oct 2022 19:07:45 +0200
Subject: [PATCH] accept periodic solution in simulations ONLY IF two regimes
 differ by one period, to avoid pathological solutions.

We also do not check for periodicity when check ahead periods have been increased endogenously, again to avoid mis-identified periodicity.

Any other type of periodicity, is flagged as non-convergence with error code 313 (infinite loop of solutions).

(cherry picked from commit d0150997f6f3b9ee909eaafedeaf27fefca0a36a)
---
 matlab/+occbin/solve_one_constraint.m  | 49 ++++++++++++++++++++++----
 matlab/+occbin/solve_two_constraints.m | 47 ++++++++++++++++++++----
 matlab/get_error_message.m             |  2 ++
 3 files changed, 86 insertions(+), 12 deletions(-)

diff --git a/matlab/+occbin/solve_one_constraint.m b/matlab/+occbin/solve_one_constraint.m
index 27d01a7204..5b710cff5a 100644
--- a/matlab/+occbin/solve_one_constraint.m
+++ b/matlab/+occbin/solve_one_constraint.m
@@ -125,20 +125,24 @@ for shock_period = 1:n_shocks_periods
     
     regime_change_this_iteration=true;
     iter = 0;
+    nperiods_endogenously_increased = false;
     guess_history_it = false;
     if guess_history && (shock_period<=length(regime_history_guess)) %beyond guess regime history
         guess_history_it = true;
     end
     
     is_periodic=false;
+    is_periodic_loop=false;
     binding_indicator_history={};
     max_err = NaN(max_iter,1);
+    regime_violates_constraint_in_expectation = false(max_iter,1);
     
-    while (regime_change_this_iteration && iter<max_iter && ~is_periodic)
+    while (regime_change_this_iteration && iter<max_iter && ~is_periodic && ~is_periodic_loop)
         iter = iter +1;
         if binding_indicator(end) && nperiods_0<opts_simul_.max_periods
             binding_indicator = [binding_indicator; false ];
             nperiods_0 = nperiods_0 + 1;
+            nperiods_endogenously_increased = true;
             disp_verbose(['nperiods has been endogenously increased up to ' int2str(nperiods_0) '.'],opts_simul_.debug)
         elseif nperiods_0>=opts_simul_.max_periods
             % enforce endogenously increased nperiods to not violate max_periods
@@ -201,6 +205,7 @@ for shock_period = 1:n_shocks_periods
                 err_relax = err.relax_constraint_1(relax.constraint_1(1:end_periods) & binding_indicator(1:end_periods));
                 max_err(iter) = max(abs([err_viol;err_relax]));
                 regime_change_this_iteration = true;
+                regime_violates_constraint_in_expectation(iter) = any(binding.constraint_1(1:end_periods) & ~binding_indicator(1:end_periods));
             else
                 regime_change_this_iteration = false;
                 max_err(iter) = 0;
@@ -220,16 +225,43 @@ for shock_period = 1:n_shocks_periods
                 binding_indicator= (binding_indicator | binding.constraint_1) & ~(binding_indicator & relax.constraint_1);
             end
             
-            if iter>1 && regime_change_this_iteration
+            if iter>1 && regime_change_this_iteration && ~nperiods_endogenously_increased
+                % check for periodic solution only if nperiods is not
+                % increased endogenously
+                % first check for infinite loop
+                is_periodic_loop = false(iter-1,1);
                 for kiter=1:iter-1
-                    vvv = [binding_indicator_history{kiter}; false(size(binding_indicator,1)- size(binding_indicator_history{kiter},1), 1)];
-                    is_periodic(kiter) = isequal(vvv, binding_indicator);
+                    if size(binding_indicator,1)== size(binding_indicator_history{kiter},1)
+                        %                     vvv = [binding_indicator_history{kiter}; false(size(binding_indicator,1)- size(binding_indicator_history{kiter},1), 1)];
+                        %                     is_periodic(kiter) = isequal(vvv, binding_indicator);
+                        is_periodic_loop(kiter) = isequal(binding_indicator_history{kiter}, binding_indicator);
+                    else
+                        is_periodic_loop(kiter) = false;
+                    end
+                end
+                is_periodic_loop_all =is_periodic_loop;
+                is_periodic_loop =  any(is_periodic_loop);
+                % only accept periodicity where regimes differ by one
+                % period!
+                is_periodic = false(iter-1,1);
+                for kiter=iter-1
+                    if size(binding_indicator,1)== size(binding_indicator_history{kiter},1)
+                        %                     vvv = [binding_indicator_history{kiter}; false(size(binding_indicator,1)- size(binding_indicator_history{kiter},1), 1)];
+                        %                     is_periodic(kiter) = isequal(vvv, binding_indicator);
+                        is_periodic(kiter) = isequal(binding_indicator_history{kiter}, binding_indicator) && length(find(binding_indicator_history{iter}-binding_indicator))==1;
+                    else
+                        is_periodic(kiter)=false;
+                    end
                 end
                 is_periodic_all =is_periodic;
                 is_periodic =  any(is_periodic);
                 if is_periodic && periodic_solution
-                    [merr,imerr]=min(max_err(find(is_periodic_all,1):end));
                     inx = find(is_periodic_all,1):iter;
+                    inx1 = inx(find(~regime_violates_constraint_in_expectation(inx)));
+                    if not(isempty(inx1))
+                        inx=inx1;
+                    end
+                    [merr,imerr]=min(max_err(inx));
                     inx = inx(imerr);
                     binding_indicator=binding_indicator_history{inx};
                     if inx<iter
@@ -281,9 +313,14 @@ for shock_period = 1:n_shocks_periods
                     return
                 end
             else
+                if is_periodic_loop
+                    disp_verbose('Did not converge -- infinite loop of regimes.',opts_simul_.debug)
+                    error_flag = 313;
+                else
                 disp_verbose('Did not converge -- increase maxit.',opts_simul_.debug)
-                if opts_simul_.waitbar; dyn_waitbar_close(hh); end
                     error_flag = 311;
+                end
+                if opts_simul_.waitbar; dyn_waitbar_close(hh); end
                 return
             end
         else
diff --git a/matlab/+occbin/solve_two_constraints.m b/matlab/+occbin/solve_two_constraints.m
index 4e448b241f..15c8ae1c2a 100644
--- a/matlab/+occbin/solve_two_constraints.m
+++ b/matlab/+occbin/solve_two_constraints.m
@@ -133,20 +133,24 @@ for shock_period = 1:n_shocks_periods
         dyn_waitbar(shock_period/n_shocks_periods, hh, sprintf('Period %u of %u', shock_period,n_shocks_periods));
     end
     regime_change_this_iteration=true;
+    nperiods_endogenously_increased = false;
     iter = 0;
     guess_history_it = false;
     if guess_history && (shock_period<=length(regime_history_guess)) %beyond guess regime history
         guess_history_it = true;
     end
     is_periodic=false;
+    is_periodic_loop=false;
     binding_indicator_history={};
     max_err = NaN(max_iter,1);
+    regime_violates_constraint_in_expectation = false(max_iter,1);
     
-    while (regime_change_this_iteration && iter<max_iter && ~is_periodic)
+    while (regime_change_this_iteration && iter<max_iter && ~is_periodic && ~is_periodic_loop)
         iter = iter +1;
         if any(binding_indicator(end,:)) && nperiods_0<opts_simul_.max_periods
             binding_indicator = [binding_indicator; false(1,2)];
             nperiods_0 = nperiods_0 + 1;
+            nperiods_endogenously_increased = true;
             disp_verbose(['nperiods has been endogenously increased up to ' int2str(nperiods_0) '.'],opts_simul_.debug)
         elseif nperiods_0>=opts_simul_.max_periods
             % enforce endogenously increased nperiods to not violate max_periods
@@ -248,17 +252,43 @@ for shock_period = 1:n_shocks_periods
             end
             binding_indicator = reshape(binding_indicator,nperiods_0+1,2);
             
-            if iter>1 && regime_change_this_iteration
-                is_periodic=false(1,iter-1);
+            if iter>1 && regime_change_this_iteration && ~nperiods_endogenously_increased
+                % check for periodic solution only if nperiods is not
+                % increased endogenously
+                % first check for infinite loop
+                is_periodic_loop = false(iter-1,1);
                 for kiter=1:iter-1
-                    vvv = [binding_indicator_history{kiter}; false(size(binding_indicator,1)- size(binding_indicator_history{kiter},1), 2)];
-                    is_periodic(kiter) = isequal(vvv, binding_indicator);
+                    if size(binding_indicator,1)== size(binding_indicator_history{kiter},1)
+                        %                     vvv = [binding_indicator_history{kiter}; false(size(binding_indicator,1)- size(binding_indicator_history{kiter},1), 1)];
+                        %                     is_periodic(kiter) = isequal(vvv, binding_indicator);
+                        is_periodic_loop(kiter) = isequal(binding_indicator_history{kiter}, binding_indicator);
+                    else
+                        is_periodic_loop(kiter) = false;
+                    end
+                end
+%                 is_periodic_loop_all =is_periodic_loop;
+                is_periodic_loop =  any(is_periodic_loop);
+                % only accept periodicity where regimes differ by one
+                % period!
+                is_periodic=false(1,iter-1);
+                for kiter=iter-1
+                    if size(binding_indicator,1)== size(binding_indicator_history{kiter},1)
+                        %                     vvv = [binding_indicator_history{kiter}; false(size(binding_indicator,1)- size(binding_indicator_history{kiter},1), 1)];
+                        %                     is_periodic(kiter) = isequal(vvv, binding_indicator);
+                        is_periodic(kiter) = isequal(binding_indicator_history{kiter}, binding_indicator) && length(find(binding_indicator_history{iter}(:,1)-binding_indicator(:,1)))<=1  && length(find(binding_indicator_history{iter}(:,2)-binding_indicator(:,2)))<=1;
+                    else
+                        is_periodic(kiter)=false;
+                    end
                 end
                 is_periodic_all = is_periodic;
                 is_periodic =  any(is_periodic);
                 if is_periodic && periodic_solution
-                    [min_err,index_min_err]=min(max_err(find(is_periodic_all,1):end));
                     inx = find(is_periodic_all,1):iter;
+                    inx1 = inx(find(~regime_violates_constraint_in_expectation(inx)));
+                    if not(isempty(inx1))
+                        inx=inx1;
+                    end
+                    [min_err,index_min_err]=min(max_err(inx));
                     inx = inx(index_min_err);
                     binding_indicator=binding_indicator_history{inx}; %select regime history with same result, but smallest error
                     if inx<iter %update if needed
@@ -312,8 +342,13 @@ for shock_period = 1:n_shocks_periods
                     return;
                 end
             else
+                if is_periodic_loop
+                    disp_verbose('Did not converge -- infinite loop of regimes.',opts_simul_.debug)
+                    error_flag = 313;
+                else
                 disp_verbose('Did not converge -- increase maxit.',opts_simul_.debug)
                 error_flag = 311;
+                end
                 if opts_simul_.waitbar; dyn_waitbar_close(hh); end
                 return;
             end
diff --git a/matlab/get_error_message.m b/matlab/get_error_message.m
index 1013184d69..cb236cfd2b 100644
--- a/matlab/get_error_message.m
+++ b/matlab/get_error_message.m
@@ -188,6 +188,8 @@ switch info(1)
         message = 'Occbin: Simulation did not converge, increase maxit or check_ahead_periods.';        
     case 312
         message = 'Occbin: Constraint(s) are binding at the end of the sample.';        
+    case 313
+        message = 'Occbin: Simulation did not converge -- infinite loop of regimes';        
     case 320
         message = 'Piecewise linear Kalman filter: There was a problem in obtaining the likelihood.';        
     otherwise
-- 
GitLab