def test_uc_transmission_models(): ## the network tests can optionally specify some kwargs so we can pass them into solve_unit_commitment tc_networks = {'btheta_power_flow': [dict()], 'ptdf_power_flow':[ {'ptdf_options': {'lazy':False}}, dict(), ], 'power_balance_constraints':[dict()], } no_network = 'copperplate_power_flow' test_names = ['tiny_uc_tc'] + ['tiny_uc_tc_{}'.format(i) for i in range(2,11+1)] ## based on tiny_uc, tiny_uc_tc_2 has an interface, tiny_uc_tc_3 has a relaxed interface, tiny_uc_tc_4 has a relaxed flow limit; tiny_uc_tc_7 has a HVDC line for test_name in test_names: input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'.json') md_in = ModelData(input_json_file_name) for tc in tc_networks: for kwargs in tc_networks[tc]: md_results = solve_unit_commitment(md_in, solver=test_solver, mipgap=0.0, slack_type=SlackType.TRANSMISSION_LIMITS, uc_model_generator = _make_get_dcopf_uc_model(tc), **kwargs) reference_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'_results.json') md_reference = ModelData(reference_json_file_name) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'], rel_tol=rel_tol) ## test copperplate test_name = 'tiny_uc_1' md_in = ModelData(os.path.join(current_dir, 'uc_test_instances', 'tiny_uc_tc_2.json')) md_results = solve_unit_commitment(md_in, solver=test_solver, mipgap=0.0, uc_model_generator = _make_get_dcopf_uc_model(no_network)) reference_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'_results.json') md_reference = ModelData(reference_json_file_name) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'], rel_tol=rel_tol)
def test_init_read(): md = ModelData(testdata) md.write('testdata.json') md_read = ModelData('testdata.json') assert md.data == md_read.data
def get_initial_model(self, options: Options, num_time_steps: int, minutes_per_timestep: int) -> EgretModel: ''' Get a model ready to be populated with data Returns ------- A model object populated with static system information, such as buses and generators, and with time series arrays that are large enough to hold num_time_steps entries. Initial values in time time series do not have meaning. ''' # Get data for the first simulation day first_day_model = self._get_forecast_by_date(self._first_day) # Copy it, making sure we've got the right number of time periods data = _recurse_copy_with_time_series_length(first_day_model.data, num_time_steps) new_model = EgretModel(data) new_model.data['system']['time_keys'] = list( str(i) for i in range(1, num_time_steps + 1)) new_model.data['system'][ 'time_period_length_minutes'] = minutes_per_timestep return new_model
def test_scuc(): input_json_file_name = os.path.join(current_dir, 'uc_test_instances', 'test_scuc_masked.json') md_in = ModelData.read(input_json_file_name) md_results = solve_unit_commitment(md_in, solver=test_solver, relaxed=True) md_baseline = ModelData.read( os.path.join(current_dir, 'uc_test_instances', 'test_scuc_full_enforce_relaxed_sol.json')) assert math.isclose(md_results.data['system']['total_cost'], md_baseline.data['system']['total_cost'], rel_tol=rel_tol) ptdf_options = {'branch_kv_threshold': 300, 'kv_threshold_type': 'both'} md_results = solve_unit_commitment(md_in, solver=test_solver, mipgap=0.0, ptdf_options=ptdf_options) md_baseline = ModelData.read( os.path.join(current_dir, 'uc_test_instances', 'test_scuc_sparse_enforce_sol.json')) assert math.isclose(md_results.data['system']['total_cost'], md_baseline.data['system']['total_cost'], rel_tol=rel_tol)
def test_attributes(): md = ModelData(dict(testdata)) attr = md.attributes(element_type='generator') attr_cmp = { 'names': ['G1', 'G2'], 'generator_type': { 'G1': 'thermal', 'G2': 'solar' }, 'connected_bus': { 'G1': 'B1', 'G2': 'B1' }, 'pg': { 'G1': 100.0, 'G2': 200.0 }, 'qg': { 'G1': 11.0, 'G2': 22.0 }, 'in_service': { 'G1': True, 'G2': True } } assert attr == attr_cmp
def test_json_gz_read_write(): md = ModelData(testdata) md.write('testdata.json.gz') md_read = ModelData.read('testdata.json.gz') assert md.data == md_read.data
def _ensure_contingencies_monitored(options:Options, md:EgretModel, initial_ruc:bool = False) -> None: ''' Add contingency screening, if that option is enabled ''' if initial_ruc: _ensure_contingencies_monitored.contingency_dicts = {} for bn, b in md.elements('branch'): if not b.get('in_service', True): raise RuntimeError(f"Remove branches from service by setting the `planned_outage` attribute. " f"Branch {bn} has `in_service`:False") for bn, b in md.elements('bus'): if not b.get('in_service', True): raise RuntimeError(f"Buses cannot be removed from service in Prescient") if options.monitor_all_contingencies: key = [] for bn, b in md.elements('branch'): if 'planned_outage' in b: if isinstance(b['planned_outage'], dict): if any(b['planned_outage']['values']): key.append(b) elif b['planned_outage']: key.append(b) key = tuple(key) if key not in _ensure_contingencies_monitored.contingency_dicts: mapping_bus_to_idx = { k : i for i,k in enumerate(md.data['elements']['bus'].keys())} graph = construct_connection_graph(md.data['elements']['branch'], mapping_bus_to_idx) contingency_list = get_N_minus_1_branches(graph, md.data['elements']['branch'], mapping_bus_to_idx) contingency_dict = { cn : {'branch_contingency':cn} for cn in contingency_list} _ensure_contingencies_monitored.contingency_dicts[key] = contingency_dict md.data['elements']['contingency'] = _ensure_contingencies_monitored.contingency_dicts[key]
def test_ptdf_dcopf_model(self, test_case, soln_case, include_kwargs=False): dcopf_model = create_ptdf_dcopf_model md_soln = ModelData.read(soln_case) md_dict = ModelData.read(test_case) kwargs = {} if include_kwargs: kwargs = {'include_feasibility_slack': True} md, results = solve_dcopf(md_dict, "ipopt", dcopf_model_generator=dcopf_model, solver_tee=False, return_results=True, **kwargs) self.assertTrue(results.solver.termination_condition == TerminationCondition.optimal) comparison = math.isclose(md.data['system']['total_cost'], md_soln.data['system']['total_cost'], rel_tol=1e-6) self.assertTrue(comparison)
def populate_initial_state_data(self, options: Options, day: date, model: EgretModel) -> None: ''' Populate an existing model with initial state data for the requested day Sets T0 information from actuals: * initial_state_of_charge for each storage element * initial_status for each generator * initial_p_output for each generator Arguments --------- options: Option values day:date The day whose initial state will be saved in the model model: EgretModel The model whose values will be modifed ''' if day < self._first_day: day = self._first_day elif day > self._final_day: day = self._final_day actuals = self._get_actuals_by_date(day) for s, sdict in model.elements('storage'): soc = actuals.data['elements']['storage'][s][ 'initial_state_of_charge'] sdict['initial_state_of_charge'] = soc for g, gdict in model.elements('generator', generator_type='thermal'): source = actuals.data['elements']['generator'][g] gdict['initial_status'] = source['initial_status'] gdict['initial_p_output'] = source['initial_p_output']
def test_clone_at_timestamp(): md = ModelData(testdata) cloned_md = md.clone_at_timestamp(2.0) comparison_md = md.clone() comparison_md.data['elements']['load']['L1']['Pl'] = 111.1 assert cloned_md.data == comparison_md.data
def _copy_initial_state_into_model(options:Options, current_state:SimulationState, md:EgretModel): for g, g_dict in md.elements('generator', generator_type='thermal'): g_dict['initial_status'] = current_state.get_initial_generator_state(g) g_dict['initial_p_output'] = current_state.get_initial_power_generated(g) for s,s_dict in md.elements('storage'): s_dict['initial_state_of_charge'] = current_state.get_initial_state_of_charge(s)
def _populate_with_forecastable_data(self, options:Options, start_time:datetime, num_time_periods: int, time_period_length_minutes: int, model: EgretModel, identify_dat: Callable[[date], EgretModel] ) -> None: # For now, require the time period to always be 60 minutes assert(time_period_length_minutes == 60.0) step_delta = timedelta(minutes=time_period_length_minutes) # See if we have space to store all the requested data. # If not, only supply what we have space for if len(model.data['system']['time_keys']) < num_time_periods: num_time_periods = len(model.data['system']['time_keys']) # Collect a list of non-dispatchable generators renewables = list(model.elements('generator', generator_type='renewable')) start_hour = start_time.hour start_day = start_time.date() # Loop through each time step for step_index in range(0, num_time_periods): step_time = start_time + step_delta*step_index day = step_time.date() # 0-based hour, useable as index into forecast arrays hour = step_time.hour # For data starting at time 0, we collect tomorrow's data # from today's dat file if start_hour == 0 and day != start_day: day = start_day hour += 24 # If request is beyond the last day, just repeat the final day's values if day > self._final_day: day = self._final_day dat = identify_dat(day) # fill in renewables limits for gen, gdata in renewables: pmin = dat.data['elements']['generator'][gen]['p_min']['values'][hour] pmax = dat.data['elements']['generator'][gen]['p_max']['values'][hour] gdata['p_min']['values'][step_index] = pmin gdata['p_max']['values'][step_index] = pmax # Fill in load data for bus, bdata in model.elements('load'): load = dat.data['elements']['load'][bus]['p_load']['values'][hour] bdata['p_load']['values'][step_index] = load # Fill in reserve data reserve_req = dat.data['system']['reserve_requirement']['values'][hour] model.data['system']['reserve_requirement']['values'][step_index] = reserve_req
def test_clone_at_time_index(): md = ModelData(testdata) cloned_md = md.clone_at_time_index(2) comparison_md = md.clone() comparison_md.data['elements']['load']['L1']['Pl'] = 111.1 del comparison_md.data['system']['time_keys'] assert cloned_md.data == comparison_md.data
def test_uc_relaxation(): test_name = 'tiny_uc_tc' input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'.json') md_in = ModelData(input_json_file_name) md_results = solve_unit_commitment(md_in, solver=test_solver, slack_type=SlackType.TRANSMISSION_LIMITS, relaxed=True) reference_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'_relaxed_results.json') md_reference = ModelData(reference_json_file_name) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'], rel_tol=rel_tol)
def test_uc_runner(): test_names = ['tiny_uc_1', 'tiny_uc_2'] for test_name in test_names: input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'.json') md_in = ModelData(json.load(open(input_json_file_name, 'r'))) md_results = solve_unit_commitment(md_in, solver='cbc', mipgap=0.0) reference_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'_results.json') md_reference = ModelData(json.load(open(reference_json_file_name, 'r'))) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'])
def test_uc_runner(): test_names = ['tiny_uc_{}'.format(i) for i in range(1,12+1)] for test_name in test_names: input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'.json') md_in = ModelData(input_json_file_name) md_results = solve_unit_commitment(md_in, solver=test_solver, mipgap=0.0, slack_type=SlackType.BUS_BALANCE ) reference_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'_results.json') md_reference = ModelData(reference_json_file_name) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'], rel_tol=rel_tol)
def test_uc_relaxation(): test_name = 'tiny_uc_tc' input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'.json') md_in = ModelData(json.load(open(input_json_file_name, 'r'))) md_results = solve_unit_commitment(md_in, solver=test_solver, relaxed=True) reference_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'_relaxed_results.json') md_reference = ModelData(json.load(open(reference_json_file_name, 'r'))) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'], rel_tol=rel_tol)
def test_clone_at_time_indices(): md = ModelData(testdata) cloned_md = md.clone_at_time_keys([1, 2]) comparison_md = md.clone() comparison_md.data['elements']['load']['L1']['Pl'] = \ {'data_type':'time_series', 'values': [111.0, 111.1]} comparison_md.data['system']['time_keys'] = [1.0, 2.0] assert cloned_md.data == comparison_md.data
def test_uc_transmission_models(): ## the network tests can optionally specify some kwargs so we can pass them into solve_unit_commitment tc_networks = { 'btheta_power_flow': [dict()], 'ptdf_power_flow': [{ 'ptdf_options': { 'lazy': False } }, dict()], 'power_balance_constraints': [dict()], } no_network = 'copperplate_power_flow' test_names = ['tiny_uc_tc', 'tiny_uc_tc_2' ] ## based on tiny_uc_1, tiny_uc_tc_2 has an interface for test_name in test_names: input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name + '.json') md_in = ModelData(json.load(open(input_json_file_name, 'r'))) for tc in tc_networks: for kwargs in tc_networks[tc]: md_results = solve_unit_commitment( md_in, solver=test_solver, mipgap=0.0, uc_model_generator=_make_get_dcopf_uc_model(tc), **kwargs) reference_json_file_name = os.path.join( current_dir, 'uc_test_instances', test_name + '_results.json') md_reference = ModelData( json.load(open(reference_json_file_name, 'r'))) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'], rel_tol=rel_tol) ## test copperplate test_name = 'tiny_uc_1' md_results = solve_unit_commitment( md_in, solver=test_solver, mipgap=0.0, uc_model_generator=_make_get_dcopf_uc_model(no_network)) reference_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name + '_results.json') md_reference = ModelData(json.load(open(reference_json_file_name, 'r'))) assert math.isclose(md_reference.data['system']['total_cost'], md_results.data['system']['total_cost'], rel_tol=rel_tol)
def test_copperplate_dispatch_model(self, test_case, soln_case, include_kwargs=False): md_soln = ModelData() md_soln.read_from_json(soln_case) md_dict = create_ModelData(test_case) kwargs = {} if include_kwargs: kwargs = {'include_feasibility_slack':True} md, results = solve_copperplate_dispatch(md_dict, "ipopt", solver_tee=False, return_results=True, **kwargs) self.assertTrue(results.solver.termination_condition == TerminationCondition.optimal) comparison = math.isclose(md.data['system']['total_cost'], md_soln.data['system']['total_cost'], rel_tol=1e-6) self.assertTrue(comparison)
def test_individual_generator_stack_graph(self): """Tests stack graph generation when plotting individual generators.""" current_dir = os.path.dirname(os.path.abspath(__file__)) test_cases = [ os.path.join(current_dir, '..', '..', 'models', 'tests', 'uc_test_instances', 'test_case_{}.json'.format(i)) for i in range(1, 2) ] for test_case in test_cases: with open(test_case, 'r') as f: md_dict = json.load(f) md = ModelData(md_dict) solved_md = solve_unit_commitment( md, 'cbc', mipgap=0.001, timelimit=None, solver_tee=True, symbolic_solver_labels=False, solver_options=None, uc_model_generator=create_tight_unit_commitment_model, relaxed=False, return_model=False) with self.assertRaises(ValueError): # The number of generators in the test case exceeds the maximum for this feature. fig, ax = generate_stack_graph( solved_md, title=repr(test_case), show_individual_components=False, plot_individual_generators=True, )
def test_individual_component_stack_graph(self): """Tests stack graph generation when breaking out individual components per generation type.""" current_dir = os.path.dirname(os.path.abspath(__file__)) test_cases = [ os.path.join(current_dir, '..', '..', 'models', 'tests', 'uc_test_instances', 'test_case_{}.json'.format(i)) for i in range(1, 2) ] for test_case in test_cases: with open(test_case, 'r') as f: md_dict = json.load(f) md = ModelData(md_dict) solved_md = solve_unit_commitment( md, 'cbc', mipgap=0.001, timelimit=None, solver_tee=True, symbolic_solver_labels=False, solver_options=None, uc_model_generator=create_tight_unit_commitment_model, relaxed=False, return_model=False) fig, ax = generate_stack_graph( solved_md, title=repr(test_case), show_individual_components=True, plot_individual_generators=False, )
def test_uc_ptdf_serialization_deserialization(): test_name = 'tiny_uc_tc_2' ## based on tiny_uc_1 input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name + '.json') md_in = ModelData(json.load(open(input_json_file_name, 'r'))) ptdf_file_name = test_name + '.pickle' kwargs = {'ptdf_options': {'save_to': ptdf_file_name}} md_serialization = solve_unit_commitment( md_in, solver=test_solver, mipgap=0.0, uc_model_generator=_make_get_dcopf_uc_model('ptdf_power_flow'), **kwargs) ## ensure the file is present assert os.path.isfile(ptdf_file_name) kwargs = {'ptdf_options': {'load_from': ptdf_file_name}} md_deserialization = solve_unit_commitment( md_in, solver=test_solver, mipgap=0.0, uc_model_generator=_make_get_dcopf_uc_model('ptdf_power_flow'), **kwargs) assert math.isclose(md_serialization.data['system']['total_cost'], md_deserialization.data['system']['total_cost'], rel_tol=rel_tol)
def test_scale_unscale(): md = ModelData.read(scuc_fn) ## do type conversions original_base_MVA = md.data['system']['baseMVA'] md.data['system']['baseMVA'] = 1. scale_ModelData_to_pu(md, inplace=True) md.data['system']['baseMVA'] = original_base_MVA md_transformed = scale_ModelData_to_pu(md, inplace=False) # test inplace flag assert id(md.data) != id(md_transformed.data) unscale_ModelData_to_pu(md_transformed, inplace=True) assert md.data['system'] == md_transformed.data['system'] for esn, esd in md.data['elements'].items(): for en, ed in esd.items(): assert ed == md_transformed.data['elements'][esn][en] for esn, esd in md_transformed.data['elements'].items(): for en, ed in esd.items(): assert ed == md.data['elements'][esn][en]
def test_individual_generator_stack_graph_exception(self): """Tests for stack graph generation to fail when both 'show_individual_components' and 'plot_individual_generators' are simultaneously True.""" current_dir = os.path.dirname(os.path.abspath(__file__)) test_cases = [ os.path.join(current_dir, '..', '..', 'models', 'tests', 'uc_test_instances', 'test_case_{}.json'.format(i)) for i in range(1, 2) ] for test_case in test_cases: with open(test_case, 'r') as f: md_dict = json.load(f) md = ModelData(md_dict) solved_md = solve_unit_commitment( md, 'cbc', mipgap=0.001, timelimit=None, solver_tee=True, symbolic_solver_labels=False, solver_options=None, uc_model_generator=create_tight_unit_commitment_model, relaxed=False, return_model=False) with self.assertRaises(ValueError): # You cannot set both options to True simultaneously. fig, ax = generate_stack_graph( solved_md, title=repr(test_case), show_individual_components=True, plot_individual_generators=True, )
def test_uc_lazy_ptdf_thresholding(): test_name = 'tiny_uc_tc' input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'.json') md_in = ModelData(input_json_file_name) tc_sol_fn = test_name + '_relaxed_results.json' ntc_sol_fn = test_name + '_relaxed_unconstrained_results.json' tc_cost = json.load(open(os.path.join(current_dir, 'uc_test_instances', tc_sol_fn), 'r'))['system']['total_cost'] ntc_cost = json.load(open(os.path.join(current_dir, 'uc_test_instances', ntc_sol_fn), 'r'))['system']['total_cost'] # NOTE: This test case has one congested branch, one end of which has bus_kv = 300, and # the other end has bus_kv = 200, so these test cases attempt to capture all the # logic of the thresholding ptdf_sol_options = [ (tc_cost, {'branch_kv_threshold':100, 'kv_threshold_type':'one' }), (tc_cost, {'branch_kv_threshold':100, 'kv_threshold_type':'both'}), (tc_cost, {'branch_kv_threshold':200, 'kv_threshold_type':'one' }), (tc_cost, {'branch_kv_threshold':200, 'kv_threshold_type':'both'}), (tc_cost, {'branch_kv_threshold':201, 'kv_threshold_type':'one' }), (ntc_cost, {'branch_kv_threshold':201, 'kv_threshold_type':'both'}), (tc_cost, {'branch_kv_threshold':300, 'kv_threshold_type':'one' }), (ntc_cost, {'branch_kv_threshold':300, 'kv_threshold_type':'both'}), (ntc_cost, {'branch_kv_threshold':301, 'kv_threshold_type':'one' }), (ntc_cost, {'branch_kv_threshold':301, 'kv_threshold_type':'both'}), ] for c, ptdf_opt in ptdf_sol_options: md_results = solve_unit_commitment(md_in, solver=test_solver, relaxed=True, slack_type=SlackType.TRANSMISSION_LIMITS, ptdf_options=ptdf_opt) assert math.isclose(c, md_results.data['system']['total_cost'], rel_tol=rel_tol)
def test_uc_ptdf_termination(): test_name = 'tiny_uc_tc_3' input_json_file_name = os.path.join(current_dir, 'uc_test_instances', test_name+'.json') md_in = ModelData(input_json_file_name) kwargs = {'ptdf_options':{'lazy': True, 'rel_ptdf_tol':10.}} md_results, results = solve_unit_commitment(md_in, solver=test_solver, relaxed=True, slack_type=SlackType.TRANSMISSION_LIMITS, return_results=True, **kwargs) assert results.egret_metasolver['iterations'] == 1
def test_acopf_model(self, test_case, soln_case): md_soln = ModelData.read(soln_case) md_dict = create_ModelData(test_case) model, scaled_md = create_atan_acopf_model(md_dict) opt = pe.SolverFactory('ipopt') res = opt.solve(model) self.assertTrue(res.solver.termination_condition == TerminationCondition.optimal) self.assertAlmostEqual(pe.value(model.obj)/md_soln.data['system']['total_cost'], 1, 4)
def test_scaling_solve(): md = ModelData.read(tiny_uc_7_fn) assert md.data['system']['baseMVA'] == 1. mdo_unscaled = solve_unit_commitment(md, test_solver, relaxed=True) md.data['system']['baseMVA'] = 100. mdo_scaled = solve_unit_commitment(md, test_solver, relaxed=True) assert math.isclose(mdo_scaled.data['system']['total_cost'], mdo_unscaled.data['system']['total_cost'])
def test_elements(): md = ModelData(dict(testdata)) for n, e in md.elements(element_type="generator"): assert n == 'G1' or n == 'G2' if n == 'G1': assert e["generator_type"] == 'thermal' if n == 'G2': assert e["generator_type"] == 'solar' buses = dict(md.elements(element_type='bus')) assert len(buses) == 3 assert 'B1' in buses assert 'B2' in buses assert 'B3' in buses assert buses['B1']['bus_type'] == 'PV' buses = dict(md.elements(element_type='bus', bus_type='PQ')) assert len(buses) == 2 assert 'B2' in buses assert 'B3' in buses