def feasible(f_params, bvec_guess): b_cnstr=np.zeros(bvec_guess.shape,dtype=bool) nvec, A, alpha, delta, bq_distr, beta=f_params K,K_cnstr=utils.get_K(bvec_guess) L=utils.get_L(nvec) r=utils.get_r(K,L,(A, alpha, delta)) w=utils.get_w(K,L,(A, alpha)) c_vec,c_cnstr=utils.get_cvec_ss(r, w, bvec_guess, nvec, bq_distr) b_cnstr[0]=c_cnstr[0] b_cnstr[-1]=c_cnstr[-1] for k in range(1,len(c_cnstr)-1): b_cnstr[k]=c_cnstr[k] b_cnstr[k-1]=b_cnstr[k] return b_cnstr, c_cnstr, K_cnstr
def get_SS(params, bvec_guess, SS_graphs): start_time = time.clock() beta, sigma, nvec, L, A, alpha, delta, SS_tol, bq_distr, chi = params f_params = (nvec, A, alpha, delta, bq_distr, beta) b1_cnstr, c1_cnstr, K1_cnstr = feasible(f_params, bvec_guess) try: if b1_cnstr.max() or c1_cnstr.max() or K1_cnstr.max(): raise cstError else: # errors=zero_func(bvec_guess,beta, sigma, nvec, L, A, alpha, delta) b = opt.root(utils.EulerSys_ss, bvec_guess, args=(beta, sigma, nvec, A, alpha, delta, bq_distr,chi), tol=SS_tol) except cstError: print ('Did not pass the feasible test') if b.success: b_ss = b.x # iterations=b.nit K_ss, K_cnstr = utils.get_K(b_ss) L=utils.get_L(nvec) w_ss = utils.get_w(K_ss,L, (A, alpha)) r_ss = utils.get_r(K_ss, L, (A, alpha, delta)) Y_ss = utils.get_Y(K_ss, L, (A, alpha)) c_ss, c_cnstr = utils.get_cvec_ss(r_ss, w_ss, b_ss, nvec, bq_distr) EulErr_ss = utils.EulerSys_ss(b_ss, beta, sigma, nvec, A, alpha, delta, bq_distr, chi) C_ss=utils.get_C(c_ss) RCerr_ss = Y_ss - C_ss - delta * K_ss BQ_ss = b_ss[-1] ss_time = time.clock() - start_time ss_output={'b_ss': b_ss, 'c_ss': c_ss, 'w_ss': w_ss, 'r_ss': r_ss, 'K_ss': K_ss, 'Y_ss': Y_ss, 'C_ss': C_ss, 'EulErr_ss': EulErr_ss, 'RCerr_ss': RCerr_ss, 'BQ_ss': BQ_ss, 'ss_time': ss_time} # print('\n Savings: \t\t\t {} \n Capital and Labor: \t\t {} \n Wage and Interest rate: \t {} \n Consumption: \t\t\t {}'.format( # b_ss, np.array([K_ss, L]), np.array([w_ss, r_ss]), c_ss)) # # print('Euler errors: ', EulErr_ss) # print('Resource Constraint error: ', RCerr_ss) # print('Time needed: ', ss_time) # print ('It took {iterations} iterations to get the solution.') if SS_graphs: cur_path = os.path.split(os.path.abspath(__file__))[0] output_fldr = "images" output_dir = os.path.join(cur_path, output_fldr) if not os.access(output_dir, os.F_OK): os.makedirs(output_dir) age = np.arange(21, 101) fig, ax = plt.subplots() plt.plot(age, c_ss, marker='D', label='Consumption') plt.plot(age, b_ss, marker='D', label='Savings') minorLocator = MultipleLocator(1) ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Steady-state consumption and savings') plt.xlabel('Age') plt.ylabel('Consumption units') plt.legend() output_path = os.path.join(output_dir, 'ss_bc') plt.savefig(output_path) # plt.show() plt.close() return ss_output
def get_TPI(b1vec, c1_guess, ss_params, params): t1=time.time() # r_guess: guess of interest rate path from period 1 to T1 beta, sigma, S, l_ub, b, upsilon, chi, A, alpha, delta, tpi_max_iter, tpi_tol, xi_tpi, T1, T2 = params r_ss, w_ss, c_ss, n_ss, b_ss, K_ss, L_ss = ss_params abs_tpi = 1 tpi_iter = 0 rpath_old = np.zeros(T2 + S - 1) rpath_old[:T1] = get_path(r_ss, r_ss, T1, 'quadratic') rpath_old[T1:] = r_ss while abs_tpi > tpi_tol and tpi_iter < tpi_max_iter: tpi_iter += 1 wpath_old = utils.get_w(rpath_old, (A, alpha, delta)) bmat = np.zeros((S, T2 + S - 1)) bmat[:, 0] = b1vec bmat[:, T2:] = numpy.matlib.repmat(b_ss, S - 1, 1).T nmat = np.zeros((S, T2 + S - 1)) nmat[:, T2:] = numpy.matlib.repmat(n_ss, S - 1, 1).T cmat = np.zeros((S, T2 + S - 1)) cmat[:, T2:] = numpy.matlib.repmat(c_ss, S-1, 1).T # Solve the incomplete remaining lifetime decisions of agents alive # in period t=1 but not born in period t=1 for p in range(S): # p is remaining periods of life c1_args = (rpath_old[:p + 1], wpath_old[:p + 1], beta, sigma, l_ub, b, upsilon, p + 1, b1vec[S - p - 1], chi[S-p-1:]) result_c1 = opt.root(utils.get_b_last, c1_guess, args = (c1_args)) if result_c1.success: c1 = result_c1.x else: raise ValueError("failed to find an appropriate initial consumption") # Calculate aggregate supplies for capital and labor cvec = utils.get_c(c1, rpath_old[:p + 1], beta, sigma, p + 1) # print (np.shape(cvec)) nvec = utils.get_n(cvec, sigma, l_ub, b, upsilon, wpath_old[:p + 1], p + 1,chi[S-p-1:]) bvec = utils.get_b(cvec, nvec, rpath_old[:p + 1], wpath_old[:p + 1], p + 1, bs = b1vec[S - p - 1])[1:] # Insert the vector lifetime solutions diagonally (twist donut) DiagMaskbp = np.eye(p) bp_path = DiagMaskbp * bvec bmat[S - p:, 1:p + 1] += bp_path DiagMasknp = np.eye(p + 1) np_path = DiagMasknp * nvec nmat[S - p - 1:, :p + 1] += np_path DiagMasknp = np.eye(p + 1) c_path = DiagMasknp * cvec cmat[S - p - 1:, :p + 1] += c_path # Solve for complete lifetime decisions of agents born in periods # 1 to T2 and insert the vector lifetime solutions diagonally (twist # donut) into the cpath, bpath, and EulErrPath matrices for t in range(1, T2): c1_args = (rpath_old[t: S + t], wpath_old[t: S + t], beta, sigma, l_ub, b, upsilon, S, 0.0, chi) result_c1 = opt.root(utils.get_b_last, c1_guess, args = (c1_args)) if result_c1.success: c1 = result_c1.x else: raise ValueError("failed to find an appropriate initial consumption") # Calculate aggregate supplies for capital and labor cvec = utils.get_c(c1, rpath_old[t : S + t], beta, sigma, S) nvec = utils.get_n(cvec, sigma, l_ub, b, upsilon, wpath_old[t: S + t], S, chi) bvec = utils.get_b(cvec, nvec, rpath_old[t: S + t], wpath_old[t: S + t], S) # print ("nvec,cvec,bvec: {}".format(np.shape(nvec),np.shape(cvec),np.shape(bvec))) DiagMaskbt = np.eye(S) bt_path = DiagMaskbt * bvec bmat[:, t: t + S] += bt_path DiagMasknp = np.eye(S) np_path = DiagMasknp * nvec nmat[:, t: t + S] += np_path DiagMasknp = np.eye(S) c_path = DiagMasknp * cvec cmat[:, t: t + S] += c_path bmat[:, T2:] = np.matlib.repmat(b_ss, S - 1, 1).T nmat[:, T2:] = np.matlib.repmat(n_ss, S - 1, 1).T cmat[:, T2:] = np.matlib.repmat(c_ss, S - 1, 1).T K = utils.get_K(bmat)[0] L = utils.get_L(nmat)[0] Y = utils.get_Y(K, L, (A, alpha)) C = utils.get_C(cmat) rpath_new = utils.get_r(K, L, (A, alpha, delta)) # Calculate the implied capital stock from conjecture and the error abs_tpi = ((rpath_old[:T2] - rpath_new[:T2]) ** 2).sum() # Update guess rpath_old[:T2] = xi_tpi * rpath_new[:T2] + (1 - xi_tpi) * rpath_old[:T2] print('iteration:', tpi_iter, ' squared distance: ', abs_tpi) b_last = abs(bmat[S - 1, :]).max() b_err = abs(utils.get_b_errors(cmat[:, :T2 + S - 1], rpath_old[:T2 + S - 1], beta, sigma)).max() n_err = abs(utils.get_n_errors(nmat[:, :T2 + S - 1], cmat[:, :T2 + S - 1], sigma, l_ub, b, upsilon, wpath_old[:T2 + S - 1], chi[0] * np.ones(T2 + S - 1))).max() cnt_err = abs(Y[:-1] - C[:-1] - K[1:] + (1 - delta) * K[:-1]).max() t2=time.time() t=t2-t1 print (f'It took {t} seconds to solve TPI.') k_first = [k for k in K if abs(k - K_ss) < 0.0001][0] T_1 = np.where(K == k_first)[0][0] return rpath_old, wpath_old, K, L, bmat, nmat, cmat, b_last, b_err, n_err, cnt_err, T_1
def get_TPI(params, b1vec, graphs): start_time = time.clock() S, T, beta, sigma, nvec, L, A, alpha, delta, b_ss, K_ss, C_ss, BQ_ss, \ maxiter_TPI, mindist_TPI, xi, TPI_tol, bq_distr, chi = params K1 = utils.get_K(b1vec)[0] # K: s=1 ~ S, sum of b: s=2 ~ S+1 Kpath_old = np.zeros(T + S) # print (f'K1: {K1}, K_ss: {K_ss}') Kpath_old[:T] = np.linspace(K1, K_ss, T) # Until reaching steady state Kpath_old[T:] = K_ss r = utils.get_r(Kpath_old[0], utils.get_L(nvec), (A, alpha, delta)) BQ1 = (1 + r) * b1vec[-1] BQpath_old = np.zeros(T + S) BQpath_old[:T] = np.linspace(BQ1, BQ_ss, T) # Until reaching steady state BQpath_old[T:] = BQ_ss L = np.sum(nvec) iter_TPI = int(0) abs2 = 10. Kpath_new = Kpath_old.copy() r_params = (A, alpha, delta) w_params = (A, alpha) # cbe_params = (S, T, beta, sigma, nvec, b_ss, TPI_tol, A, alpha, delta, bq_distr, chi) while (iter_TPI < maxiter_TPI) and (abs2 >= mindist_TPI): iter_TPI += 1 # Kpath_init = xi * Kpath_new + (1 - xi) * Kpath_old rpath = utils.get_r(Kpath_old, L, r_params) wpath = utils.get_w(Kpath_old, L, w_params) # cpath, bpath, EulErrPath = get_cbepath(cbe_params, rpath, wpath, b1vec) # b: 2~S+1 bpath = np.append(b1vec.reshape(S, 1), np.zeros((S, T + S - 2)), axis=1) cpath = np.zeros((S, T + S - 1)) EulErrPath = np.zeros((S, T + S - 1)) cpath[S - 1, 0] = ((1 + rpath[0]) * b1vec[S - 2] + wpath[0] * nvec[S - 1]) for p in range(1, S): bvec_guess = np.diagonal(bpath[S - p:, :p]) beg_wealth = bpath[S - p - 1, 0] #beg_wealth, nvec, beta, sigma, wpath, rpath, BQpath, chi, bq_distr args_sol = (beg_wealth, nvec[-p:], beta, sigma, wpath[:p], rpath[:p], BQpath_old[:p], chi[-p:], bq_distr[-p:]) b_sol = opt.root(utils.EulerSys_tpi, bvec_guess, args=(args_sol)).x #rpath, wpath, bvec, nvec, bq, bq_distr cp, c_cnstr_p = utils.get_cvec_tpi(rpath[:p], wpath[:p], np.append(beg_wealth, b_sol), nvec[-p:], BQpath_old[:p], bq_distr[-p:]) # rpath, wpath, bvec, nvec, bq, bq_distr b_err_p = utils.EulerSys_tpi(b_sol, beg_wealth, nvec[-p:], beta, sigma, wpath[:p], rpath[:p], BQpath_old[:p], chi[-p:], bq_distr[-p:]) # Insert the vector lifetime solutions diagonally (twist donut) bp_path = np.eye(p) * b_sol cp_path = np.eye(p) * cp ep_path = np.eye(p) * b_err_p bpath[S - p:, 1:p + 1] += bp_path cpath[S - p:, 1:p + 1] += cp_path EulErrPath[S - p:, 1:p + 1] += ep_path for t in range(1, T): bvec_guess_t = np.diagonal(bpath[:, t - 1:S + t - 1]) args_bt = (0, nvec, beta, sigma, wpath[t - 1:S + t - 1], rpath[t - 1:S + t - 1], BQpath_old[t - 1:S + t - 1], chi, bq_distr) bt = opt.root(utils.EulerSys_tpi, bvec_guess_t, args=(args_bt)).x # print (f'bt.shape: {bt.shape}, ') #np.append(nvec,[0.2]) # rpath, wpath, bvec, nvec, bq, bq_distr ct, c_cnstr_t = utils.get_cvec_tpi(rpath[t - 1:S + t - 1], wpath[t - 1:S + t - 1], np.append([0], bt), nvec, BQpath_old[t - 1:S + t - 1], bq_distr) b_err_t = utils.EulerSys_tpi(bt, 0, nvec, beta, sigma, wpath[t - 1:S + t - 1], rpath[t - 1:S + t - 1], BQpath_old[t - 1:S + t - 1], chi, bq_distr) # DiagMask = np.eye(S) bt_path = np.eye(p + 1) * bt # print(f'bt: {bt.shape}, btpath:{bt_path.shape}, t:{t}, bpath:{bpath.shape}') ct_path = np.eye(p + 1) * ct et_path = np.eye(p + 1) * b_err_t bpath[:, t:S + t] += bt_path cpath[:, t:S + t] += ct_path EulErrPath[:, t:S + t] += et_path Kpath_new = np.zeros(T + S) Kpath_new[:T], Kpath_cnstr = utils.get_K(bpath[:, :T]) Kpath_new[T:] = K_ss * np.ones(S) Kpath_cnstr = np.append(Kpath_cnstr, np.zeros(S, dtype=bool)) Kpath_new[Kpath_cnstr] = 0.1 BQpath_new = np.zeros(T + S) # print (f'Here: bpath {bpath[S, :T].shape}') #, BQ:{BQpath_new.shape}, rpath:{rpath[:T].shape} BQpath_new[:T] = (1 + rpath[:T]) * bpath[-1, :T] BQpath_new[T:] = BQ_ss * np.ones(S) abs2 = (((Kpath_old[:T] - Kpath_new[:T]) / Kpath_old[:T] * 100) ** 2).sum() + \ (((BQpath_old[:T] - BQpath_new[:T]) / BQpath_old[:T] * 100) ** 2).sum() Kpath_old[:T] = xi * Kpath_new[:T] + (1 - xi) * Kpath_old[:T] BQpath_old[:T] = xi * BQpath_new[:T] + (1 - xi) * BQpath_old[:T] print('iter: ', iter_TPI, ', squared pct deviation sum: ', abs2, ',max Eul err: ', np.absolute(EulErrPath).max()) BQ_path = BQpath_old Kpath = Kpath_old Ypath = utils.get_Y(Kpath, L, (A, alpha)) Cpath = np.zeros(Kpath.shape) Cpath[:T - 1] = utils.get_C(cpath[:, :T - 1]) Cpath[T - 1:] = C_ss * np.ones(S + 1) RCerrPath = (Ypath[:-1] - Cpath[:-1] - Kpath[1:] + (1 - delta) * Kpath[:-1]) tpi_time = time.clock() - start_time tpi_output = { 'bpath': bpath, 'cpath': cpath, 'wpath': wpath, 'rpath': rpath, 'Kpath': Kpath_new, 'Ypath': Ypath, 'Cpath': Cpath, 'EulErrPath': EulErrPath, 'RCerrPath': RCerrPath, 'tpi_time': tpi_time, 'BQ_path': BQ_path } # Print TPI computation time print(f'It took {tpi_time} seconds to run.') if graphs: ''' ---------------------------------------------------------------- cur_path = string, path name of current directory output_fldr = string, folder in current path to save files output_dir = string, total path of images folder output_path = string, path of file name of figure to be saved tvec = (T+S-2,) vector, time period vector tgridTm1 = (T-1,) vector, time period vector to T-1 tgridT = (T,) vector, time period vector to T-1 sgrid = (S,) vector, all ages from 1 to S sgrid2 = (S-1,) vector, all ages from 2 to S tmatb = (2, 18) matrix, time periods for all savings decisions ages (S-1) and time periods (T) smatb = (2, 18) matrix, ages for all savings decision ages (S-1) and time periods (T) tmatc = (3, 17) matrix, time periods for all consumption decisions ages (S) and time periods (T-1) smatc = (3, 17) matrix, ages for all consumption decisions ages (S) and time periods (T-1) ---------------------------------------------------------------- ''' # Create directory if images directory does not already exist cur_path = os.path.split(os.path.abspath(__file__))[0] output_fldr = "images" output_dir = os.path.join(cur_path, output_fldr) if not os.access(output_dir, os.F_OK): os.makedirs(output_dir) # Plot time path of aggregate capital stock tvec = np.linspace(1, T + 5, T + 5) minorLocator = MultipleLocator(1) fig, ax = plt.subplots() plt.plot(tvec, Kpath[:T + 5], marker='D') # for the minor ticks, use no labels; default NullFormatter ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Time path for aggregate capital stock K') plt.xlabel(r'Period $t$') plt.ylabel(r'Aggregate capital $K_{t}$') output_path = os.path.join(output_dir, "Kpath") plt.savefig(output_path) # plt.show() # Plot time path of aggregate capital stock tvec = np.linspace(1, T + 5, T + 5) minorLocator = MultipleLocator(1) fig, ax = plt.subplots() plt.plot(tvec, BQ_path[:T + 5], marker='D') # for the minor ticks, use no labels; default NullFormatter ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Time path for bequest BQ') plt.xlabel(r'Period $t$') plt.ylabel(r'Bequest $BQ$') output_path = os.path.join(output_dir, "BQpath") plt.savefig(output_path) # plt.show() # Plot time path of aggregate output (GDP) fig, ax = plt.subplots() plt.plot(tvec, Ypath[:T + 5], marker='D') # for the minor ticks, use no labels; default NullFormatter ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Time path for aggregate output (GDP) Y') plt.xlabel(r'Period $t$') plt.ylabel(r'Aggregate output $Y_{t}$') output_path = os.path.join(output_dir, "Ypath") plt.savefig(output_path) # plt.show() # Plot time path of aggregate consumption fig, ax = plt.subplots() plt.plot(tvec, Cpath[:T + 5], marker='D') # for the minor ticks, use no labels; default NullFormatter ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Time path for aggregate consumption C') plt.xlabel(r'Period $t$') plt.ylabel(r'Aggregate consumption $C_{t}$') output_path = os.path.join(output_dir, "C_aggr_path") plt.savefig(output_path) # plt.show() # Plot time path of real wage fig, ax = plt.subplots() plt.plot(tvec, wpath[:T + 5], marker='D') # for the minor ticks, use no labels; default NullFormatter ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Time path for real wage w') plt.xlabel(r'Period $t$') plt.ylabel(r'Real wage $w_{t}$') output_path = os.path.join(output_dir, "wpath") plt.savefig(output_path) # plt.show() # Plot time path of real interest rate fig, ax = plt.subplots() plt.plot(tvec, rpath[:T + 5], marker='D') # for the minor ticks, use no labels; default NullFormatter ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Time path for real interest rate r') plt.xlabel(r'Period $t$') plt.ylabel(r'Real interest rate $r_{t}$') output_path = os.path.join(output_dir, "rpath") plt.savefig(output_path) # plt.show() # Plot time path of real wage fig, ax = plt.subplots() plt.plot(tvec, bpath[24, :T + 5], marker='D') # for the minor ticks, use no labels; default NullFormatter ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Time path for savings by 25-year-olds $b_{25}$') plt.xlabel(r'Period $t$') plt.ylabel(r'$b_{25}$') output_path = os.path.join(output_dir, "b25path") plt.savefig(output_path) # plt.show() return tpi_output
def get_SS(params, bvec_guess, SS_graphs): start_time = time.clock() beta, sigma, nvec, A, alpha, delta, SS_tol, fert_rates, mort_rates, imm_rates, omega_SS, gn_SS, g_y = params b = opt.root(utils.EulerSys_ss, bvec_guess, args=(beta, sigma, nvec, A, alpha, delta, gn_SS, g_y, omega_SS, mort_rates, imm_rates), tol=SS_tol) if b.success: b_ss = b.x # iterations=b.nit K_ss = utils.get_K(b_ss, omega_SS, gn_SS, imm_rates) L = utils.get_L(nvec, omega_SS) w_ss = utils.get_w(K_ss, L, alpha, A) r_ss = utils.get_r(K_ss, L, alpha, delta, A) Y_ss = utils.get_Y(K_ss, L, (A, alpha)) c_ss, c_cnstr = utils.get_cvec_ss(r_ss, w_ss, b_ss, nvec, gn_SS, g_y, omega_SS, mort_rates) EulErr_ss = utils.EulerSys_ss(b_ss, beta, sigma, nvec, A, alpha, delta, gn_SS, g_y, omega_SS, mort_rates, imm_rates) C_ss = utils.get_C(c_ss, omega_SS) RCerr_ss = Y_ss - C_ss - ( (1 + gn_SS) * np.exp(g_y) - 1 + delta) * K_ss + np.exp(g_y) * ( imm_rates * omega_SS * np.append(b_ss, [0])) BQ_ss = utils.get_BQ(b_ss, r_ss, gn_SS, omega_SS, mort_rates) ss_time = time.clock() - start_time ss_output = { 'b_ss': b_ss, 'c_ss': c_ss, 'w_ss': w_ss, 'r_ss': r_ss, 'K_ss': K_ss, 'Y_ss': Y_ss, 'C_ss': C_ss, 'EulErr_ss': EulErr_ss, 'RCerr_ss': RCerr_ss, 'BQ_ss': BQ_ss, 'ss_time': ss_time } # print('\n Savings: \t\t\t {} \n Capital and Labor: \t\t {} \n Wage and Interest rate: \t {} \n Consumption: \t\t\t {}'.format( # b_ss, np.array([K_ss, L]), np.array([w_ss, r_ss]), c_ss)) # # print('Euler errors: ', EulErr_ss) # print('Resource Constraint error: ', RCerr_ss) # print('Time needed: ', ss_time) # print ('It took {iterations} iterations to get the solution.') if SS_graphs: cur_path = os.path.split(os.path.abspath(__file__))[0] output_fldr = "images_ss_tpi" output_dir = os.path.join(cur_path, output_fldr) if not os.access(output_dir, os.F_OK): os.makedirs(output_dir) age = np.arange(21, 101) fig, ax = plt.subplots() plt.plot(age, c_ss, marker='D', label='Consumption') plt.plot(age, np.append([0], b_ss), marker='D', label='Savings') minorLocator = MultipleLocator(1) ax.xaxis.set_minor_locator(minorLocator) plt.grid(b=True, which='major', color='0.65', linestyle='-') plt.title('Steady-state consumption and savings') plt.xlabel('Age') plt.ylabel('Consumption units') plt.legend() output_path = os.path.join(output_dir, 'ss_bc') plt.savefig(output_path) # plt.show() plt.close() return ss_output