def introduce_inputs_override(self, num, introduced_stock_changes, introduced_specified_stock, introduced_specified_sales, decimals=10): list_steps = range(self.i, self.num_years*self.spy) if num is None else range(self.i, min(self.i+num*self.spy, self.num_years*self.spy)) if introduced_stock_changes is not None: if num!=len(util.ensure_iterable_and_not_string(introduced_stock_changes)): raise ValueError("length of annual stock_changes must match the number of years to run") if np.any(np.isnan(introduced_stock_changes)): raise ValueError("introduced annual stock_changes cannot be nan") self.stock_changes[list_steps] = np.reshape(np.repeat(introduced_stock_changes/self.spy, self.spy, axis=0), len(list_steps)) if introduced_specified_stock is not None: if num!=len(util.ensure_iterable_and_not_string(introduced_specified_stock)): raise ValueError("length of annual specified_stock must match the number of years to run") self.specified_stock[list_steps] = np.array(util.flatten_list([[[np.nan]*self.num_techs]*(self.spy-1) + [list(util.ensure_iterable_and_not_string(ss))] for ss in np.round(introduced_specified_stock, decimals)])) if np.any(self.specified_stock[list_steps]<0): raise ValueError("introduced specified stock cannot be negative") i = self.i self.prior_year_stock = self.initial_stock if i == 0 else np.sum(self.stock[:, :i + 1, i - 1], axis=1) if np.sum(self.prior_year_stock) + np.sum(self.stock_changes[list_steps])>np.nansum(self.specified_stock[list_steps]) and not np.all(np.isnan(self.specified_stock[list_steps])) and self.stock_changes_as_min and self.use_stock_changes: self.specified_stock[list_steps] *= (np.sum(self.prior_year_stock) + np.sum(self.stock_changes[list_steps]))/np.nansum(self.specified_stock[list_steps]) # self.stock_changes[list_steps] = np.reshape(np.repeat(0/self.spy, self.spy, axis=0), len(list_steps)) # self.specified_stock[list_steps] = np.reshape(np.repeat(introduced_specified_stock, self.spy, axis=0), len(list_steps)) if introduced_specified_sales is not None: if num!=len(util.ensure_iterable_and_not_string(introduced_specified_sales)): raise ValueError("length of annual specified_sales must match the number of years to run") self.specified_sales[list_steps] = np.reshape(np.repeat(np.round(introduced_specified_sales, decimals)/self.spy, self.spy, axis=0), len(list_steps)) if np.any(self.specified_sales[list_steps]<0): raise ValueError("introduced specified sales cannot be negative")
def dispatch_to_energy_budget(load, energy_budgets, dispatch_periods=None, pmins=0, pmaxs=None): """ Dispatch to energy budget produces a dispatch shape for a load or generating energy budget Common uses would be hydro, power2gas, and hydrogen electrolysis energy_budget > 0 is generation (hydro) energy_budget < 0 is load (P2G) Args: load: net load shape (ndarray) energy_budgets: availabile energy (float or ndarray) dispatch_periods: identifiers for each load hour (ndarray) defaults to None pmins: min power for the dispatch (float or ndarray) defaults to zero pmaxs: max power for the dispatch (float or ndarray) defaults to None Returns: dispatch: (ndarray) This function solves based on a huristic, which returns the same solution as optimization Note that every change in dispatch period results in a new dispatch group, for instance, range(12) + range(12) will result in 24 dispatch groups, not 12, as might be expected. """ if dispatch_periods is not None and len(dispatch_periods) != len(load): raise ValueError('Dispatch period identifiers must match the length of the load data') # spit the load into dispatch groups load_groups = (load,) if dispatch_periods is None else np.array_split(load, np.where(np.diff(dispatch_periods) != 0)[ 0] + 1) energy_budgets = util.ensure_iterable_and_not_string(energy_budgets) pmins, pmaxs = util.ensure_iterable_and_not_string(pmins), util.ensure_iterable_and_not_string(pmaxs) if len(energy_budgets) != len(load_groups): raise ValueError('Number of energy_budgets must match the number of dispatch periods') if len(pmins) != len(load_groups): if len(pmins) == 1: # expand to match the number of load groups pmins *= len(load_groups) else: raise ValueError('Number of pmin values must match the number of dispatch periods') if len(pmaxs) != len(load_groups): if len(pmaxs) == 1: # expand to match the number of load groups pmaxs *= len(load_groups) else: raise ValueError('Number of pmax values must match the number of dispatch periods') # call solve for dispatch on each group and concatenate return np.concatenate([solve_for_dispatch_shape(load_group, energy_budget, pmin, pmax) for load_group, energy_budget, pmin, pmax in zip(load_groups, energy_budgets, pmins, pmaxs)])
def map_df(self, subsection, supersection, column=None, reset_index=False, eliminate_zeros=True): """ main function that maps geographies to one another Two options for two overlapping areas (A u B) / A (A is supersection) (A u B) / B (B is supersection) Examples: self.map_df('households', subsection=('state'), supersection=('census division')) "what fraction of each census division is in each state" self.map_df('households', subsection=('census division'), supersection=('state')) "what fraction of each state is in each census division """ subsection = util.ensure_iterable_and_not_string(subsection) column = config.cfg.cfgfile.get('case', 'default_geography_map_key') if column is None else column table = self.values[column].groupby(level=[supersection]+subsection).sum() table = pd.DataFrame(table.groupby(level=supersection).transform(self.normalize)) if reset_index or eliminate_zeros: names = table.index.names table.reset_index(inplace=True) if eliminate_zeros: table = table[table[column]>0] if not reset_index and eliminate_zeros: table.set_index(names, inplace=True) return table
def map_df(self, current_geography, converted_geography, normalize_as='total', map_key=None, reset_index=False, eliminate_zeros=True, primary_subset_id='from config', geomap_data='from self',filter_geo=True): """ main function that maps geographies to one another Two options for two overlapping areas (A u B) / A (A is supersection) (A u B) / B (B is supersection) Examples: self.map_df('households', subsection=('state'), supersection=('census division')) "what fraction of each census division is in each state" self.map_df('households', subsection=('census division'), supersection=('state')) "what fraction of each state is in each census division """ assert normalize_as=='total' or normalize_as=='intensity' geomap_data = self.values if geomap_data=='from self' else geomap_data if primary_subset_id=='from config' and filter_geo: primary_subset_id = cfg.primary_subset_id elif (primary_subset_id is None) or (primary_subset_id is False) or (not filter_geo): primary_subset_id = [] subset_geographies = set(cfg.geo.gau_to_geography[id] for id in primary_subset_id) current_geography = util.ensure_iterable_and_not_string(current_geography) converted_geography = util.ensure_iterable_and_not_string(converted_geography) union_geo = list(subset_geographies | set(current_geography) | set(converted_geography)) level_to_remove = list(subset_geographies - set(current_geography) - set(converted_geography)) map_key = cfg.cfgfile.get('case', 'default_geography_map_key') if map_key is None else map_key table = geomap_data[map_key].groupby(level=union_geo).sum().to_frame() if normalize_as=='total': table = self._normalize(table, current_geography) if primary_subset_id: # filter the table table = table.iloc[self._get_iloc_geo_subset(table, primary_subset_id)] table = util.remove_df_levels(table, level_to_remove) table = table.reset_index().set_index(table.index.names) if normalize_as=='intensity': table = self._normalize(table, converted_geography) if reset_index: table = table.reset_index() if not eliminate_zeros: index = pd.MultiIndex.from_product(table.index.levels, names=table.index.names) table = table.reorder_levels(index.names) table = table.reindex(index, fill_value=0.0) return table
def initialize_initial_stock(self, initial_stock): if initial_stock is None: self.initial_stock = np.zeros(self.num_techs) else: self.initial_stock = np.nanmax( (np.zeros(self.num_techs), np.array(util.ensure_iterable_and_not_string(initial_stock))), axis=0)
def account_for_specified_stock(self): i = self.i # if np.any((self.specified_sales[i]!=self.specified_stock[i])[np.array(list(set(self.stock_specified) & set(self.sales_specified)), dtype=int)]): # raise RuntimeError('Missmatch between specified sales and specified stock with no existing stock') # # if you have specified stock, it implies sales that supersede natural sales self.defined_sales = self.specified_stock[i] - (self.prior_year_stock - self.rolloff) # may result in a negative self.defined_sales[self.sales_specified] = self.specified_sales[i, self.sales_specified] # a negative specified sale means you need to do early retirement self.sum_defined_sales = np.sum(self.defined_sales[self.specified]) if len(self.specified) and np.sum(self.prior_year_stock): if not self.stock_changes_as_min and (round(sum(self.specified_stock[i][self.stock_specified]), 5) / round(np.sum(self.prior_year_stock) + self.stock_changes[i], 5))>1.01: print round(sum(self.specified_stock[i][self.stock_specified]), 5) print round(np.sum(self.prior_year_stock) + self.stock_changes[i], 5) print (round(sum(self.specified_stock[i][self.stock_specified]), 5) / round(np.sum(self.prior_year_stock) + self.stock_changes[i], 5)) raise RuntimeError('Specified stock in a given year is greater than the total stock') # if we have both a specified stock and specified sales for a tech, we need to true up the vintaged stock to make both match, if possible if len(set(self.sales_specified) & set(self.stock_specified)): for overlap_index in set(self.sales_specified) & set(self.stock_specified): retireable = np.array([overlap_index], dtype=int) needed_retirements = self.specified_sales[i, overlap_index] - (self.specified_stock[i, overlap_index] - (self.prior_year_stock[overlap_index] - self.rolloff[overlap_index])) if needed_retirements < 0: if len(util.ensure_iterable_and_not_string(overlap_index))>1: self.specified_sales[i, overlap_index] *= (sum(self.specified_sales[i, overlap_index]) - needed_retirements)/sum(self.specified_sales[i, overlap_index]) else: self.specified_sales[i, overlap_index] *= (self.specified_sales[i, overlap_index] - needed_retirements)/self.specified_sales[i, overlap_index] else: self.update_prinxy(needed_retirements, retireable) # Because of the retirements, we have a new natural rolloff number self.update_rolloff() # if specified sales are negative, we need to accelerate retirement for that technology if np.any(self.defined_sales[self.specified] < 0): for neg_index in np.nonzero(self.defined_sales < 0)[0]: retireable = np.array([neg_index], dtype=int) # negative is needed before self.defined_sales because a postive number indicates retirement self.update_prinxy(-self.defined_sales[neg_index], retireable) self.defined_sales[neg_index] = 0 self.sum_defined_sales = np.sum(self.defined_sales[self.specified]) # Because of the retirements, we have a new natural rolloff number self.update_rolloff() # Here, if stock changes as min if self.stock_changes_as_min: self.stock_changes[i] = max(self.stock_changes[i], self.sum_defined_sales - self.rolloff_summed) # We need additional early retirement to make room for defined_sales if round(self.sum_defined_sales, 6) > round(self.rolloff_summed + self.stock_changes[i], 6): # print 'We need additional early retirement to make room for defined_sales ' + str(i) incremental_retirement = self.sum_defined_sales - (self.rolloff_summed + self.stock_changes[i]) self.update_prinxy(incremental_retirement, self.solvable) # Because of the retirements, we have a new natural rolloff number self.update_rolloff()
def init_output_parameters(): global currency_name, output_currency, output_tco, output_payback, evolved_run, evolved_blend_nodes, evolved_years currency_name = cfgfile.get('case', 'currency_name') output_currency = cfgfile.get('case', 'currency_year_id') + ' ' + currency_name output_tco = cfgfile.get('output_detail', 'output_tco').lower() output_payback = cfgfile.get('output_detail', 'output_payback').lower() evolved_run = cfgfile.get('evolved','evolved_run').lower() evolved_years = [int(x) for x in util.ensure_iterable_and_not_string(cfgfile.get('evolved','evolved_years'))] evolved_blend_nodes = [int(g) for g in cfgfile.get('evolved','evolved_blend_nodes').split(',') if len(g)] init_output_levels() init_outputs_id_map()
def add_service_links(self): """adds all technology service links""" self.service_links = {} service_links = util.sql_read_table('DemandTechsServiceLink', 'service_link_id', return_unique=True, demand_tech_id=self.id) if service_links is not None: service_links = util.ensure_iterable_and_not_string(service_links) for service_link in service_links: id = util.sql_read_table('DemandTechsServiceLink', 'id', return_unique=True, demand_tech_id=self.id, service_link_id=service_link) self.service_links[service_link] = DemandTechServiceLink(self, id, 'DemandTechsServiceLink', 'DemandTechsServiceLinkData')
def init_output_parameters(): global currency_name, output_currency, output_tco, output_payback, evolved_run, evolved_blend_nodes, evolved_years currency_name = cfgfile.get('case', 'currency_name') output_currency = cfgfile.get('case', 'currency_year_id') + ' ' + currency_name output_tco = cfgfile.get('output_detail', 'output_tco').lower() output_payback = cfgfile.get('output_detail', 'output_payback').lower() evolved_run = cfgfile.get('evolved', 'evolved_run').lower() evolved_years = [ int(x) for x in util.ensure_iterable_and_not_string( cfgfile.get('evolved', 'evolved_years')) ] evolved_blend_nodes = [ int(g) for g in cfgfile.get('evolved', 'evolved_blend_nodes').split(',') if len(g) ] init_removed_levels() init_output_levels() init_outputs_id_map()
def initialize_specified_stock(self, specified_stock): """ Stock gets specified in the past period of the year """ shape = (self.num_years * self.spy, self.num_techs) if specified_stock is None: self.specified_stock = np.empty(shape) self.specified_stock.fill(np.nan) else: self.specified_stock = np.array( util.flatten_list( [[[np.nan] * self.num_techs] * (self.spy - 1) + [list(util.ensure_iterable_and_not_string(ss))] for ss in specified_stock])) # if self.spy == 1: # self.specified_stock = specified_stock # else: # self.specified_stock = np.array(util.flatten_list([[[np.nan]*self.num_techs]*(self.spy-1) + [list(util.ensure_iterable_and_not_string(ss))] for ss in specified_stock])) # if self.num_techs == 1: # self.specified_stock = self.specified_stock.flatten() if np.any(self.specified_stock < 0): raise ValueError( "Specified stock cannot be initialized with negative numbers")
def account_for_specified_stock(self): i = self.i # if np.any((self.specified_sales[i]!=self.specified_stock[i])[np.array(list(set(self.stock_specified) & set(self.sales_specified)), dtype=int)]): # raise RuntimeError('Missmatch between specified sales and specified stock with no existing stock') # # if you have specified stock, it implies sales that supersede natural sales # if self.stock_changes_as_min and self.use_stock_changes: # self.defined_sales = np.maximum(self.specified_stock[i] - (self.prior_year_stock - self.rolloff),np.array(range(1))) # may result in a negative # else: self.defined_sales = self.specified_stock[i] - (self.prior_year_stock - self.rolloff) self.defined_sales[self.sales_specified] = self.specified_sales[ i, self.sales_specified] # a negative specified sale means you need to do early retirement self.sum_defined_sales = np.sum(self.defined_sales[self.specified]) if len(self.specified) and np.sum(self.prior_year_stock): if round(np.sum(self.prior_year_stock) + self.stock_changes[i], 5) > 0: if not self.stock_changes_as_min and util.percent_larger( sum(self.specified_stock[i][self.stock_specified]), np.sum(self.prior_year_stock) + self.stock_changes[i]) > self.exceedance_tolerance: logging.error( 'round(sum(self.specified_stock[i][self.stock_specified]), 5) = {}' .format( round( sum(self.specified_stock[i][ self.stock_specified]), 5))) logging.error( 'round(np.sum(self.prior_year_stock) + self.stock_changes[i], 5) = {}' .format( round( np.sum(self.prior_year_stock) + self.stock_changes[i], 5))) logging.error( '(round(sum(self.specified_stock[i][self.stock_specified]), 5) / round(np.sum(self.prior_year_stock) + self.stock_changes[i], 5)) = {}' .format((round( sum(self.specified_stock[i][self.stock_specified]), 5) / round( np.sum(self.prior_year_stock) + self.stock_changes[i], 5)))) raise RuntimeError( 'Specified stock in a given year is greater than the total stock' ) # if we have both a specified stock and specified sales for a tech, we need to true up the vintaged stock to make both match, if possible if len(set(self.sales_specified) & set(self.stock_specified)): for overlap_index in set(self.sales_specified) & set( self.stock_specified): retireable = np.array([overlap_index], dtype=int) needed_retirements = self.specified_sales[ i, overlap_index] - ( self.specified_stock[i, overlap_index] - (self.prior_year_stock[overlap_index] - self.rolloff[overlap_index])) if needed_retirements < 0: if len( util.ensure_iterable_and_not_string( overlap_index)) > 1: self.specified_sales[i, overlap_index] *= ( sum(self.specified_sales[i, overlap_index]) - needed_retirements) / sum( self.specified_sales[i, overlap_index]) else: self.specified_sales[i, overlap_index] *= ( self.specified_sales[i, overlap_index] - needed_retirements ) / self.specified_sales[i, overlap_index] else: self.update_prinxy(needed_retirements, retireable) # Because of the retirements, we have a new natural rolloff number self.update_rolloff() # if specified sales are negativ/e, we need to accelerate retirement for that technology if np.any(self.defined_sales[self.specified] < 0): for neg_index in np.nonzero(self.defined_sales < 0)[0]: retireable = np.array([neg_index], dtype=int) # negative is needed before self.defined_sales because a postive number indicates retirement self.update_prinxy(-self.defined_sales[neg_index], retireable) self.defined_sales[neg_index] = 0 self.sum_defined_sales = np.sum( self.defined_sales[self.specified]) # Because of the retirements, we have a new natural rolloff number self.update_rolloff() # Here, if stock changes as min, gross up stock changes if self.stock_changes_as_min and self.use_stock_changes: if self.stock_changes[i] > 0 and self.sum_defined_sales <> 0: self.stock_changes[i] = max( self.stock_changes[i], self.sum_defined_sales - self.rolloff_summed) elif self.stock_changes[i] < 0 and self.sum_defined_sales <> 0: self.stock_changes[i] = min( self.stock_changes[i], self.sum_defined_sales - self.rolloff_summed) elif self.stock_changes_as_min and not self.use_stock_changes: self.stock_changes[ i] = self.sum_defined_sales - self.rolloff_summed # We need additional early retirement to make room for defined_sales if round(self.sum_defined_sales, 6) > round( self.rolloff_summed + self.stock_changes[i], 6): incremental_retirement = self.sum_defined_sales - ( self.rolloff_summed + self.stock_changes[i]) self.update_prinxy(incremental_retirement, self.solvable) # Because of the retirements, we have a new natural rolloff number self.update_rolloff()
def initialize_initial_stock(self, initial_stock): if initial_stock is None: self.initial_stock = np.zeros(self.num_techs) else: self.initial_stock = np.nanmax((np.zeros(self.num_techs), np.array(util.ensure_iterable_and_not_string(initial_stock))), axis=0)
def initialize_specified_stock(self, specified_stock): """ Stock gets specified in the past period of the year """ shape = (self.num_years*self.spy, self.num_techs) if specified_stock is None: self.specified_stock = np.empty(shape) self.specified_stock.fill(np.nan) else: self.specified_stock = np.array(util.flatten_list([[[np.nan]*self.num_techs]*(self.spy-1) + [list(util.ensure_iterable_and_not_string(ss))] for ss in specified_stock])) # if self.spy == 1: # self.specified_stock = specified_stock # else: # self.specified_stock = np.array(util.flatten_list([[[np.nan]*self.num_techs]*(self.spy-1) + [list(util.ensure_iterable_and_not_string(ss))] for ss in specified_stock])) # if self.num_techs == 1: # self.specified_stock = self.specified_stock.flatten() if np.any(self.specified_stock<0): raise ValueError("Specified stock cannot be initialized with negative numbers")
def dispatch_to_energy_budget(load, energy_budgets, dispatch_periods=None, pmins=0, pmaxs=None): """ Dispatch to energy budget produces a dispatch shape for a load or generating energy budget Common uses would be hydro, power2gas, and hydrogen electrolysis energy_budget > 0 is generation (hydro) energy_budget < 0 is load (P2G) Args: load: net load shape (ndarray) energy_budgets: availabile energy (float or ndarray) dispatch_periods: identifiers for each load hour (ndarray) defaults to None pmins: min power for the dispatch (float or ndarray) defaults to zero pmaxs: max power for the dispatch (float or ndarray) defaults to None Returns: dispatch: (ndarray) This function solves based on a huristic, which returns the same solution as optimization Note that every change in dispatch period results in a new dispatch group, for instance, range(12) + range(12) will result in 24 dispatch groups, not 12, as might be expected. """ if dispatch_periods is not None and len(dispatch_periods) != len(load): raise ValueError( 'Dispatch period identifiers must match the length of the load data' ) # spit the load into dispatch groups load_groups = (load, ) if dispatch_periods is None else np.array_split( load, np.where(np.diff(dispatch_periods) != 0)[0] + 1) energy_budgets = util.ensure_iterable_and_not_string(energy_budgets) pmins, pmaxs = util.ensure_iterable_and_not_string( pmins), util.ensure_iterable_and_not_string(pmaxs) if len(energy_budgets) != len(load_groups): raise ValueError( 'Number of energy_budgets must match the number of dispatch periods' ) if len(pmins) != len(load_groups): if len(pmins) == 1: # expand to match the number of load groups pmins *= len(load_groups) else: raise ValueError( 'Number of pmin values must match the number of dispatch periods' ) if len(pmaxs) != len(load_groups): if len(pmaxs) == 1: # expand to match the number of load groups pmaxs *= len(load_groups) else: raise ValueError( 'Number of pmax values must match the number of dispatch periods' ) # call solve for dispatch on each group and concatenate return np.concatenate([ solve_for_dispatch_shape(load_group, energy_budget, pmin, pmax) for load_group, energy_budget, pmin, pmax in zip( load_groups, energy_budgets, pmins, pmaxs) ])