def report_load_generation_mismatch_for_deterministic_ruc(ruc_instance): time_periods = ruc_instance.data['system']['time_keys'] buses = ruc_instance.data['elements']['bus'] for i, t in enumerate(time_periods): mismatch_reported = False sum_mismatch = round_small_values( sum(bdict['p_balance_violation']['values'][i] for bdict in buses.values())) if sum_mismatch != 0.0: posLoadGenerateMismatch = round_small_values( sum( max(bdict['p_balance_violation']['values'][i], 0.) for bdict in buses.values())) negLoadGenerateMismatch = round_small_values( sum( min(bdict['p_balance_violation']['values'][i], 0.) for bdict in buses.values())) if negLoadGenerateMismatch != 0.0: print( "Projected over-generation reported at t=%s - total=%12.2f" % (t, negLoadGenerateMismatch)) if posLoadGenerateMismatch != 0.0: print( "Projected load shedding reported at t=%s - total=%12.2f" % (t, posLoadGenerateMismatch)) if 'reserve_shortfall' in ruc_instance.data['system']: reserve_shortfall_value = round_small_values( ruc_instance.data['system']['reserve_shortfall']['values'][i]) if reserve_shortfall_value != 0.0: print( "Projected reserve shortfall reported at t=%s - total=%12.2f" % (t, reserve_shortfall_value))
def get_load_mismatches(self, sced: OperationsModel) -> Tuple[float, float]: """ Get under-generation (load shedding) and over-generation. Returns ------- load_shedding: float The total load shedding across all buses over_generation: float The total over-generation across all buses """ load_generation_mismatch_value = round_small_values( sum(self.get_load_mismatch(sced, b) for b in self.get_buses(sced))) if load_generation_mismatch_value != 0.0: load_shedding_value = round_small_values( sum( self.get_positive_load_mismatch(sced, b) for b in self.get_buses(sced))) over_generation_value = round_small_values( sum( self.get_negative_load_mismatch(sced, b) for b in self.get_buses(sced))) else: load_shedding_value = 0.0 over_generation_value = 0.0 return load_shedding_value, over_generation_value
def get_total_renewables_curtailment(self, sced: OperationsModel) -> float: """ get how much renewable power could have been used but wasn't """ total_curtailment = round_small_values( sum( self.get_renewables_curtailment(sced, g) for g in self.get_nondispatchable_generators(sced))) return total_curtailment
def _get_reserve_property(sced: OperationsModel, reserve_id: ReserveIdentifier, suffix: str) -> float: ''' Get the value of a particular reserve property. Reserve property name must follow a standard convention, f'{reserve_id.reserve_name}{suffix}'. ''' data = ScedDataExtractor._get_reserve_parent(sced, reserve_id) attr = f'{reserve_id.reserve_name}{suffix}' if attr in data: if isinstance(data[attr], dict): return round_small_values(data[attr]['values'][0]) else: return round_small_values(data[attr]) else: return 0.
def get_reserve_shortfall(self, sced: OperationsModel) -> float: if 'reserve_shortfall' in sced.data['system']: return round_small_values( sced.data['system']['reserve_shortfall']['values'][0]) else: return 0.
def solve_deterministic_day_ahead_pricing_problem(solver, ruc_results, options, ptdf_manager): ## create a copy because we want to maintain the solution data ## in ruc_results pricing_type = options.day_ahead_pricing print("Computing day-ahead prices using method "+pricing_type.name+".") pricing_instance = ruc_results.clone() if pricing_type == PricingType.LMP: for g, g_dict in pricing_instance.elements(element_type='generator', generator_type='thermal'): g_dict['fixed_commitment'] = g_dict['commitment'] if 'reg_provider' in g_dict: g_dict['fixed_regulation'] = g_dict['reg_provider'] ## TODO: add fixings for storage; need hooks in EGRET elif pricing_type == PricingType.ELMP: ## for ELMP we fix all commitment binaries that were 0 in the RUC solve time_periods = pricing_instance.data['system']['time_keys'] for g, g_dict in pricing_instance.elements(element_type='generator', generator_type='thermal'): g_dict['fixed_commitment'] = _get_fixed_if_off(g_dict['commitment'], g_dict.get('fixed_commitment', None)) if 'reg_provider' in g_dict: g_dict['fixed_regulation'] = _get_fixed_if_off(g_dict['reg_provider'], g_dict.get('fixed_regulation', None)) ## TODO: add fixings for storage; need hooks in EGRET elif pricing_type == PricingType.ACHP: # don't do anything pass else: raise RuntimeError("Unknown pricing type "+pricing_type+".") ## change the penalty prices to the caps, if necessary reserve_requirement = ('reserve_requirement' in pricing_instance.data['system']) system = pricing_instance.data['system'] # In case of shortfall, the price skyrockets, so we threshold the value. for system_key, threshold_value in get_attrs_to_price_option(options): if threshold_value is not None and ((system_key not in system) or (system[system_key] > threshold_value)): system[system_key] = threshold_value ptdf_manager.mark_active(pricing_instance) pyo_model = create_pricing_model(pricing_instance, relaxed=True, ptdf_options=ptdf_manager.damarket_ptdf_options, PTDF_matrix_dict=ptdf_manager.PTDF_matrix_dict) pyo_model.dual = Suffix(direction=Suffix.IMPORT) try: ## TODO: Should there be separate options for this run? pricing_results, _, _ = call_solver(solver, pyo_model, options, options.deterministic_ruc_solver_options, relaxed=True) except: print("Failed to solve pricing instance - likely because no feasible solution exists!") output_filename = "bad_pricing.json" pricing_instance.write(output_filename) print("Wrote failed RUC model to file=" + output_filename) raise ptdf_manager.update_active(pricing_results) ## Debugging if pricing_results.data['system']['total_cost'] > ruc_results.data['system']['total_cost'] and not \ math.isclose(pricing_results.data['system']['total_cost'], ruc_results.data['system']['total_cost'], rel_tol=1e-06, abs_tol=1e-06): print("The pricing run had a higher objective value than the MIP run. This is indicative of a bug.") print(f"pricing run cost: {pricing_results.data['system']['total_cost']}") print(f"MIP run cost : {ruc_results.data['system']['total_cost']}") print("Writing LP pricing_problem.json") output_filename = 'pricing_instance.json' pricing_results.write(output_filename) output_filename = 'ruc_results.json' ruc_results.write(output_filename) raise RuntimeError("Halting due to bug in pricing.") day_ahead_prices = {} for b, b_dict in pricing_results.elements(element_type='bus'): for t,lmp in enumerate(b_dict['lmp']['values']): day_ahead_prices[b,t] = lmp if reserve_requirement: day_ahead_reserve_prices = {} for t,price in enumerate(pricing_results.data['system']['reserve_price']['values']): # Thresholding the value of the reserve price to the passed in option day_ahead_reserve_prices[t] = price print("Recalculating RUC reserve procurement") ## scale the provided reserves by the amount over we are thermal_reserve_cleared_DA = {} g_reserve_values = { g : g_dict['rg']['values'] for g, g_dict in ruc_results.elements(element_type='generator', generator_type='thermal') } reserve_shortfall = ruc_results.data['system']['reserve_shortfall']['values'] reserve_requirement = ruc_results.data['system']['reserve_requirement']['values'] for t in range(0,options.ruc_every_hours): reserve_provided_t = sum(reserve_vals[t] for reserve_vals in g_reserve_values.values()) reserve_shortfall_t = reserve_shortfall[t] reserve_requirement_t = reserve_requirement[t] surplus_reserves_t = reserve_provided_t + reserve_shortfall_t - reserve_requirement_t ## if there's a shortfall, grab the full amount from the RUC solve ## or if there's no provided reserves this can safely be set to 1. if round_small_values(reserve_shortfall_t) > 0 or reserve_provided_t == 0: surplus_multiple_t = 1. else: ## scale the reserves from the RUC down by the same fraction ## so that they exactly meed the needed reserves surplus_multiple_t = reserve_requirement_t/reserve_provided_t for g, reserve_vals in g_reserve_values.items(): thermal_reserve_cleared_DA[g,t] = reserve_vals[t]*surplus_multiple_t else: day_ahead_reserve_prices = { t : 0. for t,_ in enumerate(ruc_results.data['system']['time_keys']) } thermal_reserve_cleared_DA = { (g,t) : 0. \ for t,_ in enumerate(ruc_results.data['system']['time_keys']) \ for g,_ in ruc_results.elements(element_type='generator', generator_type='thermal') } thermal_gen_cleared_DA = {} renewable_gen_cleared_DA = {} virtual_gen_cleared_DA = {} for g, g_dict in ruc_results.elements(element_type='generator'): pg = g_dict['pg']['values'] if g_dict['generator_type'] == 'thermal': store_dict = thermal_gen_cleared_DA elif g_dict['generator_type'] == 'renewable': store_dict = renewable_gen_cleared_DA elif g_dict['generator_type'] == 'virtual': store_dict = virtual_gen_cleared_DA else: raise RuntimeError(f"Unrecognized generator type {g_dict['generator_type']}") for t in range(0,options.ruc_every_hours): store_dict[g,t] = pg[t] return RucMarket(day_ahead_prices=day_ahead_prices, day_ahead_reserve_prices=day_ahead_reserve_prices, thermal_gen_cleared_DA=thermal_gen_cleared_DA, thermal_reserve_cleared_DA=thermal_reserve_cleared_DA, renewable_gen_cleared_DA=renewable_gen_cleared_DA, virtual_gen_cleared_DA=virtual_gen_cleared_DA)
def has_load_shedding(self, sced: OperationsModel) -> bool: return any(round_small_values(self.get_positive_load_mismatch(sced, b)) > 0.0 for b in self.get_buses(sced))