This is a static copy of a profile report

Home

Function details for ff_abz_vfThis is a static copy of a profile report

Home

ff_abz_vf (Calls: 1, Time: 91.667 s)
Generated 07-Jul-2019 01:08:27 using performance time.
function in file C:\Users\fan\CodeDynaAsset\m_abz\solve\ff_abz_vf.m
Copy to new window for comparing multiple runs

Parents (calling functions)
No parent
Lines where the most time was spent

Line NumberCodeCallsTotal Time% TimeTime Plot
275
fl_beta*mt_z_trans(it_z_i, it_...
6443754055.784 s60.9%
264
fl_c = f_cons_coh(fl_coh, fl_a...
902812513.603 s14.8%
289
ar_val_cur(it_ap_k) = f_util_c...
47322897.463 s8.1%
294
fl_beta*mt_z_trans(it_z_i, it_...
709843352.474 s2.7%
276
end
644375402.316 s2.5%
All other lines  10.027 s10.9%
Totals  91.667 s100% 
Children (called functions)

Function NameFunction TypeCallsTotal Time% TimeTime Plot
...unctions>@(coh,bprime)(coh-bprime)anonymous function91485007.045 s7.7%
...c)(((c).^(1-fl_crra)-1)./(1-fl_crra))anonymous function47322903.972 s4.3%
...b>0)+b.*(1+fl_r_borr).*(b<=0)))anonymous function1203750.107 s0.1%
Self time (built-ins, overhead, etc.)  80.544 s87.9%
Totals  91.667 s100% 
Code Analyzer results
No Code Analyzer messages.
Coverage results
Show coverage for parent directory
Total lines in function392
Non-code lines (comments, blank lines)235
Code lines (lines that can run)157
Code lines that did run71
Code lines that did not run86
Coverage (did run/can run)45.22 %
Function listing
time 
Calls 
 line
   7 
function result_map = ff_abz_vf(varargin)
   8 
%% FF_ABZ_VF solve infinite horizon exo shock + endo asset problem
   9 
% This program solves the infinite horizon dynamic single asset and single
  10 
% shock problem with loops. This file contains codes that processes
  11 
% borrowing.
  12 
%
  13 
% The borrowing problem is very similar to the savings problem. The code
  14 
% could be identical if one does not have to deal with default. The main
  15 
% addition here in comparison to the savings only code
  16 
% <https://fanwangecon.github.io/CodeDynaAsset/m_az/solve/html/ff_az_vf.html
  17 
% ff_az_vf> is the ability to deal with default. 
  18 
%
  19 
% @param param_map container parameter container
  20 
%
  21 
% @param support_map container support container
  22 
%
  23 
% @param armt_map container container with states, choices and shocks
  24 
% grids that are inputs for grid based solution algorithm
  25 
%
  26 
% @param func_map container container with function handles for
  27 
% consumption cash-on-hand etc.
  28 
%
  29 
% @return result_map container contains policy function matrix, value
  30 
% function matrix, iteration results, and policy function, value function
  31 
% and iteration results tables.
  32 
%
  33 
% keys included in result_map:
  34 
%
  35 
% * mt_val matrix states_n by shock_n matrix of converged value function grid
  36 
% * mt_pol_a matrix states_n by shock_n matrix of converged policy function grid
  37 
% * ar_val_diff_norm array if bl_post = true it_iter_last by 1 val function
  38 
% difference between iteration
  39 
% * ar_pol_diff_norm array if bl_post = true it_iter_last by 1 policy
  40 
% function difference between iterations
  41 
% * mt_pol_perc_change matrix if bl_post = true it_iter_last by shock_n the
  42 
% proportion of grid points at which policy function changed between
  43 
% current and last iteration for each element of shock
  44 
%
  45 
% @example
  46 
%
  47 
%    % Get Default Parameters
  48 
%    it_param_set = 2;
  49 
%    [param_map, support_map] = ffs_abz_set_default_param(it_param_set);
  50 
%    % Chnage param_map keys for borrowing
  51 
%    param_map('fl_b_bd') = -20; % borrow bound
  52 
%    param_map('bl_default') = false; % true if allow for default
  53 
%    param_map('fl_c_min') = 0.0001; % u(c_min) when default
  54 
%    % Change Keys in param_map
  55 
%    param_map('it_a_n') = 500;
  56 
%    param_map('fl_z_r_borr_n') = 5;
  57 
%    param_map('it_z_wage_n') = 15;
  58 
%    param_map('it_z_n') = param_map('it_z_wage_n') * param_map('fl_z_r_borr_n');
  59 
%    param_map('fl_a_max') = 100;
  60 
%    param_map('fl_w') = 1.3;
  61 
%    % Change Keys support_map
  62 
%    support_map('bl_display') = false;
  63 
%    support_map('bl_post') = true;
  64 
%    support_map('bl_display_final') = false;
  65 
%    % Call Program with external parameters that override defaults.
  66 
%    ff_abz_vf(param_map, support_map);
  67 
%
  68 
% @include
  69 
%
  70 
% * <https://fanwangecon.github.io/CodeDynaAsset/m_abz/paramfunc/html/ffs_abz_set_default_param.html ffs_abz_set_default_param>
  71 
% * <https://fanwangecon.github.io/CodeDynaAsset/m_abz/paramfunc/html/ffs_abz_get_funcgrid.html ffs_abz_get_funcgrid>
  72 
% * <https://fanwangecon.github.io/CodeDynaAsset/m_az/solvepost/html/ff_az_vf_post.html ff_az_vf_post>
  73 
%
  74 
% @seealso
  75 
%
  76 
% * save loop: <https://fanwangecon.github.io/CodeDynaAsset/m_az/solve/html/ff_az_vf.html ff_az_vf>
  77 
% * save vectorized: <https://fanwangecon.github.io/CodeDynaAsset/m_az/solve/html/ff_az_vf_vec.html ff_az_vf_vec>
  78 
% * save optimized-vectorized: <https://fanwangecon.github.io/CodeDynaAsset/m_az/solve/html/ff_az_vf_vecsv.html ff_az_vf_vecsv>
  79 
% * save + borr loop: <https://fanwangecon.github.io/CodeDynaAsset/m_abz/solve/html/ff_abz_vf.html ff_abz_vf>
  80 
% * save + borr vectorized: <https://fanwangecon.github.io/CodeDynaAsset/m_abz/solve/html/ff_abz_vf_vec.html ff_abz_vf_vec>
  81 
% * save + borr optimized-vectorized: <https://fanwangecon.github.io/CodeDynaAsset/m_abz/solve/html/ff_abz_vf_vecsv.html ff_abz_vf_vecsv>
  82 
%
  83 

  84 
%% Default
  85 
% * it_param_set = 1: quick test
  86 
% * it_param_set = 2: benchmark run
  87 
% * it_param_set = 3: benchmark profile
  88 
% * it_param_set = 4: press publish button
  89 

  90 
it_param_set = 3;
  91 
bl_input_override = true;
  92 
[param_map, support_map] = ffs_abz_set_default_param(it_param_set);
  93 

  94 
% Note: param_map and support_map can be adjusted here or outside to override defaults
  95 
param_map('it_a_n') = 75;
  96 
param_map('fl_z_r_borr_n') = 3;
  97 
param_map('it_z_wage_n') = 5;
  98 
param_map('it_z_n') = param_map('it_z_wage_n') * param_map('fl_z_r_borr_n');
  99 
% param_map('fl_r_save') = 0.025;
 100 
% param_map('fl_r_borr') = 0.035;
 101 

 102 
[armt_map, func_map] = ffs_abz_get_funcgrid(param_map, support_map, bl_input_override); % 1 for override
 103 
default_params = {param_map support_map armt_map func_map};
 104 

 105 
%% Parse Parameters 1
 106 

 107 
% if varargin only has param_map and support_map,
 108 
params_len = length(varargin);
 109 
[default_params{1:params_len}] = varargin{:};
 110 
param_map = [param_map; default_params{1}];
 111 
support_map = [support_map; default_params{2}];
 112 
if params_len >= 1 && params_len <= 2
 113 
    % If override param_map, re-generate armt and func if they are not
 114 
    % provided
 115 
    bl_input_override = true;
 116 
    [armt_map, func_map] = ffs_abz_get_funcgrid(param_map, support_map, bl_input_override);
 117 
else
 118 
    % Override all
 119 
    armt_map = [armt_map; default_params{3}];
 120 
    func_map = [func_map; default_params{4}];
 121 
end
 122 

 123 
% append function name
 124 
st_func_name = 'ff_abz_vf';
 125 
support_map('st_profile_name_main') = [st_func_name support_map('st_profile_name_main')];
 126 
support_map('st_mat_name_main') = [st_func_name support_map('st_mat_name_main')];
 127 
support_map('st_img_name_main') = [st_func_name support_map('st_img_name_main')];
 128 

 129 
%% Parse Parameters 2
 130 

 131 
% armt_map
 132 
params_group = values(armt_map, {'ar_a', 'mt_z_trans', 'ar_z_r_borr_mesh_wage', 'ar_z_wage_mesh_r_borr'});
 133 
[ar_a, mt_z_trans, ar_z_r_borr_mesh_wage, ar_z_wage_mesh_r_borr] = params_group{:};
 134 

 135 
% func_map
 136 
params_group = values(func_map, {'f_util_log', 'f_util_crra', 'f_cons_checkcmin', 'f_coh', 'f_cons_coh'});
 137 
[f_util_log, f_util_crra, f_cons_checkcmin, f_coh, f_cons_coh] = params_group{:};
 138 

 139 
% param_map
 140 
params_group = values(param_map, {'it_a_n', 'it_z_n', 'fl_crra', 'fl_beta', 'fl_c_min',...
 141 
    'fl_nan_replace', 'bl_default', 'fl_default_aprime'});
 142 
[it_a_n, it_z_n, fl_crra, fl_beta, fl_c_min, ...
 143 
    fl_nan_replace, bl_default, fl_default_aprime] = params_group{:};
 144 
params_group = values(param_map, {'it_maxiter_val', 'fl_tol_val', 'fl_tol_pol', 'it_tol_pol_nochange'});
 145 
[it_maxiter_val, fl_tol_val, fl_tol_pol, it_tol_pol_nochange] = params_group{:};
 146 

 147 
% support_map
 148 
params_group = values(support_map, {'bl_profile', 'st_profile_path', ...
 149 
    'st_profile_prefix', 'st_profile_name_main', 'st_profile_suffix',...
 150 
    'bl_time', 'bl_display', 'it_display_every', 'bl_post'});
 151 
[bl_profile, st_profile_path, ...
 152 
    st_profile_prefix, st_profile_name_main, st_profile_suffix, ...
 153 
    bl_time, bl_display, it_display_every, bl_post] = params_group{:};
 154 

 155 
%% Initialize Output Matrixes
 156 

 157 
mt_val_cur = zeros(length(ar_a),it_z_n);
 158 
mt_val = mt_val_cur - 1;
 159 
mt_pol_a = zeros(length(ar_a),it_z_n);
 160 
mt_pol_a_cur = mt_pol_a - 1;
 161 

 162 
%% Initialize Convergence Conditions
 163 

 164 
bl_vfi_continue = true;
 165 
it_iter = 0;
 166 
ar_val_diff_norm = zeros([it_maxiter_val, 1]);
 167 
ar_pol_diff_norm = zeros([it_maxiter_val, 1]);
 168 
mt_pol_perc_change = zeros([it_maxiter_val, it_z_n]);
 169 

 170 
%% Iterate Value Function
 171 
% Loop solution with 4 nested loops
 172 
%
 173 
% # loop 1: over exogenous states
 174 
% # loop 2: over endogenous states
 175 
% # loop 3: over choices
 176 
% # loop 4: add future utility, integration--loop over future shocks
 177 
%
 178 

 179 
% Start Profile
 180 
if (bl_profile)
 181 
    close all;
 182 
    profile off;
 183 
    profile on;
< 0.001 
      1 
 184
end 
 185 

 186 
% Start Timer
< 0.001 
      1 
 187
if (bl_time) 
< 0.001 
      1 
 188
    tic; 
< 0.001 
      1 
 189
end 
 190 

 191 
% Utility at-Default/at-limiting-case-when-nodefault
< 0.001 
      1 
 192
if (fl_crra == 1) 
 193 
    fl_u_cmin = f_util_log(fl_c_min);
< 0.001 
      1 
 194
else 
< 0.001 
      1 
 195
    fl_u_cmin = f_util_crra(fl_c_min); 
< 0.001 
      1 
 196
end 
 197 

 198 

 199 
% Value Function Iteration
< 0.001 
      1 
 200
while bl_vfi_continue 
< 0.001 
    107 
 201
    it_iter = it_iter + 1; 
 202 

 203 
    %% Iterate over a and z states
 204 
    % Solve Problem if Default: coh < borr_bound, then: u(c_min) + EV(a'=0,z')
 205 
    % these are the values for defaulter
 206 
    % if do not borrow enough to pay debt: a'=0
 207 
    % if save more than what you have: a'=0
 208 
    % if borrowing, possible that all ar_val_cur values are
 209 
    % u(cmin), when that is the case, utility equal to
 210 
    % c_min, this means given current choice set, can only default,
 211 
    % get utility at c_min level, and a' go to 0. See
 212 
    % maximization over loop 3 choices for loop 1+2 states. see
 213 
    % <https://fanwangecon.github.io/CodeDynaAsset/docs/README_cminymin_borrsave.html
 214 
    % README_cminymin_borrsave> for additional discussions.
 215 
    %
 216 
    % when default is not allowed, there should be exactly one
 217 
    % element of the state space where we enter this condition.
 218 
    % That is at a_min and z_min. At this point, the natural
 219 
    % borrowing constraint when fully used up leads to c = 0.
 220 
    % When default is not allowed, this is the limiting case.
 221 
    % We solve the limiting case as if default is possible. see
 222 
    % the figure here:
 223 
    % <https://fanwangecon.github.io/CodeDynaAsset/m_az/test/ffs_abz_get_funcgrid/test_borr/html/ffs_abz_get_funcgrid_nodefault.html
 224 
    % ffs_abz_get_funcgrid_nodefault>. For the figure under
 225 
    % section *Generate Borrowing A Grid with Default*, there are
 226 
    % no points to the lower right of the red horizontal and
 227 
    % verticle lines, because no default is allowed. But the
 228 
    % intersection of the two red lines is this limiting case
 229 
    % where c = 0.
 230 
    %
 231 
    % when default is allowed. See again
 232 
    % <https://fanwangecon.github.io/CodeDynaAsset/m_az/test/ffs_abz_get_funcgrid/test_borr/html/ffs_abz_get_funcgrid_nodefault.html
 233 
    % ffs_abz_get_funcgrid_nodefault>. Again under section
 234 
    % *Generate Borrowing A Grid with Default*, see that there
 235 
    % is an area to the right bottom of the two blue horizontal
 236 
    % and vertical lines. For all points in that area, the
 237 
    % household has to default, they can not borrow even at max
 238 
    % to get positive consumption. At other points higher than
 239 
    % the blue horizontal line, it is also possible for
 240 
    % defaulting to be the optimal choice.
 241 

 242 
    % loop 1: over exogenous states
< 0.001 
    107 
 243
    for it_z_i = 1:it_z_n 
 244 
        
 245 
        % Current Shock
< 0.001 
   1605 
 246
        fl_z_r_borr = ar_z_r_borr_mesh_wage(it_z_i); 
< 0.001 
   1605 
 247
        fl_z_wage = ar_z_wage_mesh_r_borr(it_z_i); 
 248 

 249 
        % loop 2: over endogenous states
< 0.001 
   1605 
 250
        for it_a_j = 1:length(ar_a) 
  0.005 
 120375 
 251
            fl_a = ar_a(it_a_j); 
  0.097 
 120375 
 252
            ar_val_cur = zeros(size(ar_a)); 
 253 

 254 
            % calculate cash on hand
  0.230 
 120375 
 255
            fl_coh = f_coh(fl_z_r_borr, fl_z_wage, fl_a); 
 256 
            
 257 
            % loop 3: over choices
  0.006 
 120375 
 258
            for it_ap_k = 1:length(ar_a) 
 259 

 260 
                % get next period asset choice
  0.310 
9028125 
 261
                fl_ap = ar_a(it_ap_k); 
 262 

 263 
                % calculate consumption
 13.603 
9028125 
 264
                fl_c = f_cons_coh(fl_coh, fl_ap); 
 265 

 266 
                % assign u(c)
  0.314 
9028125 
 267
                if (fl_c <= fl_c_min) 
  0.149 
4295836 
 268
                    if (bl_default) 
 269 
                        % defaults
 270 
                        % current utility: only today u(cmin)
  0.149 
4295836 
 271
                        ar_val_cur(it_ap_k) = fl_u_cmin; 
 272 
                        % transition out next period, debt wiped out
  0.168 
4295836 
 273
                        for it_az_q = 1:it_z_n 
 57.845 
64437540 
 274
                            ar_val_cur(it_ap_k) = ar_val_cur(it_ap_k) + ... 
64437540 
 275
                                fl_beta*mt_z_trans(it_z_i, it_az_q)*mt_val_cur((ar_a == fl_default_aprime), it_az_q); 
  2.316 
64437540 
 276
                        end 
 277 
                    else
 278 
                        % if default is not allowed: v = fl_nan_replace
 279 
                        ar_val_cur(it_ap_k) = fl_nan_replace;
  0.170 
4295836 
 280
                    end 
  0.156 
4732289 
 281
                else 
 282 
                    % Solve Optimization Problem: max_{a'} u(c(a,a',z)) + beta*EV(a',z')
 283 
                    % borrowed enough to pay debt (and borrowing limit not exceeded)
 284 
                    % saved only the coh available.
 285 
                    % current utility
  0.168 
4732289 
 286
                    if (fl_crra == 1) 
 287 
                        ar_val_cur(it_ap_k) = f_util_log(fl_c);
  0.159 
4732289 
 288
                    else 
  7.463 
4732289 
 289
                        ar_val_cur(it_ap_k) = f_util_crra(fl_c); 
  0.145 
4732289 
 290
                    end 
 291 
                    % loop 4: add future utility, integration--loop over future shocks
  0.180 
4732289 
 292
                    for it_az_q = 1:it_z_n 
  4.701 
70984335 
 293
                        ar_val_cur(it_ap_k) = ar_val_cur(it_ap_k) + ... 
70984335 
 294
                            fl_beta*mt_z_trans(it_z_i, it_az_q)*mt_val_cur(it_ap_k, it_az_q); 
  2.187 
70984335 
 295
                    end 
  0.311 
9028125 
 296
                end 
  0.333 
9028125 
 297
            end 
 298 

 299 
            % optimal choice value
  0.236 
 120375 
 300
            [fl_opti_val_z, fl_opti_idx_z] = max(ar_val_cur); 
  0.006 
 120375 
 301
            fl_opti_aprime_z = ar_a(fl_opti_idx_z); 
  0.210 
 120375 
 302
            fl_opti_c_z = f_cons_coh(fl_coh, fl_opti_aprime_z); 
 303 

 304 
            % Handle Default is optimal or not
  0.005 
 120375 
 305
            if (fl_opti_c_z <= fl_c_min) 
< 0.001 
    963 
 306
                if (bl_default) 
 307 
                    % if defaulting is optimal choice, at these states, not required
 308 
                    % to default, non-default possible, but default could be optimal
< 0.001 
    963 
 309
                    fl_opti_aprime_z = fl_default_aprime; 
 310 
                else
 311 
                    % if default is not allowed, then next period same state as now
 312 
                    % this is absorbing state, this is the limiting case, single
 313 
                    % state space point, lowest a and lowest shock has this.
 314 
                    fl_opti_aprime_z = fl_a;
< 0.001 
    963 
 315
                end 
< 0.001 
    963 
 316
            end 
 317 

 318 
            %% Store Optimal Choices and Value Given(a,z)
  0.006 
 120375 
 319
            mt_val(it_a_j,it_z_i) = fl_opti_val_z; 
  0.006 
 120375 
 320
            mt_pol_a(it_a_j,it_z_i) = fl_opti_aprime_z; 
 321 

  0.006 
 120375 
 322
        end 
  0.001 
   1605 
 323
    end 
 324 

 325 
    %% Check Tolerance and Continuation
 326 

 327 
    % Difference across iterations
  0.009 
    107 
 328
    ar_val_diff_norm(it_iter) = norm(mt_val - mt_val_cur); 
  0.004 
    107 
 329
    ar_pol_diff_norm(it_iter) = norm(mt_pol_a - mt_pol_a_cur); 
  0.001 
    107 
 330
    mt_pol_perc_change(it_iter, :) = sum((mt_pol_a ~= mt_pol_a_cur))/(it_a_n); 
 331 

 332 
    % Update
< 0.001 
    107 
 333
    mt_val_cur = mt_val; 
< 0.001 
    107 
 334
    mt_pol_a_cur = mt_pol_a; 
 335 

 336 
    % Print Iteration Results
< 0.001 
    107 
 337
    if (bl_display && (rem(it_iter, it_display_every)==0)) 
 338 
        fprintf('VAL it_iter:%d, fl_diff:%d, fl_diff_pol:%d\n', ...
 339 
            it_iter, ar_val_diff_norm(it_iter), ar_pol_diff_norm(it_iter));
 340 
        tb_valpol_iter = array2table([mean(mt_val_cur,1); mean(mt_pol_a_cur,1); ...
 341 
            mt_val_cur(it_a_n,:); mt_pol_a_cur(it_a_n,:)]);
 342 
        tb_valpol_iter.Properties.VariableNames = strcat('z', string((1:size(mt_val_cur,2))));
 343 
        tb_valpol_iter.Properties.RowNames = {'mval', 'map', 'Hval', 'Hap'};
 344 
        disp('mval = mean(mt_val_cur,1), average value over a')
 345 
        disp('map  = mean(mt_pol_a_cur,1), average choice over a')
 346 
        disp('Hval = mt_val_cur(it_a_n,:), highest a state val')
 347 
        disp('Hap = mt_pol_a_cur(it_a_n,:), highest a state choice')
 348 
        disp(tb_valpol_iter);
 349 
    end
 350 

 351 
    % Continuation Conditions:
 352 
    % 1. if value function convergence criteria reached
 353 
    % 2. if policy function variation over iterations is less than
 354 
    % threshold
< 0.001 
    107 
 355
    if (it_iter == (it_maxiter_val + 1)) 
< 0.001 
      1 
 356
        bl_vfi_continue = false; 
  0.001 
    106 
 357
    elseif ((it_iter == it_maxiter_val) || ... 
    106 
 358
            (ar_val_diff_norm(it_iter) < fl_tol_val) || ... 
    106 
 359
            (sum(ar_pol_diff_norm(max(1, it_iter-it_tol_pol_nochange):it_iter)) < fl_tol_pol)) 
 360 
        % Fix to max, run again to save results if needed
< 0.001 
      1 
 361
        it_iter_last = it_iter; 
< 0.001 
      1 
 362
        it_iter = it_maxiter_val; 
< 0.001 
      1 
 363
    end 
 364 

< 0.001 
    107 
 365
end 
 366 

 367 
% End Timer
< 0.001 
      1 
 368
if (bl_time) 
< 0.001 
      1 
 369
    toc; 
< 0.001 
      1 
 370
end 
 371 

 372 
% End Profile
< 0.001 
      1 
 373
if (bl_profile) 
  0.004 
      1 
 374
    profile off 
 375 
    profile viewer
 376 
    st_file_name = [st_profile_prefix st_profile_name_main st_profile_suffix];
 377 
    profsave(profile('info'), strcat(st_profile_path, st_file_name));
 378 
end
 379 

 380 
%% Process Optimal Choices
 381 

 382 
result_map = containers.Map('KeyType','char', 'ValueType','any');
 383 
result_map('mt_val') = mt_val;
 384 

 385 
result_map('cl_mt_coh') = {f_coh(ar_z_r_borr_mesh_wage, ar_z_wage_mesh_r_borr, ar_a'), zeros(1)};
 386 
result_map('cl_mt_pol_a') = {mt_pol_a, zeros(1)};
 387 
result_map('cl_mt_pol_c') = {f_cons_checkcmin(ar_z_r_borr_mesh_wage, ar_z_wage_mesh_r_borr, ar_a', mt_pol_a), zeros(1)};
 388 
result_map('ar_st_pol_names') = ["cl_mt_pol_a", "cl_mt_coh", "cl_mt_pol_c"];
 389 

 390 
if (bl_post)
 391 
    bl_input_override = true;
 392 
    result_map('ar_val_diff_norm') = ar_val_diff_norm(1:it_iter_last);
 393 
    result_map('ar_pol_diff_norm') = ar_pol_diff_norm(1:it_iter_last);
 394 
    result_map('mt_pol_perc_change') = mt_pol_perc_change(1:it_iter_last, :);
 395 
    result_map = ff_az_vf_post(param_map, support_map, armt_map, func_map, result_map, bl_input_override);
 396 
end
 397 

 398 
end

Other subfunctions in this file are not included in this listing.