From b823b8dae3e3a53de371f354f360d939b9af57bf Mon Sep 17 00:00:00 2001 From: ann-sherin Date: Mon, 26 Jun 2023 12:50:25 -0600 Subject: [PATCH 1/2] add more model checks --- disco/exceptions.py | 10 ++++++++++ .../upgrades/automated_thermal_upgrades.py | 3 ++- .../upgrades/automated_voltage_upgrades.py | 1 + .../upgrades/voltage_upgrade_functions.py | 17 +++++++++++++++++ 4 files changed, 30 insertions(+), 1 deletion(-) diff --git a/disco/exceptions.py b/disco/exceptions.py index dc6e8d3d..8704d485 100644 --- a/disco/exceptions.py +++ b/disco/exceptions.py @@ -33,6 +33,10 @@ class OpenDssCompileError(DiscoBaseException): class OpenDssConvergenceError(DiscoBaseException): """Raise when OpenDSS fails to converge""" + + +class OpenDSSModelError(DiscoBaseException): + """Raise when OpenDSS network model has issues""" class PyDssConvergenceError(DiscoBaseException): @@ -124,6 +128,12 @@ class InvalidOpenDssElementError(DiscoBaseException): "modify OpenDSS model for such instances.", "error_code": 125, }, + OpenDSSModelError: { + "description": "Issues identified in OpenDSS Network Model.", + "corrective_action": "This could happen in cases when voltage bases are incorrectly defined, network has isolated nodes, or lines/transformers are extremely overloaded. Check and " + "modify OpenDSS model for such instances.", + "error_code": 126, + }, } diff --git a/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py b/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py index 243c9cad..6970be43 100644 --- a/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py +++ b/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py @@ -7,7 +7,7 @@ from jade.utils.utils import load_data, dump_data from .thermal_upgrade_functions import * -from .voltage_upgrade_functions import plot_thermal_violations, plot_voltage_violations, plot_feeder +from .voltage_upgrade_functions import plot_thermal_violations, plot_voltage_violations, plot_feeder, check_circuit from disco.models.upgrade_cost_analysis_generic_input_model import UpgradeTechnicalCatalogModel from disco.models.upgrade_cost_analysis_generic_output_model import UpgradeViolationResultModel, AllUpgradesTechnicalResultModel @@ -156,6 +156,7 @@ def determine_thermal_upgrades( dump_data(overall_outputs, overall_output_summary_filepath, indent=2, allow_nan=False) title = "Feeder" plot_feeder(fig_folder=thermal_upgrades_directory, title=title, circuit_source=circuit_source, enable_detailed=True) + check_circuit(initial_results=initial_results) # check circuit before running upgrades # Mitigate thermal violations iteration_counter = 0 # if number of violations is very high, limit it to a small number diff --git a/disco/extensions/upgrade_simulation/upgrades/automated_voltage_upgrades.py b/disco/extensions/upgrade_simulation/upgrades/automated_voltage_upgrades.py index 4798b443..73c51b36 100644 --- a/disco/extensions/upgrade_simulation/upgrades/automated_voltage_upgrades.py +++ b/disco/extensions/upgrade_simulation/upgrades/automated_voltage_upgrades.py @@ -143,6 +143,7 @@ def determine_voltage_upgrades( else: overall_outputs = {"violation_summary": [temp_results]} dump_data(overall_outputs, overall_output_summary_filepath, indent=2, allow_nan=False) + check_circuit(initial_results=initial_results) # check circuit before running upgrades circuit_source = orig_ckt_info["source_bus"] bus_voltages_df, undervoltage_bus_list, overvoltage_bus_list, buses_with_violations = get_bus_voltages( voltage_upper_limit=voltage_upper_limit, voltage_lower_limit=voltage_lower_limit, **simulation_params) diff --git a/disco/extensions/upgrade_simulation/upgrades/voltage_upgrade_functions.py b/disco/extensions/upgrade_simulation/upgrades/voltage_upgrade_functions.py index 62c1fd3f..155ea809 100644 --- a/disco/extensions/upgrade_simulation/upgrades/voltage_upgrade_functions.py +++ b/disco/extensions/upgrade_simulation/upgrades/voltage_upgrade_functions.py @@ -10,6 +10,7 @@ from .common_functions import * from .thermal_upgrade_functions import define_xfmr_object +from disco.exceptions import OpenDSSModelError from disco import timer_stats_collector from disco.models.upgrade_cost_analysis_generic_output_model import VoltageUpgradesTechnicalResultModel from opendssdirect import DSSException @@ -29,6 +30,22 @@ EDGE_COLORLEGEND = {'Violation': {'edge_color': 'violet', 'edge_size': 75, 'alpha': 0.75, "label": "Line Violation"}} +def check_circuit(initial_results): + # raise flag is there are issues with network model + min_voltage_threshold = 0.6 + max_voltage_threshold = 2 + + G = generate_networkx_representation() + isolated = list(nx.isolates(G)) + if isolated: + raise OpenDSSModelError(f"OpenDSS model has isolated nodes: {isolated}") + if initial_results.min_bus_voltage < min_voltage_threshold: + raise OpenDSSModelError(f"Minimum bus voltage is less than {min_voltage_threshold}") + if initial_results.max_bus_voltage > max_voltage_threshold: + raise OpenDSSModelError(f"Maximum bus voltage is greater than {max_voltage_threshold}") + return + + def edit_capacitor_settings_for_convergence(voltage_config=None, control_command=''): """This function edits the dss command string with new capacitor settings, in case of convergence issues From 4c0ef8f676ead2b9ecf170dd81f27d17f12c01d5 Mon Sep 17 00:00:00 2001 From: ann-sherin Date: Wed, 28 Jun 2023 16:52:29 -0600 Subject: [PATCH 2/2] add flag to enable/disable thermal upgrades --- .../upgrades/automated_thermal_upgrades.py | 138 +++++++++--------- ...grade_cost_analysis_generic_input_model.py | 3 + 2 files changed, 76 insertions(+), 65 deletions(-) diff --git a/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py b/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py index 6970be43..9f900a4e 100644 --- a/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py +++ b/disco/extensions/upgrade_simulation/upgrades/automated_thermal_upgrades.py @@ -56,6 +56,8 @@ def determine_thermal_upgrades( voltage_upper_limit = thermal_config["voltage_upper_limit"] voltage_lower_limit = thermal_config["voltage_lower_limit"] + perform_thermal_upgrades = thermal_config["perform_thermal_upgrades"] + if thermal_config["read_external_catalog"]: with open(thermal_config["external_catalog"]) as json_file: external_upgrades_technical_catalog = json.load(json_file) @@ -110,7 +112,7 @@ def determine_thermal_upgrades( orig_capacitors_df = get_capacitor_info(correct_PT_ratio=False) feeder_stats["stage_results"].append(get_upgrade_stage_stats(dss, upgrade_stage="initial", upgrade_type="thermal", xfmr_loading_df=initial_xfmr_loading_df, line_loading_df=initial_line_loading_df, bus_voltages_df=initial_bus_voltages_df, capacitors_df=orig_capacitors_df, regcontrols_df=orig_regcontrols_df) ) - dump_data(feeder_stats, feeder_stats_json_file, indent=2) # save feeder stats + dump_data(feeder_stats, feeder_stats_json_file, indent=2) # save feeder stats if len(initial_overloaded_xfmr_list) > 0 or len(initial_overloaded_line_list) > 0: n = len(initial_overloaded_xfmr_list) + len(initial_overloaded_line_list) equipment_with_violations = {"Transformer": initial_xfmr_loading_df, "Line": initial_line_loading_df} @@ -169,73 +171,79 @@ def determine_thermal_upgrades( xfmr_upgrades_df = pd.DataFrame() overloaded_line_list = initial_overloaded_line_list overloaded_xfmr_list = initial_overloaded_xfmr_list - while (len(overloaded_line_list) > 0 or len(overloaded_xfmr_list) > 0) and ( - iteration_counter < max_upgrade_iteration): - line_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["line_upper_limit"], - equipment_type="line", ignore_switch=ignore_switch, **simulation_params) - overloaded_line_list = list(line_loading_df.loc[line_loading_df["status"] == "overloaded"]["name"].unique()) - logger.info(f"Iteration_{iteration_counter}: Determined line loadings.") - logger.info(f"Iteration_{iteration_counter}: Number of line violations: {len(overloaded_line_list)}") - before_upgrade_num_line_violations = len(overloaded_line_list) - if len(overloaded_line_list) > 0: - line_commands_list, temp_line_upgrades_df = correct_line_violations( - line_loading_df=line_loading_df, - line_design_pu=thermal_config["line_design_pu"], - line_upgrade_options=line_upgrade_options.copy(deep=True), - parallel_lines_limit=thermal_config["parallel_lines_limit"], - external_upgrades_technical_catalog=external_upgrades_technical_catalog,) - logger.info(f"Iteration_{iteration_counter}: Corrected line violations.") - commands_list = commands_list + line_commands_list - line_upgrades_df = pd.concat([line_upgrades_df, temp_line_upgrades_df]) - xfmr_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["transformer_upper_limit"], - equipment_type="transformer", **simulation_params) - overloaded_xfmr_list = list(xfmr_loading_df.loc[xfmr_loading_df["status"] == "overloaded"]["name"].unique()) - logger.info(f"Iteration_{iteration_counter}: Determined xfmr loadings.") - logger.info(f"Iteration_{iteration_counter}: Number of xfmr violations: {len(overloaded_xfmr_list)}") - before_upgrade_num_xfmr_violations = len(overloaded_xfmr_list) - - if len(overloaded_xfmr_list) > 0: - xfmr_commands_list, temp_xfmr_upgrades_df = correct_xfmr_violations( - xfmr_loading_df=xfmr_loading_df, - xfmr_design_pu=thermal_config["transformer_design_pu"], - xfmr_upgrade_options=xfmr_upgrade_options.copy(deep=True), - parallel_transformers_limit=thermal_config["parallel_transformers_limit"]) - logger.info(f"Iteration_{iteration_counter}: Corrected xfmr violations.") - commands_list = commands_list + xfmr_commands_list - xfmr_upgrades_df = pd.concat([xfmr_upgrades_df, temp_xfmr_upgrades_df]) - # compute loading after upgrades - xfmr_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["transformer_upper_limit"], - equipment_type="transformer", **simulation_params) - overloaded_xfmr_list = list(xfmr_loading_df.loc[xfmr_loading_df["status"] == "overloaded"]["name"].unique()) - line_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["line_upper_limit"], - equipment_type="line", ignore_switch=ignore_switch, **simulation_params) - overloaded_line_list = list(line_loading_df.loc[line_loading_df["status"] == "overloaded"]["name"].unique()) + if perform_thermal_upgrades: - if len(overloaded_line_list) > before_upgrade_num_line_violations: - logger.debug(overloaded_line_list) - logger.info("Write upgrades till this step to debug upgrades") - write_text_file(string_list=commands_list, text_file_path=thermal_upgrades_dss_filepath) - raise UpgradesInvalidViolationIncrease(f"Line violations increased from {before_upgrade_num_line_violations} to {len(overloaded_line_list)} " - f"during upgrade process") - if len(overloaded_xfmr_list) > before_upgrade_num_xfmr_violations: - logger.info("Write upgrades till this step to debug upgrades") - write_text_file(string_list=commands_list, text_file_path=thermal_upgrades_dss_filepath) - raise UpgradesInvalidViolationIncrease(f"Xfmr violations increased from {before_upgrade_num_xfmr_violations} to {len(overloaded_xfmr_list)} " - f"during upgrade process") + while (len(overloaded_line_list) > 0 or len(overloaded_xfmr_list) > 0) and ( + iteration_counter < max_upgrade_iteration): + line_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["line_upper_limit"], + equipment_type="line", ignore_switch=ignore_switch, **simulation_params) + overloaded_line_list = list(line_loading_df.loc[line_loading_df["status"] == "overloaded"]["name"].unique()) + logger.info(f"Iteration_{iteration_counter}: Determined line loadings.") + logger.info(f"Iteration_{iteration_counter}: Number of line violations: {len(overloaded_line_list)}") + before_upgrade_num_line_violations = len(overloaded_line_list) + if len(overloaded_line_list) > 0: + line_commands_list, temp_line_upgrades_df = correct_line_violations( + line_loading_df=line_loading_df, + line_design_pu=thermal_config["line_design_pu"], + line_upgrade_options=line_upgrade_options.copy(deep=True), + parallel_lines_limit=thermal_config["parallel_lines_limit"], + external_upgrades_technical_catalog=external_upgrades_technical_catalog,) + logger.info(f"Iteration_{iteration_counter}: Corrected line violations.") + commands_list = commands_list + line_commands_list + line_upgrades_df = pd.concat([line_upgrades_df, temp_line_upgrades_df]) + xfmr_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["transformer_upper_limit"], + equipment_type="transformer", **simulation_params) + overloaded_xfmr_list = list(xfmr_loading_df.loc[xfmr_loading_df["status"] == "overloaded"]["name"].unique()) + logger.info(f"Iteration_{iteration_counter}: Determined xfmr loadings.") + logger.info(f"Iteration_{iteration_counter}: Number of xfmr violations: {len(overloaded_xfmr_list)}") + before_upgrade_num_xfmr_violations = len(overloaded_xfmr_list) + + if len(overloaded_xfmr_list) > 0: + xfmr_commands_list, temp_xfmr_upgrades_df = correct_xfmr_violations( + xfmr_loading_df=xfmr_loading_df, + xfmr_design_pu=thermal_config["transformer_design_pu"], + xfmr_upgrade_options=xfmr_upgrade_options.copy(deep=True), + parallel_transformers_limit=thermal_config["parallel_transformers_limit"]) + logger.info(f"Iteration_{iteration_counter}: Corrected xfmr violations.") + commands_list = commands_list + xfmr_commands_list + xfmr_upgrades_df = pd.concat([xfmr_upgrades_df, temp_xfmr_upgrades_df]) + # compute loading after upgrades + xfmr_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["transformer_upper_limit"], + equipment_type="transformer", **simulation_params) + overloaded_xfmr_list = list(xfmr_loading_df.loc[xfmr_loading_df["status"] == "overloaded"]["name"].unique()) + line_loading_df = get_thermal_equipment_info(compute_loading=True, upper_limit=thermal_config["line_upper_limit"], + equipment_type="line", ignore_switch=ignore_switch, **simulation_params) + overloaded_line_list = list(line_loading_df.loc[line_loading_df["status"] == "overloaded"]["name"].unique()) + + if len(overloaded_line_list) > before_upgrade_num_line_violations: + logger.debug(overloaded_line_list) + logger.info("Write upgrades till this step to debug upgrades") + write_text_file(string_list=commands_list, text_file_path=thermal_upgrades_dss_filepath) + raise UpgradesInvalidViolationIncrease(f"Line violations increased from {before_upgrade_num_line_violations} to {len(overloaded_line_list)} " + f"during upgrade process") + if len(overloaded_xfmr_list) > before_upgrade_num_xfmr_violations: + logger.info("Write upgrades till this step to debug upgrades") + write_text_file(string_list=commands_list, text_file_path=thermal_upgrades_dss_filepath) + raise UpgradesInvalidViolationIncrease(f"Xfmr violations increased from {before_upgrade_num_xfmr_violations} to {len(overloaded_xfmr_list)} " + f"during upgrade process") - logger.info(f"Iteration_{iteration_counter}: Number of devices with violations after this iteration: Transformers:{len(overloaded_xfmr_list)}, Lines: {len(overloaded_line_list)}") - iteration_counter += 1 - if iteration_counter > max_upgrade_iteration: - logger.info(f"Max iterations limit reached, quitting algorithm. This means all thermal violations were not resolved with these limited iterations." - f"You can increase the Iteration limit in the thermal_config['upgrade_iteration_threshold']") - break - if iteration_counter > 1: - logger.info(f"Multiple iterations ({iteration_counter}) were needed to resolve thermal violations." - f"This indicates that feeder was extremely overloaded to start with.") + logger.info(f"Iteration_{iteration_counter}: Number of devices with violations after this iteration: Transformers:{len(overloaded_xfmr_list)}, Lines: {len(overloaded_line_list)}") + iteration_counter += 1 + if iteration_counter > max_upgrade_iteration: + logger.info(f"Max iterations limit reached, quitting algorithm. This means all thermal violations were not resolved with these limited iterations." + f"You can increase the Iteration limit in the thermal_config['upgrade_iteration_threshold']") + break + if iteration_counter > 1: + logger.info(f"Multiple iterations ({iteration_counter}) were needed to resolve thermal violations." + f"This indicates that feeder was extremely overloaded to start with.") + + if any("new " in string.lower() for string in commands_list): # if new equipment is added. + commands_list.append("CalcVoltageBases") + commands_list.append("Solve") + else: + commands_list = [] + logger.info(f" Thermal upgrades disabled - upgrades not performed.") - if any("new " in string.lower() for string in commands_list): # if new equipment is added. - commands_list.append("CalcVoltageBases") - commands_list.append("Solve") write_text_file(string_list=commands_list, text_file_path=thermal_upgrades_dss_filepath) redirect_command_list = create_upgraded_master_dss(dss_file_list=initial_dss_file_list + [thermal_upgrades_dss_filepath], upgraded_master_dss_filepath=upgraded_master_dss_filepath, original_master_filename=os.path.basename(master_path)) diff --git a/disco/models/upgrade_cost_analysis_generic_input_model.py b/disco/models/upgrade_cost_analysis_generic_input_model.py index c732d7c4..aadf11d1 100644 --- a/disco/models/upgrade_cost_analysis_generic_input_model.py +++ b/disco/models/upgrade_cost_analysis_generic_input_model.py @@ -306,6 +306,9 @@ class ThermalUpgradeParamsModel(UpgradeParamsBaseModel): ) # Optional fields + perform_thermal_upgrades: Optional[bool] = Field( + title="perform_thermal_upgrades", description="Flag to enable or disable thermal upgrades", default=True + ) create_plots: Optional[bool] = Field( title="create_plots", description="Flag to enable or disable figure creation", default=True )