def penalty_cost(self, m): """Calculate and return the penalty cost of GHG levels below 280. The penalties in previous nodes in the path leading to the current node is summed and added to current period's penalty, given by max(0, min((280-GHG level)/GHG level, max_penalty) The method returns a `BigStorageTree` object with penalties for every period where utilities are calculated. Parameters ---------- m : ndarray or list array of mitigation Returns ------- `BigStorageTree` penalties in every period where utilities are calculated """ penalty_cost = BigStorageTree(self.period_len, self.decision_times) if not self.add_penalty_cost: return penalty_cost ghg_levels = self.damage.ghg_level(m, periods=self.tree.num_periods) interval_length = self.decision_times[1:] - self.decision_times[:-1] sum_size = 1 prev_ghg_level = ghg_levels[0] for i in range(1, len(self.tree.decision_times)): time_period = self.tree.decision_times[i] prev_time_period = self.tree.decision_times[i - 1] prev_penalty_cost = penalty_cost[prev_time_period] len_arr = len(penalty_cost[time_period]) ghg_level = ghg_levels[sum_size:sum_size + len_arr] increment = self.period_len total_increment = time_period - prev_time_period if prev_ghg_level.shape != ghg_level.shape: prev_ghg_level = np.repeat(prev_ghg_level, 2) prev_penalty_cost = np.repeat(prev_penalty_cost, 2) while prev_time_period < time_period: prev_time_period += self.period_len this_period_ghg = prev_ghg_level + ( increment / total_increment) * (ghg_level - prev_ghg_level) this_period_ghg[ this_period_ghg == 0] = 1.0 # doesn't really matter if we put 1.0 or a value closer to 0 penalty_cost.set_value( prev_time_period, prev_penalty_cost + self._interval_penalty(this_period_ghg) / self.period_len) prev_penalty_cost = penalty_cost[prev_time_period] increment += self.period_len prev_ghg_level = ghg_level sum_size += len_arr return penalty_cost
def marginal_utility(self, m, utility_tree, cons_tree, cost_tree, ce_tree): """Calculating marginal utility for sensitivity analysis, e.g. in the SSC decomposition. Parameters ---------- m : ndarray array of mitigations utility_tree : `BigStorageTree` object utility values from using mitigation `m` cons_tree : `BigStorageTree` object consumption values from using mitigation `m` cost_tree : `SmallStorageTree` object cost values from using mitigation `m` ce_tree : `BigStorageTree` object certain equivalence values from using mitigation `m` Returns ------- tuple marginal utility tree Examples -------- Assuming we have declared a EZUtility object as 'ezu' and have a mitigation array 'm'. >>> >>> utility_tree, cons_tree, cost_tree, ce_tree = ezu.utility(m, return_trees=True) >>> mu_0_tree, mu_1_tree, mu_2_tree = ezu.marginal_utility(m, utility_tree, cons_tree, cost_tree, ce_tree) >>> mu_0_tree[0] # value at period 0 array([ 0.33001256]) >>> mu_1_tree[0] # value at period 0 array([ 0.15691619]) >>> mu_2_tree[0] # value at period 0 array([ 0.13948175]) """ #could add ce_tree to parameter list. mu_tree_0 = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) mu_tree_1 = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) mu_tree_2 = SmallStorageTree(decision_times=self.decision_times) self._end_period_marginal_utility(mu_tree_0, mu_tree_1, ce_tree, utility_tree, cons_tree) periods = utility_tree.periods[::-1] for period in periods[2:]: mu_0, mu_1, mu_2 = self._period_marginal_utility( mu_tree_0.get_next_period_array(period), mu_tree_1.get_next_period_array(period), m, period, utility_tree, cons_tree, ce_tree) mu_tree_0.set_value(period, mu_0) mu_tree_1.set_value(period, mu_1) if mu_2 is not None: mu_tree_2.set_value(period, mu_2) return mu_tree_0, mu_tree_1, mu_tree_2
def utility(self, m, return_trees=False): """Calculating utility for the specific mitigation decisions 'm'. Args: m (ndarray): Array of mitigations. return_trees (bool): True if method should return trees calculculated in producing the utility. Returns: tuple of ndarrays if return_trees else float of period 0's utility. """ # can we make this smarter and not create these every time we call the utility? utility_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cons_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) ce_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cost_tree = SmallStorageTree(decision_times=self.decision_times) self._end_period_utility(m, utility_tree, cons_tree, cost_tree) it = self._utility_generator(m, utility_tree, cons_tree, cost_tree, ce_tree) for u, period in it: utility_tree.set_value(period, u) if return_trees: return utility_tree, cons_tree, cost_tree, ce_tree return utility_tree[0]
def numerical_scc(m, utility, delta_m): utility_t, cons_t, cost_t, ce_t = utility.utility(m, return_trees=True) m_copy = m.copy() m_copy[0] += delta_m delta_utility_t, delta_cons_t, delta_cost_t, delta_ce_t = utility.utility( m_copy, return_trees=True) delta_utility = (delta_utility_t[0] - utility_t[0]) node_eps = BigStorageTree(5.0, [0, 15, 45, 85, 185, 285, 385]) scc = 0 for period in cons_t.decision_times[1:]: cons_t.tree[period] = (delta_cons_t[period] - cons_t[period]) for node in range(len(cons_t[period])): node_eps.tree[period][node] = delta_m adj_utiity = utility.adjusted_utility(m, node_cons_eps=node_eps) node_eps.tree[period][node] = 0.0 cons_t.tree[period][node] = (cons_t[period][node] / (delta_m)) * ( (adj_utiity - utility_t[0]) / cons_t[period][node]) cons_t.tree[period][node] = np.nan_to_num(cons_t[period][node]) scc += cons_t.tree[period].sum() scc = scc * ( (delta_cons_t[0] - cons_t[0]) / delta_utility) * delta_m * m[0] return scc
def marginal_utility(self, m, utility_tree, cons_tree, cost_tree, ce_tree): """Calculating marginal utility for sensitivity analysis, e.g. in the SSC decomposition. Args: m (ndarray): 1D-array of mitigations. period_cons_eps (optionla, ndarray): Array of increases in consumption per period. #77 node_cons_eps (optional, ndarray): Array of increases in consumption per node. #(big_storage) final_cons_eps (optional, float): Number to increase the final utility. first_period_consadj (optional, float): Adjustment of consumption at time 0. return_trees (bool): True if method should return trees calculculated in producing the utility. Returns: tuple of ndarrays if return_trees else float of period 0's utility. """ #could add ce_tree to parameter list. mu_tree_0 = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) mu_tree_1 = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) mu_tree_2 = SmallStorageTree(decision_times=self.decision_times) #ce_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) self._end_period_marginal_utility(mu_tree_0, mu_tree_1, ce_tree, utility_tree, cons_tree) periods = utility_tree.periods[::-1] for period in periods[2:]: mu_0, mu_1, mu_2 = self._period_marginal_utility( mu_tree_0.get_next_period_array(period), mu_tree_1.get_next_period_array(period), m, period, utility_tree, cons_tree, ce_tree) mu_tree_0.set_value(period, mu_0) mu_tree_1.set_value(period, mu_1) if mu_2 is not None: mu_tree_2.set_value(period, mu_2) return mu_tree_0, mu_tree_1, mu_tree_2
def utility(self, m, return_trees=False): """Calculating utility for the specific mitigation decisions `m`. Parameters ---------- m : ndarray or list array of mitigations return_trees : bool True if methid should return trees calculated in producing the utility Returns ------- ndarray or tuple tuple of `BaseStorageTree` if return_trees else ndarray with utility at period 0 Examples: --------- Assuming we have declared a EZUtility object as 'ezu' and have a mitigation array 'm' >>> ezu.utility(m) array([ 9.83391921]) >>> utility_tree, cons_tree, cost_tree, ce_tree = ezu.utility(m, return_trees=True) """ utility_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cons_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) ce_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cost_tree = SmallStorageTree(decision_times=self.decision_times) penalty_cost = self.penalty_cost(m) self._end_period_utility(m, utility_tree, cons_tree, cost_tree, penalty_cost) it = self._utility_generator(m, utility_tree, cons_tree, cost_tree, ce_tree, penalty_cost) for u, period in it: utility_tree.set_value(period, u) if return_trees: return utility_tree, cons_tree, cost_tree, ce_tree return utility_tree[0]
def adjusted_utility(self, m, period_cons_eps=None, node_cons_eps=None, final_cons_eps=0.0, first_period_consadj=0.0, return_trees=False): """Calculating adjusted utility for sensitivity analysis. Used e.g. to find zero-coupon bond price. Values in parameters are used to adjusted the utility in different ways. Parameters ---------- m : ndarray array of mitigations period_cons_eps : ndarray, optional array of increases in consumption per period node_cons_eps : `SmallStorageTree`, optional increases in consumption per node final_cons_eps : float, optional value to increase the final utilities by first_period_consadj : float, optional value to increase consumption at period 0 by return_trees : bool, optional True if method should return trees calculculated in producing the utility Returns ------- ndarray or tuple tuple of `BaseStorageTree` if return_trees else ndarray with utility at period 0 Examples --------- Assuming we have declared a EZUtility object as 'ezu' and have a mitigation array 'm' >>> ezu.adjusted_utility(m, final_cons_eps=0.1) array([ 9.83424045]) >>> utility_tree, cons_tree, cost_tree, ce_tree = ezu.adjusted_utility(m, final_cons_eps=0.1, return_trees=True) >>> arr = np.zeros(int(ezu.decision_times[-1]/ezu.period_len) + 1) >>> arr[-1] = 0.1 >>> ezu.adjusted_utility(m, period_cons_eps=arr) array([ 9.83424045]) >>> bst = BigStorageTree(5.0, [0, 15, 45, 85, 185, 285, 385]) >>> bst.set_value(bst.last_period, np.repeat(0.01, len(bst.last))) >>> ezu.adjusted_utility(m, node_cons_eps=bst) array([ 9.83391921]) The last example differs from the rest in that the last values of the `node_cons_eps` will never be used. Hence if you want to update the last period consumption, use one of these two methods. >>> ezu.adjusted_utility(m, first_period_consadj=0.01) array([ 9.84518772]) """ utility_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cons_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) ce_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cost_tree = SmallStorageTree(decision_times=self.decision_times) periods = utility_tree.periods[::-1] if period_cons_eps is None: period_cons_eps = np.zeros(len(periods)) if node_cons_eps is None: node_cons_eps = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) penalty_cost = self.penalty_cost(m) self._end_period_utility(m, utility_tree, cons_tree, cost_tree, penalty_cost) it = self._utility_generator(m, utility_tree, cons_tree, cost_tree, ce_tree, penalty_cost, first_period_consadj) i = len(utility_tree) - 2 for u, period in it: if period == periods[1]: mu_0 = (1.0 - self.b) * (u / cons_tree[period])**(1.0 - self.r) next_term = self.b * (1.0 - self.b) / ( 1.0 - self.b * self.growth_term**self.r) mu_1 = (u**(1.0 - self.r)) * next_term * (cons_tree.last **(self.r - 1.0)) u += (final_cons_eps + period_cons_eps[-1] + node_cons_eps.last) * mu_1 u += (period_cons_eps[i] + node_cons_eps.tree[period]) * mu_0 utility_tree.set_value(period, u) else: mu_0, m_1, m_2 = self._period_marginal_utility( mu_0, mu_1, m, period, utility_tree, cons_tree, ce_tree) u += (period_cons_eps[i] + node_cons_eps.tree[period]) * mu_0 utility_tree.set_value(period, u) i -= 1 if return_trees: return utility_tree, cons_tree, cost_tree, ce_tree return utility_tree.tree[0]
def save_sensitivity_analysis(m, utility, utility_tree, cons_tree, cost_tree, ce_tree, prefix=None, return_delta_utility=False): """Calculate and save sensitivity analysis based on the optimal mitigation. For every sub-period, i.e. the periods given by the utility calculations, the function calculates and saves: * discount prices * net expected damages * expected damages * risk premium * expected SDF * cross SDF & damages * discounted expected damages * cov term * scaled net expected damages * scaled risk premiums into the file `prefix` + 'sensitivity_output' in the 'data' directory in the current working directory. Furthermore, for every node the function calculates and saves: * SDF * delta consumption * forward marginal utility * up-node marginal utility * down-node marginal utility into the file `prefix` + 'tree' in the 'data' directory in the current working directory. If there is no 'data' directory, one is created. Parameters ---------- m : ndarray or list array of mitigation utility : `Utility` object object of utility class utility_tree : `BigStorageTree` object utility values from optimal mitigation values cons_tree : `BigStorageTree` object consumption values from optimal mitigation values cost_tree : `SmallStorageTree` object cost values from optimal mitigation values ce_tree : `BigStorageTree` object certain equivalence values from optimal mitigation values prefix : str, optional prefix to be added to file_name """ from tools import write_columns_csv, append_to_existing sdf_tree = BigStorageTree(utility.period_len, utility.decision_times) sdf_tree.set_value(0, np.array([1.0])) discount_prices = np.zeros(len(sdf_tree)) net_expected_damages = np.zeros(len(sdf_tree)) expected_damages = np.zeros(len(sdf_tree)) risk_premiums = np.zeros(len(sdf_tree)) expected_sdf = np.zeros(len(sdf_tree)) cross_sdf_damages = np.zeros(len(sdf_tree)) discounted_expected_damages = np.zeros(len(sdf_tree)) net_discount_damages = np.zeros(len(sdf_tree)) cov_term = np.zeros(len(sdf_tree)) discount_prices[0] = 1.0 cost_sum = 0 end_price = find_term_structure(m, utility, 0.01) perp_yield = perpetuity_yield(end_price, sdf_tree.periods[-2]) delta_cons_tree, delta_cost_array, delta_utility = delta_consumption( m, utility, cons_tree, cost_tree, 0.01) mu_0, mu_1, mu_2 = utility.marginal_utility(m, utility_tree, cons_tree, cost_tree, ce_tree) sub_len = sdf_tree.subinterval_len i = 1 for period in sdf_tree.periods[1:]: node_period = sdf_tree.decision_interval(period) period_probs = utility.tree.get_probs_in_period(node_period) expected_damage = np.dot(delta_cons_tree[period], period_probs) expected_damages[i] = expected_damage if sdf_tree.is_information_period(period - sdf_tree.subinterval_len): total_probs = period_probs[::2] + period_probs[1::2] mu_temp = np.zeros(2 * len(mu_1[period - sub_len])) mu_temp[::2] = mu_1[period - sub_len] mu_temp[1::2] = mu_2[period - sub_len] sdf = (np.repeat(total_probs, 2) / period_probs) * ( mu_temp / np.repeat(mu_0[period - sub_len], 2)) period_sdf = np.repeat(sdf_tree.tree[period - sub_len], 2) * sdf else: sdf = mu_1[period - sub_len] / mu_0[period - sub_len] period_sdf = sdf_tree[period - sub_len] * sdf expected_sdf[i] = np.dot(period_sdf, period_probs) cross_sdf_damages[i] = np.dot(period_sdf, delta_cons_tree[period] * period_probs) cov_term[i] = cross_sdf_damages[i] - expected_sdf[i] * expected_damage discount_prices[i] = expected_sdf[i] sdf_tree.set_value(period, period_sdf) if i < len(delta_cost_array): net_discount_damages[i] = -(expected_damage + delta_cost_array[ i, 1]) * expected_sdf[i] / delta_cons_tree[0] cost_sum += -delta_cost_array[ i, 1] * expected_sdf[i] / delta_cons_tree[0] else: net_discount_damages[ i] = -expected_damage * expected_sdf[i] / delta_cons_tree[0] risk_premiums[i] = -cov_term[i] / delta_cons_tree[0] discounted_expected_damages[ i] = -expected_damage * expected_sdf[i] / delta_cons_tree[0] i += 1 damage_scale = utility.cost.price( 0, m[0], 0) / (net_discount_damages.sum() + risk_premiums.sum()) scaled_discounted_ed = net_discount_damages * damage_scale scaled_risk_premiums = risk_premiums * damage_scale if prefix is not None: prefix += "_" else: prefix = "" write_columns_csv([ discount_prices, net_discount_damages, expected_damages, risk_premiums, expected_sdf, cross_sdf_damages, discounted_expected_damages, cov_term, scaled_discounted_ed, scaled_risk_premiums ], prefix + "sensitivity_output", [ "Year", "Discount Prices", "Net Expected Damages", "Expected Damages", "Risk Premium", "Expected SDF", "Cross SDF & Damages", "Discounted Expected Damages", "Cov Term", "Scaled Net Expected Damages", "Scaled Risk Premiums" ], [sdf_tree.periods.astype(int) + 2015]) append_to_existing( [[end_price], [perp_yield], [scaled_discounted_ed.sum()], [scaled_risk_premiums.sum()], [utility.cost.price(0, m[0], 0)], cost_sum], prefix + "sensitivity_output", header=[ "Zero Bound Price", "Perp Yield", "Expected Damages", "Risk Premium", "SCC", "Sum Delta Cost" ], start_char='\n') store_trees(prefix=prefix, SDF=sdf_tree, DeltaConsumption=delta_cons_tree, MU_0=mu_0, MU_1=mu_1, MU_2=mu_2) if return_delta_utility: return delta_utility
def save_sensitivity_analysis(m, utility, utility_tree, cons_tree, cost_tree, ce_tree, new_cons_tree, cost_array, start_filename): """ create_output in dlw_optimization. Maybe we only want to use gradient desecent here""" from tools import write_columns_csv sdf_tree = BigStorageTree(utility.period_len, utility.decision_times) sdf_tree.set_value(0, np.array([1.0])) discount_prices = np.zeros(len(sdf_tree)) net_expected_damages = np.zeros(len(sdf_tree)) expected_damages = np.zeros(len(sdf_tree)) risk_premiums = np.zeros(len(sdf_tree)) expected_sdf = np.zeros(len(sdf_tree)) cross_sdf_damages = np.zeros(len(sdf_tree)) discounted_expected_damages = np.zeros(len(sdf_tree)) net_discount_damages = np.zeros(len(sdf_tree)) cov_term = np.zeros(len(sdf_tree)) discount_prices[0] = 1.0 cost_sum = 0 end_price = find_term_structure(m, utility, len(utility_tree), 0.01) perp_yield = perpetuity_yield(end_price, sdf_tree.periods[-2]) print("Zero coupon bond maturing in {} has price {} and yield {}".format( sdf_tree.periods[-2], end_price, perp_yield)) #grad = utility.numerical_gradient(m) #years_to_maturity = utility_tree.last_period - utility_tree.subinterval_len mu_0, mu_1, mu_2 = utility.marginal_utility(m, utility_tree, cons_tree, cost_tree, ce_tree) sub_len = sdf_tree.subinterval_len i = 1 for period in sdf_tree.periods[1:]: node_period = sdf_tree.decision_interval(period) period_probs = utility.tree.get_probs_in_period(node_period) expected_damage = np.dot(new_cons_tree[period], period_probs) expected_damages[i] = expected_damage if sdf_tree.is_information_period(period - sdf_tree.subinterval_len): total_probs = period_probs[::2] + period_probs[1::2] mu_temp = np.zeros(2 * len(mu_1[period - sub_len])) mu_temp[::2] = mu_1[period - sub_len] mu_temp[1::2] = mu_2[period - sub_len] sdf = (np.repeat(total_probs, 2) / period_probs) * ( mu_temp / np.repeat(mu_0[period - sub_len], 2)) period_sdf = np.repeat(sdf_tree.tree[period - sub_len], 2) * sdf else: sdf = mu_1[period - sub_len] / mu_0[period - sub_len] period_sdf = sdf_tree[period - sub_len] * sdf expected_sdf[i] = np.dot(period_sdf, period_probs) cross_sdf_damages[i] = np.dot(period_sdf, new_cons_tree[period] * period_probs) cov_term[i] = cross_sdf_damages[i] - expected_sdf[i] * expected_damage discount_prices[i] = expected_sdf[i] sdf_tree.set_value(period, period_sdf) if i < len(cost_array): net_discount_damages[i] = -(expected_damage + cost_array[ i, 1]) * expected_sdf[i] / new_cons_tree.tree[0] cost_sum += -cost_array[ i, 1] * expected_sdf[i] / new_cons_tree.tree[0] else: net_discount_damages[ i] = -expected_damage * expected_sdf[i] / new_cons_tree.tree[0] risk_premiums[i] = -cov_term[i] / new_cons_tree.tree[0] discounted_expected_damages[ i] = -expected_damage * expected_sdf[i] / new_cons_tree.tree[0] i += 1 damage_scale = utility.cost.price( 0, m[0], 0) / (net_discount_damages.sum() + risk_premiums.sum()) scaled_discounted_ed = net_discount_damages * damage_scale scaled_risk_premiums = risk_premiums * damage_scale write_columns_csv([ discount_prices, net_discount_damages, expected_damages, risk_premiums, expected_sdf, cross_sdf_damages, discounted_expected_damages, cov_term, scaled_discounted_ed, scaled_risk_premiums ], start_filename + "sensitivity_output", [ "Year", "Discount Prices", "Net Expected Damages", "Expected Damages", "Risk Premium", "Expected SDF", "Cross SDF & Damages", "Discounted Expected Damages", "Cov Term", "Scaled Net Expected Damages", "Scaled Risk Premiums" ], [sdf_tree.periods.astype(int) + 2015]) store_trees(prefix=start_filename, SDF=sdf_tree, DeltaConsumption=new_cons_tree, MU_0=mu_0, MU_1=mu_1, MU_2=mu_2)
def adjusted_utility(self, m, period_cons_eps=None, node_cons_eps=None, final_cons_eps=0.0, first_period_consadj=0.0, return_trees=False): """Calculating adjusted utility for sensitivity analysis. Used e.g. to find zero-coupon bond price. Args: m (ndarray): 1D-array of mitigations. period_cons_eps (optionla, ndarray): Array of increases in consumption per period. #77 node_cons_eps (optional, ndarray): Array of increases in consumption per node. #(big_storage) final_cons_eps (optional, float): Number to increase the final utility. first_period_consadj (optional, float): Adjustment of consumption at time 0. return_trees (bool): True if method should return trees calculculated in producing the utility. Returns: tuple of ndarrays if return_trees else float of period 0's utility. """ # can we make this smarter and not create these every time we call the utility? utility_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cons_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) ce_tree = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) cost_tree = SmallStorageTree(decision_times=self.decision_times) self._end_period_utility(m, utility_tree, cons_tree, cost_tree) periods = utility_tree.periods[::-1] if period_cons_eps is None: period_cons_eps = np.zeros(len(periods)) if node_cons_eps is None: node_cons_eps = BigStorageTree(subinterval_len=self.period_len, decision_times=self.decision_times) it = self._utility_generator(m, utility_tree, cons_tree, cost_tree, ce_tree, first_period_consadj) i = len(utility_tree) - 2 for u, period in it: if period == periods[1]: mu_0 = (1.0 - self.b) * (u / cons_tree[period])**(1.0 - self.r) next_term = self.b * (1.0 - self.b) / ( 1.0 - self.b * self.growth_term**self.r) mu_1 = (u**(1.0 - self.r)) * next_term * (cons_tree.last **(self.r - 1.0)) u += (final_cons_eps + period_cons_eps[-1]) * mu_1 u += (period_cons_eps[i] + node_cons_eps.tree[period]) * mu_0 utility_tree.set_value(period, u) else: mu_0, m_1, m_2 = self._period_marginal_utility( mu_0, mu_1, m, period, utility_tree, cons_tree, ce_tree) u += (period_cons_eps[i] + node_cons_eps.tree[period]) * mu_0 utility_tree.set_value(period, u) i -= 1 if return_trees: return utility_tree, cons_tree, cost_tree, ce_tree return utility_tree.tree[0]