From cc2f3ec0a57ded486fb128081e7f2df27366b616 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Mon, 11 Nov 2024 00:11:09 -0700 Subject: [PATCH 01/11] Add the minimum voltage for high voltage buses as a command line option --- disco/cli/pv_deployments.py | 14 ++++++++++++-- disco/sources/source_tree_1/pv_deployments.py | 14 +++++++++----- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/disco/cli/pv_deployments.py b/disco/cli/pv_deployments.py index ff40da45..3f259a41 100644 --- a/disco/cli/pv_deployments.py +++ b/disco/cli/pv_deployments.py @@ -25,7 +25,7 @@ logger = logging.getLogger(__name__) -def create_pv_deployments(input_path: str, hierarchy: str, config: dict): +def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: float): """A method for generating pv deployments""" hierarchy = DeploymentHierarchy(hierarchy) config = SimpleNamespace(**config) @@ -33,7 +33,7 @@ def create_pv_deployments(input_path: str, hierarchy: str, config: dict): print(f"'-p' or '--placement' should not be None for this action, choose from {PLACEMENT_CHOICE}") sys.exit() manager = PVDeploymentManager(input_path, hierarchy, config) - summary = manager.generate_pv_deployments() + summary = manager.generate_pv_deployments(hv_min=hv_min) print(json.dumps(summary, indent=2)) @@ -289,6 +289,13 @@ def pv_deployments(): default=random.randint(1, 1000000), help="Set an initial integer seed for making PV deployments reproducible" ) +@click.option( + "-mhv", "--minimum-high-voltage", + type=click.FLOAT, + default=1, + show_default=True, + help="Minimum voltage level for high voltage buses.", +) @click.option( "--verbose", type=click.BOOL, @@ -316,6 +323,7 @@ def source_tree_1( pv_upscale, pv_deployments_dirname, random_seed, + hv_min, verbose ): """Generate PV deployments for source tree 1.""" @@ -348,6 +356,8 @@ def source_tree_1( if action == "create-configs": args.append(control_name) args.append(kw_limit) + if action == "create-pv": + args.append(hv_min) action_function(*args) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 8550f5f6..b9c96d28 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -356,7 +356,7 @@ def load_pvdss_instance(self) -> PVDSSInstance: raise return pvdss_instance - def deploy_all_pv_scenarios(self) -> dict: + def deploy_all_pv_scenarios(self, hv_min) -> dict: """Given a feeder path, generate all PV scenarios for the feeder""" feeder_name = self.get_feeder_name() pvdss_instance = self.load_pvdss_instance() @@ -372,7 +372,7 @@ def deploy_all_pv_scenarios(self) -> dict: # combined bus distance customer_distance = pvdss_instance.get_customer_distance() - highv_buses = pvdss_instance.get_highv_buses() + highv_buses = pvdss_instance.get_highv_buses(kv_min=hv_min) combined_bus_distance = pvdss_instance.combine_bus_distances(customer_distance, highv_buses) if max(combined_bus_distance.values()) == 0: logger.warning( @@ -420,7 +420,7 @@ def deploy_all_pv_scenarios(self) -> dict: bus_kv=highv_buses.bus_kv, pv_records=pv_records, penetration=penetration, - sample=sample + sample=sample, ) existing_pv, pv_records = self.deploy_pv_scenario(data) @@ -696,6 +696,10 @@ def write_pv_string(self, pv_string: str, data: SimpleNamespace) -> None: def get_pv_bus_subset(self, bus_distance: dict, subset_idx: int, priority_buses: list) -> list: """Return candidate buses""" + if not bus_distance: + logger.warning("bus_distance is empty. Returning an empty candidate_bus_array.") + return [] + max_dist = max(bus_distance.values()) min_dist = min(bus_distance.values()) if self.config.placement == Placement.CLOSE.value: @@ -1584,7 +1588,7 @@ def __init__(self, input_path: str, hierarchy: DeploymentHierarchy, config: Simp """ super().__init__(input_path, hierarchy, config) - def generate_pv_deployments(self) -> dict: + def generate_pv_deployments(self, hv_min: float = 1) -> dict: """Given input path, generate pv deployments""" summary = {} feeder_paths = self.get_feeder_paths() @@ -1594,7 +1598,7 @@ def generate_pv_deployments(self) -> dict: "Set initial integer seed %s for PV deployments on feeder - %s", self.config.random_seed, feeder_path ) - feeder_stats = generator.deploy_all_pv_scenarios() + feeder_stats = generator.deploy_all_pv_scenarios(hv_min) summary[feeder_path] = feeder_stats return summary From d5da15ad1939b2096daa3bbe933733e9c74d1177 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Thu, 14 Nov 2024 09:50:46 -0700 Subject: [PATCH 02/11] Ensure no overlapping between high voltage and costumer load buses --- disco/sources/source_tree_1/pv_deployments.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index b9c96d28..0b9de991 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -373,6 +373,13 @@ def deploy_all_pv_scenarios(self, hv_min) -> dict: # combined bus distance customer_distance = pvdss_instance.get_customer_distance() highv_buses = pvdss_instance.get_highv_buses(kv_min=hv_min) + + # Filter out overlapping buses from customer_distance + customer_distance.bus_distance = { + bus: dist for bus, dist in customer_distance.bus_distance.items() + if bus not in highv_buses.hv_bus_distance + } + combined_bus_distance = pvdss_instance.combine_bus_distances(customer_distance, highv_buses) if max(combined_bus_distance.values()) == 0: logger.warning( From 691d125671baface76c06326d68e2322a9f1c328 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Thu, 14 Nov 2024 09:53:54 -0700 Subject: [PATCH 03/11] Set a voltage upper bound for high voltage buses --- disco/sources/source_tree_1/pv_deployments.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 0b9de991..c68c24eb 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -237,7 +237,7 @@ def get_customer_distance(self) -> SimpleNamespace: flag = dss.Loads.Next() return result - def get_highv_buses(self, kv_min: int = 1) -> SimpleNamespace: + def get_highv_buses(self, kv_min: float = 1, kv_max: float = None) -> SimpleNamespace: """Return highv buses""" result = SimpleNamespace(bus_kv={}, hv_bus_distance={}) flag = dss.Lines.First() @@ -246,7 +246,7 @@ def get_highv_buses(self, kv_min: int = 1) -> SimpleNamespace: for bus in buses: dss.Circuit.SetActiveBus(bus) kvbase = dss.Bus.kVBase() - if kvbase >= kv_min: + if kvbase >= kv_min and (kv_max is None or kvbase <= kv_max): result.bus_kv[bus] = dss.Bus.kVBase() result.hv_bus_distance[bus] = dss.Bus.Distance() flag = dss.Lines.Next() From b1fffe085af7cf2d29eac041ad1208d19e542ac8 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Thu, 14 Nov 2024 09:59:15 -0700 Subject: [PATCH 04/11] Add the maximum high voltage threshold to CLI --- disco/cli/pv_deployments.py | 15 ++++++++++++--- disco/sources/source_tree_1/pv_deployments.py | 8 ++++---- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/disco/cli/pv_deployments.py b/disco/cli/pv_deployments.py index 3f259a41..14775c57 100644 --- a/disco/cli/pv_deployments.py +++ b/disco/cli/pv_deployments.py @@ -25,7 +25,7 @@ logger = logging.getLogger(__name__) -def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: float): +def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: float, hv_max: float): """A method for generating pv deployments""" hierarchy = DeploymentHierarchy(hierarchy) config = SimpleNamespace(**config) @@ -33,7 +33,7 @@ def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: print(f"'-p' or '--placement' should not be None for this action, choose from {PLACEMENT_CHOICE}") sys.exit() manager = PVDeploymentManager(input_path, hierarchy, config) - summary = manager.generate_pv_deployments(hv_min=hv_min) + summary = manager.generate_pv_deployments(hv_min=hv_min, hv_max=hv_max) print(json.dumps(summary, indent=2)) @@ -290,12 +290,18 @@ def pv_deployments(): help="Set an initial integer seed for making PV deployments reproducible" ) @click.option( - "-mhv", "--minimum-high-voltage", + "-min-hv", "--minimum-high-voltage", type=click.FLOAT, default=1, show_default=True, help="Minimum voltage level for high voltage buses.", ) +@click.option( + "-max-hv", "--maximum-high-voltage", + type=click.FLOAT, + default=None, + help="Maximum voltage level for high voltage buses.", +) @click.option( "--verbose", type=click.BOOL, @@ -324,6 +330,7 @@ def source_tree_1( pv_deployments_dirname, random_seed, hv_min, + hv_max, verbose ): """Generate PV deployments for source tree 1.""" @@ -358,6 +365,8 @@ def source_tree_1( args.append(kw_limit) if action == "create-pv": args.append(hv_min) + args.append(hv_max) + action_function(*args) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index c68c24eb..701e8483 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -356,7 +356,7 @@ def load_pvdss_instance(self) -> PVDSSInstance: raise return pvdss_instance - def deploy_all_pv_scenarios(self, hv_min) -> dict: + def deploy_all_pv_scenarios(self, hv_min, hv_max) -> dict: """Given a feeder path, generate all PV scenarios for the feeder""" feeder_name = self.get_feeder_name() pvdss_instance = self.load_pvdss_instance() @@ -372,7 +372,7 @@ def deploy_all_pv_scenarios(self, hv_min) -> dict: # combined bus distance customer_distance = pvdss_instance.get_customer_distance() - highv_buses = pvdss_instance.get_highv_buses(kv_min=hv_min) + highv_buses = pvdss_instance.get_highv_buses(kv_min=hv_min, kv_max=hv_max) # Filter out overlapping buses from customer_distance customer_distance.bus_distance = { @@ -1595,7 +1595,7 @@ def __init__(self, input_path: str, hierarchy: DeploymentHierarchy, config: Simp """ super().__init__(input_path, hierarchy, config) - def generate_pv_deployments(self, hv_min: float = 1) -> dict: + def generate_pv_deployments(self, hv_min: float = 1, hv_max: float = None) -> dict: """Given input path, generate pv deployments""" summary = {} feeder_paths = self.get_feeder_paths() @@ -1605,7 +1605,7 @@ def generate_pv_deployments(self, hv_min: float = 1) -> dict: "Set initial integer seed %s for PV deployments on feeder - %s", self.config.random_seed, feeder_path ) - feeder_stats = generator.deploy_all_pv_scenarios(hv_min) + feeder_stats = generator.deploy_all_pv_scenarios(hv_min, hv_max) summary[feeder_path] = feeder_stats return summary From 0f3a3b647cd7c03e33333a6a04a39b2f907636f5 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Thu, 14 Nov 2024 14:32:31 -0700 Subject: [PATCH 05/11] add upper bounds for small and large pv sizes --- disco/cli/pv_deployments.py | 29 ++++++++++++++++--- disco/sources/source_tree_1/pv_deployments.py | 19 +++++++----- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/disco/cli/pv_deployments.py b/disco/cli/pv_deployments.py index 14775c57..1b83d9af 100644 --- a/disco/cli/pv_deployments.py +++ b/disco/cli/pv_deployments.py @@ -25,7 +25,7 @@ logger = logging.getLogger(__name__) -def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: float, hv_max: float): +def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: float, hv_max: float, **kwargs): """A method for generating pv deployments""" hierarchy = DeploymentHierarchy(hierarchy) config = SimpleNamespace(**config) @@ -33,7 +33,7 @@ def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: print(f"'-p' or '--placement' should not be None for this action, choose from {PLACEMENT_CHOICE}") sys.exit() manager = PVDeploymentManager(input_path, hierarchy, config) - summary = manager.generate_pv_deployments(hv_min=hv_min, hv_max=hv_max) + summary = manager.generate_pv_deployments(hv_min=hv_min, hv_max=hv_max, **kwargs) print(json.dumps(summary, indent=2)) @@ -302,6 +302,18 @@ def pv_deployments(): default=None, help="Maximum voltage level for high voltage buses.", ) +@click.option( + "-large-pv-max", "--large-pv-upper-bound", + type=click.FLOAT, + default=None, + help="Upper bound for large PV power.", +) +@click.option( + "-small-pv-max", "--small-pv-upper-bound", + type=click.FLOAT, + default=None, + help="Upper bound for small PV power.", +) @click.option( "--verbose", type=click.BOOL, @@ -331,6 +343,8 @@ def source_tree_1( random_seed, hv_min, hv_max, + large_pv_upper_bound, + small_pv_upper_bound, verbose ): """Generate PV deployments for source tree 1.""" @@ -360,14 +374,21 @@ def source_tree_1( } action_function = ACTION_MAPPING[action] args = [input_path, hierarchy, config] + kwargs = {} if action == "create-configs": args.append(control_name) args.append(kw_limit) if action == "create-pv": args.append(hv_min) args.append(hv_max) - - action_function(*args) + + if large_pv_upper_bound: + kwargs['large_pv_upper_bound'] = large_pv_upper_bound + + if small_pv_upper_bound: + kwargs['small_pv_upper_bound'] = small_pv_upper_bound + + action_function(*args, **kwargs) pv_deployments.add_command(source_tree_1) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 701e8483..3d39b5d6 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -356,7 +356,7 @@ def load_pvdss_instance(self) -> PVDSSInstance: raise return pvdss_instance - def deploy_all_pv_scenarios(self, hv_min, hv_max) -> dict: + def deploy_all_pv_scenarios(self, hv_min, hv_max, **kwargs) -> dict: """Given a feeder path, generate all PV scenarios for the feeder""" feeder_name = self.get_feeder_name() pvdss_instance = self.load_pvdss_instance() @@ -429,7 +429,7 @@ def deploy_all_pv_scenarios(self, hv_min, hv_max) -> dict: penetration=penetration, sample=sample, ) - existing_pv, pv_records = self.deploy_pv_scenario(data) + existing_pv, pv_records = self.deploy_pv_scenario(data, **kwargs) return feeder_stats.__dict__ @@ -463,7 +463,7 @@ def get_pv_systems_file(self, sample: int, penetration: int) -> str: pv_systems_file = os.path.join(penetration_path, PV_SYSTEMS_FILENAME) return pv_systems_file - def deploy_pv_scenario(self, data: SimpleNamespace) -> dict: + def deploy_pv_scenario(self, data: SimpleNamespace, **kwargs) -> dict: """Generate PV deployments dss file in scenario Parameters @@ -503,7 +503,7 @@ def deploy_pv_scenario(self, data: SimpleNamespace) -> dict: if base_min_pv_size > 0: continue min_pv_size = existing_pv[bus] - max_pv_size = self.get_maximum_pv_size(bus, data) + max_pv_size = self.get_maximum_pv_size(bus, data, **kwargs) random_pv_size = self.generate_pv_size_from_pdf(min_pv_size, max_pv_size) pv_size = min(random_pv_size, min_pv_size + remaining_pv_to_install) pv_added_capacity = pv_size - min_pv_size @@ -553,7 +553,7 @@ def deploy_pv_scenario(self, data: SimpleNamespace) -> dict: if (base_min_pv_size > 0 or min_pv_size > 0) and (not self.config.pv_upscale): pass else: - max_pv_size = self.get_maximum_pv_size(picked_candidate, data) + max_pv_size = self.get_maximum_pv_size(picked_candidate, data, **kwargs) random_pv_size = self.generate_pv_size_from_pdf(0, max_pv_size) pv_size = min(random_pv_size, remaining_pv_to_install) pv_string = self.add_pv_string(picked_candidate, pv_type.value, pv_size, pv_string) @@ -988,7 +988,8 @@ def get_categorical_remaining_pvs(self, data: SimpleNamespace) -> dict: @classmethod def get_maximum_pv_size(cls, bus: str, data: SimpleNamespace, **kwargs) -> int: - max_bus_pv_size = 100 * random.randint(1, 50) + upper_bound = kwargs.get('large_pv_upper_bound', 50) + max_bus_pv_size = 100 * random.randint(1, upper_bound) return max_bus_pv_size @@ -1014,6 +1015,8 @@ def get_maximum_pv_size(cls, bus: str, data: SimpleNamespace, max_load_factor: f annual_sun_hours = kwargs.get("annual_sun_hours", None) pv_size_array = [max_load_factor * data.bus_totalload[bus]] + if 'small_pv_upper_bound' in kwargs: + pv_size_array.append(kwargs['small_pv_upper_bound']) if roof_area and pv_efficiency: value = roof_area[bus] * pv_efficiency pv_size_array.append(value) @@ -1595,7 +1598,7 @@ def __init__(self, input_path: str, hierarchy: DeploymentHierarchy, config: Simp """ super().__init__(input_path, hierarchy, config) - def generate_pv_deployments(self, hv_min: float = 1, hv_max: float = None) -> dict: + def generate_pv_deployments(self, hv_min: float = 1, hv_max: float = None, **kwargs) -> dict: """Given input path, generate pv deployments""" summary = {} feeder_paths = self.get_feeder_paths() @@ -1605,7 +1608,7 @@ def generate_pv_deployments(self, hv_min: float = 1, hv_max: float = None) -> di "Set initial integer seed %s for PV deployments on feeder - %s", self.config.random_seed, feeder_path ) - feeder_stats = generator.deploy_all_pv_scenarios(hv_min, hv_max) + feeder_stats = generator.deploy_all_pv_scenarios(hv_min, hv_max, **kwargs) summary[feeder_path] = feeder_stats return summary From 53d187810f3ae63728d956f85acb52ca690aced4 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Thu, 14 Nov 2024 14:52:09 -0700 Subject: [PATCH 06/11] Prioritize user defined upper bound when selecting max small pv size --- disco/sources/source_tree_1/pv_deployments.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 3d39b5d6..01378746 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -1014,16 +1014,17 @@ def get_maximum_pv_size(cls, bus: str, data: SimpleNamespace, max_load_factor: f customer_annual_kwh = kwargs.get("customer_annual_kwh", {}) annual_sun_hours = kwargs.get("annual_sun_hours", None) - pv_size_array = [max_load_factor * data.bus_totalload[bus]] if 'small_pv_upper_bound' in kwargs: - pv_size_array.append(kwargs['small_pv_upper_bound']) - if roof_area and pv_efficiency: - value = roof_area[bus] * pv_efficiency - pv_size_array.append(value) - if customer_annual_kwh and annual_sun_hours: - value = customer_annual_kwh[bus] / annual_sun_hours - pv_size_array.append(value) - max_bus_pv_size = min(pv_size_array) + max_bus_pv_size = [kwargs['small_pv_upper_bound']] + else: + pv_size_array = [max_load_factor * data.bus_totalload[bus]] + if roof_area and pv_efficiency: + value = roof_area[bus] * pv_efficiency + pv_size_array.append(value) + if customer_annual_kwh and annual_sun_hours: + value = customer_annual_kwh[bus] / annual_sun_hours + pv_size_array.append(value) + max_bus_pv_size = min(pv_size_array) return max_bus_pv_size From f972b0bd76305fdd57b2c7c4278a6c3a382058b4 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Mon, 18 Nov 2024 12:21:41 -0700 Subject: [PATCH 07/11] WIP --- disco/cli/pv_deployments.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/disco/cli/pv_deployments.py b/disco/cli/pv_deployments.py index 1b83d9af..370766f9 100644 --- a/disco/cli/pv_deployments.py +++ b/disco/cli/pv_deployments.py @@ -290,26 +290,26 @@ def pv_deployments(): help="Set an initial integer seed for making PV deployments reproducible" ) @click.option( - "-min-hv", "--minimum-high-voltage", + "-v", "--min-high-voltage", type=click.FLOAT, default=1, show_default=True, help="Minimum voltage level for high voltage buses.", ) @click.option( - "-max-hv", "--maximum-high-voltage", + "-w", "--max-high-voltage", type=click.FLOAT, default=None, help="Maximum voltage level for high voltage buses.", ) @click.option( - "-large-pv-max", "--large-pv-upper-bound", + "-L", "--large-pv-upper-bound", type=click.FLOAT, default=None, help="Upper bound for large PV power.", ) @click.option( - "-small-pv-max", "--small-pv-upper-bound", + "-X", "--small-pv-upper-bound", type=click.FLOAT, default=None, help="Upper bound for small PV power.", @@ -341,8 +341,8 @@ def source_tree_1( pv_upscale, pv_deployments_dirname, random_seed, - hv_min, - hv_max, + min_high_voltage, + max_high_voltage, large_pv_upper_bound, small_pv_upper_bound, verbose @@ -379,8 +379,8 @@ def source_tree_1( args.append(control_name) args.append(kw_limit) if action == "create-pv": - args.append(hv_min) - args.append(hv_max) + args.append(min_high_voltage) + args.append(max_high_voltage) if large_pv_upper_bound: kwargs['large_pv_upper_bound'] = large_pv_upper_bound From ee121028ba54c5bdbb2c29250045d1969bced574 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Mon, 2 Dec 2024 10:55:19 -0700 Subject: [PATCH 08/11] Initial changes to PV deployment done --- disco/sources/source_tree_1/pv_deployments.py | 56 +++---------------- 1 file changed, 8 insertions(+), 48 deletions(-) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 01378746..2b2f4795 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -346,7 +346,7 @@ def load_pvdss_instance(self) -> PVDSSInstance: # is incorrect. # unidecode is no longer being installed with disco. # pvdss_instance.convert_to_ascii() - pvdss_instance.disable_loadshapes_redirect() + # pvdss_instance.disable_loadshapes_redirect() pvdss_instance.load_feeder() flag = pvdss_instance.ensure_energy_meter() if flag: @@ -375,10 +375,10 @@ def deploy_all_pv_scenarios(self, hv_min, hv_max, **kwargs) -> dict: highv_buses = pvdss_instance.get_highv_buses(kv_min=hv_min, kv_max=hv_max) # Filter out overlapping buses from customer_distance - customer_distance.bus_distance = { - bus: dist for bus, dist in customer_distance.bus_distance.items() - if bus not in highv_buses.hv_bus_distance - } + # customer_distance.bus_distance = { + # bus: dist for bus, dist in customer_distance.bus_distance.items() + # if bus not in highv_buses.hv_bus_distance + # } combined_bus_distance = pvdss_instance.combine_bus_distances(customer_distance, highv_buses) if max(combined_bus_distance.values()) == 0: @@ -1255,9 +1255,6 @@ def __init__(self, input_path: str, hierarchy: DeploymentHierarchy, config: Simp super().__init__(input_path, hierarchy, config) def redirect(self, input_path: str) -> bool: - """Given a path, update the master file by redirecting PVShapes.dss""" - self._copy_pv_shapes_file(input_path) - master_file = os.path.join(input_path, self.config.master_filename) if not os.path.exists(master_file): raise FileNotFoundError(f"{self.config.master_filename} not found in {input_path}") @@ -1282,30 +1279,6 @@ def redirect(self, input_path: str) -> bool: fw.writelines(data) return True - def _copy_pv_shapes_file(self, input_path: str) -> None: - """Copy PVShapes.dss file from source to feeder/substatation directories""" - input_path = Path(input_path) - # NOTE: Coordinate different path patterns among different cities - if "solar_none_batteries_none_timeseries" in str(input_path): - index = 3 if input_path.parent.name == "opendss" else 4 - else: - index = 4 if input_path.parent.name == "opendss" else 5 - src_file = input_path.parents[index] / "pv-profiles" / PV_SHAPES_FILENAME - if not src_file.exists(): - raise ValueError("PVShapes.dss file does not exist - " + str(src_file)) - dst_file = input_path / PV_SHAPES_FILENAME - - with open(src_file, "r") as fr, open(dst_file, "w") as fw: - new_lines = [] - for line in fr.readlines(): - pv_profile = re.findall(r"file=[a-zA-Z0-9\-\_\/\.]*", line)[0] - city_path = Path(os.path.sep.join([".."] * (index + 1))) - relative_pv_profile = city_path / "pv-profiles" / os.path.basename(pv_profile) - relative_pv_profile = "file=" + str(relative_pv_profile) - new_line = line.replace(pv_profile, relative_pv_profile) - new_lines.append(new_line) - fw.writelines(new_lines) - def redirect_substation_pv_shapes(self) -> None: """Run PVShapes redirect in substation directories in parallel""" substation_paths = self.get_substation_paths() @@ -1414,8 +1387,7 @@ def transform(self, feeder_path: str) -> None: load_lines = fr.readlines() rekeyed_load_dict = self.build_load_dictionary(load_lines) updated_lines = self.update_loads(load_lines, rekeyed_load_dict) - new_lines = self.strip_pv_profile(updated_lines) - fw.writelines(new_lines) + fw.writelines(updated_lines) logger.info("Loads transformed - '%s'.", loads_file) def restore_loads_file(self, original_loads_file: str) -> bool: @@ -1449,19 +1421,6 @@ def backup_loads_file(self, loads_file: str) -> bool: pass return True - def strip_pv_profile(self, load_lines: list) -> list: - """To strip 'yearly=' from load lines during PV deployments""" - regex = re.compile(r"\syearly=\S+", flags=re.IGNORECASE) - new_lines = [] - for line in load_lines: - match = regex.search(line.strip()) - if not match: - new_lines.append(line) - else: - line = "".join(line.split(match.group(0))) - new_lines.append(line) - return new_lines - def get_attribute(self, line: str, attribute_id: str) -> str: """ Get the attribute from line string. @@ -1561,7 +1520,8 @@ def update_loads(self, lines: dict, rekeyed_load_dict: dict) -> list: kv = v["kv"] phases = v["phases"] - lowered_line = lines[k].lower() + #lowered_line = lines[k].lower() + lowered_line = lines[k] lowered_line = lowered_line.replace(f"kv={self.get_attribute(lines[k], 'kv=')}", f"kv={kv}") lowered_line = lowered_line.replace(f"phases={self.get_attribute(lines[k], 'phases=')}", f"phases={phases}") if "kw=" in lowered_line: From 7b3907034122e96089439857d8a4f79ffdebd97d Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Mon, 9 Dec 2024 12:37:03 -0700 Subject: [PATCH 09/11] set maximum voltage level for customer buses and ensure no overlap with high voltage buses --- disco/cli/pv_deployments.py | 31 ++++--------------- disco/sources/source_tree_1/pv_deployments.py | 27 ++++++++++------ 2 files changed, 23 insertions(+), 35 deletions(-) diff --git a/disco/cli/pv_deployments.py b/disco/cli/pv_deployments.py index 370766f9..a950d7e8 100644 --- a/disco/cli/pv_deployments.py +++ b/disco/cli/pv_deployments.py @@ -25,7 +25,7 @@ logger = logging.getLogger(__name__) -def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: float, hv_max: float, **kwargs): +def create_pv_deployments(input_path: str, hierarchy: str, config: dict, max_bus_voltage: float, **kwargs): """A method for generating pv deployments""" hierarchy = DeploymentHierarchy(hierarchy) config = SimpleNamespace(**config) @@ -33,7 +33,7 @@ def create_pv_deployments(input_path: str, hierarchy: str, config: dict, hv_min: print(f"'-p' or '--placement' should not be None for this action, choose from {PLACEMENT_CHOICE}") sys.exit() manager = PVDeploymentManager(input_path, hierarchy, config) - summary = manager.generate_pv_deployments(hv_min=hv_min, hv_max=hv_max, **kwargs) + summary = manager.generate_pv_deployments(max_bus_voltage=max_bus_voltage, **kwargs) print(json.dumps(summary, indent=2)) @@ -290,23 +290,10 @@ def pv_deployments(): help="Set an initial integer seed for making PV deployments reproducible" ) @click.option( - "-v", "--min-high-voltage", - type=click.FLOAT, - default=1, - show_default=True, - help="Minimum voltage level for high voltage buses.", -) -@click.option( - "-w", "--max-high-voltage", + "-w", "--max-bus-voltage", type=click.FLOAT, default=None, - help="Maximum voltage level for high voltage buses.", -) -@click.option( - "-L", "--large-pv-upper-bound", - type=click.FLOAT, - default=None, - help="Upper bound for large PV power.", + help="Maximum voltage level for customer buses in kV.", ) @click.option( "-X", "--small-pv-upper-bound", @@ -341,9 +328,7 @@ def source_tree_1( pv_upscale, pv_deployments_dirname, random_seed, - min_high_voltage, - max_high_voltage, - large_pv_upper_bound, + max_bus_voltage, small_pv_upper_bound, verbose ): @@ -379,11 +364,7 @@ def source_tree_1( args.append(control_name) args.append(kw_limit) if action == "create-pv": - args.append(min_high_voltage) - args.append(max_high_voltage) - - if large_pv_upper_bound: - kwargs['large_pv_upper_bound'] = large_pv_upper_bound + args.append(max_bus_voltage) if small_pv_upper_bound: kwargs['small_pv_upper_bound'] = small_pv_upper_bound diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 2b2f4795..4beb3dc1 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -226,18 +226,20 @@ def get_total_loads(self) -> SimpleNamespace: flag = dss.Loads.Next() return result - def get_customer_distance(self) -> SimpleNamespace: + def get_customer_distance(self, max_bus_voltage: float) -> SimpleNamespace: """Return custmer distance""" result = SimpleNamespace(load_distance={}, bus_distance={}) flag = dss.Loads.First() while flag > 0: dss.Circuit.SetActiveBus(dss.Properties.Value("bus1")) - result.load_distance[dss.Loads.Name()] = dss.Bus.Distance() - result.bus_distance[dss.Properties.Value("bus1")] = dss.Bus.Distance() + kvbase = dss.Bus.kVBase() + if kvbase <= max_bus_voltage: + result.load_distance[dss.Loads.Name()] = dss.Bus.Distance() + result.bus_distance[dss.Properties.Value("bus1")] = dss.Bus.Distance() flag = dss.Loads.Next() return result - def get_highv_buses(self, kv_min: float = 1, kv_max: float = None) -> SimpleNamespace: + def get_highv_buses(self, kv_min: int = 1) -> SimpleNamespace: """Return highv buses""" result = SimpleNamespace(bus_kv={}, hv_bus_distance={}) flag = dss.Lines.First() @@ -246,7 +248,7 @@ def get_highv_buses(self, kv_min: float = 1, kv_max: float = None) -> SimpleName for bus in buses: dss.Circuit.SetActiveBus(bus) kvbase = dss.Bus.kVBase() - if kvbase >= kv_min and (kv_max is None or kvbase <= kv_max): + if kvbase >= kv_min: result.bus_kv[bus] = dss.Bus.kVBase() result.hv_bus_distance[bus] = dss.Bus.Distance() flag = dss.Lines.Next() @@ -356,7 +358,7 @@ def load_pvdss_instance(self) -> PVDSSInstance: raise return pvdss_instance - def deploy_all_pv_scenarios(self, hv_min, hv_max, **kwargs) -> dict: + def deploy_all_pv_scenarios(self, max_bus_voltage, **kwargs) -> dict: """Given a feeder path, generate all PV scenarios for the feeder""" feeder_name = self.get_feeder_name() pvdss_instance = self.load_pvdss_instance() @@ -371,14 +373,19 @@ def deploy_all_pv_scenarios(self, hv_min, hv_max, **kwargs) -> dict: ) # combined bus distance - customer_distance = pvdss_instance.get_customer_distance() - highv_buses = pvdss_instance.get_highv_buses(kv_min=hv_min, kv_max=hv_max) + customer_distance = pvdss_instance.get_customer_distance(max_bus_voltage) + highv_buses = pvdss_instance.get_highv_buses() # Filter out overlapping buses from customer_distance # customer_distance.bus_distance = { # bus: dist for bus, dist in customer_distance.bus_distance.items() # if bus not in highv_buses.hv_bus_distance # } + # Remove redundant buses from highv_buses + highv_buses.hv_bus_distance = { + bus: dist for bus, dist in highv_buses.hv_bus_distance.items() + if bus not in customer_distance.bus_distance + } combined_bus_distance = pvdss_instance.combine_bus_distances(customer_distance, highv_buses) if max(combined_bus_distance.values()) == 0: @@ -1559,7 +1566,7 @@ def __init__(self, input_path: str, hierarchy: DeploymentHierarchy, config: Simp """ super().__init__(input_path, hierarchy, config) - def generate_pv_deployments(self, hv_min: float = 1, hv_max: float = None, **kwargs) -> dict: + def generate_pv_deployments(self, max_bus_voltage: float = 1, **kwargs) -> dict: """Given input path, generate pv deployments""" summary = {} feeder_paths = self.get_feeder_paths() @@ -1569,7 +1576,7 @@ def generate_pv_deployments(self, hv_min: float = 1, hv_max: float = None, **kwa "Set initial integer seed %s for PV deployments on feeder - %s", self.config.random_seed, feeder_path ) - feeder_stats = generator.deploy_all_pv_scenarios(hv_min, hv_max, **kwargs) + feeder_stats = generator.deploy_all_pv_scenarios(max_bus_voltage, **kwargs) summary[feeder_path] = feeder_stats return summary From 8f66b2b617a2959388492f5490edd63b54959a0a Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Mon, 9 Dec 2024 12:37:21 -0700 Subject: [PATCH 10/11] remove unused code --- disco/sources/source_tree_1/pv_deployments.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 4beb3dc1..0913f18a 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -376,11 +376,6 @@ def deploy_all_pv_scenarios(self, max_bus_voltage, **kwargs) -> dict: customer_distance = pvdss_instance.get_customer_distance(max_bus_voltage) highv_buses = pvdss_instance.get_highv_buses() - # Filter out overlapping buses from customer_distance - # customer_distance.bus_distance = { - # bus: dist for bus, dist in customer_distance.bus_distance.items() - # if bus not in highv_buses.hv_bus_distance - # } # Remove redundant buses from highv_buses highv_buses.hv_bus_distance = { bus: dist for bus, dist in highv_buses.hv_bus_distance.items() From 617dbf9066adf134c62134564bf4ebcdf7e6e761 Mon Sep 17 00:00:00 2001 From: Tengis Dashmunkh Date: Mon, 9 Dec 2024 13:29:39 -0700 Subject: [PATCH 11/11] Specify the maximum PV size and choose a random value for a scenario --- disco/cli/pv_deployments.py | 2 +- disco/sources/source_tree_1/pv_deployments.py | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/disco/cli/pv_deployments.py b/disco/cli/pv_deployments.py index a950d7e8..904a0838 100644 --- a/disco/cli/pv_deployments.py +++ b/disco/cli/pv_deployments.py @@ -299,7 +299,7 @@ def pv_deployments(): "-X", "--small-pv-upper-bound", type=click.FLOAT, default=None, - help="Upper bound for small PV power.", + help="Upper bound for small PV power in kVA.", ) @click.option( "--verbose", diff --git a/disco/sources/source_tree_1/pv_deployments.py b/disco/sources/source_tree_1/pv_deployments.py index 0913f18a..9f082a27 100644 --- a/disco/sources/source_tree_1/pv_deployments.py +++ b/disco/sources/source_tree_1/pv_deployments.py @@ -643,8 +643,11 @@ def get_maximum_pv_size(cls, bus: str, data: SimpleNamespace, **kwargs) -> float @staticmethod def generate_pv_size_from_pdf(min_size: float, max_size: float, pdf: Sequence = None) -> float: - # TODO: A placeholder function for later update - pv_size = max_size + if pdf is None: + pv_size = random.uniform(min_size, max_size) + else: + # TODO: A placeholder function for later update + pv_size = max_size return pv_size def add_pv_string(self, bus: str, pv_type: str, pv_size: float, pv_string: str) -> str: @@ -1017,7 +1020,7 @@ def get_maximum_pv_size(cls, bus: str, data: SimpleNamespace, max_load_factor: f annual_sun_hours = kwargs.get("annual_sun_hours", None) if 'small_pv_upper_bound' in kwargs: - max_bus_pv_size = [kwargs['small_pv_upper_bound']] + max_bus_pv_size = kwargs['small_pv_upper_bound'] else: pv_size_array = [max_load_factor * data.bus_totalload[bus]] if roof_area and pv_efficiency: