Example #1
0
    def _forcing_init(self):
        """Initialize `Forcing` object and cum_forcings used in calculating the force mitigation up to a node."""
        if self.emit_pct is None:
            bau_emission = self.bau.ghg_end - self.bau.ghg_start
            self.emit_pct = 1.0 - (self.ghg_levels -
                                   self.bau.ghg_start) / bau_emission

        self.cum_forcings = np.zeros((self.tree.num_periods, self.dnum))
        mitigation = np.ones(
            (self.dnum,
             self.tree.num_decision_nodes)) * self.emit_pct[:, np.newaxis]

        for i in range(0, self.dnum):
            for n in range(1, self.tree.num_periods + 1):
                node = self.tree.get_node(n, 0)
                self.cum_forcings[n - 1, i] = Forcing.forcing_at_node(
                    mitigation[i], node, self.tree, self.bau,
                    self.subinterval_len)
Example #2
0
    def _damage_function_node(self, m, node):
        """Calculate the damage at any given node, based on mitigation actions in `m`."""
        if self.damage_coefs is None:
            self._damage_interpolation()
        if self.cum_forcings is None:
            self._forcing_init()
        if node == 0:
            return 0.0

        period = self.tree.get_period(node)
        forcing = Forcing.forcing_at_node(m, node, self.tree, self.bau,
                                          self.subinterval_len)
        force_mitigation = self._forcing_based_mitigation(forcing, period)

        worst_end_state, best_end_state = self.tree.reachable_end_states(
            node, period=period)
        probs = self.tree.final_states_prob[worst_end_state:best_end_state + 1]

        if force_mitigation < self.emit_pct[1]:
            damage = (probs *(self.damage_coefs[worst_end_state:best_end_state+1, period-1, 1, 1] * force_mitigation \
               + self.damage_coefs[worst_end_state:best_end_state+1, period-1, 1, 2])).sum()

        elif force_mitigation < self.emit_pct[0]:
            damage = (probs * (self.damage_coefs[worst_end_state:best_end_state+1, period-1, 0, 0] * force_mitigation**2 \
                + self.damage_coefs[worst_end_state:best_end_state+1, period-1, 0, 1] * force_mitigation \
                + self.damage_coefs[worst_end_state:best_end_state+1, period-1, 0, 2])).sum()
        else:
            damage = 0.0
            i = 0
            for state in range(worst_end_state, best_end_state + 1):
                if self.d[0, state, period - 1] > 1e-5:
                    deriv = 2.0 * self.damage_coefs[state, period-1, 0, 0]*self.emit_pct[0] \
                      + self.damage_coefs[state, period-1, 0, 1]
                    decay_scale = deriv / (self.d[0, state, period - 1] *
                                           np.log(0.5))
                    dist = force_mitigation - self.emit_pct[0] + np.log(self.d[0, state, period-1]) \
                        / (np.log(0.5) * decay_scale)
                    damage += probs[i] * (0.5**(decay_scale * dist) * np.exp(
                        -np.square(force_mitigation - self.emit_pct[0]) / 60.0)
                                          )
                i += 1
        return damage / probs.sum()
Example #3
0
class DLWDamage(Damage):
	"""Damage class for the DLW-model. Provides the damages from emissions and mitigation outcomes.

	Parameters:
		tree (obj: 'TreeModel'): Provides the tree structure used.
		bau (obj: 'BusinessAsUsual'): Business-as-usual scenario of emissions.
		cons_growth (float): Constant consumption growth rate.
		ghg_levels (ndarray or list): End GHG levels for each end scenario.

	TODO:
		* re-write the _recombine_nodes
	"""

	def __init__(self, tree, bau, cons_growth, ghg_levels):
		super(DLWDamage, self).__init__(tree, bau)
		self.ghg_levels = ghg_levels
		if isinstance(self.ghg_levels, list):
			self.ghg_levels = np.array(self.ghg_levels)
		self.cons_growth = cons_growth
		self.dnum = len(ghg_levels)
		self.cum_forcings = None
		self.d = None
		self.forcing = None
		self.damage_coefs = None

	def _recombine_nodes(self):
		nperiods = self.tree.num_periods
		sum_class = np.zeros(nperiods, dtype=int)
		new_state = np.zeros([nperiods, self.tree.num_final_states], dtype=int)
		temp_prob = self.tree.final_states_prob.copy()

		for old_state in range(self.tree.num_final_states):
			temp = old_state
			n = nperiods-2
			d_class = 0
			while n >= 0:
				if temp >= 2**n:
					temp -= 2**n
					d_class += 1
				n -= 1
			sum_class[d_class] += 1
			new_state[d_class, sum_class[d_class]-1] = old_state
		
		sum_nodes = np.append(0, sum_class.cumsum())
		prob_sum = np.array([self.tree.final_states_prob[sum_nodes[i]:sum_nodes[i+1]].sum() for i in range(len(sum_nodes)-1)])
		
		for period in range(nperiods):
			for k in range(self.dnum):
				d_sum = np.zeros(nperiods)
				old_state = 0
				for d_class in range(nperiods):
					for i in range(sum_class[d_class]):
						d_sum[d_class] += self.tree.final_states_prob[old_state] * self.d[k, old_state, period]
						old_state += 1
				for d_class in range(nperiods):
					for i in range(sum_class[d_class]):
						self.d[k, new_state[d_class, i], period] = d_sum[d_class] / prob_sum[d_class]

		old_state = 0
		for d_class in range(nperiods):
			for i in range(sum_class[d_class]):
				self.tree.final_states_prob[new_state[d_class, i]] = temp_prob[old_state]

		self.tree.node_prob[-len(self.tree.final_states_prob):] = self.tree.final_states_prob
		for p in range(1,nperiods-1):
			nodes = self.tree.get_nodes_in_period(p)
			for node in range(nodes[0], nodes[1]+1):
				worst_end_state, best_end_state = self.tree.reachable_end_states(node, period=p)
				self.tree.node_prob[node] = self.tree.final_states_prob[worst_end_state:best_end_state+1].sum()

	def _damage_interpolation(self):
		"""Create the interpolation coeffiecients used to calculate damages.
		"""
		if self.d is None:
			print("Importing stored damage simulation")
			self.import_damages()

		self._recombine_nodes()
		
		self.damage_coefs = np.zeros((self.tree.num_final_states, self.tree.num_periods, self.dnum-1, self.dnum))
		amat = np.ones((self.tree.num_periods, self.dnum, self.dnum))
		bmat = np.ones((self.tree.num_periods, self.dnum))

		self.damage_coefs[:, :, -1,  -1] = self.d[-1, :, :]
		self.damage_coefs[:, :, -1,  -2] = (self.d[-2, :, :] - self.d[-1, :, :]) / self.emit_pct[-2]
		amat[:, 0, 0] = 2.0 * self.emit_pct[-2]
		amat[:, 1:, 0] = self.emit_pct[:-1]**2
		amat[:, 1:, 1] = self.emit_pct[:-1]
		amat[:, 0, -1] = 0.0

		for state in range(0, self.tree.num_final_states):
			bmat[:, 0] = self.damage_coefs[state, :, -1,  -2] * self.emit_pct[-2]
			bmat[:, 1:] = self.d[:-1, state, :].T
			self.damage_coefs[state, :, 0] = np.linalg.solve(amat, bmat)

	def import_damages(self, loc="data/simulated_damages.csv"):
		try:
			with open(loc, 'r') as f:
				d = np.loadtxt(f, delimiter=";", comments="#")
		except IOError as e:
			import sys
			print("Could not import simulated damages:\n\t{}".format(e))
			sys.exit(0)

		n = self.tree.num_final_states	
		self.d = np.array([d[n*i:n*(i+1)] for i in range(0, self.dnum)])

	def damage_simulation(self, draws, peak_temp, disaster_tail, tip_on, temp_map, temp_dist_params,
				          maxh, cons_growth, save_simulation=True):					  	  
		"""Initializion of simulation of damages. Either import stored simulation 
		of damages or simulate new values.

		Args:
			import_damages (bool): If program should import already stored values.
				Default is True.
			**kwargs: Arguments to initialize DamageSimulation class, in the
				case of import_damages = False. See DamageSimulation class for 
				more info.

		"""
		ds = DamageSimulation(tree=self.tree, ghg_levels=self.ghg_levels, peak_temp=peak_temp,
					disaster_tail=disaster_tail, tip_on=tip_on, temp_map=temp_map, 
					temp_dist_params=temp_dist_params, maxh=maxh, cons_growth=cons_growth)
		self.d = ds.simulate(draws)
		return self.d

	def _forcing_based_mitigation(self, forcing, period): 
		"""Calculation of mitigation based on forcing up to period.

		Args:
			forcing (float): Cumulative forcing up to node.
			period (int): Period of node.

		Returns:
			float: Mitigation.
		"""
		p = period - 1
		if forcing > self.cum_forcings[p][1]:
			weight_on_sim2 = (self.cum_forcings[p][2] - forcing) / (self.cum_forcings[p][2] - self.cum_forcings[p][1])
			weight_on_sim3 = 0
		elif forcing > self.cum_forcings[p][0]:
			weight_on_sim2 = (forcing - self.cum_forcings[p][0]) / (self.cum_forcings[p][1] - self.cum_forcings[p][0])
			weight_on_sim3 = (self.cum_forcings[p][1] - forcing) / (self.cum_forcings[p][1] - self.cum_forcings[p][0])
		else:
			weight_on_sim2 = 0
			weight_on_sim3 = 1.0 + (self.cum_forcings[p][0] - forcing) / self.cum_forcings[p][0]
		
		return weight_on_sim2 * self.emit_pct[1] + weight_on_sim3*self.emit_pct[0]

	def forcing_init(self, sink_start, forcing_start, ghg_start, partition_interval, forcing_p1, 
					 forcing_p2, forcing_p3, absorbtion_p1, absorbtion_p2, lsc_p1, lsc_p2): 
		"""Initialize Forcing object and cum_forcings used in calculating 
		the mitigation up to a node. 

		Args:
			**kwargs: Arguments to initialize Forcing object, see
				Forcing class for more info.

		"""
		bau_emission = self.bau.ghg_end - self.bau.ghg_start
		self.emit_pct = 1.0 - (self.ghg_levels-self.bau.ghg_start) / bau_emission
		self.cum_forcings = np.zeros((self.tree.num_periods, self.dnum))
		self.forcing = Forcing(self.tree, self.bau, sink_start, forcing_start, ghg_start, partition_interval,
							forcing_p1, forcing_p2, forcing_p3, absorbtion_p1, absorbtion_p2, lsc_p1, lsc_p2)

		mitigation = np.ones((self.dnum, self.tree.num_decision_nodes)) * self.emit_pct[:, np.newaxis]
		path_ghg_levels = np.zeros((self.dnum, self.tree.num_periods+1))
		path_ghg_levels[0,:] = self.bau.ghg_start

		for i in range(0, self.dnum):
			for n in range(1, self.tree.num_periods+1):
				node = self.tree.get_node(n, 0)
				self.cum_forcings[n-1, i] = self.forcing.forcing_at_node(mitigation[i], node, i)

	def average_mitigation_node(self, m, node, period):
		"""Calculate the average mitigation until node.

		Args:
			m (ndarray): Array of mitigation.
			node (int): The node for which average mitigation is to be calculated for.

		Returns:
			float: Average mitigation.

		"""
		if period == 0:
			return 0
		period = self.tree.get_period(node)
		state = self.tree.get_state(node, period)
		path = self.tree.get_path(node, period)
		new_m = np.zeros(len(path)-1)
		for i in range(len(new_m)):
			new_m[i] = m[path[i]]
		period_len = self.tree.decision_times[1:period+1] - self.tree.decision_times[:period]
		bau_emissions = self.bau.emission_by_decisions[:period]
		total_emission = np.dot(bau_emissions, period_len)
		ave_mitigation = np.dot(new_m, bau_emissions*period_len)
		return ave_mitigation / total_emission

	def average_mitigation(self, m, period):
		nodes = self.tree.get_num_nodes_period(period)
		ave_mitigation = np.zeros(nodes)
		for i in range(nodes):
			node = self.tree.get_node(period, i)
			ave_mitigation[i] = self.average_mitigation_node(m, node, period)
		return ave_mitigation

	def _damage_function_node(self, m, node):
		"""Calculate the damage at any given node, based on mitigation actions.

		Args:
			m (ndarray): Array of mitigation.
			node (int): The node for which damage is to be calculated for.

		Returns:
			float: damage at node.

		"""
		if self.damage_coefs is None:
			self._damage_interpolation()
		if node == 0:
			return 0.0

		period = self.tree.get_period(node)
		forcing = self.forcing.forcing_at_node(m, node)
		force_mitigation = self._forcing_based_mitigation(forcing, period)

		worst_end_state, best_end_state = self.tree.reachable_end_states(node, period=period)
		probs = self.tree.final_states_prob[worst_end_state:best_end_state+1]

		if force_mitigation < self.emit_pct[1]:
			damage = (probs *(self.damage_coefs[worst_end_state:best_end_state+1, period-1, 1, 1] * force_mitigation \
					 + self.damage_coefs[worst_end_state:best_end_state+1, period-1, 1, 2])).sum()
		
		elif force_mitigation < self.emit_pct[0]: #do dot product instead?
			damage = (probs * (self.damage_coefs[worst_end_state:best_end_state+1, period-1, 0, 0] * force_mitigation**2 \
					  + self.damage_coefs[worst_end_state:best_end_state+1, period-1, 0, 1] * force_mitigation \
					  + self.damage_coefs[worst_end_state:best_end_state+1, period-1, 0, 2])).sum()
		
		######### what's happening here? ##############
		else: 
			damage = 0.0
			i = 0
			for state in range(worst_end_state, best_end_state+1): 
				if self.d[0, state, period-1] > 1e-5:
					deriv = 2.0 * self.damage_coefs[state, period-1, 0, 0]*self.emit_pct[0] \
							+ self.damage_coefs[state, period-1, 0, 1]
					decay_scale = deriv / (self.d[0, state, period-1]*np.log(0.5))
					dist = force_mitigation - self.emit_pct[0] + np.log(self.d[0, state, period-1]) \
						   / (np.log(0.5) * decay_scale) 
					damage += probs[i] * (0.5**(decay_scale*dist) * np.exp(-np.square(force_mitigation-self.emit_pct[0])/60.0))
				i += 1
		return damage / probs.sum()

	def damage_function(self, m, period):
		"""Calculate the damage for every node in a period, based on mitigation actions.

		Args:
			m (ndarray): Array of mitigation.
			period (int): The period for which damage is to be calculated.

		Returns:
			ndarray: Array of damages.
		"""
		nodes = self.tree.get_num_nodes_period(period)
		damages = np.zeros(nodes)
		for i in range(nodes):
			node = self.tree.get_node(period, i)
			damages[i] = self._damage_function_node(m, node)
		return damages