def find_term_structure(self, price, *var_args): ''' Function called by a zero root finder which is used to find the price of a bond that creates equal utility at time 0 as adding .01 to the value of consumption in the final period the purpose of this function is to find the interest rate embedded in the EZ Utility model first calculate the utility with a final payment ''' my_tree = var_args[0] my_damage_model = var_args[1] my_optimization = var_args[2] my_cost_model = var_args[3] time_period = var_args[4] payment = 0.01 # self.my_tree.period_consumption_epsilon[time_period] = 0. # utility_with_payment = fm.utility_function( my_optimization.guess, my_tree, my_damage_model, my_cost_model ) self.my_tree.period_consumption_epsilon[time_period] = payment utility_with_payment = fm.utility_function(my_optimization.guess, my_tree, my_damage_model, my_cost_model) self.my_tree.period_consumption_epsilon[time_period] = 0. # utility_without_payment = fm.utility_function( my_optimization.guess, my_tree, my_damage_model, my_cost_model ) ''' then calculate the utility with an initial payment equal to the final payment discounted to today at the target interest_rate ''' self.my_tree.first_period_epsilon = payment * price utility_with_initial_payment = fm.utility_function( my_optimization.guess, my_tree, my_damage_model, my_cost_model) self.my_tree.first_period_epsilon = 0.0 distance = (utility_with_payment - utility_with_initial_payment) return (distance)
def find_bec(self, delta_con, *var_args): ''' Function called by a zero root finder which is used to find a value for consumption that equalizes utility at time 0 in two different solutions first calculate the utility with a base case ''' my_tree = var_args[0] my_damage_model = var_args[1] my_optimization = var_args[2] my_cost_model = var_args[3] base_case = my_optimization.guess ''' base_case_utility = fm.utility_function( base_case, my_tree, my_damage_model, my_cost_model ) then calculate the utility with the alternative case alternative_case_utility = fm.utility_function( alternative_case, my_tree, my_damage_model, my_cost_model ) distance = (alternative_case_utility - base_case_utility) ''' my_tree.first_period_epsilon = 0.0 base_utility = fm.utility_function(base_case, my_tree, my_damage_model, my_cost_model) my_tree.first_period_epsilon = delta_con new_utility = fm.utility_function(base_case, my_tree, my_damage_model, my_cost_model) my_tree.first_period_epsilon = 0.0 distance = (new_utility - base_utility) - my_optimization.constraint_cost return (distance)
def run_model(tp1=30, tree_analysis=4, tree_final_states=32, damage_peak_temp=11.0, damage_disaster_tail=18.0, draws=50): print('These arguments set in batch mode') #print('growth rate = ', sys.argv[1] # Original 1st parm: print('period_1_years =', sys.argv[1]) #print('analysis =', sys.argv[2] #print('force_simul =', sys.argv[3] #scenario_df = pd.read_excel('./scenarios.xls') #tp1 = scenario_df.parm_value[(scenario_df.parm_name == 'tp1')].values[0] ''' initialize the tree class ''' #self.update_state(state='PROGRESS', # meta={'current': 10, 'total': 98, # 'status': 'Starting model tree'}) # TREE MODEL #Orig: my_tree = tree_model(tp1=int(sys.argv[1])) my_tree = tree_model(tp1=tp1, analysis=tree_analysis, final_states=tree_final_states) #my_tree = tree_model() print('tree nodes', my_tree.decision_period_pointer) print('horizon times', my_tree.decision_times) print('utility periods', my_tree.utility_nperiods) print('utility full_tree', my_tree.utility_full_tree) print('u_times', my_tree.utility_times) print('decision_period?', my_tree.decision_period) print('branch_period?', my_tree.information_period) print('u_first_node', my_tree.utility_period_pointer) print('u_nodes', my_tree.utility_period_nodes) print('u_tree_period', my_tree.utility_decision_period) # The return parameters: delta_emissions_gigatons = None cost_per_ton = None #self.update_state(state='PROGRESS', # meta={'current': 25, 'total': 99, # 'status': 'Finished model tree'}) ''' initialize the damage class ''' # DAMAGE CLASS my_damage_model = damage_model(my_tree=my_tree, peak_temp=damage_peak_temp, disaster_tail=damage_disaster_tail, draws=draws) my_damage_model.damage_function_initialization() ''' initialize the cost class ''' # COST CLASS my_cost_model = cost_model(tree=my_tree) print('economic growth', my_tree.growth, 'risk aversion', my_tree.ra, 'elasticity of intertemporal substitution', my_tree.eis) ''' initialize the tree and damage function interpolation coefficients ''' my_damage_model.initialize_tree() my_damage_model.dfc = my_damage_model.damage_function_interpolation() ''' get the initial parameters and output the parameters and initial fit ''' # = optimize_plan(my_tree=my_tree,randomize=float(sys.argv[2]),alt_input=int(sys.argv[3])) my_optimization = optimize_plan(my_tree=my_tree) if my_tree.nperiods <= 5 : my_optimization.get_initial_guess() else : my_optimization.get_initial_guess6() print("initial guess", my_optimization.guess) base = fm.utility_function( my_optimization.guess, my_tree, my_damage_model, my_cost_model ) print('initial parameter fit', base) ''' numerical derivative check of the gradient ''' base_grad = fm.analytic_utility_gradient(my_optimization.guess, my_tree, my_damage_model, my_cost_model ) delta = .00001 guess = my_optimization.guess for p in tqdm(range(0,my_tree.x_dim)): guess[p] += delta base_plus_p = fm.utility_function( guess, my_tree, my_damage_model, my_cost_model ) num_deriv = (base_plus_p-base)/delta ''' TODO: Determine if this code block can be commented out for production runs. if abs((base_grad[p]-num_deriv)/num_deriv) > .05 : print('CHECK GRADIENT: ','p = ', p, 'derivative calculation = ', base_grad[p], 'numerical derivative = ', num_deriv) if my_optimization.derivative_check == 1 : print('p', p, 'derivative =', base_grad[p], 'numerical derivative', num_deriv) ''' guess[p] -= delta # OPTIMIZE WITH SCIPY ''' if my_tree.analysis == 1 or 2 then find the unconstrained optimal mitigation plan use scipy minimization function fmin_l_bfgs_b to maximize the utility function with respect to choices of mitigation ''' if my_tree.analysis == 1 or my_tree.analysis == 2 : my_optimization.set_constraints(constrain=0) res = fmin_l_bfgs_b( fm.utility_function,guess,fprime=fm.analytic_utility_gradient,factr=1.,pgtol=1.0e-5,bounds=(my_optimization.xbounds),maxfun=600,args=([my_tree, my_damage_model, my_cost_model])) bestfit = res[1] #print('best fit', bestfit) bestparams = res[0] #print('best parameters', bestparams) retparam = res[2] #print('gradient', retparam['grad']) #print('function calls', retparam['funcalls']) else : bestfit = base bestparams = guess ''' if analysis = 1, then the only step is optimization: now print(output to the terminal ''' if my_tree.analysis == 1: my_optimization.create_output(bestparams, bestfit, my_damage_model, my_cost_model, my_tree) if my_tree.nperiods <= 5 : my_optimization.put_optimal_plan(bestparams) else : my_optimization.put_optimal_plan6(bestparams) ''' if my_tree.analysis = 2 then find the decomposition of the social cost of carbon into its risk premium and expected damage components and their decomposition over time first step is to save the initial optimized consumption values for all nodes in the utility tree ( in my_tree.d_consumption_by_state[] ) and the cost component of consumption in decision period 0 (in my_tree.d_cost_by_state), which reflect the costs by node of decision period 0 mitigation this is done so that in the next we can increment the mitigation at time 0 and to calculate the marginal changes in consumption and cost ''' if my_tree.analysis == 2: for node in tqdm(range(0, my_tree.utility_full_tree)): my_tree.d_consumption_by_state[node] = my_tree.consumption_by_state[node] for sub_period in tqdm(range(0, my_tree.first_period_intervals)): potential_consumption = (1.+my_tree.growth)**(my_tree.sub_interval_length * sub_period) my_tree.d_cost_by_state[sub_period,0] = potential_consumption * my_tree.cost_by_state[0] delta_x = .01 bestparams[0] += delta_x ''' next increment time 0 mitigation and run an optimization in which mitigation at time 0 is constained to = previous optimal mitigation + delta_x ''' my_optimization.set_constraints(constrain=-1, node_0 = bestparams[0]) guess = bestparams res = fmin_l_bfgs_b( fm.utility_function,guess,fprime=fm.analytic_utility_gradient,factr=1.,pgtol=1.0e-5,bounds=(my_optimization.xbounds),maxfun=600,args=([my_tree, my_damage_model, my_cost_model])) ''' now calculate the changes in consumption and the mitigation cost component of consumption per unit change in mitigation in the new optimal plan ''' for node in tqdm(range(0, my_tree.utility_full_tree)): my_tree.d_consumption_by_state[node] = (my_tree.consumption_by_state[node]-my_tree.d_consumption_by_state[node])/delta_x for sub_period in tqdm(range(0, my_tree.first_period_intervals)): potential_consumption = (1.+my_tree.growth)**(my_tree.sub_interval_length * sub_period) my_tree.d_cost_by_state[sub_period,1] = ( potential_consumption * my_tree.cost_by_state[0] - my_tree.d_cost_by_state[sub_period,0] )/delta_x bestparams[0] -= delta_x base = fm.utility_function( bestparams, my_tree, my_damage_model, my_cost_model ) ''' create the output, including the decomposition of SCC into the time paths of the net present value contributions from expected damage and risk premium components ''' ''' this section of code addresses the question: what is the cost of waiting to start mitigation untilt the end of the first period if my_tree.analysis == 3, then calculate the marginal cost of carbon when mitigation is delayed for the first period if my_tree.analysis == 4, then calculate the total consumption-equivalent cost of delay (relative to an optimal plan) for the first period ''' if my_tree.analysis >= 3: ''' analysis == 3 calculates the marginal value of reducing emissions by 1 ton when the initial endowment has already been incremented by the payment equal to "lump_sum" base_x is the starting level of mitigation delta_x is used to calculate a numerical derivative of utility as a function of a marginal percentage increase in emissions reduction today while reoptimizing emissions reductions at all decision nodes in the future increment is the amount to increase base_x in each run the ipass loop increments base_x and repeats the analysis ''' base_x = 0.0 delta_x = .01 increment = .025 for ipass in range(0, 1): ''' first step is to calculate the optimal plan when mitigation is constrained to base_x for the first period ''' lump_sum = .0 my_tree.first_period_epsilon = lump_sum my_optimization.set_constraints(constrain=1, node_0 = base_x, node_1 = base_x, node_2 = base_x) res = fmin_l_bfgs_b( fm.utility_function,guess,fprime=fm.analytic_utility_gradient,factr=1.,pgtol=1.0e-5,bounds=(my_optimization.xbounds),maxfun=600,args=([my_tree, my_damage_model, my_cost_model])) ''' save the parameters for the run with mitigation = base_x in baseparams save the utility value in basefit save the cost of emissions reductions when mitigation = base_x in marginal_cost use the optimal mitigation parameters as the starting point for an optiization with first period emissions reduction constrained to be base_x + delta_x ''' baseparams = res[0] basefit = res[1] marginal_cost = my_cost_model.price_by_state( baseparams[0],0.,0.) newparams = baseparams ''' create output for the optimal plan with current mitigation constrained to equal base_x (the optimal plan subject to no action in period 0) ''' my_optimization.create_output(baseparams, basefit, my_damage_model, my_cost_model, my_tree) ''' next calculate the optimal plan when my_tree.analysis = 3 the "optimal" plan is constrained so that current mitigation is equal to base_x + delta_x when my_tree.analysis = 4 the current mitigation is indeed from the unconstrained optimal plan ''' if my_tree.analysis == 3 : #print('base_x', base_x, 'delta_x', delta_x) newparams[0] += delta_x my_tree.first_period_epsilon = lump_sum my_optimization.set_constraints(constrain=1, node_0 = newparams[0], node_1 = newparams[1], node_2 = newparams[2]) res = fmin_l_bfgs_b( fm.utility_function,newparams,fprime=fm.analytic_utility_gradient,factr=1.,pgtol=1.0e-5,bounds=(my_optimization.xbounds),maxfun=600,args=([my_tree, my_damage_model, my_cost_model])) else : my_optimization.set_constraints(constrain=0) res = fmin_l_bfgs_b( fm.utility_function,guess,fprime=fm.analytic_utility_gradient,factr=1.,pgtol=1.0e-5,bounds=(my_optimization.xbounds),maxfun=600,args=([my_tree, my_damage_model, my_cost_model])) ''' save the parameters for the run with mitigation = base_x + delta_x in newparams save the utility value in newfit ''' newparams = res[0] newfit = res[1] ''' create output for the new optimal plan with either a marginal, or a fully optimal mitigation at time 0 ''' my_optimization.create_output(newparams, newfit, my_damage_model, my_cost_model, my_tree) ''' delta_util_x is the change in utility from base_x to base_x + delta_x ''' delta_util_x = newfit - basefit #print('delta_util_x', delta_util_x) if my_tree.analysis == 3: ''' calculate the change in utility for a marginal percentage change in consumption starting from the mitigation plan with time 0 mitigation = base_x the utility_funtion calculates utility for a given mitigation plan, but if my_tree.first_period_epsilon is non-zero, adds the value of first_period_epsilon to first period consumption without changing any other consumption value the marginal impact on utility of a marginal increase in consumption is saved in delta_util_c ''' delta_con = .01 my_tree.first_period_epsilon = lump_sum my_tree.first_period_epsilon += delta_con baseparams[0]= base_x #print('epsilon', my_tree.first_period_epsilon) util_given_delta_con = fm.utility_function( baseparams, my_tree, my_damage_model, my_cost_model ) delta_util_c = util_given_delta_con - basefit my_tree.first_period_epsilon = 0.0 #print('basefit', basefit, 'newfit', newfit, 'util_given_delta', util_given_delta_con) #print('delta_con', delta_con) #print('delta_util_c', delta_util_c) else: delta_x = newparams[0] #print('delta_x', delta_x) # BRENTQ OPTIMIZATION ''' my_optimization.constraint_cost is the utility cost of constraining first period mitigation to zero ''' my_optimization.constraint_cost = newfit-basefit for miti in range(0, my_tree.x_dim): my_optimization.guess[miti] = baseparams[miti] ''' when my_tree.analysis = 4, calculate deadweight loss by using the root finder routine to find a change in consumption that increases the zero mitigation constrained optimization utility to equal that of the unconstrained plan plus a lump sum that is, find delta_con such that [ delta_utility - my_optimization.constraint_cost] = 0 where delta_utility = util(constrained plan, consumption[today] + delta_con)-util(unconstrained plan, consumption[today]) ''' delta_con = brentq( my_optimization.find_bec, -.1, .99, args=( my_tree, my_damage_model, my_optimization, my_cost_model)) #print('delta consumption to match unconstrained optimal plan', delta_con) cost_per_ton = my_cost_model.consperton0 if my_tree.analysis == 3: #print('Marginal cost of emissions reduction at x = ', base_x, 'is', marginal_cost) ''' my_cost_model.consperton0 is consumption in $ today per ton of emissions so the marginal benefit is the slope of the utility function wrt x / slope of the utility function wrt c * ($ consumption / ton of emissions) ''' #print('Marginal benefit emissions reduction is', (delta_util_x / delta_util_c ) * delta_con * my_cost_model.consperton0 / delta_x) base_x += increment else : #print('delta_consumption_billions', delta_con * my_cost_model.consperton0 * my_tree.bau_emit_level[0]) delta_emissions_gigatons = delta_x * my_tree.bau_emit_level[0] #print('delta_emissions_gigatons', delta_emissions_gigatons) deadweight = delta_con * my_cost_model.consperton0 / delta_x #print('Deadweight $ loss of consumption per year per ton of mitigation of not pricing carbon in period 0', deadweight) ''' calculate the marginal utility at time 0 of state contingent increases in consumption at each node ''' #print(' marginal utility at time 0 of state contingent increases in consumption at each node ') delta_con = .01 base_util = fm.utility_function( bestparams, my_tree, my_damage_model, my_cost_model ) #print(' base utility ', base_util) for time_period in range(0, my_tree.utility_nperiods-1): is_tree_node = my_tree.decision_period[ time_period] tree_period = my_tree.utility_decision_period[time_period] tree_node = my_tree.decision_period_pointer[ tree_period ] if is_tree_node == 1: first_u_node = my_tree.utility_period_pointer[time_period] for period_node in tqdm(range(0, my_tree.utility_period_nodes[time_period])): my_tree.node_consumption_epsilon[first_u_node+period_node] = delta_con new_util = fm.utility_function( bestparams, my_tree, my_damage_model, my_cost_model ) my_tree.node_consumption_epsilon[first_u_node+period_node] = 0.0 marginal_utility = (base_util - new_util) / delta_con #r[tree_period] = {'year':2015+my_tree.utility_times[time_period]} #print(' period ', tree_period, ' year ', 2015+my_tree.utility_times[time_period], ' node ', tree_node+period_node, ' utility', new_util, ' marginal_utility ', marginal_utility ) return cost_per_ton, delta_emissions_gigatons '''
def create_output(self, best_mitigation_plan, best_fit, my_damage_model, my_cost_model, my_tree): ''' writes the output of the optimization to the console Parameters ---------- best_mitigation_plan : float vector of optimal degrees of mitigation best_fit : float the value of the utility function at the best_mitigation_plan my_damage_model : damage_class object the damage model used in the optimization my_cost_model : cost_class object the cost model used in the optimization ''' ''' TODO: Determine if these print statements are required for production runs. if my_tree.analysis >= 1 : if my_tree.print_options[0] == 1: print('Print_Option[0] Maximized_utility_=', -best_fit) if my_tree.analysis >= 1 : if my_tree.print_options[1] == 1: print('Print_Option[1] Optimized_mitigation_plan_=') lines = int(my_tree.x_dim/6)-1 for line in range(0, lines) : for i in range(0,5) : print(best_mitigation_plan[line*6+i]) #, print(best_mitigation_plan[line*6+5]) for i in range(lines*6,my_tree.x_dim-1) : print(best_mitigation_plan[i]) #, print(best_mitigation_plan[my_tree.x_dim-1]) ''' price = my_cost_model.price_by_state(best_mitigation_plan[0], 0., 0.) if my_tree.analysis >= 1: if my_tree.print_options[2] == 1: log.log_it('Print_Option[2] Social_Cost_of_Carbon_=%f' % price) if my_tree.print_options[3] == 1: log.log_it("dlw_optimize: my_tree.print_options[3] == 1") emissions_to_bau = my_tree.emissions_to_ghg[ my_tree.nperiods - 1] / my_tree.emissions_per_period[my_tree.nperiods - 1] bau_path = 400 for p in tqdm(range(0, my_tree.nperiods)): ave_price = 0. first_node = my_tree.decision_period_pointer[p] this_period_time = my_tree.decision_times[ p + 1] - my_tree.decision_times[p] for n in range(0, my_tree.decision_nodes[p]): average_mitigation = best_mitigation_plan[ 0] * my_tree.decision_times[1] for pp in range(1, p): j = int(n / 2**(p - pp)) average_mitigation += best_mitigation_plan[ my_tree.node_map[pp - 1][my_tree.node_mapping[ pp - 1][j][0]]] * (my_tree.decision_times[pp + 1] - my_tree.decision_times[pp]) price = my_cost_model.g * my_cost_model.a * best_mitigation_plan[ first_node + n]**(my_cost_model.a - 1.) if p == 0: consump = 1. average_mitigation = 0. else: consump = self.my_tree.potential_consumption[p] average_mitigation = average_mitigation / my_tree.decision_times[ p] price = my_cost_model.price_by_state( best_mitigation_plan[first_node + n], average_mitigation, my_tree.decision_times[p]) ave_price += my_tree.node_probs[first_node + n] * price average_mitigation = my_tree.ave_mitigation[first_node + n] average_emissions = my_tree.additional_emissions_by_state[ first_node + n] / (this_period_time * emissions_to_bau) #print('Print_Option[3] Period', p, 'time', int(2015+my_tree.decision_times[p]), 'node', first_node+n, 'has_prob', my_tree.node_probs[first_node+n], 'Emission_mitigation_of', best_mitigation_plan[first_node+n], 'Price', price, 'Consumption', consump*(1.-my_tree.damage_by_state[first_node+n])*(1.-my_tree.cost_by_state[first_node+n]),'Average_mitigation', average_mitigation, 'Cost', my_tree.cost_by_state[first_node+n], 'Damage', my_tree.damage_by_state[first_node+n],' GHG_level_in_state', my_tree.ghg_by_state[first_node+n], 'Average_annual_emissions_in_state', average_emissions) bau_path += emissions_to_bau * my_tree.emissions_per_period[p] #print('Print_Option[3] Period', p, 'time', int(2015+my_tree.decision_times[p]), 'Average_price', ave_price, 'BAU_average_annual_emissions_in_period', my_tree.emissions_per_period[p]/this_period_time, 'end_of_period_bau_ghg_level', bau_path) #print('Print_Option[3] Final_period_consumption_and_damage') ''' TODO: Determine if these print statements are required for production runs. for state in range(0, my_tree.final_states): print('Print_Option[3] Period', my_tree.nperiods, 'time', int(2015+my_tree.decision_times[my_tree.nperiods]), 'final_state', state, 'consumption', my_tree.potential_consumption[my_tree.nperiods]*(1.-my_tree.final_damage_by_state[state]), 'forward_damage', my_tree.final_damage_by_state[state]) ''' ''' use root finder and the function "find_term_structure" to find the bond price (and yield) that reflects the value of a fixed $1 payment in all nodes at time np ''' from scipy.optimize import brentq np = my_tree.utility_nperiods - 2 utility = fm.utility_function(best_mitigation_plan, self.my_tree, my_damage_model, my_cost_model) res = brentq(self.find_term_structure, 0., .9999, args=(my_tree, my_damage_model, self, my_cost_model, np)) res = max(.00000000001, res) my_tree.discount_prices[np] = res years_to_maturity = my_tree.utility_times[np] if my_tree.print_options[4] == 1: log.log_it("dlw_optimize: my_tree.print_options[4] == 1") log.log_it( 'Print_Option[4] Zero_coupon_bond_maturing_at_time %i has_price_=%f and_yield_=%f' % ((2015 + 5 * np), res, 100. * (1. / (res**(1. / years_to_maturity)) - 1.))) ''' output for the decomposition of SCC into expected damage and risk premium ''' # TODO: Determine if/how 'utility' and 'base_grad' should be used. utility = fm.utility_function(best_mitigation_plan, self.my_tree, my_damage_model, my_cost_model) base_grad = fm.analytic_utility_gradient(best_mitigation_plan, self.my_tree, my_damage_model, my_cost_model) d_cost_sum = 0.0 discounted_expected_damages = 0.0 net_discounted_expected_damages = 0.0 risk_premium = 0.0 if my_tree.analysis == 2: consumption_cost = my_tree.d_consumption_by_state[0] if my_tree.print_options[5] == 1: log.log_it('Print_Option[5] Period_0_delta_consumption: %f' % consumption_cost) if my_tree.print_options[6] == 1: log.log_it( 'Print_Option[6] Period_0_marginal_utility_wrt_c(0): %i Period_0_marginal_utility_wrt_c(node1)_up_node: %i Period_0_marginal_utility_wrt_c(node2)_down_node: %i' % (my_tree.marginal_utility_by_state[0][0], my_tree.marginal_utility_by_state[0][1], my_tree.marginal_utility_by_state[0][2])) ''' in this loop calculate and print the expected damage and the risk premium term at each point in time ''' my_tree.sdf_in_tree[0] = 1.0 log.log_it("dlw_optimize: my_tree.analysis == 2") for time_period in tqdm(range(1, self.my_tree.utility_nperiods)): ''' for a given time_period in the utility_tree, tree_node points to the first node in the period of the last decision in the decision tree''' tree_node = my_tree.decision_period_pointer[min( my_tree.nperiods - 1, my_tree.utility_decision_period[time_period - 1] + 1)] ''' first_node points to the first node in period time_period of the utility tree ''' first_node = my_tree.utility_period_pointer[time_period] expected_damages = 0.0 expected_sdf = 0.0 cross_product_sdf_damages = 0.0 ''' Now loop over all the nodes in the utility tree in period time_period find the expected damages, the expected Stochastic Discount Factor, and the Covariance term ''' for period_node in range( 0, my_tree.utility_period_nodes[time_period]): ''' d_consumption_by_state holds the change in consumption per unit change in emissions mitigation in all periods except the first this "damage_in_node" equals the damage in the node caused by additional emissions note however, in the first period this change also reflects the change in cost, the d_cost term, from reducing the mitigation calculate the expected damage by probability weighting the damage in each node ''' damage_in_node = ( my_tree.d_consumption_by_state[first_node + period_node]) expected_damages += damage_in_node * my_tree.node_probs[ tree_node + period_node] ''' TODO: Determine if these print statements are required for production runs. if time_period <= my_tree.print_options[7] : # if this is a risk decomposition and utility sub-interval output is desired. print('Print_Option[7] Period', time_period, 'node', first_node+period_node, 'delta_consumption', my_tree.d_consumption_by_state[first_node+period_node], ' consumption_level', my_tree.consumption_by_state[first_node+period_node]) ''' # from_node is the node from the previous period that leads to period_node. if my_tree.information_period[time_period - 1] == 1: from_node = my_tree.utility_period_pointer[ time_period - 1] + int(period_node / 2) else: from_node = my_tree.utility_period_pointer[ time_period - 1] + period_node if my_tree.information_period[time_period - 1] == 1: ''' information_period = 1 when there is a branch in the tree first if (even values of period_node) considers upper branch nodes, second if considers lower branch nodes ''' if int(period_node / 2) * 2 == period_node: ''' total_prob is the sum of the probabilities in the up and down nodes reached from "from_node" ''' total_prob = my_tree.node_probs[ tree_node + period_node] + my_tree.node_probs[ tree_node + period_node + 1] ''' sdf is the stochastic discount factor required to discount consumption in period_node back to from_node ''' sdf = ( total_prob / my_tree.node_probs[tree_node + period_node] ) * my_tree.marginal_utility_by_state[from_node][ 1] / my_tree.marginal_utility_by_state[ from_node][0] ''' TODO: Determine if these print statements are required for production runs. if time_period <= my_tree.print_options[7] : print('Print_Option[7] Branch_from_node', from_node, 'MU_wrt_c(t)', my_tree.marginal_utility_by_state[from_node][0]) print('Print_Option[7] Node:', tree_node+period_node,'SDF',sdf,'MU_wrt_c(t+1)_in_node', my_tree.marginal_utility_by_state[from_node][1]) print('Print_Option[7] Probs:_up_node', tree_node+period_node, 'down_node', tree_node+period_node+1, 'probs', my_tree.node_probs[tree_node+period_node], my_tree.node_probs[tree_node+period_node+1]) ''' else: total_prob = my_tree.node_probs[ tree_node + period_node] + my_tree.node_probs[ tree_node + period_node - 1] sdf = ( total_prob / my_tree.node_probs[tree_node + period_node] ) * my_tree.marginal_utility_by_state[from_node][ 2] / my_tree.marginal_utility_by_state[ from_node][0] ''' TODO: Determine if these print statements are required for production runs. if time_period <= my_tree.print_options[7] : print('Print_Option[7] Branch_from_node', from_node, 'MU_wrt_c(t)', my_tree.marginal_utility_by_state[from_node][0]) print('Print_Option[7] Node:', tree_node+period_node, 'SDF', sdf, 'MU_wrt_c(t+1)_in_node', my_tree.marginal_utility_by_state[from_node][2], 'MU_wrt_c(t)', my_tree.marginal_utility_by_state[from_node][0]) print('Print_Option[7] Probs:_down_node', tree_node+period_node, 'up_node', tree_node+period_node-1, 'probs', my_tree.node_probs[tree_node+period_node], my_tree.node_probs[tree_node+period_node-1]) ''' else: # if no branching occurs this period then the probability of reaching period_node is 1 sdf = my_tree.marginal_utility_by_state[from_node][ 1] / my_tree.marginal_utility_by_state[from_node][0] mu_next = my_tree.marginal_utility_by_state[from_node][ 1] # in the final period the marginal utility is with respect to the steady-state continuation value if time_period == my_tree.utility_nperiods - 1: sdf = my_tree.final_total_derivative_term[ period_node] / my_tree.marginal_utility_by_state[ from_node][0] mu_next = my_tree.final_total_derivative_term[ period_node] ''' TODO: Determine if these print statements are required for production runs. if time_period <= my_tree.print_options[7] : print('Print_Option[7] No_branch_from_node', from_node, 'SDF', sdf, 'MU_wrt_c(t)', my_tree.marginal_utility_by_state[from_node][0], 'MU_wrt_c(t+1)_in_next_node', mu_next) ''' # sdf_in_tree is the discount factor used to present value consumption in node period_node (at time time_period) my_tree.sdf_in_tree[ first_node + period_node] = my_tree.sdf_in_tree[from_node] * sdf # the expected_sdf is the probability weighted discount factors in the nodes at time time_period expected_sdf += my_tree.sdf_in_tree[ first_node + period_node] * my_tree.node_probs[tree_node + period_node] # cross_product_sdf_damages is the expected cross_product of sdf's and damages at time time_period cross_product_sdf_damages += my_tree.sdf_in_tree[ first_node + period_node] * damage_in_node * my_tree.node_probs[ tree_node + period_node] # store the expected sdf, which is the present value at time 0 of $1 received at time time_period. my_tree.discount_prices[time_period] = expected_sdf # cov_term is the cross_product minus the product of the expected SDF and the expected damage at time time_period. cov_term = cross_product_sdf_damages - expected_sdf * expected_damages ''' now calculated the components of net discounted damage and the risk premium which arise in period time_period per $ spent on mitigation at time 0 (consumption_cost) ''' ''' if sub_interval time_period occurs during the first decision period we need to account for the impact of the cost of increased marginal mitigation on consumption during this interval in order to be left with only the marginal damage impact on consumption ''' if my_tree.utility_decision_period[time_period] == 0: net_discounted_damage = -( expected_damages + my_tree.d_cost_by_state[ time_period, 1]) * expected_sdf / consumption_cost ''' TODO: Determine if these print statements are required for production runs. if my_tree.print_options[10] == 1 : print('Print_Option[10] Period', time_period, 'expected_damages', -expected_damages/consumption_cost, 'discount_price', expected_sdf, 'cross_product', -cross_product_sdf_damages/consumption_cost, 'cov_term', -cov_term/consumption_cost, 'net_discounted_damage', net_discounted_damage,'d_cost', -my_tree.d_cost_by_state[time_period,1]/consumption_cost) ''' # sum the present value of the costs of mitigation throughout the first decision period per $ spent on mitigation at time 0 d_cost_sum += -my_tree.d_cost_by_state[ time_period, 1] * expected_sdf / consumption_cost else: net_discounted_damage = -expected_damages * expected_sdf / consumption_cost ''' TODO: Determine if these print statements are required for production runs. if my_tree.print_options[10] == 1 : print('Print_Option[10] Period', time_period, 'expected_damages', -expected_damages/consumption_cost, 'discount_price', expected_sdf, 'cross_product', -cross_product_sdf_damages/consumption_cost, 'cov_term', -cov_term/consumption_cost, 'net_discounted_damage', net_discounted_damage) ''' my_tree.net_expected_damages[ time_period] = net_discounted_damage my_tree.risk_premium[ time_period] = -cov_term / consumption_cost ''' sum (over time) the components of the present value of expected damages and the risk premium''' discounted_expected_damages += -expected_damages * expected_sdf / consumption_cost ''' the net discounted damages nets out the cost associated with increasing mitigation throughout the first period ''' net_discounted_expected_damages += net_discounted_damage risk_premium += -cov_term / consumption_cost ''' if desired, print out the SDF's and Marginal Utilities for the decision tree nodes''' for p in range(0, self.my_tree.nperiods): first_node = self.my_tree.decision_period_pointer[p] for n in range(0, self.my_tree.decision_nodes[p]): ''' no branching in the last period ''' if p == self.my_tree.nperiods-1 : ''' TODO: Determine if these print statements are required for production runs. if my_tree.print_options[6] == 1 : print('Print_Option[6] Period', p,'time', int(2015+my_tree.decision_times[p]), 'node', first_node+n, 'SDF=', my_tree.marginal_utility_in_tree[first_node+n,1] / my_tree.marginal_utility_in_tree[ first_node+n, 0 ]) #, print('Print_Option[6] Marginal_utility(c(t)) ', my_tree.marginal_utility_in_tree[first_node+n, 0], 'Marginal_utility(c(t+1))', self.my_tree.marginal_utility_in_tree[first_node+n, 1]) ''' else: if my_tree.print_options[6] == 1: to_node = self.my_tree.next_node[first_node + n][0] prob_up = self.my_tree.node_probs[to_node] prob_down = self.my_tree.node_probs[to_node + 1] total_prob = prob_up + prob_down ''' TODO: Determine if these print statements are required for production runs. print('Print_Option[6] Period', p, 'time', int(2015+my_tree.decision_times[p]), 'node', first_node+n, 'SDF_up= ', (total_prob/prob_up) * self.my_tree.marginal_utility_in_tree[first_node+n,1] / self.my_tree.marginal_utility_in_tree[ first_node+n, 0 ]) #, print('Print_Option[6] SDF_down=', (total_prob/prob_down) * self.my_tree.marginal_utility_in_tree[first_node+n,2] / self.my_tree.marginal_utility_in_tree[ first_node+n, 0 ], 'MU ', self.my_tree.marginal_utility_in_tree[first_node+n, 0], 'Marginal_utility(c(t+1))_up ', self.my_tree.marginal_utility_in_tree[first_node+n, 1]) #, print('Print_Option[6] Marginal_utility(c(t+1))_down ', self.my_tree.marginal_utility_in_tree[first_node+n, 2]) ''' ''' if desired, print out the levels of GHG in each node ''' if my_tree.print_options[8] == 1: for p in range(0, my_tree.nperiods): first_node = my_tree.decision_period_pointer[p] ''' TODO: Determine if these print statements are required for production runs. for n in range(0, my_tree.decision_nodes[p]): print('Print_Option[8] Period', p, 'time', int(2015+my_tree.decision_times[p]), 'node', first_node+n, 'GHG_level=', my_tree.ghg_by_state[first_node+n]) ''' first_node = my_tree.x_dim ''' TODO: Determine if these print statements are required for production runs. for n in range(0, my_tree.final_states): print('Print_Option[8] Period', my_tree.nperiods, 'time', int(2015+my_tree.decision_times[my_tree.nperiods]), 'node', first_node+n, 'GHG_level= ', my_tree.ghg_by_state[first_node+n]) ''' if my_tree.analysis == 2: total = net_discounted_expected_damages + risk_premium # decompose the Social Cost of Carbon into the expected damages component versus the risk premium. ''' TODO: Determine if these print statements are required for production runs. print('Social_cost_of_carbon', my_cost_model.price_by_state( best_mitigation_plan[0],0.,0.)) print('Discounted_expected_damages', (net_discounted_expected_damages/total) * my_cost_model.price_by_state( best_mitigation_plan[0],0.,0.)) print('Risk_premium', (risk_premium/total) * my_cost_model.price_by_state( best_mitigation_plan[0],0.,0.)) ''' # print the decomposition of expected damages and risk premium over time. if my_tree.print_options[9] == 1: damage_scale = my_cost_model.price_by_state( best_mitigation_plan[0], 0., 0.) / (net_discounted_expected_damages + risk_premium) for np in range(1, my_tree.utility_nperiods): my_tree.net_expected_damages[np] *= damage_scale my_tree.risk_premium[np] *= damage_scale # TODO: Determine if these print statements are required for production runs. #print('Print_Option[9] Period', np, 'Year', 2015+np*my_tree.sub_interval_length, 'Net_discounted_expected_damage', my_tree.net_expected_damages[np], 'Risk_premium', my_tree.risk_premium[np]) ''' if desired, print out the yield curve ''' if my_tree.print_options[4] == 1: if my_tree.analysis == 2: for np in range(1, my_tree.utility_nperiods - 1): years_to_maturity = self.my_tree.utility_times[np] # TODO: Determine if these print statements are required for production runs. #print('Print_Option[4] Period', np, 'years-to-maturity', years_to_maturity, 'price of bond', self.my_tree.discount_prices[np], ' yield ', 100. * (1./(self.my_tree.discount_prices[np]**(1./years_to_maturity))-1.)) ''' find the yield on a perpetuity that begins paying at the time of the steady state continuation term ''' np = my_tree.utility_nperiods - 1 years_to_maturity = self.my_tree.utility_times[np] perp_yield = brentq(self.perpetuity_yield, 0.1, 10., args=(np * 5, self.my_tree.discount_prices[np])) # TODO: Determine if these print statements are required for production runs. #print('Print_Option[4] Period', my_tree.utility_nperiods-1, 'years-to-maturity', years_to_maturity, 'price of bond', self.my_tree.discount_prices[np], ' yield ', perp_yield) return price