From 6febd6bebaf4efaf3728268c4b356846621271c3 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Thu, 25 Aug 2022 11:37:47 -0600 Subject: [PATCH 01/83] rm redundant logging from opendss write.py --- ditto/writers/opendss/write.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 6d1ca197..961bacc5 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -169,7 +169,6 @@ def write(self, model, **kwargs): self.separate_substations = False # Write the bus coordinates - logger.info("Writing the bus coordinates...") if self.verbose: logger.debug("Writing the bus coordinates...") s = self.write_bus_coordinates(model) @@ -177,7 +176,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # Write the transformers - logger.info("Writing the transformers...") if self.verbose: logger.debug("Writing the transformers...") s = self.write_transformers(model) @@ -185,7 +183,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # Write the regulators - logger.info("Writing the regulators...") if self.verbose: logger.debug("Writing the regulators...") s = self.write_regulators(model) @@ -193,7 +190,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # write the timeseries - logger.info("Writing the timeseries...") if self.verbose: logger.debug("Writing the timeseries...") s = self.write_timeseries(model) @@ -201,7 +197,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # write the loads - logger.info("Writing the loads...") if self.verbose: logger.debug("Writing the loads...") s = self.write_loads(model) @@ -209,7 +204,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # Write the lines - logger.info("Writting the lines...") if self.verbose: logger.debug("Writting the lines...") s = self.write_lines(model) @@ -217,7 +211,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # Write the capacitors - self.logger.info("Writing the capacitors...") if self.verbose: logger.debug("Writing the capacitors...") s = self.write_capacitors(model) @@ -225,7 +218,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # Write the storage elements - logger.info("Writting the storage devices...") if self.verbose: logger.debug("Writting the storage devices...") s = self.write_storages(model) @@ -233,7 +225,6 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # Write the PV - logger.info("Writting the PVs...") if self.verbose: logger.debug("Writting the PVs...") s = self.write_PVs(model) @@ -241,14 +232,12 @@ def write(self, model, **kwargs): logger.debug("Succesful!") # Write the Master file - logger.info("Writting the master file...") if self.verbose: logger.debug("Writting the master file...") s = self.write_master_file(model) if self.verbose and s != -1: logger.debug("Succesful!") - logger.info("Done.") if self.verbose: logger.debug("Writting done.") From c2a780fe00b4408658661401acb29768f87c9810 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Thu, 25 Aug 2022 11:56:51 -0600 Subject: [PATCH 02/83] fix openDSS writer kvarlimit is now kvarmax --- ditto/writers/opendss/write.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 961bacc5..63cdec00 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -1387,7 +1387,7 @@ def write_PVs(self, model): ) # DiTTo in watts if hasattr(i, "reactive_rating") and i.reactive_rating is not None: - txt += " kvarlimit={kvar}".format( + txt += " kvarmax={kvar}".format( kvar=i.reactive_rating * 10 ** -3 # Set the inverter to be oversized by 10% if active rating not specified From fc3fd0269a1fdc89ce5a7a0138df3f7bfc769e0e Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 09:17:45 -0600 Subject: [PATCH 03/83] cyme reader: improve warning msgs for bad load values --- ditto/readers/cyme/read.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 43012000..a5b42423 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -5714,9 +5714,7 @@ def parse_loads(self, model): try: p, q = float(settings["value1"]), float(settings["value2"]) except: - logger.warning( - "WARNING:: Skipping load on section {}".format(sectionID) - ) + logger.warning(f"Problem with load on section {sectionID} with value_type=0 (P,Q). value1={settings['value1']} value2={settings['value2']}") continue elif value_type == 1: # KVA and PF are given try: @@ -5729,9 +5727,7 @@ def parse_loads(self, model): p = kva * PF q = math.sqrt(kva ** 2 - p ** 2) except: - logger.warning( - "WARNING:: Skipping load on section {}".format(sectionID) - ) + logger.warning(f"Problem with load on section {sectionID} with value_type=1 (kVa, PF). value1={settings['value1']} value2={settings['value2']}") continue elif value_type == 2: # P and PF are given @@ -5743,16 +5739,15 @@ def parse_loads(self, model): PF /= 100.0 q = p * math.sqrt((1 - PF ** 2) / PF ** 2) else: - logger.warning("problem with PF") - logger.warning(PF) + logger.warning(f"Problem with load on section {sectionID} with value_type=2 (P, PF). value1={settings['value1']} value2={settings['value2']}") except: - logger.warning("Skipping load on section {}".format(sectionID)) + logger.warning(f"Problem with load on section {sectionID} with value_type=2 (P, PF). value1={settings['value1']} value2={settings['value2']}") continue elif value_type == 3: # AMP and PF are given # TODO logger.warning( - "WARNING:: Skipping load on section {}".format(sectionID) + "WARNING:: Skipping load on section {} because value_type=3 (AMP and PF)".format(sectionID) ) continue From 218e9746c69ebf706f402a9145777d185523a770 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 09:20:40 -0600 Subject: [PATCH 04/83] handle zero power factor in cyme reader for loads --- ditto/readers/cyme/read.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index a5b42423..7919193a 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -5733,7 +5733,9 @@ def parse_loads(self, model): try: p, PF = float(settings["value1"]), float(settings["value2"]) - if 0 <= PF <= 1: + if PF == 0: + q = 0 + elif 0 < PF <= 1: q = p * math.sqrt((1 - PF ** 2) / PF ** 2) elif 1 < PF <= 100: PF /= 100.0 From 753649434c458cf03b7ba11414c6fb554883a6b0 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 09:47:11 -0600 Subject: [PATCH 05/83] handle negative power factor in cyme reader --- ditto/readers/cyme/read.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 7919193a..fb20c0a6 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -5735,9 +5735,9 @@ def parse_loads(self, model): p, PF = float(settings["value1"]), float(settings["value2"]) if PF == 0: q = 0 - elif 0 < PF <= 1: + elif 0 < abs(PF) <= 1: q = p * math.sqrt((1 - PF ** 2) / PF ** 2) - elif 1 < PF <= 100: + elif 1 < abs(PF) <= 100: PF /= 100.0 q = p * math.sqrt((1 - PF ** 2) / PF ** 2) else: From 7377b65b00ba3a8136224e6d1750f249af147e13 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 11:18:18 -0600 Subject: [PATCH 06/83] rm debug msg in cyme reader not sure why this is here, just printing cable sectionID --- ditto/readers/cyme/read.py | 1 - 1 file changed, 1 deletion(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index fb20c0a6..1f4da5d8 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -3024,7 +3024,6 @@ def parse_lines(self, model): line_data = self.concentric_neutral_cable[settings["linecableid"]] line_data["type"] = "balanced_line" if settings["linecableid"] in self.cables: - logger.debug("cables {}".format(sectionID)) line_data = self.cables[settings["linecableid"]] line_data["type"] = "balanced_line" From 943d497bac51f2f13bf567cbb739cd445ee8dfea Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 13:28:29 -0600 Subject: [PATCH 07/83] cyme reader clarify parser_helper role - cannot parse a list of objects though it was taking a obj_list as input; - it would only parse the first value in the list; - now it takes in a string instead of list of string - also improved notes/documentation --- ditto/readers/cyme/read.py | 148 ++++++++++++++++++------------------- 1 file changed, 73 insertions(+), 75 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 1f4da5d8..aa9f9c94 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -328,9 +328,9 @@ def update_header_mapping(self, update): # Replace the old mapping by the new one self.header_mapping = new_mapping - def get_file_content(self, filename): + def get_file_content(self, filename: str) -> None: """ - Open the requested file and returns the content. + Open the requested file and set self.content to iter(file_pointer.readlines()) For convinience, filename can be either the full file path or: -'network': Will get the content of the network file given in the constructor @@ -719,7 +719,7 @@ def transformer_connection_configuration_mapping(self, value, winding): return res[winding] - def check_object_in_line(self, line, obj): + def check_object_in_line(self, line: str, obj: str) -> bool: """ Check if the header corresponding to object is in the given line. @@ -754,13 +754,14 @@ def check_object_in_line(self, line, obj): return np.any([x in line for x in self.header_mapping[obj]]) - def parser_helper(self, line, obj_list, attribute_list, mapping, *args, **kwargs): + def parser_helper(self, line, obj, attribute_list, mapping: dict, *args, **kwargs) -> dict: """ .. warning:: This is a helper function for the parsers. Do not use directly. - Takes as input the list of objects we want to parse as well as the list of attributes we want to extract. - Also takes the default positions of the attributes (mapping). - The function returns a list of dictionaries, where each dictionary contains the values of the desired attributes of a CYME object. + Takes as input the object we want to parse (eg. "section" maps to "[SECTION]") + as well as the list of attributes we want to extract (eg. ["sectionid", "fromnodeid", "tonodeid", "phase"]). + Also takes the default positions of the attributes (mapping), which is overwritten if "format" is found in the lines. + The function returns a dictionary of dictionaries, where each sub-dictionary contains the values of the desired attributes of a CYME object. """ if isinstance(attribute_list, list): attribute_list = np.array(attribute_list) @@ -783,11 +784,8 @@ def parser_helper(self, line, obj_list, attribute_list, mapping, *args, **kwargs result = {} - # Check the presence of headers in the given line - checks = [self.check_object_in_line(line, obj) for obj in obj_list] - - # If we have a least one - if any(checks): + # If header in line + if self.check_object_in_line(line, obj): # Get the next line next_line = next(self.content) @@ -815,7 +813,7 @@ def parser_helper(self, line, obj_list, attribute_list, mapping, *args, **kwargs # At this point, we should have the mapping for the parameters of interest # while next_line[0] not in ['[','',' ','\n','\r\n']: - while len(next_line) > 2: + while len(next_line) > 2: # blank lines separate objects in CYME .txt files if "=" not in next_line.lower(): data = next_line.split(",") @@ -824,7 +822,7 @@ def parser_helper(self, line, obj_list, attribute_list, mapping, *args, **kwargs if len(data) > 1: - while ID in result: + while ID in result: # redundant keys get *'s ID += "*" result[ID] = {} @@ -982,7 +980,7 @@ def parse_subnetwork_connections(self, model): self.subnetwork_connections.update( self.parser_helper( line, - ["subnetwork_connections"], + "subnetwork_connections", ["nodeid"], mapp_subnetwork_connections, ) @@ -1004,7 +1002,7 @@ def parse_head_nodes(self, model): headnodes = {} for line in self.content: headnodes.update( - self.parser_helper(line, ["headnodes"], ["nodeid", "networkid"], mapp) + self.parser_helper(line, "headnodes", ["nodeid", "networkid"], mapp) ) for sid, headnode in headnodes.items(): @@ -1036,11 +1034,11 @@ def parse_sources(self, model): subs = {} source_equivalents = {} - for line in self.content: + for line in self.content: # parser_helper will only loop over lines in self.content if it finds obj in the `line`; o.w. returns empty dict sources.update( self.parser_helper( line, - ["source"], + "source", ["sourceid", "nodeid", "networkid", "desiredvoltage"], mapp, ) @@ -1048,7 +1046,7 @@ def parse_sources(self, model): source_equivalents.update( self.parser_helper( line, - ["source_equivalent"], + "source_equivalent", [ "nodeid", "voltage", @@ -1078,7 +1076,7 @@ def parse_sources(self, model): for line in self.content: subs.update( self.parser_helper( - line, ["substation"], ["id", "mva", "kvll", "conn"], mapp_sub + line, "substation", ["id", "mva", "kvll", "conn"], mapp_sub ) ) if len(sources.items()) == 0: @@ -1371,7 +1369,7 @@ def parse_nodes(self, model): nodes.update( self.parser_helper( line, - ["node"], + "node", ["nodeid", "coordx", "coordy", "ratedvoltage"], mapp, **kwargs @@ -1381,7 +1379,7 @@ def parse_nodes(self, model): for line in self.content: node_connectors.update( self.parser_helper( - line, ["node_connector"], ["nodeid", "coordx", "coordy"], mapp + line, "node_connector", ["nodeid", "coordx", "coordy"], mapp ) ) @@ -1556,7 +1554,7 @@ def parse_sections(self, model): ... [SECTION] - FORMAT_section=sectionid,fromnodeid,tonodeid,phase + FORMAT_section=sectionid,fromnodeid,tonodeid,phase FORMAT_Feeder=networkid,headnodeid Feeder=feeder_1,head_feeder_1 section_1_feeder_1,node_1,node_2,ABC @@ -1947,7 +1945,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["overhead_unbalanced_line_settings"], + "overhead_unbalanced_line_settings", ["sectionid", "coordx", "coordy", "linecableid", "length"], mapp_overhead, {"type": "overhead_unbalanced"}, @@ -1964,7 +1962,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["overhead_line_settings"], + "overhead_line_settings", ["sectionid", "coordx", "coordy", "linecableid", "length"], mapp_overhead, {"type": "overhead_balanced"}, @@ -1981,7 +1979,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["overhead_byphase_settings"], + "overhead_byphase_settings", [ "sectionid", "devicenumber", @@ -2011,7 +2009,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["underground_line_settings"], + "underground_line_settings", ["sectionid", "coordx", "coordy", "linecableid", "length", "amps"], mapp_underground, {"type": "underground"}, @@ -2028,7 +2026,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["switch_settings"], + "switch_settings", ["sectionid", "coordx", "coordy", "eqid", "closedphase"], mapp_switch, {"type": "switch"}, @@ -2045,7 +2043,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["sectionalizer_settings"], + "sectionalizer_settings", ["sectionid", "coordx", "coordy", "eqid", "closedphase"], mapp_sectionalizer, {"type": "sectionalizer"}, @@ -2062,7 +2060,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["fuse_settings"], + "fuse_settings", ["sectionid", "coordx", "coordy", "eqid"], mapp_switch, # Same as switches {"type": "fuse"}, @@ -2079,7 +2077,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["recloser_settings"], + "recloser_settings", ["sectionid", "coordx", "coordy", "eqid"], mapp_switch, # Same as switches {"type": "recloser"}, @@ -2096,7 +2094,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["breaker_settings"], + "breaker_settings", ["sectionid", "coordx", "coordy", "eqid", "closedphase"], mapp_switch, # Same as switches {"type": "breaker"}, @@ -2113,7 +2111,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["network_protector_settings"], + "network_protector_settings", ["sectionid", "coordx", "coordy", "eqid", "closedphase"], mapp_switch, # Same as switches {"type": "network_protector"}, @@ -2130,7 +2128,7 @@ def parse_lines(self, model): self.settings, self.parser_helper( line, - ["section"], + "section", ["sectionid", "fromnodeid", "tonodeid", "phase"], mapp_section, ), @@ -2157,7 +2155,7 @@ def parse_lines(self, model): self.balanced_lines.update( self.parser_helper( line, - ["line"], + "line", [ "id", "phasecondid", @@ -2185,7 +2183,7 @@ def parse_lines(self, model): self.unbalanced_lines.update( self.parser_helper( line, - ["unbalanced_line"], + "unbalanced_line", [ "id", "condid_a", @@ -2225,7 +2223,7 @@ def parse_lines(self, model): self.spacings.update( self.parser_helper( line, - ["spacing_table"], + "spacing_table", [ "id", "posofcond1_x", @@ -2252,7 +2250,7 @@ def parse_lines(self, model): self.conductors.update( self.parser_helper( line, - ["conductor"], + "conductor", ["id", "diameter", "gmr", "r25", "amps", "withstandrating"], mapp_conductor, ) @@ -2267,7 +2265,7 @@ def parse_lines(self, model): self.concentric_neutral_cable.update( self.parser_helper( line, - ["concentric_neutral_cable"], + "concentric_neutral_cable", [ "id", "r1", @@ -2291,7 +2289,7 @@ def parse_lines(self, model): self.cables.update( self.parser_helper( line, - ["cable"], + "cable", ["id", "r1", "r0", "x1", "x0", "amps"], mapp_concentric_neutral_cable, ) @@ -2305,7 +2303,7 @@ def parse_lines(self, model): # self.switches.update( self.parser_helper( - line, ["switch"], ["id", "amps", "kvll"], mapp_switch_eq + line, "switch", ["id", "amps", "kvll"], mapp_switch_eq ) ) @@ -2318,7 +2316,7 @@ def parse_lines(self, model): self.fuses.update( self.parser_helper( line, - ["fuse"], + "fuse", ["id", "amps", "kvll", "interruptingrating"], mapp_network_protectors, # Same as network protectors ) @@ -2333,7 +2331,7 @@ def parse_lines(self, model): self.reclosers.update( self.parser_helper( line, - ["recloser"], + "recloser", ["id", "amps", "kvll", "interruptingrating"], mapp_network_protectors, # Same as network protectors ) @@ -2348,7 +2346,7 @@ def parse_lines(self, model): self.sectionalizers.update( self.parser_helper( line, - ["sectionalizer"], + "sectionalizer", ["id", "amps", "kvll", "interruptingrating"], mapp_sectionalizers, ) @@ -2363,7 +2361,7 @@ def parse_lines(self, model): self.breakers.update( self.parser_helper( line, - ["breaker"], + "breaker", ["id", "amps", "kvll", "interruptingrating"], mapp_network_protectors, # Same as network protectors ) @@ -2378,7 +2376,7 @@ def parse_lines(self, model): self.network_protectors.update( self.parser_helper( line, - ["network_protector"], + "network_protector", ["id", "amps", "kvll", "interruptingrating"], mapp_network_protectors, ) @@ -3876,7 +3874,7 @@ def parse_capacitors(self, model): self.settings.update( self.parser_helper( line, - ["serie_capacitor_settings"], + "serie_capacitor_settings", ["sectionid", "eqid", "coordx", "coordy"], mapp_serie_capacitor_settings, {"type": "serie"}, @@ -3892,7 +3890,7 @@ def parse_capacitors(self, model): self.settings.update( self.parser_helper( line, - ["shunt_capacitor_settings"], + "shunt_capacitor_settings", [ "sectionid", "shuntcapacitorid", @@ -3931,7 +3929,7 @@ def parse_capacitors(self, model): # self.capacitors.update( self.parser_helper( - line, ["serie_capacitor"], ["id", "reactance"], mapp_serie_capacitor + line, "serie_capacitor", ["id", "reactance"], mapp_serie_capacitor ) ) @@ -3944,7 +3942,7 @@ def parse_capacitors(self, model): self.capacitors.update( self.parser_helper( line, - ["shunt_capacitor"], + "shunt_capacitor", ["id", "kvar", "kv", "type"], mapp_shunt_capacitor, ) @@ -4311,7 +4309,7 @@ def parse_transformers(self, model): self.settings.update( self.parser_helper( line, - ["auto_transformer_settings"], + "auto_transformer_settings", [ "sectionid", "eqid", @@ -4334,7 +4332,7 @@ def parse_transformers(self, model): self.settings.update( self.parser_helper( line, - ["grounding_transformer_settings"], + "grounding_transformer_settings", ["sectionid", "equipmentid", "connectionconfiguration", "phase"], mapp_grounding_transformer_settings, {"type": "grounding_transformer"}, @@ -4350,7 +4348,7 @@ def parse_transformers(self, model): self.settings.update( self.parser_helper( line, - ["three_winding_auto_transformer_settings"], + "three_winding_auto_transformer_settings", [ "sectionid", "eqid", @@ -4377,7 +4375,7 @@ def parse_transformers(self, model): self.settings.update( self.parser_helper( line, - ["three_winding_transformer_settings"], + "three_winding_transformer_settings", [ "sectionid", "eqid", @@ -4404,7 +4402,7 @@ def parse_transformers(self, model): self.settings.update( self.parser_helper( line, - ["transformer_settings"], + "transformer_settings", [ "sectionid", "eqid", @@ -4436,7 +4434,7 @@ def parse_transformers(self, model): self.settings.update( self.parser_helper( line, - ["phase_shifter_transformer_settings"], + "phase_shifter_transformer_settings", ["sectionid", "eqid", "coordx", "coordy"], mapp_phase_shifter_transformer_settings, {"type": "phase_shifter_transformer"}, @@ -4464,7 +4462,7 @@ def parse_transformers(self, model): self.auto_transformers.update( self.parser_helper( line, - ["auto_transformer"], + "auto_transformer", [ "id", "kva", @@ -4488,7 +4486,7 @@ def parse_transformers(self, model): self.grounding_transformers.update( self.parser_helper( line, - ["grounding_transformer"], + "grounding_transformer", ["id", "ratedcapacity", "ratedvoltage", "connection_configuration"], mapp_grounding_transformer, ) @@ -4504,7 +4502,7 @@ def parse_transformers(self, model): self.three_winding_auto_transformers.update( self.parser_helper( line, - ["three_winding_auto_transformer"], + "three_winding_auto_transformer", [ "id", "primaryratedcapacity", @@ -4529,7 +4527,7 @@ def parse_transformers(self, model): self.three_winding_transformers.update( self.parser_helper( line, - ["three_winding_transformer"], + "three_winding_transformer", [ "id", "primaryratedcapacity", @@ -4553,7 +4551,7 @@ def parse_transformers(self, model): self.transformers.update( self.parser_helper( line, - ["transformer"], + "transformer", [ "id", "type", @@ -5041,7 +5039,7 @@ def parse_regulators(self, model): self.settings.update( self.parser_helper( line, - ["regulator_settings"], + "regulator_settings", [ "sectionid", "eqid", @@ -5080,7 +5078,7 @@ def parse_regulators(self, model): self.regulators.update( self.parser_helper( line, - ["regulator"], + "regulator", [ "id", "type", @@ -5358,7 +5356,7 @@ def parse_network_equivalent(self, model): self.settings, self.parser_helper( line, - ["network_equivalent_setting"], + "network_equivalent_setting", ['sectionid', 'devicenumber', 'coordx', 'coordy', 'zraa', 'zrab', 'zrac', 'zrba', 'zrbb', 'zrbc', 'zrca', 'zrcb', 'zrcc', 'zxaa', 'zxab', 'zxac', 'zxba', 'zxbb', 'zxbc', 'zxca', 'zxcb', 'zxcc', 'loadfromkwa', 'loadfromkwb', 'loadfromkwc', 'loadfromkvara', 'loadfromkvarb', 'loadfromkvarc', 'loadtokwa', 'loadtokwb', 'loadtokwc', 'loadtokvara', 'loadtokvarb', 'loadtokvarc', 'totallengtha', 'totallengthb', 'totallengthc'], mapp_network_equivalents, @@ -5376,7 +5374,7 @@ def parse_network_equivalent(self, model): self.settings, self.parser_helper( line, - ["section"], + "section", ["sectionid", "fromnodeid", "tonodeid", "phase"], mapp_section, ), @@ -5620,7 +5618,7 @@ def parse_loads(self, model): self.loads.update( self.parser_helper( line, - ["loads"], + "loads", ["sectionid", "devicenumber", "loadtype", "connection"], mapp_loads, ) @@ -5635,7 +5633,7 @@ def parse_loads(self, model): self.customer_loads.update( self.parser_helper( line, - ["customer_loads"], + "customer_loads", [ "sectionid", "devicenumber", @@ -5663,7 +5661,7 @@ def parse_loads(self, model): self.customer_class.update( self.parser_helper( line, - ["customer_class"], + "customer_class", [ "id", "constantpower", @@ -6021,7 +6019,7 @@ def parse_dg(self, model): self.converter.update( self.parser_helper( line, - ["converter"], + "converter", [ "devicenumber", "devicetype", @@ -6047,7 +6045,7 @@ def parse_dg(self, model): self.converter_settings.update( self.parser_helper( line, - ["converter_control_settings"], + "converter_control_settings", [ "devicenumber", "devicetype", @@ -6072,7 +6070,7 @@ def parse_dg(self, model): self.photovoltaic_settings.update( self.parser_helper( line, - ["photovoltaic_settings"], + "photovoltaic_settings", ["sectionid", "devicenumber", "eqphase", "ambienttemperature"], mapp_photovoltaic_settings, {"type": "photovoltaic_settings"}, @@ -6088,7 +6086,7 @@ def parse_dg(self, model): self.bess_settings.update( self.parser_helper( line, - ["bess_settings"], + "bess_settings", [ "sectionid", "devicenumber", @@ -6112,7 +6110,7 @@ def parse_dg(self, model): self.long_term_dynamics.update( self.parser_helper( line, - ["long_term_dynamics_curve_ext"], + "long_term_dynamics_curve_ext", [ "devicenumber", "devicetype", @@ -6133,7 +6131,7 @@ def parse_dg(self, model): self.dg_generation.update( self.parser_helper( line, - ["dggenerationmodel"], + "dggenerationmodel", [ "devicenumber", "devicetype", @@ -6167,7 +6165,7 @@ def parse_dg(self, model): self.bess.update( self.parser_helper( line, - ["bess"], + "bess", [ "id", "ratedstorageenergy", From 14804e4fd5dbb9b9c47c3a8772c67cfcdeddda83 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 13:45:00 -0600 Subject: [PATCH 08/83] improve warning messages from cyme reader --- ditto/readers/cyme/read.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index aa9f9c94..38033866 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -758,7 +758,7 @@ def parser_helper(self, line, obj, attribute_list, mapping: dict, *args, **kwarg """ .. warning:: This is a helper function for the parsers. Do not use directly. - Takes as input the object we want to parse (eg. "section" maps to "[SECTION]") + Takes as input the object we want to parse (eg. "section" maps to "[SECTION]") as well as the list of attributes we want to extract (eg. ["sectionid", "fromnodeid", "tonodeid", "phase"]). Also takes the default positions of the attributes (mapping), which is overwritten if "format" is found in the lines. The function returns a dictionary of dictionaries, where each sub-dictionary contains the values of the desired attributes of a CYME object. @@ -807,7 +807,7 @@ def parser_helper(self, line, obj, attribute_list, mapping: dict, *args, **kwarg idx2 = temp[0] mapping[attribute_list[idx2]] = idx except: - pass + logger.warn(f"Unable to parse CYME line FORMAT for {obj} in {next_line}") next_line = next(self.content) @@ -860,10 +860,7 @@ def parser_helper(self, line, obj, attribute_list, mapping: dict, *args, **kwarg attribute_list = additional_attributes additional_attributes = [] except: - logger.warning( - "Attempted to apply additional attributes but failed" - ) - pass + logger.warn(f"Unable to parse additional CYME line FORMAT for {obj} in {next_line}") try: next_line = next(self.content) From 73375996eb324bfbec520402fac9a65b0973ca03 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 15:35:43 -0600 Subject: [PATCH 09/83] fix opendss writer when bus/node names have "." or " " --- ditto/writers/opendss/write.py | 55 +++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 63cdec00..e010b73e 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -138,6 +138,9 @@ def float_to_str(self, f): def write(self, model, **kwargs): """General writing function responsible for calling the sub-functions. + Note: .replace(".","_").replace(" ", "_") are used to fix node/bus names for OpenDSS, + which uses dots for phase designation and spaces for paramater delimiters. + :param model: DiTTo model :type model: DiTTo model :param verbose: Set verbose mode. Optional. Default=False @@ -239,7 +242,7 @@ def write(self, model, **kwargs): logger.debug("Succesful!") if self.verbose: - logger.debug("Writting done.") + logger.debug("Writing done.") return 1 @@ -341,7 +344,7 @@ def write_bus_coordinates(self, model): txt = feeder_text_map[substation_name + "_" + feeder_name] txt += "{name} {X} {Y}\n".format( - name=i.name.lower(), X=i.positions[0].long, Y=i.positions[0].lat + name=i.name.lower().replace(".","_").replace(" ", "_"), X=i.positions[0].long, Y=i.positions[0].lat ) feeder_text_map[substation_name + "_" + feeder_name] = txt @@ -678,7 +681,7 @@ def write_transformers(self, model): if buses is not None: bus = buses[cnt] - txt += " bus={bus}".format(bus=str(bus)) + txt += " bus={bus}".format(bus=str(bus).replace(".","_").replace(" ", "_")) if len(winding.phase_windings) != 3: @@ -781,9 +784,9 @@ def write_transformers(self, model): if buses is not None: if cnt == 0 or cnt == 1: - txt += " bus={b}".format(b=buses[cnt]) + txt += " bus={b}".format(b=buses[cnt].replace(".","_").replace(" ", "_")) elif cnt == 2: - txt += " bus={b}".format(b=buses[cnt - 1]) + txt += " bus={b}".format(b=buses[cnt - 1].replace(".","_").replace(" ", "_")) # These are the configurations for center tap transformers if cnt == 0: @@ -1072,7 +1075,7 @@ def write_storages(self, model): hasattr(i, "connecting_element") and i.connecting_element is not None ): - txt += " Bus1={elt}".format(elt=i.connecting_element) + txt += " Bus1={elt}".format(elt=i.connecting_element.replace(".", "_").replace(" ", "_")) if ( hasattr(i, "phase_storages") and i.phase_storages is not None @@ -1290,7 +1293,7 @@ def write_PVs(self, model): and i.connecting_element is not None ): txt += " bus1={connecting_elt}".format( - connecting_elt=i.connecting_element + connecting_elt=i.connecting_element.replace(".", "_").replace(" ", "_") ) if hasattr(i, "phases") and i.phases is not None: for phase in i.phases: @@ -1816,7 +1819,7 @@ def write_loads(self, model): hasattr(i, "connecting_element") and i.connecting_element is not None ): - txt += " bus1={bus}".format(bus=i.connecting_element) + txt += " bus1={bus}".format(bus=i.connecting_element.replace(".", "_").replace(" ", "_")) if hasattr(i, "phase_loads") and i.phase_loads is not None: for phase_load in i.phase_loads: if ( @@ -2093,7 +2096,7 @@ def write_regulators(self, model): if hasattr(i, "connected_transformer"): # If we have a valid connected_transformer then job's easy... - if i.connected_transformer is not None: + if i.connected_transformer is not None: # not setting the connected_transformer in reader parse_regulators txt += " transformer={trans}".format( trans=i.connected_transformer ) @@ -2147,7 +2150,7 @@ def write_regulators(self, model): and i.to_element is not None ): transfo_creation_string += " buses=({b1}.{p},{b2}.{p})".format( - b1=i.from_element, b2=i.to_element, p=phase_string + b1=i.from_element.replace(".","_").replace(" ", "_"), b2=i.to_element.replace(".","_").replace(" ", "_"), p=phase_string ) # Conns @@ -2249,8 +2252,8 @@ def write_regulators(self, model): ) pass # XLT: - try: - if isinstance(i.reactances[1], (int, float)): + try: # probably an index error b/c cyme reader only has api_transformer.reactances = [float(xhl)] + if isinstance(i.reactances[1], (int, float)): transfo_creation_string += " XLT={}".format( i.reactances[1] ) @@ -2262,7 +2265,7 @@ def write_regulators(self, model): ) pass # XHT: - try: + try: # probably an index error b/c cyme reader only has api_transformer.reactances = [float(xhl)] if isinstance(i.reactances[2], (int, float)): transfo_creation_string += " XHT={}".format( i.reactances[2] @@ -2500,7 +2503,7 @@ def write_capacitors(self, model): # Connecting element if i.connecting_element is not None: - txt += " Bus1=" + i.connecting_element + txt += " Bus1=" + i.connecting_element.replace(".", "_").replace(" ", "_") # For a 3-phase capbank we don't add any suffixes to the output. if ( @@ -2838,7 +2841,7 @@ def write_lines(self, model): # from_element if hasattr(i, "from_element") and i.from_element is not None: - txt += " bus1={from_el}".format(from_el=i.from_element) + txt += " bus1={from_el}".format(from_el=i.from_element.replace(".", "_").replace(" ", "_")) if hasattr(i, "wires") and i.wires is not None: for wire in i.wires: if ( @@ -2850,7 +2853,7 @@ def write_lines(self, model): # to_element if hasattr(i, "to_element") and i.to_element is not None: - txt += " bus2={to_el}".format(to_el=i.to_element) + txt += " bus2={to_el}".format(to_el=i.to_element.replace(".", "_").replace(" ", "_")) if hasattr(i, "wires") and i.wires is not None: for wire in i.wires: if ( @@ -2913,7 +2916,7 @@ def write_lines(self, model): if i in lines_to_geometrify: txt += " geometry={g}".format(g=i.nameclass) elif i in lines_to_linecodify: - txt += " Linecode={c}".format(c=i.nameclass) + txt += " Linecode={c}".format(c=i.nameclass.replace(".","_").replace(" ", "_")) txt += "\n\n" if fuse_line != "": @@ -3446,7 +3449,7 @@ def write_linecodes(self, list_of_lines, feeder_name=None, substation_name=None) ) for linecode_name, linecode_data in txt.items(): - fp.write("New Linecode.{name}".format(name=linecode_name)) + fp.write("New Linecode.{name}".format(name=linecode_name.replace(".","_").replace(" ", "_"))) for k, v in linecode_data.items(): fp.write(" {k}={v}".format(k=k, v=v)) fp.write("\n\n") @@ -3753,7 +3756,7 @@ def write_master_file(self, model): with open( os.path.join(self.output_path, self.output_filenames["master"]), "w" ) as fp: - fp.write("Clear\n\nNew Circuit.Full_Network ") + fp.write("Clear\n\nNew Circuit.Full_Network ") # not reached for PPL model? it is but gets overwritten? for obj in model.models: if ( isinstance(obj, PowerSource) and obj.is_sourcebus == 1 @@ -3768,7 +3771,7 @@ def write_master_file(self, model): ): fp.write( "bus1={name} pu={pu}".format( - name=obj.connecting_element, pu=obj.per_unit + name=obj.connecting_element.replace(".", "_").replace(" ", "_"), pu=obj.per_unit ) ) else: @@ -3779,7 +3782,7 @@ def write_master_file(self, model): ) fp.write( "bus1={name} pu={pu}".format( - name=cleaned_name, pu=obj.per_unit + name=cleaned_name.replace(".", "_").replace(" ", "_"), pu=obj.per_unit ) ) @@ -3878,6 +3881,10 @@ def write_master_file(self, model): ) # The buscoords are also written to base folder as well as the subfolders fp.write("\nSolve") + + # return # below is opening Master.dss again !? + + for i in model.models: if isinstance(i, Node) and i.is_substation_connection: feeder_name = i.feeder_name @@ -3886,7 +3893,7 @@ def write_master_file(self, model): substation_name = substation_name.replace(">", "-") # Note that subtransmission has no substation_connection and hence doesn't have a master file, even though it does have other .dss files if ( - feeder_name == "" and i.nominal_voltage < 30000 + feeder_name == "" #and i.nominal_voltage < 30000 ): # A hack to deal with dangling feeders. TODO: Fix this in layerstack continue with open( @@ -3918,7 +3925,7 @@ def write_master_file(self, model): fp.write( "Clear\n\nNew Circuit.feeder_{name} ".format(name=i.name) ) - fp.write("bus1={name} pu={pu}".format(name=i.name, pu=i.setpoint)) + fp.write("bus1={name} pu={pu}".format(name=i.name.replace(".", "_").replace(" ", "_"), pu=i.setpoint)) if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: fp.write( " basekV={volt}".format(volt=i.nominal_voltage * 10 ** -3) @@ -3953,7 +3960,7 @@ def write_master_file(self, model): ] ) else: - _baseKV_list_ = [] + _baseKV_list_ = [] # getting here? _baseKV_list_ = sorted(_baseKV_list_) fp.write("\nSet Voltagebases={}\n".format(_baseKV_list_)) From 63897ff83495b020c445b27795c3e1a71c5722ea Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 16:21:51 -0600 Subject: [PATCH 10/83] Update check_transformer_phase_path.py was not handling missing "wires" in a Line --- .../check_transformer_phase_path.py | 56 ++++++++++++------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/ditto/consistency/check_transformer_phase_path.py b/ditto/consistency/check_transformer_phase_path.py index dc423392..626a86e5 100644 --- a/ditto/consistency/check_transformer_phase_path.py +++ b/ditto/consistency/check_transformer_phase_path.py @@ -54,6 +54,8 @@ def check_transformer_phase_path(model,needs_transformers=False,verbose=True): source_name = source.connecting_element all_paths = nx.single_source_shortest_path(ditto_graph.graph,source_name) break_load = False + msgs = [] + for load in all_loads: load_connection = load.connecting_element if load_connection in all_paths: @@ -75,7 +77,7 @@ def check_transformer_phase_path(model,needs_transformers=False,verbose=True): transformer_low_side = path[i-1] ### Check that the low side of the transformer is connected to a line that leads to a load - if num_transformers ==1: + if num_transformers == 1: load_transformer_map[load.name] = element['name'] if model[transformer_names[0]].to_element != transformer_low_side: if verbose: @@ -121,18 +123,24 @@ def check_transformer_phase_path(model,needs_transformers=False,verbose=True): if element['equipment'] == 'PowerTransformer' and not element['is_substation']: break if element['equipment'] == 'Line': - line_phases = [wire.phase for wire in element['wires'] if wire.phase != 'N'] #Neutral phases not included in the transformer - if not set(high_phases).issubset(set(line_phases)): #MV phase line phase must be able to support transformer phase - if verbose: - print('Load '+load.name+ ' has incorrect phases '+str(line_phases)+' '+str(high_phases)+' on high side of transformer for line '+element['name']) - result = False - break - if len(line_phases) > len(prev_line_phases): - if verbose: - print('Number of phases increases along line '+element['name'] +' from '+str(len(prev_line_phases))+' to '+str(len(line_phases))) - result = False - break - prev_line_phases = line_phases + if not "wires" in element.keys(): + msg = f"Warning: Line {element['equipment_name']} has no wires!" + if not msg in msgs: + print(msg) + msgs.append(msg) + else: + line_phases = [wire.phase for wire in element['wires'] if wire.phase != 'N'] #Neutral phases not included in the transformer + if not set(high_phases).issubset(set(line_phases)): #MV phase line phase must be able to support transformer phase + if verbose: + print('Load '+load.name+ ' has incorrect phases '+str(line_phases)+' '+str(high_phases)+' on high side of transformer for line '+element['name']) + result = False + break + if len(line_phases) > len(prev_line_phases): + if verbose: + print('Number of phases increases along line '+element['name'] +' from '+str(len(prev_line_phases))+' to '+str(len(line_phases))) + result = False + break + prev_line_phases = line_phases elif element['equipment'] != 'Regulator': print('Warning: element of type '+element['equipment'] +' found on path to load '+load.name) @@ -143,13 +151,21 @@ def check_transformer_phase_path(model,needs_transformers=False,verbose=True): for i in range(len(path)-1): element = ditto_graph.graph[path[i]][path[i+1]] if element['equipment'] == 'Line': - line_phases = [wire.phase for wire in element['wires'] if wire.phase != 'N'] #Neutral phases not included in the transformer - if len(line_phases) > len(prev_line_phases): - if verbose: - print('Number of phases increases along line '+element['name'] +' from '+str(len(prev_line_phases))+' to '+str(len(line_phases))) - result = False - break - prev_line_phases = line_phases + if not "wires" in element.keys(): + msg = f"Warning: Line {element['equipment_name']} has no wires!" + if not msg in msgs: + print(msg) + msgs.append(msg) + else: + line_phases = [wire.phase for wire in element['wires'] if wire.phase != 'N'] #Neutral phases not included in the transformer + if len(line_phases) > len(prev_line_phases): + msg = 'Number of phases increases along line '+element['name'] +' from '+str(len(prev_line_phases))+' to '+str(len(line_phases)) + if verbose and not msg in msgs: + print(msg) + msgs.append(msg) + result = False + break + prev_line_phases = line_phases elif element['equipment'] != 'Regulator' and element['equipment'] != 'PowerTransformer': print('Warning: element of type '+element['equipment'] +' found on path to load '+load.name) return result From a4e564d57e4f115774ae938b8cea3ff50aef48d6 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 18:03:29 -0600 Subject: [PATCH 11/83] rm impossible log in models/base.py was trying to log objects ? --- ditto/models/base.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/ditto/models/base.py b/ditto/models/base.py index 568d2ade..3fa37574 100644 --- a/ditto/models/base.py +++ b/ditto/models/base.py @@ -2,7 +2,6 @@ from __future__ import absolute_import, division, print_function from builtins import super, range, zip, round, map -import warnings from traitlets.traitlets import ( ObserveHandler, _deprecated_method, @@ -29,9 +28,7 @@ def set_name(self, model): try: name = self.name if name in model.model_names: - warnings.warn("Duplicate name %s being set. Object overwritten." % name) - logger.debug("Duplicate name %s being set. Object overwritten." % name) - logger.debug(model.model_names[name], self) + logger.warn("Duplicate name %s being set. Object overwritten." % name) model.model_names[name] = self except AttributeError: pass From 2938132be90c48bb1433f15873582af5519f304b Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 26 Aug 2022 18:14:29 -0600 Subject: [PATCH 12/83] handle LoadModelID in CYME Reader was added up multiple loads which could lead to 12 phase loads for example, with 4 LoadModelIDs and 3 phases --- ditto/readers/cyme/read.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 38033866..e09a0c6e 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -198,6 +198,11 @@ def __init__(self, **kwargs): else: self.load_filename = "load.txt" + # optional load_model_id will parse only the loads with the corresponding LoadModelID (value in [CUSTOMER LOADS]) + self.load_model_id = None + if "load_model_id" in kwargs.keys(): + self.load_model_id = int(kwargs["load_model_id"]) + # Set the Network Type to be None. This is set in the parse_sections() function self.network_type = None @@ -916,7 +921,6 @@ def parse(self, model, **kwargs): modifier.set_nominal_voltages_recur() modifier.set_nominal_voltages_recur_line() - def parse_header(self): """ Parse the information available in the header. @@ -5551,9 +5555,6 @@ def parse_network_equivalent(self, model): api_load_to.phase_loads.append(api_phase_load_to) - - - def parse_loads(self, model): """Parse the loads from CYME to DiTTo.""" # Instanciate the list in which we store the DiTTo load objects @@ -5680,12 +5681,27 @@ def parse_loads(self, model): for sectionID in self.customer_loads.keys(): if sectionID.endswith("*"): duplicate_loads.add(sectionID.lower().strip("*")) + for sectionID, settings in self.customer_loads.items(): + """ + if more than one loadmodelid is in Loads.txt then all of the loadmodels get added together in one Load object. + for example, four differnt loadmodelids on a phase Cresults in 4 loads on busName.3.3.3.3 added together :/ + However, if the Reader is instantiated with a load_model_id then we only parse the loads with that load_model_id. + """ + if self.load_model_id is not None and "loadmodelid" in settings: + # we only want to create a load if it has the matching LoadModelID + try: + if self.load_model_id != int(settings["loadmodelid"]): + continue + except ValueError: + logger.warn(f"Cannot convert LoadModelID {settings['loadmodelid']} for {sectionID} to integer.") + sectionID = sectionID.strip("*").lower() if sectionID in self.loads: - load_data = self.loads[sectionID] + # FORMAT_LOADS=SectionID,DeviceNumber,DeviceStage,Flags,LoadType,Connection,Location + load_data = self.loads[sectionID] else: load_data = {} @@ -5747,7 +5763,7 @@ def parse_loads(self, model): ) continue - if p >= 0 or q >= 0: + if p >= 0 or q >= 0: # then a Load is created if "loadphase" in settings: phases = settings["loadphase"] From 184c63bd7c916168ae9c21aa5b9fc5ca17527dbb Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 30 Aug 2022 13:48:27 -0600 Subject: [PATCH 13/83] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3095c5dd..8b27bffa 100644 --- a/.gitignore +++ b/.gitignore @@ -128,6 +128,7 @@ venv/ ENV/ env.bak/ venv.bak/ +testenv/ # Spyder project settings .spyderproject From 138088443a7a9f8b32c85deb36e156e3f78d2db5 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 30 Aug 2022 13:50:10 -0600 Subject: [PATCH 14/83] openDSS writer use re.sub to remove special chars - was only replacing dot or space with under score - now handling all special characters (which openDSS cannot handle) --- ditto/writers/opendss/write.py | 37 ++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index e010b73e..69188774 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -7,6 +7,7 @@ import math import logging import decimal +import re import numpy as np import pandas as pd @@ -138,7 +139,7 @@ def float_to_str(self, f): def write(self, model, **kwargs): """General writing function responsible for calling the sub-functions. - Note: .replace(".","_").replace(" ", "_") are used to fix node/bus names for OpenDSS, + Note: re.sub('[^0-9a-zA-Z]+', '_', object_name) is used to fix node/bus names for OpenDSS, which uses dots for phase designation and spaces for paramater delimiters. :param model: DiTTo model @@ -344,7 +345,7 @@ def write_bus_coordinates(self, model): txt = feeder_text_map[substation_name + "_" + feeder_name] txt += "{name} {X} {Y}\n".format( - name=i.name.lower().replace(".","_").replace(" ", "_"), X=i.positions[0].long, Y=i.positions[0].lat + name=re.sub('[^0-9a-zA-Z]+', '_', i.name.lower()), X=i.positions[0].long, Y=i.positions[0].lat ) feeder_text_map[substation_name + "_" + feeder_name] = txt @@ -681,7 +682,7 @@ def write_transformers(self, model): if buses is not None: bus = buses[cnt] - txt += " bus={bus}".format(bus=str(bus).replace(".","_").replace(" ", "_")) + txt += " bus={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', str(bus))) if len(winding.phase_windings) != 3: @@ -784,9 +785,9 @@ def write_transformers(self, model): if buses is not None: if cnt == 0 or cnt == 1: - txt += " bus={b}".format(b=buses[cnt].replace(".","_").replace(" ", "_")) + txt += " bus={b}".format(b=re.sub('[^0-9a-zA-Z]+', '_', buses[cnt])) elif cnt == 2: - txt += " bus={b}".format(b=buses[cnt - 1].replace(".","_").replace(" ", "_")) + txt += " bus={b}".format(b=re.sub('[^0-9a-zA-Z]+', '_', buses[cnt - 1])) # These are the configurations for center tap transformers if cnt == 0: @@ -1075,7 +1076,7 @@ def write_storages(self, model): hasattr(i, "connecting_element") and i.connecting_element is not None ): - txt += " Bus1={elt}".format(elt=i.connecting_element.replace(".", "_").replace(" ", "_")) + txt += " Bus1={elt}".format(elt=re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element)) if ( hasattr(i, "phase_storages") and i.phase_storages is not None @@ -1293,7 +1294,7 @@ def write_PVs(self, model): and i.connecting_element is not None ): txt += " bus1={connecting_elt}".format( - connecting_elt=i.connecting_element.replace(".", "_").replace(" ", "_") + connecting_elt=re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element) ) if hasattr(i, "phases") and i.phases is not None: for phase in i.phases: @@ -1797,6 +1798,7 @@ def write_loads(self, model): substation_text_map[substation_name] = set([feeder_name]) else: substation_text_map[substation_name].add(feeder_name) + txt = "" if substation_name + "_" + feeder_name in feeder_text_map: txt = feeder_text_map[substation_name + "_" + feeder_name] @@ -1819,7 +1821,7 @@ def write_loads(self, model): hasattr(i, "connecting_element") and i.connecting_element is not None ): - txt += " bus1={bus}".format(bus=i.connecting_element.replace(".", "_").replace(" ", "_")) + txt += " bus1={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element)) if hasattr(i, "phase_loads") and i.phase_loads is not None: for phase_load in i.phase_loads: if ( @@ -1985,6 +1987,7 @@ def write_loads(self, model): txt += "\n\n" feeder_text_map[substation_name + "_" + feeder_name] = txt + for substation_name in substation_text_map: for feeder_name in substation_text_map[substation_name]: txt = feeder_text_map[substation_name + "_" + feeder_name] @@ -2150,7 +2153,7 @@ def write_regulators(self, model): and i.to_element is not None ): transfo_creation_string += " buses=({b1}.{p},{b2}.{p})".format( - b1=i.from_element.replace(".","_").replace(" ", "_"), b2=i.to_element.replace(".","_").replace(" ", "_"), p=phase_string + b1=re.sub('[^0-9a-zA-Z]+', '_', i.from_element), b2=re.sub('[^0-9a-zA-Z]+', '_', i.to_element), p=phase_string ) # Conns @@ -2503,7 +2506,7 @@ def write_capacitors(self, model): # Connecting element if i.connecting_element is not None: - txt += " Bus1=" + i.connecting_element.replace(".", "_").replace(" ", "_") + txt += " Bus1=" + re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element) # For a 3-phase capbank we don't add any suffixes to the output. if ( @@ -2841,7 +2844,7 @@ def write_lines(self, model): # from_element if hasattr(i, "from_element") and i.from_element is not None: - txt += " bus1={from_el}".format(from_el=i.from_element.replace(".", "_").replace(" ", "_")) + txt += " bus1={from_el}".format(from_el=re.sub('[^0-9a-zA-Z]+', '_', i.from_element) if hasattr(i, "wires") and i.wires is not None: for wire in i.wires: if ( @@ -2853,7 +2856,7 @@ def write_lines(self, model): # to_element if hasattr(i, "to_element") and i.to_element is not None: - txt += " bus2={to_el}".format(to_el=i.to_element.replace(".", "_").replace(" ", "_")) + txt += " bus2={to_el}".format(to_el=re.sub('[^0-9a-zA-Z]+', '_', i.to_element) if hasattr(i, "wires") and i.wires is not None: for wire in i.wires: if ( @@ -2916,7 +2919,7 @@ def write_lines(self, model): if i in lines_to_geometrify: txt += " geometry={g}".format(g=i.nameclass) elif i in lines_to_linecodify: - txt += " Linecode={c}".format(c=i.nameclass.replace(".","_").replace(" ", "_")) + txt += " Linecode={c}".format(c=re.sub('[^0-9a-zA-Z]+', '_', i.nameclass) txt += "\n\n" if fuse_line != "": @@ -3449,7 +3452,7 @@ def write_linecodes(self, list_of_lines, feeder_name=None, substation_name=None) ) for linecode_name, linecode_data in txt.items(): - fp.write("New Linecode.{name}".format(name=linecode_name.replace(".","_").replace(" ", "_"))) + fp.write("New Linecode.{name}".format(name=re.sub('[^0-9a-zA-Z]+', '_', linecode_name))) for k, v in linecode_data.items(): fp.write(" {k}={v}".format(k=k, v=v)) fp.write("\n\n") @@ -3771,7 +3774,7 @@ def write_master_file(self, model): ): fp.write( "bus1={name} pu={pu}".format( - name=obj.connecting_element.replace(".", "_").replace(" ", "_"), pu=obj.per_unit + name=re.sub('[^0-9a-zA-Z]+', '_', obj.connecting_element), pu=obj.per_unit ) ) else: @@ -3782,7 +3785,7 @@ def write_master_file(self, model): ) fp.write( "bus1={name} pu={pu}".format( - name=cleaned_name.replace(".", "_").replace(" ", "_"), pu=obj.per_unit + name=re.sub('[^0-9a-zA-Z]+', '_', cleaned_name), pu=obj.per_unit ) ) @@ -3925,7 +3928,7 @@ def write_master_file(self, model): fp.write( "Clear\n\nNew Circuit.feeder_{name} ".format(name=i.name) ) - fp.write("bus1={name} pu={pu}".format(name=i.name.replace(".", "_").replace(" ", "_"), pu=i.setpoint)) + fp.write("bus1={name} pu={pu}".format(name=re.sub('[^0-9a-zA-Z]+', '_', i.name), pu=i.setpoint)) if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: fp.write( " basekV={volt}".format(volt=i.nominal_voltage * 10 ** -3) From d6826f59bb22a3e672031fb70db961ccfb919068 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 7 Sep 2022 11:23:58 -0600 Subject: [PATCH 15/83] typo fixes in opendss/write.py --- ditto/writers/opendss/write.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 69188774..a2e03eea 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2844,7 +2844,7 @@ def write_lines(self, model): # from_element if hasattr(i, "from_element") and i.from_element is not None: - txt += " bus1={from_el}".format(from_el=re.sub('[^0-9a-zA-Z]+', '_', i.from_element) + txt += " bus1={from_el}".format(from_el=re.sub('[^0-9a-zA-Z]+', '_', i.from_element)) if hasattr(i, "wires") and i.wires is not None: for wire in i.wires: if ( @@ -2856,7 +2856,7 @@ def write_lines(self, model): # to_element if hasattr(i, "to_element") and i.to_element is not None: - txt += " bus2={to_el}".format(to_el=re.sub('[^0-9a-zA-Z]+', '_', i.to_element) + txt += " bus2={to_el}".format(to_el=re.sub('[^0-9a-zA-Z]+', '_', i.to_element)) if hasattr(i, "wires") and i.wires is not None: for wire in i.wires: if ( @@ -2919,7 +2919,7 @@ def write_lines(self, model): if i in lines_to_geometrify: txt += " geometry={g}".format(g=i.nameclass) elif i in lines_to_linecodify: - txt += " Linecode={c}".format(c=re.sub('[^0-9a-zA-Z]+', '_', i.nameclass) + txt += " Linecode={c}".format(c=re.sub('[^0-9a-zA-Z]+', '_', i.nameclass)) txt += "\n\n" if fuse_line != "": From 73a82fc570000d1a0304d0ace9e49c38cbbc586e Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 17:02:34 -0600 Subject: [PATCH 16/83] start parse of TRANSFORMER BYPHASE SETTING in CYME reader --- ditto/readers/cyme/read.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index e09a0c6e..8320f24d 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -113,6 +113,8 @@ class Reader(AbstractReader): +-------------------------------------------+--------------------------------------------+ | 'transformer_settings' | '[TRANSFORMER SETTING]' | +-------------------------------------------+--------------------------------------------+ + | 'transformer_byphase_settings' | '[TRANSFORMER BYPHASE SETTING]' | + +-------------------------------------------+--------------------------------------------+ | 'auto_transformer' | '[AUTO TRANSFORMER]' | +-------------------------------------------+--------------------------------------------+ | 'grounding_transformer' | '[GROUNDING TRANSFORMER]' | @@ -259,6 +261,7 @@ def __init__(self, **kwargs): "[THREE WINDING TRANSFORMER SETTING]" ], "transformer_settings": ["[TRANSFORMER SETTING]"], + "transformer_byphase_settings": ["[TRANSFORMER BYPHASE SETTING]"], "phase_shifter_transformer_settings": [ "[PHASE SHIFTER TRANSFORMER SETTING]" ], @@ -4281,6 +4284,13 @@ def parse_transformers(self, model): "coordx": 10, "coordy": 11, } + mapp_transformer_byphase_settings = { + "sectionid": 0, + "PhaseTransformerID1": 8, # maps to TRANSFORMER ID + "PhaseTransformerID2": 9, + "PhaseTransformerID3": 10, + "FeedingNode": 16 + } self.auto_transformers = {} self.grounding_transformers = {} From 319824ac70056cf08d2703220d47c2d82959fafc Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 17:03:42 -0600 Subject: [PATCH 17/83] update CYME Reader.parser_helper doc string --- ditto/readers/cyme/read.py | 57 +++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 8320f24d..c0e64478 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -762,23 +762,72 @@ def check_object_in_line(self, line: str, obj: str) -> bool: return np.any([x in line for x in self.header_mapping[obj]]) - def parser_helper(self, line, obj, attribute_list, mapping: dict, *args, **kwargs) -> dict: + def parser_helper(self, line, obj: str, attribute_list, mapping: dict, *args, **kwargs) -> dict: """ .. warning:: This is a helper function for the parsers. Do not use directly. Takes as input the object we want to parse (eg. "section" maps to "[SECTION]") as well as the list of attributes we want to extract (eg. ["sectionid", "fromnodeid", "tonodeid", "phase"]). - Also takes the default positions of the attributes (mapping), which is overwritten if "format" is found in the lines. + Also takes the default positions of the attributes (mapping), which is overwritten if "format" is found in the line after `obj` is found. The function returns a dictionary of dictionaries, where each sub-dictionary contains the values of the desired attributes of a CYME object. + + The first and only top-level key in the returned dictionary is set to the first comma separated value in the line + after the "format" line. For example, given + + - line="[TRANSFORMER]" + - obj="transformer" + - attribute_list=["kva", "kvllprim", "kvllsec"] + - args={"type": "transformer"} + + the following (truncated) example lines in network.txt: + + [TRANSFORMER] + FORMAT_TRANSFORMER=ID,Type,WindingType,KVA,VoltageUnit,KVLLprim,KVLLsec, ... + 1P_7.2KV/120/240V_25KVA,1,1,25.000000,0,12.470000,0.240000, ... + 1P_7.2KV/240/120V_50KVA,1,1,50.000000,0,12.470000,0.240000, ... + + will result in a returned dictionary like: + + { + "type": "transformer", + + "1P_7.2KV/120/240V_25KVA": { + "kva": 25.000000, + "kvllprim": 12.470000, + "kvllsec": 0.240000 + }, + + "1P_7.2KV/240/120V_50KVA": { + "kva": 50.000000, + "kvllprim": 12.470000, + "kvllsec": 0.240000 + }, + } + + Note that the `mapping` argument is not used in the above example because there is a "FORMAT_TRANSFORMER=" + line after the section header, which is used to define the `mapping`. + + + TODO is this case handled?!: (i.e. when the format is only temporary?) + [SECTION] + FORMAT_SECTION=SectionID,FromNodeID,FromNodeIndex,ToNodeID,ToNodeIndex,Phase,ZoneID,SubNetworkId + FORMAT_FEEDER=NetworkID,HeadNodeID,CoordSet,Year,Description,Color,LoadFactor,LossLoadFactorK,Group1,Group2,Group3,Group4,Group5,Group6,North,South,East,West,AreaFilter,TagText,TagProperties,TagDeltaX,TagDeltaY,TagAngle,TagAlignment,TagBorder,TagBackground,TagTextColor,TagBorderColor,TagBackgroundColor,TagLocation,TagFont,TagTextSize,TagOffset,Version + FEEDER=60803,SOURCE_60803,1,1662563223,,0,1.000000,0.150000,Lancaster,UNK,,,,,225423.609616,194367.254874,2452036.118000,2423405.968000,217563134,NULL,,,,,,,,,,,,,,,-1 + 43044S21249-L,2430449.596_212244.46,0,43044S21249-L,0,C,, + 43936S21443-L,2439387.172_214373.799,0,43936S21443-L,0,A,, + ... + + """ if isinstance(attribute_list, list): - attribute_list = np.array(attribute_list) + attribute_list = np.array(attribute_list) # why do we need an numpy array? if not isinstance(attribute_list, np.ndarray): raise ValueError("Could not cast attribute list to Numpy array.") if args and isinstance(args[0], dict): - additional_information = args[0] + additional_information = args[0] # typically {"type": "same-string-as-obj-arg"}, which gets added to the returned dict + # TODO make this update explicit if this is all that it is used for else: additional_information = {} From bce563ba23f068e9a767ce6b08b6f8f9087471c4 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 17:04:12 -0600 Subject: [PATCH 18/83] update CYME Reader.parse_transformers doc string --- ditto/readers/cyme/read.py | 47 +++++++++++++++++++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index c0e64478..6cc3d8ba 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -4214,7 +4214,52 @@ def parse_capacitors(self, model): return 1 def parse_transformers(self, model): - """Parse the transformers from CYME to DiTTo. Since substation transformer can have LTCs attached, when parsing a transformer, we may also create a regulator. LTCs are represented as regulators.""" + """ + Parse the transformers from CYME to DiTTo. + Since substation transformer can have LTCs attached, when parsing a transformer, + we may also create a regulator. LTCs are represented as regulators. + + The [TRANSFORMER] section specifies generic transformers that can be place in the + network via other section specifications. For example, + [TRANSFORMER BYPHASE SETTING] contains columns for + PhaseTransformerID1, PhaseTransformerID2, and PhaseTransformerID3 + which must have at least one (string) value that corresponds to [TRANSFORMER] ID + + List of sections parsed: + + # network.txt + NOTE: When parsing the network values the "type" key is set to the corresponding + equipment.txt name by passing {"type": "grounding_transformer"} for example + to `parser_helper` when parsing "grounding_transformer_settings". + + NOTE: seems like the SETTING values have required SectionID and EqID fields but + it may not be consistent (for example PHOTOVOLTAIC SETTINGS has a required + EquipmentID field and a rewquired DeviceNumber field) + + NOTE: all of these SETTING sections start with SectionID (but there are exceptions + for other SETTING sections such as [CONVERTER CONTROL SETTING] that starts with DeviceNumber) + + - [AUTO TRANSFORMER SETTING] + - [GROUNDINGTRANSFORMER SETTINGS] + - [THREE WINDING AUTO TRANSFORMER SETTING] + - [THREE WINDING TRANSFORMER SETTING] + - [TRANSFORMER SETTING] + - [TRANSFORMER BYPHASE SETTING] + - [PHASE SHIFTER TRANSFORMER SETTING] + + # equipment.txt + NOTE: all equipment sections start with ID (not SectionID) + - [AUTO TRANSFORMER] + - [GROUNDING TRANSFORMER] + - [THREE WINDING AUTO TRANSFORMER] + - [THREE WINDING TRANSFORMER] + - [TRANSFORMER] + - TODO add [PHASE SHIFTER TRANSFORMER] + + NOTE: it appears that all BYPHASE values are only SETTINGs (no BYPHASE equipment) and so + TRANSFORMER BYPHASE SETTING defines values for a TRANSFORMER (there are also + REGULATOR BYHPASE SETTING and OVERHEAD BYPHASE SETTING) + """ # Instanciate the list in which we store the DiTTo transformer objects self._transformers = [] From 4a21844bc49420614470a053b4ad08fd434b1072 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 17:05:33 -0600 Subject: [PATCH 19/83] minor formatting in CYME Reader --- ditto/readers/cyme/read.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 6cc3d8ba..76c09e0f 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -4402,12 +4402,12 @@ def parse_transformers(self, model): # Open the network file self.get_file_content("network") - # Loop over the network file + # Loop over the network file to get the SETTINGS for line in self.content: ######################################### # # - # AUTO TRANSFORMER # + # AUTO TRANSFORMER SETTINGS # # # ######################################### # @@ -4444,11 +4444,11 @@ def parse_transformers(self, model): ) ) - ######################################### - # # - # THREE WINDING AUTO TRANSFORMER # - # # - ######################################### + ########################################### + # # + # THREE WINDING AUTO TRANSFORMER SETTINGS # + # # + ########################################### # self.settings.update( self.parser_helper( @@ -4584,7 +4584,7 @@ def parse_transformers(self, model): ######################################### # # - # GROUNDING TRANSFORMER # + # GROUNDING TRANSFORMER SETTINGS # # # ######################################### # @@ -4624,7 +4624,7 @@ def parse_transformers(self, model): ######################################### # # - # THREE WINDING TRANSFORMER # + # THREE WINDING TRANSFORMER SETTINGS # # # ######################################### # @@ -4649,7 +4649,7 @@ def parse_transformers(self, model): ######################################### # # - # TRANSFORMER # + # TRANSFORMER SETTINGS # # # ######################################### # @@ -4679,6 +4679,7 @@ def parse_transformers(self, model): ) ) + # for each line in a SETTING section define a PowerTransformer for sectionID, settings in self.settings.items(): sectionID = sectionID.strip("*").lower() From c3ca68bb6bc2a02226b7e9beb4e4eda063c96cac Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 17:06:31 -0600 Subject: [PATCH 20/83] create ditto/readers/README.md --- ditto/readers/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 ditto/readers/README.md diff --git a/ditto/readers/README.md b/ditto/readers/README.md new file mode 100644 index 00000000..25b0ca88 --- /dev/null +++ b/ditto/readers/README.md @@ -0,0 +1,26 @@ +# Readers design pattern + +## 1. Inherit from `AbstractReader` +The `AbstractReader.parse` method can optionally be overridden. The base class `parse` method calls: +1. `parse_nodes` +2. `parse_lines` +3. `parse_transformers` +4. `parse_loads` +5. `parse_regulators` +6. `parse_capacitors` +7. `parse_dg` +8. `if hasattr(self, "DSS_file_names")` then `parse_default_values` + +Each of these parse methods takes a single input argument, an instance of `Store`, commonly +called `model`. + +The `model` stores instances of the DiTTo power system objects defined in ditto/models/. + +## 2. Each `parse_x` method: +1. Define maps of header names to integer column location +2. Loop through network.txt/equipment.txt/load.txt + - use `parser_helper` for each line and each CYME object to get dictionaries of object values + - returns a dictionary of dictionaries, where each sub-dictionary contains the values of the desired attributes of a CYME object. + - TODO example output + - these dictionaries are stored in `self.settings`, which is emptied at the beginning of each `parse_x` method. +3. Loop through the parsed objects to create DiTTo model components \ No newline at end of file From 35ebbbf1e71aa241cc5a2732122df28b2fc97d06 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 17:55:11 -0600 Subject: [PATCH 21/83] create readers/cyme/utils.py working on some DRYness --- ditto/readers/cyme/utils.py | 229 ++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 ditto/readers/cyme/utils.py diff --git a/ditto/readers/cyme/utils.py b/ditto/readers/cyme/utils.py new file mode 100644 index 00000000..f9162ad9 --- /dev/null +++ b/ditto/readers/cyme/utils.py @@ -0,0 +1,229 @@ +import numpy as np +import math +import cmath +from ditto.store import Store +from ditto.models.winding import Winding +from ditto.models.phase_winding import PhaseWinding +from ditto.models.powertransformer import PowerTransformer +from six import string_types + + +def get_transformer_xhl_Rpercent(trfx: dict) -> tuple[float, float]: + # Resistance + # Note: Imported from Julietta's code + Z1 = float(trfx["z1"]) # TODO default values + Z0 = float(trfx["z0"]) + XR = float(trfx["xr"]) + XR0 = float(trfx["xr0"]) + if XR == 0: + R1 = 0 + X1 = 0 + else: + R1 = Z1 / math.sqrt(1 + XR * XR) + X1 = Z1 / math.sqrt(1 + 1 / (XR * XR)) + if XR0 == 0: + R0 = 0 + X0 = 0 + else: + R0 = Z0 / math.sqrt(1 + XR0 * XR0) + X0 = Z0 / math.sqrt(1 + 1 / (XR0 * XR0)) + complex0 = complex(R0, X0) + complex1 = complex(R1, X1) + matrix = np.array( + [[complex0, 0, 0], [0, complex1, 0], [0, 0, complex1]] + ) + a = 1 * cmath.exp(2 * math.pi * 1j / 3) + T = np.array([[1.0, 1.0, 1.0], [1.0, a * a, a], [1.0, a, a * a]]) + T_inv = np.linalg.inv(T) + Zabc = T * matrix * T_inv + Z_perc = Zabc.item((0, 0)) + R_perc = Z_perc.real / 2.0 + xhl = Z_perc.imag + return xhl, R_perc + + +def transformer_connection_configuration_mapping(value, winding): + """ + Map the connection configuration for transformer (2 windings) objects from CYME to DiTTo. + + :param value: CYME value (either string or id) + :type value: int or str + :param winding: Number of the winding (0 or 1) + :type winding: int + :returns: DiTTo connection configuration for the requested winding + :rtype: str + + **Mapping:** + + +----------+----------------+------------+ + | Value | CYME | DiTTo | + +----------+----------------+-----+------+ + | | | 1st | 2nd | + +==========+================+=====+======+ + | 0 or '0' | 'Y_Y' | 'Y' | 'Y' | + +----------+----------------+-----+------+ + | 1 or '1' | 'D_Y' | 'D' | 'Y' | + +----------+----------------+-----+------+ + | 2 or '2' | 'Y_D' | 'Y' | 'D' | + +----------+----------------+-----+------+ + | 3 or '3' | 'YNG_YNG' | 'Y' | 'Y' | + +----------+----------------+-----+------+ + | 4 or '4' | 'D_D' | 'D' | 'D' | + +----------+----------------+-----+------+ + | 5 or '5' | 'DO_DO' | 'D' | 'D' | + +----------+----------------+-----+------+ + | 6 or '6' | 'YO_DO' | 'Y' | 'D' | + +----------+----------------+-----+------+ + | 7 or '7' | 'D_YNG' | 'D' | 'Y' | + +----------+----------------+-----+------+ + | 8 or '8' | 'YNG_D' | 'Y' | 'D' | + +----------+----------------+-----+------+ + | 9 or '9' | 'Y_YNG' | 'Y' | 'Y' | + +----------+----------------+-----+------+ + |10 or '10'| 'YNG_Y' | 'Y' | 'Y' | + +----------+----------------+-----+------+ + |11 or '11'| 'Yg_Zg' | 'Y' | 'Z' | + +----------+----------------+-----+------+ + |12 or '12'| 'D_Zg' | 'D' | 'Z' | + +----------+----------------+-----+------+ + """ + if winding not in [0, 1]: + raise ValueError( + "transformer_connection_configuration_mapping expects an integer 0 or 1 for winding arg. {} was provided.".format( + winding + ) + ) + + res = (None, None) + + if isinstance(value, int): + if value == 0 or value == 3 or value == 9 or value == 10: + res = ("Y", "Y") + if value == 1 or value == 7: + res = ("D", "Y") + if value == 2 or value == 6 or value == 8: + res = ("Y", "D") + if value == 4 or value == 5: + res = ("D", "D") + if value == 11: + res = ("Y", "Z") + if value == 12: + res = ("D", "Z") + + elif isinstance(value, string_types): + if value == "0" or value.lower() == "y_y": + res = ("Y", "Y") + if value == "1" or value.lower() == "d_y": + res = ("D", "Y") + if value == "2" or value.lower() == "y_d": + res = ("Y", "D") + if value == "3" or value.lower() == "yng_yng": + res = ("Y", "Y") + if value == "4" or value.lower() == "d_d": + res = ("D", "D") + if value == "5" or value.lower() == "do_do": + res = ("D", "D") + if value == "6" or value.lower() == "yo_do": + res = ("Y", "D") + if value == "7" or value.lower() == "d_yng": + res = ("D", "Y") + if value == "8" or value.lower() == "yng_d": + res = ("Y", "D") + if value == "9" or value.lower() == "y_yng": + res = ("Y", "Y") + if value == "10" or value.lower() == "yng_y": + res = ("Y", "Y") + if value == "11" or value.lower() == "yg_zg": + res = ("Y", "Z") + if value == "12" or value.lower() == "d_zg": + res = ("D", "Z") + + else: + raise ValueError( + "transformer_connection_configuration_mapping expects an integer or a string. {} was provided.".format( + type(value) + ) + ) + + return res[winding] + + +def add_two_windings(api_transformer: PowerTransformer, trfx_data: dict, model: Store, phases: str, R_perc: float) -> None: + """ + Add Winding and PhaseWinding objects to PowerTransformer + using `trfx_data` from the [TRANSFORMER] section + """ + + # Here we know that we have two windings... + for w in range(2): + + # Instanciate a Winding DiTTo object + try: + api_winding = Winding(model) + except: + raise ValueError("Unable to instanciate Winding DiTTo object.") + + # Set the rated power + try: + if w == 0: + api_winding.rated_power = ( + float(trfx_data["kva"]) * 10 ** 3 + ) # DiTTo in volt ampere + if w == 1: + api_winding.rated_power = ( + float(trfx_data["kva"]) * 10 ** 3 + ) # DiTTo in volt ampere + except: + pass + + # Set the nominal voltage + try: + if w == 0: + api_winding.nominal_voltage = ( + float(trfx_data["kvllprim"]) * 10 ** 3 + ) # DiTTo in volt + if w == 1: + api_winding.nominal_voltage = ( + float(trfx_data["kvllsec"]) * 10 ** 3 + ) # DiTTo in volt + except: + pass + + # Connection configuration + try: + api_winding.connection_type = transformer_connection_configuration_mapping( + trfx_data["conn"], w + ) + except: + pass + + # Resistance + try: + api_winding.resistance = R_perc + except: + pass + + # For each phase... + for p in phases: + + # Instanciate a PhaseWinding DiTTo object + try: + api_phase_winding = PhaseWinding(model) + except: + raise ValueError( + "Unable to instanciate PhaseWinding DiTTo object." + ) + + # Set the phase + try: + api_phase_winding.phase = p + except: + pass + + # Add the phase winding object to the winding + api_winding.phase_windings.append(api_phase_winding) + + # Add the winding object to the transformer + api_transformer.windings.append(api_winding) + + return None From 7605dd69410b43e27fa6db6e5142776e84d25b39 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 17:56:32 -0600 Subject: [PATCH 22/83] mv some cyme reader code to a utils.py for reuse --- ditto/readers/cyme/read.py | 212 +------------------------------------ 1 file changed, 3 insertions(+), 209 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 76c09e0f..4f679510 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -2,7 +2,6 @@ import logging import math -import cmath import os from functools import reduce from six import string_types @@ -622,111 +621,6 @@ def connection_configuration_mapping(self, value): ) ) - def transformer_connection_configuration_mapping(self, value, winding): - """ - Map the connection configuration for transformer (2 windings) objects from CYME to DiTTo. - - :param value: CYME value (either string or id) - :type value: int or str - :param winding: Number of the winding (0 or 1) - :type winding: int - :returns: DiTTo connection configuration for the requested winding - :rtype: str - - **Mapping:** - - +----------+----------------+------------+ - | Value | CYME | DiTTo | - +----------+----------------+-----+------+ - | | | 1st | 2nd | - +==========+================+=====+======+ - | 0 or '0' | 'Y_Y' | 'Y' | 'Y' | - +----------+----------------+-----+------+ - | 1 or '1' | 'D_Y' | 'D' | 'Y' | - +----------+----------------+-----+------+ - | 2 or '2' | 'Y_D' | 'Y' | 'D' | - +----------+----------------+-----+------+ - | 3 or '3' | 'YNG_YNG' | 'Y' | 'Y' | - +----------+----------------+-----+------+ - | 4 or '4' | 'D_D' | 'D' | 'D' | - +----------+----------------+-----+------+ - | 5 or '5' | 'DO_DO' | 'D' | 'D' | - +----------+----------------+-----+------+ - | 6 or '6' | 'YO_DO' | 'Y' | 'D' | - +----------+----------------+-----+------+ - | 7 or '7' | 'D_YNG' | 'D' | 'Y' | - +----------+----------------+-----+------+ - | 8 or '8' | 'YNG_D' | 'Y' | 'D' | - +----------+----------------+-----+------+ - | 9 or '9' | 'Y_YNG' | 'Y' | 'Y' | - +----------+----------------+-----+------+ - |10 or '10'| 'YNG_Y' | 'Y' | 'Y' | - +----------+----------------+-----+------+ - |11 or '11'| 'Yg_Zg' | 'Y' | 'Z' | - +----------+----------------+-----+------+ - |12 or '12'| 'D_Zg' | 'D' | 'Z' | - +----------+----------------+-----+------+ - """ - if winding not in [0, 1]: - raise ValueError( - "transformer_connection_configuration_mapping expects an integer 0 or 1 for winding arg. {} was provided.".format( - winding - ) - ) - - res = (None, None) - - if isinstance(value, int): - if value == 0 or value == 3 or value == 9 or value == 10: - res = ("Y", "Y") - if value == 1 or value == 7: - res = ("D", "Y") - if value == 2 or value == 6 or value == 8: - res = ("Y", "D") - if value == 4 or value == 5: - res = ("D", "D") - if value == 11: - res = ("Y", "Z") - if value == 12: - res = ("D", "Z") - - elif isinstance(value, string_types): - if value == "0" or value.lower() == "y_y": - res = ("Y", "Y") - if value == "1" or value.lower() == "d_y": - res = ("D", "Y") - if value == "2" or value.lower() == "y_d": - res = ("Y", "D") - if value == "3" or value.lower() == "yng_yng": - res = ("Y", "Y") - if value == "4" or value.lower() == "d_d": - res = ("D", "D") - if value == "5" or value.lower() == "do_do": - res = ("D", "D") - if value == "6" or value.lower() == "yo_do": - res = ("Y", "D") - if value == "7" or value.lower() == "d_yng": - res = ("D", "Y") - if value == "8" or value.lower() == "yng_d": - res = ("Y", "D") - if value == "9" or value.lower() == "y_yng": - res = ("Y", "Y") - if value == "10" or value.lower() == "yng_y": - res = ("Y", "Y") - if value == "11" or value.lower() == "yg_zg": - res = ("Y", "Z") - if value == "12" or value.lower() == "d_zg": - res = ("D", "Z") - - else: - raise ValueError( - "transformer_connection_configuration_mapping expects an integer or a string. {} was provided.".format( - type(value) - ) - ) - - return res[winding] - def check_object_in_line(self, line: str, obj: str) -> bool: """ Check if the header corresponding to object is in the given line. @@ -4842,38 +4736,7 @@ def parse_transformers(self, model): else: transformer_data = self.transformers["DEFAULT"] - # Resistance - # - # Note: Imported from Julietta's code - # - Z1 = float(transformer_data["z1"]) - Z0 = float(transformer_data["z0"]) - XR = float(transformer_data["xr"]) - XR0 = float(transformer_data["xr0"]) - if XR == 0: - R1 = 0 - X1 = 0 - else: - R1 = Z1 / math.sqrt(1 + XR * XR) - X1 = Z1 / math.sqrt(1 + 1 / (XR * XR)) - if XR0 == 0: - R0 = 0 - X0 = 0 - else: - R0 = Z0 / math.sqrt(1 + XR0 * XR0) - X0 = Z0 / math.sqrt(1 + 1 / (XR0 * XR0)) - complex0 = complex(R0, X0) - complex1 = complex(R1, X1) - matrix = np.array( - [[complex0, 0, 0], [0, complex1, 0], [0, 0, complex1]] - ) - a = 1 * cmath.exp(2 * math.pi * 1j / 3) - T = np.array([[1.0, 1.0, 1.0], [1.0, a * a, a], [1.0, a, a * a]]) - T_inv = np.linalg.inv(T) - Zabc = T * matrix * T_inv - Z_perc = Zabc.item((0, 0)) - R_perc = Z_perc.real / 2.0 - xhl = Z_perc.imag + xhl, R_perc = get_transformer_xhl_Rpercent(transformer_data) # Check if it's an LTC # @@ -4926,77 +4789,8 @@ def parse_transformers(self, model): pass # Here we know that we have two windings... - for w in range(2): - - # Instanciate a Winding DiTTo object - try: - api_winding = Winding(model) - except: - raise ValueError("Unable to instanciate Winding DiTTo object.") - - # Set the rated power - try: - if w == 0: - api_winding.rated_power = ( - float(transformer_data["kva"]) * 10 ** 3 - ) # DiTTo in volt ampere - if w == 1: - api_winding.rated_power = ( - float(transformer_data["kva"]) * 10 ** 3 - ) # DiTTo in volt ampere - except: - pass - - # Set the nominal voltage - try: - if w == 0: - api_winding.nominal_voltage = ( - float(transformer_data["kvllprim"]) * 10 ** 3 - ) # DiTTo in volt - if w == 1: - api_winding.nominal_voltage = ( - float(transformer_data["kvllsec"]) * 10 ** 3 - ) # DiTTo in volt - except: - pass - - # Connection configuration - try: - api_winding.connection_type = self.transformer_connection_configuration_mapping( - transformer_data["conn"], w - ) - except: - pass - - # Resistance - try: - api_winding.resistance = R_perc - except: - pass - - # For each phase... - for p in phases: - - # Instanciate a PhaseWinding DiTTo object - try: - api_phase_winding = PhaseWinding(model) - except: - raise ValueError( - "Unable to instanciate PhaseWinding DiTTo object." - ) - - # Set the phase - try: - api_phase_winding.phase = p - except: - pass - - # Add the phase winding object to the winding - api_winding.phase_windings.append(api_phase_winding) - - # Add the winding object to the transformer - api_transformer.windings.append(api_winding) - + add_two_windings(api_transformer, transformer_data, model, phases, R_perc) + # Handle Grounding transformers if settings["type"] == "grounding_transformer": From cdf4c985ff0643b577575b6a35d6fe8b9b5b235e Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 9 Sep 2022 18:30:16 -0600 Subject: [PATCH 23/83] cyme reader parse TRANSFORMER BYPHASE SETTING --- ditto/readers/cyme/read.py | 60 +++++++++++++++++++++++++++++++------- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 4f679510..bd3e3b40 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -4274,10 +4274,10 @@ def parse_transformers(self, model): } mapp_transformer_byphase_settings = { "sectionid": 0, - "PhaseTransformerID1": 8, # maps to TRANSFORMER ID - "PhaseTransformerID2": 9, - "PhaseTransformerID3": 10, - "FeedingNode": 16 + "phasetransformerid1": 8, # maps to TRANSFORMER ID + "phasetransformerid2": 9, + "phasetransformerid3": 10, + "feedingnode": 16 } self.auto_transformers = {} @@ -4324,7 +4324,7 @@ def parse_transformers(self, model): ######################################### # # - # GROUNDING TRANSFORMER # + # GROUNDING TRANSFORMER SETTINGS # # # ######################################### # @@ -4367,7 +4367,7 @@ def parse_transformers(self, model): ######################################### # # - # THREE WINDING TRANSFORMER # + # THREE WINDING TRANSFORMER SETTINGS # # # ######################################### # @@ -4394,7 +4394,7 @@ def parse_transformers(self, model): ######################################### # # - # TRANSFORMER # + # TRANSFORMER SETTINGS # # # ######################################### # @@ -4426,7 +4426,23 @@ def parse_transformers(self, model): ######################################### # # - # PHASE SHIFTER TRANSFORMER # + # TRANSFORMER BYPHASE SETTING # + # # + ######################################### + + self.settings.update( # why self.settings? (and not a local dict?) + self.parser_helper( + line, + "transformer_byphase_settings", + list(mapp_transformer_byphase_settings.keys()), # why a list of keys to parse separate from the mapp dict? + mapp_transformer_byphase_settings, + {"type": "transformer_byphase"}, + ) + ) + + ######################################### + # # + # PHASE SHIFTER TRANSFORMER SETTINGS # # # ######################################### # @@ -4478,7 +4494,7 @@ def parse_transformers(self, model): ######################################### # # - # GROUNDING TRANSFORMER SETTINGS # + # GROUNDING TRANSFORMER # # # ######################################### # @@ -4518,7 +4534,7 @@ def parse_transformers(self, model): ######################################### # # - # THREE WINDING TRANSFORMER SETTINGS # + # THREE WINDING TRANSFORMER # # # ######################################### # @@ -4543,7 +4559,7 @@ def parse_transformers(self, model): ######################################### # # - # TRANSFORMER SETTINGS # + # TRANSFORMER # # # ######################################### # @@ -4594,6 +4610,7 @@ def parse_transformers(self, model): try: phases = self.section_phase_mapping[sectionID]["phase"] + # phases is a string of length 1-3 with any combination of "A", "B", "C" except: raise ValueError("Empty phases for transformer {}.".format(sectionID)) @@ -4867,6 +4884,27 @@ def parse_transformers(self, model): # Add the winding object to the transformer api_transformer.windings.append(api_winding) + if settings["type"] == "transformer_byphase": + # at least one PhaseTransformerID is required, which corresponds + # with a self.transformers key + + trfx_id = None + for phs in [1,2,3]: + k = "phasetransformerid"+str(phs) + if ( + k in settings.keys() and + len(settings[k]) > 1 + ): + trfx_id = settings[k] + break + # TODO is it possible for TRANSFORMER BYPHASE SETTING to have more than one PhaseTransformerID? + + if trfx_id is not None and trfx_id in self.transformers.keys(): + transformer_data = self.transformers[trfx_id] + xhl, R_perc = get_transformer_xhl_Rpercent(transformer_data) + api_transformer.reactances = [float(xhl)] + add_two_windings(api_transformer, transformer_data, model, phases, R_perc) + # Add the transformer object to the list of transformers self._transformers.append(api_transformer) if not sectionID in self.section_duplicates: From 9087324190f591303634089952194347918f22a0 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 13 Sep 2022 15:00:55 -0600 Subject: [PATCH 24/83] update cyme reader doc strings --- ditto/readers/cyme/read.py | 55 +++++++++++++++++++++++++++++++++++--- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index bd3e3b40..d87e3a03 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -28,6 +28,7 @@ from ditto.models.photovoltaic import Photovoltaic from ditto.models.storage import Storage from ditto.models.phase_storage import PhaseStorage +from ditto.readers.cyme.utils import get_transformer_xhl_Rpercent, transformer_connection_configuration_mapping from ditto.models.base import Unicode from ditto.modify.system_structure import system_structure_modifier @@ -161,6 +162,47 @@ class Reader(AbstractReader): +-------------------------------------------+--------------------------------------------+ | 'network_equivalent_setting' | '[NETWORK EQUIVALENT SETTING]' | +-------------------------------------------+--------------------------------------------+ + + (nlaws fall 2022) + The CYME import/export manual is essentially a list of the section headers and the column names + for each section. Most of the following has been deducted from actual CYME models and their + ASCII exports. + + An attempt at describing the CYME modeling concepts in CYME 9.0: + + The highest level ID (abstract object) appears to be a StructureID, + which can be found in: + - [HEADNODES], [STRUCTUREUDD], [UNCONNECTED NODES] + However, there can probably be more than one structure in a network, so + perhaps the NetworkID is the highest level ID. + It appears that a NetworkID is one to one with a substation or feeder. + + [HEADNODES] + - two mandatory fields: NodeID, NetworkID + - it appears that the NodeID in HEADNODES serve as the source nodes for each network + - source nodes should correspond to a substation or feeder (just like NetworkID) + + [SOURCE (EQUIVALENT)] + - I do not have an example of CYME export with a SOURCE section + - so using the SOURCE EQUIVALENT SECTION, the only mandatory field is NodeID + - in the examples that I have the [HEADNODES].NodeID are 1:1 with [SOURCE EQUIVALENT].NodeID + + [SECTION] + - the rest of the network connections are specified in the [SECTION] block + - the [SECTION] block is peculiar compared to the other object blocks because it can contain + more than one "TOPO", where a TOPO line starts with FORMAT_OBJNAME, for example: + - FORMAT_SECTION=SectionID,FromNodeID,FromNodeIndex,ToNodeID,ToNodeIndex,Phase,ZoneID,SubNetworkId + - FORMAT_FEEDER=NetworkID,HeadNodeID,CoordSet,Year,Description,Color,LoadFactor,LossLoadFactorK,Group1,Group2,Group3,Group4,Group5,Group6,North,South,East,West,AreaFilter,TagText,TagProperties,TagDeltaX,TagDeltaY,TagAngle,TagAlignment,TagBorder,TagBackground,TagTextColor,TagBorderColor,TagBackgroundColor,TagLocation,TagFont,TagTextSize,TagOffset,Version + - FORMAT_SUBSTATION=NetworkID,HeadNodeID,CoordSet,Year,Description,Color,LoadFactor,LossLoadFactorK,Group1,Group2,Group3,Group4,Group5,Group6,North,South,East,West,AreaFilter,TagText,TagProperties,TagDeltaX,TagDeltaY,TagAngle,TagAlignment,TagBorder,TagBackground,TagTextColor,TagBorderColor,TagBackgroundColor,TagLocation,TagFont,TagTextSize,TagOffset,Version + - with the exception of FORMAT_SECTION, any data line that uses FORMAT_OBJNAME must start with OBJNAME= + - for example, a feeder specification will look like: + - FEEDER=1234,SOURCE_1234,1, ... + - SectionID's tend to correspond to line specifications, but could also be a load or any piece of equipment (I think) + - perhaps the biggest gotcha in CYME is that more than one object can be in a section (i.e. between two nodes) + - for example, a node-transformer-line-node specification looks like: + - in [OVERHEADLINE SETTING] SectionID=sec1, DeviceNumber=dev1, LineCableID=lin1 + - in [TRANSFORMER SETTING] SectionID=sec1, FromNodeID=nd1 + - in [SECTION] SectionID=sec1, FromNodeID=nd1, ToNodeID=nd2 """ register_names = ["cyme", "Cyme", "CYME"] @@ -1501,8 +1543,8 @@ def parse_sections(self, model): ... [SECTION] - FORMAT_section=sectionid,fromnodeid,tonodeid,phase - FORMAT_Feeder=networkid,headnodeid + FORMAT_section=sectionid,fromnodeid,...,tonodeid,...,phase + FORMAT_Feeder=networkid,headnodeid,... Feeder=feeder_1,head_feeder_1 section_1_feeder_1,node_1,node_2,ABC ... @@ -1510,6 +1552,8 @@ def parse_sections(self, model): Feeder=feeder_2,head_feeder_2 section_1_feeder_2,node_1,node_2,ABC ... + FORMAT_SUBSTATION=NetworkID,HeadNodeID + SUBSTATION=substation_name,substation_node_id, ... **What is done in this function:** @@ -4111,7 +4155,10 @@ def parse_transformers(self, model): """ Parse the transformers from CYME to DiTTo. Since substation transformer can have LTCs attached, when parsing a transformer, - we may also create a regulator. LTCs are represented as regulators. + we also create a regulator if "isltc" column is true. (LTCs are represented as regulators.) + CYME equipment that have "isltc" column include: + - TRANSFORMER + - AUTOTRANSFORMER The [TRANSFORMER] section specifies generic transformers that can be place in the network via other section specifications. For example, @@ -4157,6 +4204,7 @@ def parse_transformers(self, model): # Instanciate the list in which we store the DiTTo transformer objects self._transformers = [] + # maps of object column names to indices mapp_auto_transformer_settings = { "sectionid": 0, "eqid": 2, @@ -4280,6 +4328,7 @@ def parse_transformers(self, model): "feedingnode": 16 } + # empty dicts to fill from txt files self.auto_transformers = {} self.grounding_transformers = {} self.three_winding_auto_transformers = {} From 0032cc67b39b75fcbaf925e5e2cb4d8201dd9690 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 13 Sep 2022 15:07:35 -0600 Subject: [PATCH 25/83] typo fix in cyme reader not sure where I lost the correct import --- ditto/readers/cyme/read.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index d87e3a03..e39c45fd 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -28,7 +28,7 @@ from ditto.models.photovoltaic import Photovoltaic from ditto.models.storage import Storage from ditto.models.phase_storage import PhaseStorage -from ditto.readers.cyme.utils import get_transformer_xhl_Rpercent, transformer_connection_configuration_mapping +from ditto.readers.cyme.utils import get_transformer_xhl_Rpercent, add_two_windings from ditto.models.base import Unicode from ditto.modify.system_structure import system_structure_modifier From 05d74d0374bc2f209108e2499e4f2eca0505a40b Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 13:13:08 -0600 Subject: [PATCH 26/83] bug fix in CYME reader parse_transformers looks like a copy and paste error (the removed names are valid for THREE WINDING AUTO TRANSFORMER SETTING and THREE WINDING TRANSFORMER SETTING (not TRANSFORMER SETTING) --- ditto/readers/cyme/read.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index e39c45fd..e7a8f33c 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -4456,12 +4456,10 @@ def parse_transformers(self, model): "eqid", "coordx", "coordy", - "primaryfixedtapsetting", - "secondaryfixedtapsetting", - "tertiaryfixedtapsetting", + "primtap", + "secondarytap", "primarybasevoltage", "secondarybasevoltage", - "tertiarybasevoltage", "setpoint", "maxbuck", "maxboost", From 48e4e069ca4d8c9ef9f00f2d3ccca16249587d6e Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 13:25:51 -0600 Subject: [PATCH 27/83] read CYME 2phase transformer tap settings --- ditto/readers/cyme/read.py | 4 ++-- ditto/readers/cyme/utils.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index e7a8f33c..655d8435 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -4853,7 +4853,7 @@ def parse_transformers(self, model): pass # Here we know that we have two windings... - add_two_windings(api_transformer, transformer_data, model, phases, R_perc) + add_two_windings(api_transformer, transformer_data, settings, model, phases, R_perc) # Handle Grounding transformers if settings["type"] == "grounding_transformer": @@ -4950,7 +4950,7 @@ def parse_transformers(self, model): transformer_data = self.transformers[trfx_id] xhl, R_perc = get_transformer_xhl_Rpercent(transformer_data) api_transformer.reactances = [float(xhl)] - add_two_windings(api_transformer, transformer_data, model, phases, R_perc) + add_two_windings(api_transformer, transformer_data, settings, model, phases, R_perc) # Add the transformer object to the list of transformers self._transformers.append(api_transformer) diff --git a/ditto/readers/cyme/utils.py b/ditto/readers/cyme/utils.py index f9162ad9..449135a7 100644 --- a/ditto/readers/cyme/utils.py +++ b/ditto/readers/cyme/utils.py @@ -148,10 +148,18 @@ def transformer_connection_configuration_mapping(value, winding): return res[winding] -def add_two_windings(api_transformer: PowerTransformer, trfx_data: dict, model: Store, phases: str, R_perc: float) -> None: +def add_two_windings( + api_transformer: PowerTransformer, + trfx_data: dict, + settings: dict, + model: Store, + phases: str, + R_perc: float + ) -> None: """ Add Winding and PhaseWinding objects to PowerTransformer using `trfx_data` from the [TRANSFORMER] section + and `settings` from either [TRANFORMER SETTINGS] or [TRANSFORMER BYPHASE SETTINGS] """ # Here we know that we have two windings... @@ -213,13 +221,20 @@ def add_two_windings(api_transformer: PowerTransformer, trfx_data: dict, model: raise ValueError( "Unable to instanciate PhaseWinding DiTTo object." ) - # Set the phase try: api_phase_winding.phase = p except: pass + # set tap + tapkey = "primtap" if w == 0 else "secondarytap" + if ( + tapkey in settings.keys() and + settings[tapkey] is not None + ): + api_phase_winding.tap_position = float(settings[tapkey]) / 100 + # Add the phase winding object to the winding api_winding.phase_windings.append(api_phase_winding) From 2c11918f606669af2b1b72928a9bc377a85d8368 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 13:28:20 -0600 Subject: [PATCH 28/83] minor formatting in opendss Writer --- ditto/writers/opendss/write.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index a2e03eea..f223130d 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -136,7 +136,7 @@ def float_to_str(self, f): d1 = ctx.create_decimal(repr(f)) return format(d1, "f") - def write(self, model, **kwargs): + def write(self, model, write_taps=False, **kwargs): """General writing function responsible for calling the sub-functions. Note: re.sub('[^0-9a-zA-Z]+', '_', object_name) is used to fix node/bus names for OpenDSS, @@ -157,10 +157,7 @@ def write(self, model, **kwargs): else: self.verbose = False - if "write_taps" in kwargs: - self.write_taps = kwargs["write_taps"] - else: - self.write_taps = False + self.write_taps = write_taps if "separate_feeders" in kwargs: self.separate_feeders = kwargs["separate_feeders"] @@ -764,8 +761,9 @@ def write_transformers(self, model): for cnt, winding in enumerate(i.windings): - txt += " wdg={N}".format(N=cnt + 1) + txt += f" wdg={cnt+1}" + # conn = wye or delta if ( hasattr(winding, "connection_type") and winding.connection_type is not None From 99fba0761db1b05a187d637973c24a635e9cdce3 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 13:38:03 -0600 Subject: [PATCH 29/83] minor simplifications in CYME reader --- ditto/readers/cyme/read.py | 11 +++-------- ditto/readers/cyme/utils.py | 11 +++-------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 655d8435..dc4f6d03 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -4876,14 +4876,9 @@ def parse_transformers(self, model): # Set the rated power try: - if w == 0: - api_winding.rated_power = ( - float(transformer_data["ratedcapacity"]) * 10 ** 3 - ) # DiTTo in volt ampere - if w == 1: - api_winding.rated_power = ( - float(transformer_data["ratedcapacity"]) * 10 ** 3 - ) # DiTTo in volt ampere + api_winding.rated_power = ( + float(transformer_data["ratedcapacity"]) * 10 ** 3 + ) # DiTTo in volt ampere except: pass diff --git a/ditto/readers/cyme/utils.py b/ditto/readers/cyme/utils.py index 449135a7..5eb46faf 100644 --- a/ditto/readers/cyme/utils.py +++ b/ditto/readers/cyme/utils.py @@ -173,14 +173,9 @@ def add_two_windings( # Set the rated power try: - if w == 0: - api_winding.rated_power = ( - float(trfx_data["kva"]) * 10 ** 3 - ) # DiTTo in volt ampere - if w == 1: - api_winding.rated_power = ( - float(trfx_data["kva"]) * 10 ** 3 - ) # DiTTo in volt ampere + api_winding.rated_power = ( + float(trfx_data["kva"]) * 10 ** 3 + ) # DiTTo in volt ampere except: pass From d5c40514ce985f8379f6a5cb400a30e205d9ef91 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 14:21:43 -0600 Subject: [PATCH 30/83] opendss writer: add PowerSource nominal voltage to Voltagebases --- ditto/writers/opendss/write.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index f223130d..22c817cb 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -3794,6 +3794,7 @@ def write_master_file(self, model): fp.write( " basekV={volt}".format(volt=obj.nominal_voltage * 10 ** -3) ) # DiTTo in volts + self._baseKV_.add(obj.nominal_voltage * 10 ** -3) if ( hasattr(obj, "positive_sequence_impedance") From fdb33b0842bba495493d187a009a14ffc0480986 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 15:34:40 -0600 Subject: [PATCH 31/83] add Winding.base_voltage came across some transformers in CYME that have actual voltages set to something different than nominal. CYME has base voltages in TRANSFORMER SETTINGS (e.g. `primarybasevoltage`) and actual voltages in TRANSFORMER (e.g. `kvllprim`). The latter are currently set to Winding.nominal_voltage. --- ditto/models/winding.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ditto/models/winding.py b/ditto/models/winding.py index 90ed573d..7d0b422f 100644 --- a/ditto/models/winding.py +++ b/ditto/models/winding.py @@ -30,6 +30,9 @@ class Winding(DiTToHasTraits): nominal_voltage = Float( help="""The nominal voltage of the transformer winding""", default_value=None ) + base_voltage = Float( + help="""The voltage basis for per unit calculations""", default_value=None + ) voltage_limit = Float( help="""The maximum voltage allowed on the PT secondary.""", default_value=None ) From a90ad086ff0284844caa88fd712fe641b0f05315 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 15:36:45 -0600 Subject: [PATCH 32/83] parse primary/secondarybasevoltage in CYME reader --- ditto/readers/cyme/utils.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/ditto/readers/cyme/utils.py b/ditto/readers/cyme/utils.py index 5eb46faf..d9994ff8 100644 --- a/ditto/readers/cyme/utils.py +++ b/ditto/readers/cyme/utils.py @@ -192,6 +192,20 @@ def add_two_windings( except: pass + # Set base voltage + try: + if w == 0: + api_winding.base_voltage = ( + float(settings["primarybasevoltage"]) * 10 ** 3 + ) # DiTTo in volt + if w == 1: + api_winding.base_voltage = ( + float(settings["secondarybasevoltage"]) * 10 ** 3 + ) # DiTTo in volt + except: + pass + + # Connection configuration try: api_winding.connection_type = transformer_connection_configuration_mapping( From 585bda0cf221f6da930221f9dd6ad457dc6fa716 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 15:37:10 -0600 Subject: [PATCH 33/83] opendss writer add Winding.base_voltage to Voltagebases --- ditto/writers/opendss/write.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 22c817cb..99651329 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -599,6 +599,14 @@ def write_transformers(self, model): # Voltage type (Not mapped) + # volage basis + if ( + hasattr(winding, "base_voltage") + and winding.base_voltage is not None + and winding.base_voltage > 0 + ): + self._baseKV_.add(winding.base_voltage * 10 ** -3) + # Nominal voltage if ( hasattr(winding, "nominal_voltage") From d0145ef01a6203f3a4624b117051b9d8101af19d Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 18:17:09 -0600 Subject: [PATCH 34/83] cyme reader handle multiple networks a cyme "network" is either a substation or a feeder. each network has a head node. the old behavior was to set all sources (i.e. head nodes of networks) as PowerSource.is_sourcebus. With multiple sources DiTTo raises a value error (multiple sources in general should be allowed, but for the openDSS writer the is_sourcebus field is used to define the network / Vsource). So maybe need a clearer definition of what is a DiTTo PowerSource and why we should always have one (slack bus?) --- ditto/readers/cyme/read.py | 69 +++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index dc4f6d03..642d7077 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -203,6 +203,7 @@ class Reader(AbstractReader): - in [OVERHEADLINE SETTING] SectionID=sec1, DeviceNumber=dev1, LineCableID=lin1 - in [TRANSFORMER SETTING] SectionID=sec1, FromNodeID=nd1 - in [SECTION] SectionID=sec1, FromNodeID=nd1, ToNodeID=nd2 + - these issues are fixed in fix_section_overlaps """ register_names = ["cyme", "Cyme", "CYME"] @@ -880,28 +881,25 @@ def parse(self, model, **kwargs): logger.info("Parsing the header...") self.parse_header() + + logger.info("Parsing the Headnodes...") + self.parse_head_nodes(model) logger.info("Parsing the sections...") self.parse_sections(model) - logger.info("Parsing the sources...") - self.parse_sources(model) - # Call parse method of abtract reader super(Reader, self).parse(model, **kwargs) + logger.info("Parsing the subnetwork connections...") + self.parse_subnetwork_connections(model) + + logger.info("Parsing the sources...") + self.parse_sources(model) + logger.info("Parsing the network equivalents...") self.parse_network_equivalent(model) - # The variable self.network_type is set in the parse_sections() function. - # i.e. parse_sections - if self.network_type == "substation": - logger.info("Parsing the subnetwork connections...") - self.parse_subnetwork_connections(model) - else: - logger.info("Parsing the Headnodes...") - self.parse_head_nodes(model) - self.fix_section_overlaps(model) model.set_names() @@ -981,7 +979,11 @@ def parse_subnetwork_connections(self, model): ].is_substation_connection = True def parse_head_nodes(self, model): - """ This parses the [HEADNODES] objects and is used to build Feeder_metadata DiTTo objects which define the feeder names and feeder headnodes""" + """ + This parses the [HEADNODES] objects and is used to build Feeder_metadata DiTTo objects which define the feeder names and feeder headnodes + HEADNODES maps NodeID <-> NetworkID + and NetworkID is used to identify SUBSTATION and FEEDER in [SECTION] + """ # Open the network file self.get_file_content("network") mapp = { @@ -993,11 +995,13 @@ def parse_head_nodes(self, model): headnodes.update( self.parser_helper(line, "headnodes", ["nodeid", "networkid"], mapp) ) + self.headnodes = headnodes - for sid, headnode in headnodes.items(): - feeder_metadata = Feeder_metadata(model) - feeder_metadata.name = headnode["networkid"].strip().lower() - feeder_metadata.headnode = headnode["nodeid"].strip().lower() + # for sid, headnode in headnodes.items(): + # feeder_metadata = Feeder_metadata(model) + # feeder_metadata.name = headnode["networkid"].strip().lower() + # feeder_metadata.headnode = headnode["nodeid"].strip().lower() + def parse_sources(self, model): """Parse the sources.""" @@ -1085,11 +1089,8 @@ def parse_sources(self, model): _from = v["tonodeid"] _to = v["fromnodeid"] phases = list(v["phase"]) - try: - api_source = PowerSource(model) - except: - pass + api_source = PowerSource(model) api_source.name = _from + "_src" try: @@ -1103,8 +1104,18 @@ def parse_sources(self, model): api_source.phases = phases except: pass - - api_source.is_sourcebus = True + + # If there is a substation then it should be the source bus not a feeder + # Or if there is only one source + # TODO multiple substations are multiple feeders without a substation? + if len(source_equivalent_data) == 1: + api_source.is_sourcebus = True + else: + if _from in self.headnodes.keys(): + if "type" in self.headnodes[_from]: + if self.headnodes[_from]["type"] == "substation": + api_source.is_sourcebus = True + try: api_source.rated_power = 10 ** 3 * float( @@ -1556,6 +1567,7 @@ def parse_sections(self, model): SUBSTATION=substation_name,substation_node_id, ... + **What is done in this function:** - We need to create a clear and fast mapping between feeders and sectionids @@ -1641,8 +1653,12 @@ def parse_sections(self, model): or line[:15].lower() == "generalnetwork=" ): self.network_type = "feeder" + network_type = "feeder" if line[:11].lower() == "substation=": self.network_type = "substation" + network_type = "substation" + # all sections parsed after this line are in a substation with the NetworkID + # from SUBSTATION=THE_NTWK_ID (until a new FORMAT is found) # We should have a format for sections and feeders, # otherwise, raise an error... @@ -1689,7 +1705,7 @@ def parse_sections(self, model): self.network_data[_netID][key] = value # Then, we create a new entry in feeder_section_mapping - self.feeder_section_mapping[_netID] = [] + self.feeder_section_mapping[_netID] = [] # never filled in ? # Otherwise, we should have a new section... else: @@ -1723,9 +1739,14 @@ def parse_sections(self, model): # Populate this new entry for key, value in zip(format_section, section_data): self.section_phase_mapping[_sectionID][key] = value + if key == "fromnodeid": + if value in self.headnodes.keys(): + self.headnodes[value]["type"] = network_type # And finally, add a new entry to section_feeder_mapping self.section_feeder_mapping[_sectionID] = _netID + # section_feeder_mapping should be section_network_mapping + # b/c network can be feeder or substation # Finally, move on to next line line = next(self.content) From d00bfdc330b8aa3f7402a560951dadaa8572779d Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 14 Sep 2022 18:24:03 -0600 Subject: [PATCH 35/83] bug fix in cyme reader for regulator voltage --- ditto/readers/cyme/read.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 642d7077..5850fd69 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -5263,7 +5263,7 @@ def parse_regulators(self, model): # Set the nominal voltage try: - api_winding.nominal_voltage = float(regulator_data["kvln"]) + api_winding.nominal_voltage = float(regulator_data["kvln"]) * 10 ** 3 except: pass From f453e04f5dabdeac7cff93252e22080ea747b270 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 10:37:33 -0600 Subject: [PATCH 36/83] system_structure_modifier: delete method that has warning not to use it _set_nominal_voltages seems to be the worst of three methods defined to set nominal voltages --- ditto/modify/system_structure.py | 79 -------------------------------- 1 file changed, 79 deletions(-) diff --git a/ditto/modify/system_structure.py b/ditto/modify/system_structure.py index b607c28a..5401e46c 100644 --- a/ditto/modify/system_structure.py +++ b/ditto/modify/system_structure.py @@ -639,85 +639,6 @@ def set_nominal_voltages(self): ): obj.nominal_voltage = node_from_object.nominal_voltage - def _set_nominal_voltages(self): - """This function looks for all nodes and lines which have empty nominal_voltage fields. - The function goes upstream in the network representation to find a transformer. - The nominal voltage of the secondary of this transformer is then used to fill the empty nominal_voltage fields. - - .. warning:: DO NOT USE. Use set_nominal_voltages instead - - .. TODO:: Remove this once everything is stable. - """ - self.model.set_names() - # Loop over the objects - for obj in self.model.models: - - # If we get a Node with an empty nominal_voltage field - if isinstance(obj, Node) and obj.nominal_voltage is None: - # Get the upstream transformer name - try: - upstream_transformer_name = self.G.get_upstream_transformer( - self.model, obj.name - ) - except: - continue - if upstream_transformer_name is not None: - # Get the corresponding object - upstream_transformer_object = self.model[upstream_transformer_name] - # If possible, get all the winding voltages and select the minimum as the secondary voltage - if ( - hasattr(upstream_transformer_object, "windings") - and upstream_transformer_object.windings is not None - ): - volts = [] - for winding in upstream_transformer_object.windings: - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - volts.append(winding.nominal_voltage) - secondary_voltage = min(volts) - # Finally, assign this value to the object's nominal voltage - obj.nominal_voltage = secondary_voltage - - # If we get a line - if isinstance(obj, Line) and obj.nominal_voltage is None: - # Get the from node - if hasattr(obj, "from_element") and obj.from_element is not None: - node_from_object = self.model[obj.from_element] - - # If the from node has a known nominal voltage, then use this value - if ( - hasattr(node_from_object, "nominal_voltage") - and node_from_object.nominal_voltage is not None - ): - obj.nominal_voltage = node_from_object.nominal_voltage - - # Otherwise, do as before with the from node - # - # TODO: Put the following code into a function - # - else: - upstream_transformer_name = self.G.get_upstream_transformer( - self.model, node_from_object.name - ) - if upstream_transformer_name is not None: - upstream_transformer_object = self.model[ - upstream_transformer_name - ] - if ( - hasattr(upstream_transformer_object, "windings") - and upstream_transformer_object.windings is not None - ): - volts = [] - for winding in upstream_transformer_object.windings: - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - volts.append(winding.nominal_voltage) - secondary_voltage = min(volts) - obj.nominal_voltage = secondary_voltage def center_tap_load_preprocessing(self): """Performs the center tap load pre-processing step. From a58b6d73d92fc97c238ce639fd3b9e9b62724b8b Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 10:39:08 -0600 Subject: [PATCH 37/83] use the recommended system_structure_modifier.set_nominal_voltages_recur in NetworkAnalyzer --- ditto/metrics/network_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ditto/metrics/network_analysis.py b/ditto/metrics/network_analysis.py index b446b24c..e85a4afb 100644 --- a/ditto/metrics/network_analysis.py +++ b/ditto/metrics/network_analysis.py @@ -157,7 +157,7 @@ def __init__(self, model, compute_network=True, *args): self.edge_equipment_name = None modifier = system_structure_modifier(self.model, source) - modifier.set_nominal_voltages() + modifier.set_nominal_voltages_recur() # IMPORTANT: the following two parameters define what is LV and what is MV. # - Object is LV if object.nominal_voltage<=LV_threshold # - Object is MV if MV_threshold>=object.nominal_voltage>LV_threshold From 98126df67a24aade83f1f6f753c9e01eba804513 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 10:39:41 -0600 Subject: [PATCH 38/83] refactor `is not ""` to correct `!= ""` in NetworkAnalyzer --- ditto/metrics/network_analysis.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ditto/metrics/network_analysis.py b/ditto/metrics/network_analysis.py index e85a4afb..eadde519 100644 --- a/ditto/metrics/network_analysis.py +++ b/ditto/metrics/network_analysis.py @@ -465,7 +465,7 @@ def tag_objects(self): hasattr(prev_obj, "feeder_name") and hasattr(prev_obj, "name") and prev_obj.feeder_name is not None - and prev_obj.feeder_name is not "" + and prev_obj.feeder_name != "" and prev_obj.name in self.node_feeder_mapping # In case a default value has been set for all feeder_name values ): @@ -498,7 +498,7 @@ def tag_objects(self): hasattr(prev_obj, "feeder_name") and hasattr(prev_obj, "name") and prev_obj.feeder_name is not None - and prev_obj.feeder_name is not "" + and prev_obj.feeder_name != "" and prev_obj.name in self.node_feeder_mapping # In case a default value has been set for all feeder_name values ): @@ -525,7 +525,7 @@ def tag_objects(self): hasattr(prev_obj, "feeder_name") and hasattr(prev_obj, "name") and prev_obj.feeder_name is not None - and prev_obj.feeder_name is not "" + and prev_obj.feeder_name != "" and prev_obj.name in self.node_feeder_mapping # In case a default value has been set for all feeder_name values ): From cb260f0a0474b973efaa27e732c4f558556abf9a Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 10:40:09 -0600 Subject: [PATCH 39/83] rm broken, unused set_nominal_voltages in system_structure_modifier --- ditto/modify/system_structure.py | 120 ------------------------------- 1 file changed, 120 deletions(-) diff --git a/ditto/modify/system_structure.py b/ditto/modify/system_structure.py index 5401e46c..b9a6b61b 100644 --- a/ditto/modify/system_structure.py +++ b/ditto/modify/system_structure.py @@ -520,126 +520,6 @@ def test_feeder_cut(self): ) logger.debug(intersection) - def set_nominal_voltages(self): - """This function does the exact same thing as _set_nominal_voltages. - The implementation is less obvious but should be much faster. - - **Algorithm:** - - - Find all edges modeling transformers in the network - - Disconnect these edges (which should disconnect the network) - - Compute the connected components - - Group the nodes according to these connected components - - Re-connect the network by adding back the removed edges - - For every group of nodes: - - Find the nominal voltage of one node (look at secondary voltage of the upstream transformer) - - All nodes in this group get the same nominal voltage - - For every line: - - Set nominal voltage as the nominal voltage of one of the end-points (that we have thanks to the previous loop...) - - .. note:: This should be faster than _set_nominal_voltages since we only look upstream once for every group instead of doing it once for every node. - - .. warning:: Use set_nominal_voltages_recur instead. - - .. TODO:: Find out why the results of this and set_nominal_voltages_recur don't match... - """ - self.model.set_names() - - # We will remove all edges representing transformers - edges_to_remove = [ - edge - for edge in self.G.graph.edges(data=True) - if "equipment" in edge[2] and edge[2]["equipment"] == "PowerTransformer" - ] - - # Do it!! - self.G.graph.remove_edges_from(edges_to_remove) - - # Get the connected components - cc = nx.connected_components(self.G.graph) - - # Extract the groups of nodes with same nominal voltage - node_mapping = [component for component in cc] - - # Restaure the network by addind back the edges previously removed - self.G.graph.add_edges_from(edges_to_remove) - - # Graph should be connected, otherwise we broke it... - assert nx.is_connected(self.G.graph) - - # Instanciate the list of nominal voltages (one value for each group) - nominal_voltage_group = [None for _ in node_mapping] - upstream_transformer_name_group = [None for _ in node_mapping] - - # For every group... - for idx, group in enumerate(node_mapping): - - # ...first node is volonteered to be searched - volonteer = group.pop() - while not isinstance(self.model[volonteer], Node): - volonteer = group.pop() - - # Get the name of the upstream transformer - upstream_transformer_name = self.G.get_upstream_transformer( - self.model, volonteer - ) - - # If we got None, there is nothing we can do. Otherwise... - if upstream_transformer_name is not None: - - # ...get the transformer object - upstream_transformer_object = self.model[upstream_transformer_name] - upstream_transformer_name_group[idx] = upstream_transformer_name - - # Get the nominal voltage of the secondary - if ( - hasattr(upstream_transformer_object, "windings") - and upstream_transformer_object.windings is not None - ): - - volts = [] - for winding in upstream_transformer_object.windings: - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - volts.append(winding.nominal_voltage) - - secondary_voltage = min(volts) - # And assign this value as the nominal voltage for the group of nodes - nominal_voltage_group[idx] = secondary_voltage - - # Now, we simply loop over all the groups - for idx, group in enumerate(node_mapping): - - # And all the nodes inside of the groups - for n in group: - - # And set the nominal voltage as the group value - self.model[n].nominal_voltage = nominal_voltage_group[idx] - if isinstance(self.model[n], Load): - self.model[ - n - ].upstream_transformer_name = upstream_transformer_name_group[idx] - - # Now we take care of the Lines. - # Since we should have the nominal voltage for every node (in a perfect world), - # We just have to grab the nominal voltage of one of the end-points. - for obj in self.model.models: - # If we get a line - if isinstance(obj, Line) and obj.nominal_voltage is None: - # Get the from node - if hasattr(obj, "from_element") and obj.from_element is not None: - node_from_object = self.model[obj.from_element] - - # If the from node has a known nominal voltage, then use this value - if ( - hasattr(node_from_object, "nominal_voltage") - and node_from_object.nominal_voltage is not None - ): - obj.nominal_voltage = node_from_object.nominal_voltage - - def center_tap_load_preprocessing(self): """Performs the center tap load pre-processing step. This function is responsible for setting the correct phase of center-tap loads. From 492513265648c6a662b922ad31788ae8312fcbe6 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 10:40:36 -0600 Subject: [PATCH 40/83] minor formatting in system_structure_modifier --- ditto/modify/system_structure.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/ditto/modify/system_structure.py b/ditto/modify/system_structure.py index b9a6b61b..8a92bcb8 100644 --- a/ditto/modify/system_structure.py +++ b/ditto/modify/system_structure.py @@ -224,9 +224,10 @@ def set_nominal_voltages_recur(self, *args): previous = self.source else: node, voltage, previous = args - if (previous, node) in self.edge_equipment and self.edge_equipment[ - (previous, node) - ] == "PowerTransformer": + if ( + (previous, node) in self.edge_equipment and + self.edge_equipment[(previous, node)] == "PowerTransformer" + ): trans_name = self.edge_equipment_name[(previous, node)] new_value = min( [ @@ -235,9 +236,10 @@ def set_nominal_voltages_recur(self, *args): if w.nominal_voltage is not None ] ) - elif (node, previous) in self.edge_equipment and self.edge_equipment[ - (node, previous) - ] == "PowerTransformer": + elif ( + (node, previous) in self.edge_equipment and + self.edge_equipment[(node, previous)] == "PowerTransformer" + ): trans_name = self.edge_equipment_name[(node, previous)] new_value = min( [ @@ -248,6 +250,7 @@ def set_nominal_voltages_recur(self, *args): ) else: new_value = voltage + if hasattr(self.model[node], "nominal_voltage"): self.model[node].nominal_voltage = new_value for child in self.G.digraph.successors(node): From 0038639b9741fc7a2ba0c3a3a5d90d63dfd14d41 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 10:56:28 -0600 Subject: [PATCH 41/83] add debug msg when changing node nominal_voltage in system_structure_modifier --- ditto/modify/system_structure.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ditto/modify/system_structure.py b/ditto/modify/system_structure.py index 8a92bcb8..7d6274ff 100644 --- a/ditto/modify/system_structure.py +++ b/ditto/modify/system_structure.py @@ -252,7 +252,9 @@ def set_nominal_voltages_recur(self, *args): new_value = voltage if hasattr(self.model[node], "nominal_voltage"): - self.model[node].nominal_voltage = new_value + if new_value != self.model[node].nominal_voltage: + logger.debug(f"Setting node {node} nominal voltage to {new_value} from {self.model[node].nominal_voltage}") + self.model[node].nominal_voltage = new_value for child in self.G.digraph.successors(node): self.set_nominal_voltages_recur(child, new_value, node) From 182173f8a8429771d0109bf1040fa552ef65454e Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 11:43:34 -0600 Subject: [PATCH 42/83] rm unused arg from Network.get_upstream_transformer --- ditto/network/network.py | 2 +- ditto/store.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ditto/network/network.py b/ditto/network/network.py index 8133b572..dc23d51c 100644 --- a/ditto/network/network.py +++ b/ditto/network/network.py @@ -346,7 +346,7 @@ def remove_open_switches(self, model): if self.digraph.has_edge(m.to_element, m.from_element): self.digraph.remove_edge(m.to_element, m.from_element) - def get_upstream_transformer(self, model, node): + def get_upstream_transformer(self, node): curr_node = node curr = list(self.digraph.predecessors(node)) diff --git a/ditto/store.py b/ditto/store.py index e6f53b7f..47663ec2 100644 --- a/ditto/store.py +++ b/ditto/store.py @@ -187,9 +187,7 @@ def set_node_voltages(self): self.set_names() for i in self.models: if isinstance(i, Node) and hasattr(i, "name") and i.name is not None: - upstream_transformer = self._network.get_upstream_transformer( - self, i.name - ) + upstream_transformer = self._network.get_upstream_transformer(i.name) try: upstream_voltage = ( self[upstream_transformer].windings[-1].nominal_voltage From 993378b724b6688121cf4eedfb348632a2f3ec38 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 11:44:01 -0600 Subject: [PATCH 43/83] update network_analysis.py doc string --- ditto/metrics/network_analysis.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ditto/metrics/network_analysis.py b/ditto/metrics/network_analysis.py index eadde519..f5dc1d6f 100644 --- a/ditto/metrics/network_analysis.py +++ b/ditto/metrics/network_analysis.py @@ -205,12 +205,10 @@ def add_feeder_information( """ Use this function to add the feeder information if available. - :param feeder_names: List of the feeder names - :type feeder_names: List(str) - :param feeder_nodes: List of lists containing feeder nodes - :type feeder_nodes: List of Lists of strings - :param feeder_types: List of feeder types. - :type feeder_types: List or string if all feeders have the same type + :param feeder_names: List(str) of the feeder names + :param feeder_nodes: List of lists of strings containing feeder nodes + :param substations: List(str) of the substations names + :param feeder_types: List(str) of feeder types or string if all feeders have the same type """ if len(feeder_names) != len(feeder_nodes): raise ValueError( From ea6380c8d8f98700458af32ac2e113e3f7828040 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 13:34:09 -0600 Subject: [PATCH 44/83] minor formatting in Network.__init__ --- ditto/network/network.py | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/ditto/network/network.py b/ditto/network/network.py index dc23d51c..04f252dd 100644 --- a/ditto/network/network.py +++ b/ditto/network/network.py @@ -15,17 +15,11 @@ class Network: def __init__(self): - self.graph = None - self.digraph = None # Doesn't contain attributes, just topology - self.class_map = ( - {} - ) # Map the networkx names to the object type (not included in attributes) - self.is_built = ( - False # Flag that indicates whether the Network has been built or not. - ) - self.attributes_set = ( - False # Flag that indicates whether the attributes have been set or not. - ) + self.graph = None # becomes nx.Graph() in self.build + self.digraph = None # becomes nx.DiGraph() in self.build. Doesn't contain attributes, just topology + self.class_map = {} # Map the networkx names to the object type (not included in attributes) + self.is_built = False # Flag that indicates whether the Network has been built or not. + self.attributes_set = False # Flag that indicates whether the attributes have been set or not. def provide_graphs(self, graph, digraph): """ From b4cb4ecf8dd68fdf326d5aa43c3f673c06b0b396 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 13:35:58 -0600 Subject: [PATCH 45/83] make warnings logger.warn (were logger.debug) --- ditto/network/network.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ditto/network/network.py b/ditto/network/network.py index 04f252dd..7bdd27ca 100644 --- a/ditto/network/network.py +++ b/ditto/network/network.py @@ -363,21 +363,19 @@ def get_all_elements_downstream(self, model, source): model.set_names() # Checking that the network is already built - # TODO: Log instead of printing... if not self.is_built: - logger.debug( + logger.warn( "Warning. Trying to use Network model without building the network." ) - logger.debug("Calling build() with source={}".format(source)) + logger.warn("Calling build() with source={}".format(source)) self.build(model, source=source) # Checking that the attributes have been set - # TODO: Log instead of printing... if not self.attributes_set: - logger.debug( + logger.warn( "Warning. Trying to use Network model without setting the attributes first." ) - logger.debug("Setting the attributes...") + logger.warn("Setting the attributes...") self.set_attributes(model) # Run the dfs or die trying... From be30db00a44e2900dfea09c044e9882da4cc718c Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 15:58:43 -0600 Subject: [PATCH 46/83] minor formatting in Cyme reader --- ditto/readers/cyme/read.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 5850fd69..2ad717d8 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -5240,10 +5240,7 @@ def parse_regulators(self, model): for w in range(2): # Instanciate a Winding DiTTo object - try: - api_winding = Winding(model) - except: - raise ValueError("Unable to instanciate Winding DiTTo object.") + api_winding = Winding(model) # Set the rated power try: @@ -6524,11 +6521,11 @@ def fix_section_overlaps(self, model, **kwargs): element.from_element = from_element # Regulators go between the same two nodes - if isinstance(element,Regulator): + if isinstance(element, Regulator): from_element = original_from_element+'_reg' else: from_element = original_from_element+'_sec_'+str(intermediate_count) - intermediate_count +=1 + intermediate_count += 1 if intermediate_count != connector_count: element.to_element = from_element api_node = Node(model) From cdd14657698eaa9d2cdd4acb5d0d443bea75e56c Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 15:58:59 -0600 Subject: [PATCH 47/83] rm unused commented code in openDSS writer --- ditto/writers/opendss/write.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 99651329..62acd99b 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -462,14 +462,6 @@ def write_transformers(self, model): ): N_phases.append(len(winding.phase_windings)) -# Doesn't apply to center-tap transformers. REMOVE? -# if len(np.unique(N_phases)) != 1: -# logger.error( -# "Did not find the same number of phases accross windings of transformer {name}".format( -# name=i.name -# ) -# ) - try: # phase-phase connection defined with phase=1 even though it's two phase. if ( From 62e5b95c71d3a869d7af6093490c53a2465671bd Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 16:59:20 -0600 Subject: [PATCH 48/83] use Store.iter_models(PowerTransformer) in openDSS writer looks like a lot of changes but primarily is a de-dent by one level because the for loop is in the iter_models method --- ditto/writers/opendss/write.py | 915 ++++++++++++++++----------------- 1 file changed, 454 insertions(+), 461 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 62acd99b..80bc815a 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -415,539 +415,532 @@ def write_transformers(self, model): feeder_text_map = {} # Loop over the DiTTo objects - for i in model.models: - # If we get a transformer object... - if isinstance(i, PowerTransformer): - # Write the data in the file - # Name - if ( - self.separate_feeders - and hasattr(i, "feeder_name") - and i.feeder_name is not None - ): - feeder_name = i.feeder_name - else: - feeder_name = "DEFAULT" - if ( - self.separate_substations - and hasattr(i, "substation_name") - and i.substation_name is not None - ): - substation_name = i.substation_name - else: - substation_name = "DEFAULT" - - if not substation_name in substation_text_map: - substation_text_map[substation_name] = set([feeder_name]) - else: - substation_text_map[substation_name].add(feeder_name) - txt = "" - if substation_name + "_" + feeder_name in feeder_text_map: - txt = feeder_text_map[substation_name + "_" + feeder_name] - - if hasattr(i, "name") and i.name is not None: - txt += "New Transformer." + i.name - else: - # If we do not have a valid name, do not even try - # to write anything for this transformer.... - continue - - # Number of phases and windings - if hasattr(i, "windings") and i.windings is not None: - N_phases = [] - for winding in i.windings: - if ( - hasattr(winding, "phase_windings") - and winding.phase_windings is not None - ): - N_phases.append(len(winding.phase_windings)) - - try: - # phase-phase connection defined with phase=1 even though it's two phase. - if ( - len(i.windings) == 3 - and i.windings[0].connection_type is not None - and i.windings[0].connection_type == "D" - ): - txt += " phases={Np}".format(Np=N_phases[0] - 1) - else: - txt += " phases={Np}".format(Np=N_phases[0]) - txt += " windings={N}".format(N=len(i.windings)) - except: - logger.error( - "Could not write the number of phases for transformer {name}".format( - name=i.name - ) - ) - - # Connection - if hasattr(i, "from_element") and i.from_element is not None: - bus1 = i.from_element - else: - bus1 = None - if hasattr(i, "to_element") and i.to_element is not None: - bus2 = i.to_element - else: - bus2 = None - - if bus1 is not None and bus2 is not None: - buses = [bus1, bus2] - else: - buses = None - - # Rated power - # if hasattr(i, 'rated_power') and i.rated_power is not None: - # fp.write(' kva='+str(i.rated_power*10**-3)) #OpenDSS in kWatts - - # Emergency power - # Emergency_power removed from powerTransformers and added to windings by Tarek - # if hasattr(i, 'emergency_power') and i.emergency_power is not None: - # fp.write(' EmergHKVA='+str(i.emergency_power*10**-3)) #OpenDSS in kWatts - - # Loadloss - if hasattr(i, "loadloss") and i.loadloss is not None: - txt += " %loadloss=" + str(i.loadloss) # OpenDSS in kWatts - - # install type (Not mapped) - - # noload_loss - if hasattr(i, "noload_loss") and i.noload_loss is not None: - txt += " %Noloadloss=" + str(i.noload_loss) - - # noload_loss - if hasattr(i, "normhkva") and i.normhkva is not None: - txt += " normhkva=" + str(i.normhkva) - - # phase shift (Not mapped) - - # Assume that we only have two or three windings. Three are used for center-tap transformers. Other single or three phase transformers use 2 windings - # For banked 3-phase transformers, separate single phase transformers are used - if hasattr(i, "windings") and i.windings is not None: - - for cnt, winding in enumerate(i.windings): - if ( - hasattr(winding, "phase_windings") - and winding.phase_windings is not None - ): - - for phase_winding in winding.phase_windings: - if ( - hasattr(phase_winding, "compensator_r") - and phase_winding.compensator_r is not None - ): - if not i.name in self.compensator: - self.compensator[i.name] = {} - self.compensator[i.name]["R"] = set( - [phase_winding.compensator_r] - ) - elif "R" in self.compensator[i.name]: - self.compensator[i.name]["R"].add( - phase_winding.compensator_r - ) - else: - self.compensator[i.name]["R"] = set( - [phase_winding.compensator_r] - ) + for i in model.iter_models(PowerTransformer): + # Write the data in the file + # Name + feeder_name = "DEFAULT" + if ( + self.separate_feeders + and hasattr(i, "feeder_name") + and i.feeder_name is not None + ): + feeder_name = i.feeder_name + + substation_name = "DEFAULT" + if ( + self.separate_substations + and hasattr(i, "substation_name") + and i.substation_name is not None + ): + substation_name = i.substation_name - if ( - hasattr(phase_winding, "compensator_x") - and phase_winding.compensator_x is not None - ): - if not i.name in self.compensator: - self.compensator[i.name] = {} - self.compensator[i.name]["X"] = set( - [phase_winding.compensator_x] - ) - elif "X" in self.compensator[i.name]: - self.compensator[i.name]["X"].add( - phase_winding.compensator_x - ) - else: - self.compensator[i.name]["X"] = set( - [phase_winding.compensator_x] - ) + if not substation_name in substation_text_map: + substation_text_map[substation_name] = set([feeder_name]) + else: + substation_text_map[substation_name].add(feeder_name) - if len(i.windings) == 2: + txt = "" + if substation_name + "_" + feeder_name in feeder_text_map: + txt = feeder_text_map[substation_name + "_" + feeder_name] - for cnt, winding in enumerate(i.windings): + if hasattr(i, "name") and i.name is not None: + txt += "New Transformer." + i.name + else: + # If we do not have a valid name, do not even try + # to write anything for this transformer.... + continue + + # Number of phases and windings + if hasattr(i, "windings") and i.windings is not None: + N_phases = [] + for winding in i.windings: + if ( + hasattr(winding, "phase_windings") + and winding.phase_windings is not None + ): + N_phases.append(len(winding.phase_windings)) - txt += " wdg={N}".format(N=cnt + 1) + try: + # phase-phase connection defined with phase=1 even though it's two phase. + if ( + len(i.windings) == 3 + and i.windings[0].connection_type is not None + and i.windings[0].connection_type == "D" + ): + txt += " phases={Np}".format(Np=N_phases[0] - 1) + else: + txt += " phases={Np}".format(Np=N_phases[0]) + txt += " windings={N}".format(N=len(i.windings)) + except: + logger.error(f"Could not write the number of phases for transformer {i.name}") + + # Connection + bus1 = None + if hasattr(i, "from_element") and i.from_element is not None: + bus1 = i.from_element + + bus2 = None + if hasattr(i, "to_element") and i.to_element is not None: + bus2 = i.to_element + + buses = None + if bus1 is not None and bus2 is not None: + buses = [bus1, bus2] + + # Rated power + # if hasattr(i, 'rated_power') and i.rated_power is not None: + # fp.write(' kva='+str(i.rated_power*10**-3)) #OpenDSS in kWatts + + # Emergency power + # Emergency_power removed from powerTransformers and added to windings by Tarek + # if hasattr(i, 'emergency_power') and i.emergency_power is not None: + # fp.write(' EmergHKVA='+str(i.emergency_power*10**-3)) #OpenDSS in kWatts + + # Loadloss + if hasattr(i, "loadloss") and i.loadloss is not None: + txt += " %loadloss=" + str(i.loadloss) # OpenDSS in kWatts + + # install type (Not mapped) + + # noload_loss + if hasattr(i, "noload_loss") and i.noload_loss is not None: + txt += " %Noloadloss=" + str(i.noload_loss) + + # noload_loss + if hasattr(i, "normhkva") and i.normhkva is not None: + txt += " normhkva=" + str(i.normhkva) + + # phase shift (Not mapped) + + # Assume that we only have two or three windings. Three are used for center-tap transformers. Other single or three phase transformers use 2 windings + # For banked 3-phase transformers, separate single phase transformers are used + if hasattr(i, "windings") and i.windings is not None: + + # set compensator_r and compensator_x + for cnt, winding in enumerate(i.windings): + if ( + hasattr(winding, "phase_windings") + and winding.phase_windings is not None + ): - # Connection type + for phase_winding in winding.phase_windings: if ( - hasattr(winding, "connection_type") - and winding.connection_type is not None + hasattr(phase_winding, "compensator_r") + and phase_winding.compensator_r is not None ): - if winding.connection_type == "Y": - txt += " conn=wye" - elif winding.connection_type == "D": - txt += " conn=delta" + if not i.name in self.compensator: + self.compensator[i.name] = {} + self.compensator[i.name]["R"] = set( + [phase_winding.compensator_r] + ) + elif "R" in self.compensator[i.name]: + self.compensator[i.name]["R"].add( + phase_winding.compensator_r + ) else: - logger.error( - "Unsupported type of connection {conn} for transformer {name}".format( - conn=winding.connection_type, name=i.name - ) + self.compensator[i.name]["R"] = set( + [phase_winding.compensator_r] ) - # Voltage type (Not mapped) - - # volage basis if ( - hasattr(winding, "base_voltage") - and winding.base_voltage is not None - and winding.base_voltage > 0 + hasattr(phase_winding, "compensator_x") + and phase_winding.compensator_x is not None ): - self._baseKV_.add(winding.base_voltage * 10 ** -3) - - # Nominal voltage - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - txt += " Kv={kv}".format( - kv=winding.nominal_voltage * 10 ** -3 - ) # OpenDSS in kvolts - if ( - not substation_name + "_" + feeder_name - in self._baseKV_feeders_ - ): - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() - - if ( - winding.nominal_voltage < 300 - ): # Line-Neutral voltage for 120 V - self._baseKV_.add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 + if not i.name in self.compensator: + self.compensator[i.name] = {} + self.compensator[i.name]["X"] = set( + [phase_winding.compensator_x] ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 + elif "X" in self.compensator[i.name]: + self.compensator[i.name]["X"].add( + phase_winding.compensator_x ) else: - self._baseKV_.add( - winding.nominal_voltage * 10 ** -3 + self.compensator[i.name]["X"] = set( + [phase_winding.compensator_x] ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add(winding.nominal_voltage * 10 ** -3) - # rated power - if ( - hasattr(winding, "rated_power") - and winding.rated_power is not None - ): - txt += " kva={kva}".format( - kva=winding.rated_power * 10 ** -3 + if len(i.windings) == 2: + + for cnt, winding in enumerate(i.windings): + + txt += " wdg={N}".format(N=cnt + 1) + + # Connection type + if ( + hasattr(winding, "connection_type") + and winding.connection_type is not None + ): + if winding.connection_type == "Y": + txt += " conn=wye" + elif winding.connection_type == "D": + txt += " conn=delta" + else: + logger.error( + "Unsupported type of connection {conn} for transformer {name}".format( + conn=winding.connection_type, name=i.name + ) ) - # emergency_power - # Was added to windings by Tarek + # Voltage type (Not mapped) + + # volage basis + if ( + hasattr(winding, "base_voltage") + and winding.base_voltage is not None + and winding.base_voltage > 0 + ): + self._baseKV_.add(winding.base_voltage * 10 ** -3) + + # Nominal voltage + if ( + hasattr(winding, "nominal_voltage") + and winding.nominal_voltage is not None + ): + txt += " Kv={kv}".format( + kv=winding.nominal_voltage * 10 ** -3 + ) # OpenDSS in kvolts if ( - hasattr(winding, "emergency_power") - and winding.emergency_power is not None + not substation_name + "_" + feeder_name + in self._baseKV_feeders_ ): - txt += " EmergHKVA={}".format( - winding.emergency_power * 10 ** -3 - ) # OpenDSS in kWatts + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ] = set() - # resistance if ( - hasattr(winding, "resistance") - and winding.resistance is not None - ): - txt += " %R={R}".format(R=winding.resistance) + winding.nominal_voltage < 300 + ): # Line-Neutral voltage for 120 V + self._baseKV_.add( + winding.nominal_voltage + * math.sqrt(3) + * 10 ** -3 + ) + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ].add( + winding.nominal_voltage + * math.sqrt(3) + * 10 ** -3 + ) + else: + self._baseKV_.add( + winding.nominal_voltage * 10 ** -3 + ) + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ].add(winding.nominal_voltage * 10 ** -3) + + # rated power + if ( + hasattr(winding, "rated_power") + and winding.rated_power is not None + ): + txt += " kva={kva}".format( + kva=winding.rated_power * 10 ** -3 + ) - # Voltage limit (Not mapped) + # emergency_power + # Was added to windings by Tarek + if ( + hasattr(winding, "emergency_power") + and winding.emergency_power is not None + ): + txt += " EmergHKVA={}".format( + winding.emergency_power * 10 ** -3 + ) # OpenDSS in kWatts - # Reverse resistance (Not mapped) + # resistance + if ( + hasattr(winding, "resistance") + and winding.resistance is not None + ): + txt += " %R={R}".format(R=winding.resistance) - # Check if winding is grounded - # this check is done here so that it happens only for 2 windings + # Voltage limit (Not mapped) - # Phase windings - if ( - hasattr(winding, "phase_windings") - and winding.phase_windings is not None - ): + # Reverse resistance (Not mapped) - if buses is not None: - bus = buses[cnt] - txt += " bus={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', str(bus))) + # Check if winding is grounded + # this check is done here so that it happens only for 2 windings - if len(winding.phase_windings) != 3: + # Phase windings + if ( + hasattr(winding, "phase_windings") + and winding.phase_windings is not None + ): - for j, phase_winding in enumerate( - winding.phase_windings - ): + if buses is not None: + bus = buses[cnt] + txt += " bus={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', str(bus))) + + if len(winding.phase_windings) != 3: - # Connection - if ( - hasattr(phase_winding, "phase") - and phase_winding.phase is not None - ): - txt += "." + str( - self.phase_mapping(phase_winding.phase) - ) + for j, phase_winding in enumerate( + winding.phase_windings + ): + # Connection if ( - winding.connection_type == "D" - and len(winding.phase_windings) == 1 + hasattr(phase_winding, "phase") + and phase_winding.phase is not None ): - print( - "Warning - only one phase specified for a delta system - adding another connection" + txt += "." + str( + self.phase_mapping(phase_winding.phase) ) - if self.phase_mapping(phase_winding.phase) == 1: - txt += ".2" - if self.phase_mapping(phase_winding.phase) == 2: - txt += ".3" - if self.phase_mapping(phase_winding.phase) == 3: - txt += ".1" - if winding.is_grounded: - txt += ".0" - - if ( - hasattr(winding, "phase_windings") - and winding.phase_windings is not None - ): - # Tap position - # THIS CAN CAUSE PROBLEMS - # Use write_taps boolean to write this information or not if ( - self.write_taps - and hasattr( - winding.phase_windings[0], "tap_position" - ) - and winding.phase_windings[0].tap_position - is not None + winding.connection_type == "D" + and len(winding.phase_windings) == 1 ): - txt += " Tap={tap}".format( - tap=winding.phase_windings[0].tap_position + print( + "Warning - only one phase specified for a delta system - adding another connection" ) + if self.phase_mapping(phase_winding.phase) == 1: + txt += ".2" + if self.phase_mapping(phase_winding.phase) == 2: + txt += ".3" + if self.phase_mapping(phase_winding.phase) == 3: + txt += ".1" - if hasattr(i, "reactances") and i.reactances is not None: - # Since we are in the case of 2 windings, we should only have one reactance - if isinstance(i.reactances, list): - if len(i.reactances) != 1: - logger.error( - "Number of reactances incorrect for transformer {name}. Expected 1, got {N}".format( - name=i.name, N=len(i.reactances) - ) - ) - else: - txt += " XHL={reac}".format(reac=i.reactances[0]) - # If it is not a list, maybe it was entered as a scalar, but should not be that way.... - elif isinstance(i.reactances, (int, float)): - txt += " XHL={reac}".format(reac=i.reactances) - else: + if winding.is_grounded: + txt += ".0" + + if ( + hasattr(winding, "phase_windings") + and winding.phase_windings is not None + ): + # Tap position + # THIS CAN CAUSE PROBLEMS + # Use write_taps boolean to write this information or not + if ( + self.write_taps + and hasattr( + winding.phase_windings[0], "tap_position" + ) + and winding.phase_windings[0].tap_position + is not None + ): + txt += " Tap={tap}".format( + tap=winding.phase_windings[0].tap_position + ) + + if hasattr(i, "reactances") and i.reactances is not None: + # Since we are in the case of 2 windings, we should only have one reactance + if isinstance(i.reactances, list): + if len(i.reactances) != 1: logger.error( - "Reactances not understood for transformer {name}.".format( - name=i.name + "Number of reactances incorrect for transformer {name}. Expected 1, got {N}".format( + name=i.name, N=len(i.reactances) ) ) + else: + txt += " XHL={reac}".format(reac=i.reactances[0]) + # If it is not a list, maybe it was entered as a scalar, but should not be that way.... + elif isinstance(i.reactances, (int, float)): + txt += " XHL={reac}".format(reac=i.reactances) + else: + logger.error( + "Reactances not understood for transformer {name}.".format( + name=i.name + ) + ) - # This is used to represent center-tap transformers - # As described in the documentation, if the R and X values are not known, the values described by default_r and default_x should be used - if len(i.windings) == 3: - default_r = [0.6, 1.2, 1.2] - default_x = [2.04, 2.04, 1.36] + # This is used to represent center-tap transformers + # As described in the documentation, if the R and X values are not known, the values described by default_r and default_x should be used + if len(i.windings) == 3: + default_r = [0.6, 1.2, 1.2] + default_x = [2.04, 2.04, 1.36] - for cnt, winding in enumerate(i.windings): + for cnt, winding in enumerate(i.windings): - txt += f" wdg={cnt+1}" + txt += f" wdg={cnt+1}" - # conn = wye or delta - if ( - hasattr(winding, "connection_type") - and winding.connection_type is not None - ): - if winding.connection_type == "Y": - txt += " conn=wye" - elif winding.connection_type == "D": - txt += " conn=delta" - else: - logger.error( - "Unsupported type of connection {conn} for transformer {name}".format( - conn=winding.connection_type, name=i.name - ) + # conn = wye or delta + if ( + hasattr(winding, "connection_type") + and winding.connection_type is not None + ): + if winding.connection_type == "Y": + txt += " conn=wye" + elif winding.connection_type == "D": + txt += " conn=delta" + else: + logger.error( + "Unsupported type of connection {conn} for transformer {name}".format( + conn=winding.connection_type, name=i.name ) + ) - # Connection - if buses is not None: - - if cnt == 0 or cnt == 1: - txt += " bus={b}".format(b=re.sub('[^0-9a-zA-Z]+', '_', buses[cnt])) - elif cnt == 2: - txt += " bus={b}".format(b=re.sub('[^0-9a-zA-Z]+', '_', buses[cnt - 1])) - - # These are the configurations for center tap transformers - if cnt == 0: + # Connection + if buses is not None: + + if cnt == 0 or cnt == 1: + txt += " bus={b}".format(b=re.sub('[^0-9a-zA-Z]+', '_', buses[cnt])) + elif cnt == 2: + txt += " bus={b}".format(b=re.sub('[^0-9a-zA-Z]+', '_', buses[cnt - 1])) + + # These are the configurations for center tap transformers + if cnt == 0: + txt += ".{}".format( + self.phase_mapping( + winding.phase_windings[ + 0 + ].phase # Should beOnly one phase if it's a Wye transformer + ) + ) + if ( + len(winding.phase_windings) > 1 + and winding.connection_type == "Y" + ): + print( + "Warning - Wye center-tap transformer with more than one phase connection. Only using first one" + ) + if ( + winding.connection_type == "D" + and len(winding.phase_windings) >= 2 + ): txt += ".{}".format( self.phase_mapping( - winding.phase_windings[ - 0 - ].phase # Should beOnly one phase if it's a Wye transformer + winding.phase_windings[1].phase ) ) - if ( - len(winding.phase_windings) > 1 - and winding.connection_type == "Y" - ): + if len(winding.phase_windings) > 2: print( - "Warning - Wye center-tap transformer with more than one phase connection. Only using first one" + "Warning - Delta center-tap transformer with more than two phase connection. Only using first two" ) - if ( - winding.connection_type == "D" - and len(winding.phase_windings) >= 2 - ): - txt += ".{}".format( - self.phase_mapping( - winding.phase_windings[1].phase - ) - ) - if len(winding.phase_windings) > 2: - print( - "Warning - Delta center-tap transformer with more than two phase connection. Only using first two" - ) - if ( - winding.connection_type == "D" - and len(winding.phase_windings) == 1 - ): - print( - "Warning - only one phase specified for a delta system - adding another connection" - ) - if self.phase_mapping(phase_winding.phase) == 1: - txt += ".2" - if self.phase_mapping(phase_winding.phase) == 2: - txt += ".3" - if self.phase_mapping(phase_winding.phase) == 3: - txt += ".1" - - if cnt == 1: - txt += ".1.0" - if cnt == 2: - txt += ".0.2" - - # Voltage type (Not mapped) - - # Nominal voltage - if ( - hasattr(winding, "nominal_voltage") - and winding.nominal_voltage is not None - ): - txt += " Kv={kv}".format( - kv=winding.nominal_voltage * 10 ** -3 - ) # OpenDSS in kvolts if ( - not substation_name + "_" + feeder_name - in self._baseKV_feeders_ + winding.connection_type == "D" + and len(winding.phase_windings) == 1 ): - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() - if ( - winding.nominal_voltage < 300 - ): # Line-Neutral voltage for 120 V - self._baseKV_.add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 + print( + "Warning - only one phase specified for a delta system - adding another connection" ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 - ) - else: - self._baseKV_.add( - winding.nominal_voltage * 10 ** -3 - ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add(winding.nominal_voltage * 10 ** -3) + if self.phase_mapping(phase_winding.phase) == 1: + txt += ".2" + if self.phase_mapping(phase_winding.phase) == 2: + txt += ".3" + if self.phase_mapping(phase_winding.phase) == 3: + txt += ".1" - # rated power - if ( - hasattr(winding, "rated_power") - and winding.rated_power is not None - ): - txt += " kva={kva}".format( - kva=winding.rated_power * 10 ** -3 - ) + if cnt == 1: + txt += ".1.0" + if cnt == 2: + txt += ".0.2" - # emergency_power - # Was added to windings by Tarek - if ( - hasattr(winding, "emergency_power") - and winding.emergency_power is not None - ): - txt += " EmergHKVA={}".format( - winding.emergency_power * 10 ** -3 - ) # OpenDSS in kWatts + # Voltage type (Not mapped) - # Tap position + # Nominal voltage + if ( + hasattr(winding, "nominal_voltage") + and winding.nominal_voltage is not None + ): + txt += " Kv={kv}".format( + kv=winding.nominal_voltage * 10 ** -3 + ) # OpenDSS in kvolts if ( - self.write_taps - and hasattr(winding, "phase_windings") - and winding.phase_windings is not None - and hasattr(winding.phase_windings[0], "tap_position") - and winding.phase_windings[0].tap_position is not None + not substation_name + "_" + feeder_name + in self._baseKV_feeders_ ): - txt += " Tap={tap}".format( - tap=winding.phase_windings[0].tap_position + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ] = set() + if ( + winding.nominal_voltage < 300 + ): # Line-Neutral voltage for 120 V + self._baseKV_.add( + winding.nominal_voltage + * math.sqrt(3) + * 10 ** -3 + ) + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ].add( + winding.nominal_voltage + * math.sqrt(3) + * 10 ** -3 + ) + else: + self._baseKV_.add( + winding.nominal_voltage * 10 ** -3 ) + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ].add(winding.nominal_voltage * 10 ** -3) - # Voltage limit (Not mapped) + # rated power + if ( + hasattr(winding, "rated_power") + and winding.rated_power is not None + ): + txt += " kva={kva}".format( + kva=winding.rated_power * 10 ** -3 + ) - # resistance - # if hasattr(winding, 'resistance') and winding.resistance is not None: - # fp.write(' %R={R}'.format(R=winding.resistance)) + # emergency_power + # Was added to windings by Tarek + if ( + hasattr(winding, "emergency_power") + and winding.emergency_power is not None + ): + txt += " EmergHKVA={}".format( + winding.emergency_power * 10 ** -3 + ) # OpenDSS in kWatts - # Reverse resistance (Not mapped) + # Tap position + if ( + self.write_taps + and hasattr(winding, "phase_windings") + and winding.phase_windings is not None + and hasattr(winding.phase_windings[0], "tap_position") + and winding.phase_windings[0].tap_position is not None + ): + txt += " Tap={tap}".format( + tap=winding.phase_windings[0].tap_position + ) - if ( - hasattr(winding, "resistance") - and winding.resistance is not None - ): - txt += " %r={R}".format(R=winding.resistance) - else: - txt += " %r={R}".format(R=default_r[cnt - 1]) + # Voltage limit (Not mapped) - if hasattr(i, "reactances") and i.reactances is not None: - # Here, we should have 3 reactances - if ( - isinstance(i.reactances, list) - and len(i.reactances) == 3 - ): - txt += " XHL={XHL} XLT={XLT} XHT={XHT}".format( - XHL=i.reactances[0], - XLT=i.reactances[1], - XHT=i.reactances[2], - ) - else: - logger.error( - "Wrong number of reactances for transformer {name}".format( - name=i.name - ) - ) + # resistance + # if hasattr(winding, 'resistance') and winding.resistance is not None: + # fp.write(' %R={R}'.format(R=winding.resistance)) + + # Reverse resistance (Not mapped) + + if ( + hasattr(winding, "resistance") + and winding.resistance is not None + ): + txt += " %r={R}".format(R=winding.resistance) + else: + txt += " %r={R}".format(R=default_r[cnt - 1]) + + if hasattr(i, "reactances") and i.reactances is not None: + # Here, we should have 3 reactances + if ( + isinstance(i.reactances, list) + and len(i.reactances) == 3 + ): + txt += " XHL={XHL} XLT={XLT} XHT={XHT}".format( + XHL=i.reactances[0], + XLT=i.reactances[1], + XHT=i.reactances[2], + ) else: - txt += " XHL=%f XHT=%f XLT=%f" % ( - default_x[0], - default_x[1], - default_x[2], + logger.error( + "Wrong number of reactances for transformer {name}".format( + name=i.name + ) ) + else: + txt += " XHL=%f XHT=%f XLT=%f" % ( + default_x[0], + default_x[1], + default_x[2], + ) - txt += "\n\n" - feeder_text_map[substation_name + "_" + feeder_name] = txt + txt += "\n\n" + feeder_text_map[substation_name + "_" + feeder_name] = txt for substation_name in substation_text_map: for feeder_name in substation_text_map[substation_name]: From 3ea4d779fff5685243e7ce52204f32c659ab9111 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 17:00:01 -0600 Subject: [PATCH 49/83] warn in system_structure_modifier when changing element nominal_voltage --- ditto/modify/system_structure.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ditto/modify/system_structure.py b/ditto/modify/system_structure.py index 7d6274ff..e0a009ab 100644 --- a/ditto/modify/system_structure.py +++ b/ditto/modify/system_structure.py @@ -253,7 +253,8 @@ def set_nominal_voltages_recur(self, *args): if hasattr(self.model[node], "nominal_voltage"): if new_value != self.model[node].nominal_voltage: - logger.debug(f"Setting node {node} nominal voltage to {new_value} from {self.model[node].nominal_voltage}") + if self.model[node].nominal_voltage is not None: + logger.warn(f"Setting node {node} nominal voltage to {new_value} from {self.model[node].nominal_voltage}") self.model[node].nominal_voltage = new_value for child in self.G.digraph.successors(node): self.set_nominal_voltages_recur(child, new_value, node) From 030fe6535ced77a4894476b37d215b998812d1b0 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 17:09:25 -0600 Subject: [PATCH 50/83] use Store.iter_models(Regulator) in opendss writer de-dent a lot of code b/c for loop is in iter_models --- ditto/writers/opendss/write.py | 551 ++++++++++++++++----------------- 1 file changed, 275 insertions(+), 276 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 80bc815a..32a5f687 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2049,329 +2049,328 @@ def write_regulators(self, model): # At the end, we simply loop over the list to write all strings to transformers.dss transfo_creation_string_map = {} - for i in model.models: - if isinstance(i, Regulator): - - if ( - self.separate_feeders - and hasattr(i, "feeder_name") - and i.feeder_name is not None - ): - feeder_name = i.feeder_name - else: - feeder_name = "DEFAULT" - if ( - self.separate_substations - and hasattr(i, "substation_name") - and i.substation_name is not None - ): - substation_name = i.substation_name - else: - substation_name = "DEFAULT" + for i in model.iter_models(Regulator): - if not substation_name in substation_text_map: - substation_text_map[substation_name] = set([feeder_name]) - else: - substation_text_map[substation_name].add(feeder_name) - txt = "" - transfo_creation_string = "" - if substation_name + "_" + feeder_name in feeder_text_map: - txt = feeder_text_map[substation_name + "_" + feeder_name] - transfo_creation_string = transfo_creation_string_map[ - substation_name + "_" + feeder_name - ] + if ( + self.separate_feeders + and hasattr(i, "feeder_name") + and i.feeder_name is not None + ): + feeder_name = i.feeder_name + else: + feeder_name = "DEFAULT" + if ( + self.separate_substations + and hasattr(i, "substation_name") + and i.substation_name is not None + ): + substation_name = i.substation_name + else: + substation_name = "DEFAULT" - if hasattr(i, "name") and i.name is not None: - txt += "New RegControl.{name}".format(name=i.name) - else: - continue + if not substation_name in substation_text_map: + substation_text_map[substation_name] = set([feeder_name]) + else: + substation_text_map[substation_name].add(feeder_name) + txt = "" + transfo_creation_string = "" + if substation_name + "_" + feeder_name in feeder_text_map: + txt = feeder_text_map[substation_name + "_" + feeder_name] + transfo_creation_string = transfo_creation_string_map[ + substation_name + "_" + feeder_name + ] - # Connected transformer - if hasattr(i, "connected_transformer"): + if hasattr(i, "name") and i.name is not None: + txt += "New RegControl.{name}".format(name=i.name) + else: + continue - # If we have a valid connected_transformer then job's easy... - if i.connected_transformer is not None: # not setting the connected_transformer in reader parse_regulators - txt += " transformer={trans}".format( - trans=i.connected_transformer - ) + # Connected transformer + if hasattr(i, "connected_transformer"): - # Otherwise, we have to create a new transformer and write it to the transformers file - else: + # If we have a valid connected_transformer then job's easy... + if i.connected_transformer is not None: # not setting the connected_transformer in reader parse_regulators + txt += " transformer={trans}".format( + trans=i.connected_transformer + ) - # Initialize the string: - transfo_creation_string += "New Transformer." + # Otherwise, we have to create a new transformer and write it to the transformers file + else: - # Name: - transfo_name = "trans_{}".format( - i.name - ) # Maybe not the best naming convention.... - transfo_creation_string += transfo_name + # Initialize the string: + transfo_creation_string += "New Transformer." - # Number of Phases - if hasattr(i, "windings") and i.windings is not None: - if ( - hasattr(i.windings[0], "phase_windings") - and i.windings[0].phase_windings is not None - ): - try: - transfo_creation_string += " phases={}".format( - len(i.windings[0].phase_windings) - ) - except: - pass - phases = [ - self.phase_mapping(x.phase) - for x in i.windings[0].phase_windings - ] - phase_string = reduce( - lambda x, y: str(x) + "." + str(y), phases - ) + # Name: + transfo_name = "trans_{}".format( + i.name + ) # Maybe not the best naming convention.... + transfo_creation_string += transfo_name - # Number of windings - if hasattr(i, "windings") and i.windings is not None: + # Number of Phases + if hasattr(i, "windings") and i.windings is not None: + if ( + hasattr(i.windings[0], "phase_windings") + and i.windings[0].phase_windings is not None + ): try: - transfo_creation_string += " windings={}".format( - len(i.windings) + transfo_creation_string += " phases={}".format( + len(i.windings[0].phase_windings) ) except: pass + phases = [ + self.phase_mapping(x.phase) + for x in i.windings[0].phase_windings + ] + phase_string = reduce( + lambda x, y: str(x) + "." + str(y), phases + ) - # buses: - if ( - hasattr(i, "from_element") - and i.from_element is not None - and hasattr(i, "to_element") - and i.to_element is not None - ): - transfo_creation_string += " buses=({b1}.{p},{b2}.{p})".format( - b1=re.sub('[^0-9a-zA-Z]+', '_', i.from_element), b2=re.sub('[^0-9a-zA-Z]+', '_', i.to_element), p=phase_string + # Number of windings + if hasattr(i, "windings") and i.windings is not None: + try: + transfo_creation_string += " windings={}".format( + len(i.windings) ) + except: + pass - # Conns - if hasattr(i, "windings") and i.windings is not None: - conns = " conns=(" - for w, winding in enumerate(i.windings): - if hasattr( - i.windings[w], "connection_type" - ) and i.windings[w].connection_type in ["Y", "D", "Z"]: - mapp = {"Y": "Wye", "D": "Delta", "Z": "Zigzag"} - conns += mapp[i.windings[w].connection_type] + ", " - conns = conns[:-2] - conns += ")" - if conns == " conns)": - conns = "" - transfo_creation_string += conns - - # kvs - if hasattr(i, "windings") and i.windings is not None: - kvs = " kvs=(" - for w, winding in enumerate(i.windings): - if hasattr(i.windings[w], "nominal_voltage"): - kvs += ( - str(i.windings[w].nominal_voltage * 10 ** -3) - + ", " - ) - if ( - not substation_name + "_" + feeder_name - in self._baseKV_feeders_ - ): - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() - if ( - i.windings[w].nominal_voltage < 300 - ): # Line-Neutral voltage for 120 V - self._baseKV_.add( - i.windings[w].nominal_voltage - * math.sqrt(3) - * 10 ** -3 - ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 - ) - else: - self._baseKV_.add( - i.windings[w].nominal_voltage * 10 ** -3 - ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add(winding.nominal_voltage * 10 ** -3) - - kvs = kvs[:-2] - kvs += ")" - transfo_creation_string += kvs - - # kvas - if hasattr(i, "windings") and i.windings is not None: - kvas = " kvas=(" - for w, winding in enumerate(i.windings): + # buses: + if ( + hasattr(i, "from_element") + and i.from_element is not None + and hasattr(i, "to_element") + and i.to_element is not None + ): + transfo_creation_string += " buses=({b1}.{p},{b2}.{p})".format( + b1=re.sub('[^0-9a-zA-Z]+', '_', i.from_element), b2=re.sub('[^0-9a-zA-Z]+', '_', i.to_element), p=phase_string + ) + + # Conns + if hasattr(i, "windings") and i.windings is not None: + conns = " conns=(" + for w, winding in enumerate(i.windings): + if hasattr( + i.windings[w], "connection_type" + ) and i.windings[w].connection_type in ["Y", "D", "Z"]: + mapp = {"Y": "Wye", "D": "Delta", "Z": "Zigzag"} + conns += mapp[i.windings[w].connection_type] + ", " + conns = conns[:-2] + conns += ")" + if conns == " conns)": + conns = "" + transfo_creation_string += conns + + # kvs + if hasattr(i, "windings") and i.windings is not None: + kvs = " kvs=(" + for w, winding in enumerate(i.windings): + if hasattr(i.windings[w], "nominal_voltage"): + kvs += ( + str(i.windings[w].nominal_voltage * 10 ** -3) + + ", " + ) if ( - hasattr(i.windings[w], "rated_power") - and i.windings[w].rated_power is not None + not substation_name + "_" + feeder_name + in self._baseKV_feeders_ ): - kvas += ( - str(i.windings[w].rated_power * 10 ** -3) + ", " + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ] = set() + if ( + i.windings[w].nominal_voltage < 300 + ): # Line-Neutral voltage for 120 V + self._baseKV_.add( + i.windings[w].nominal_voltage + * math.sqrt(3) + * 10 ** -3 ) - kvas = kvas[:-2] - kvas += ")" - transfo_creation_string += kvas - - # emergency_power - if hasattr(i, "windings") and i.windings is not None: + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ].add( + winding.nominal_voltage + * math.sqrt(3) + * 10 ** -3 + ) + else: + self._baseKV_.add( + i.windings[w].nominal_voltage * 10 ** -3 + ) + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ].add(winding.nominal_voltage * 10 ** -3) + + kvs = kvs[:-2] + kvs += ")" + transfo_creation_string += kvs + + # kvas + if hasattr(i, "windings") and i.windings is not None: + kvas = " kvas=(" + for w, winding in enumerate(i.windings): if ( - hasattr(i.windings[0], "emergency_power") - and i.windings[0].emergency_power is not None + hasattr(i.windings[w], "rated_power") + and i.windings[w].rated_power is not None ): - transfo_creation_string += " EmerghKVA={}".format( - i.windings[0].emergency_power + kvas += ( + str(i.windings[w].rated_power * 10 ** -3) + ", " ) + kvas = kvas[:-2] + kvas += ")" + transfo_creation_string += kvas - # reactances: - if hasattr(i, "reactances") and i.reactances is not None: - # XHL: - try: - if isinstance(i.reactances[0], (int, float)): - transfo_creation_string += " XHL={}".format( - i.reactances[0] - ) - except: - logger.warning( - "Could not extract XHL from regulator {name}".format( - name=i.name - ) + # emergency_power + if hasattr(i, "windings") and i.windings is not None: + if ( + hasattr(i.windings[0], "emergency_power") + and i.windings[0].emergency_power is not None + ): + transfo_creation_string += " EmerghKVA={}".format( + i.windings[0].emergency_power + ) + + # reactances: + if hasattr(i, "reactances") and i.reactances is not None: + # XHL: + try: + if isinstance(i.reactances[0], (int, float)): + transfo_creation_string += " XHL={}".format( + i.reactances[0] ) - pass - # XLT: - try: # probably an index error b/c cyme reader only has api_transformer.reactances = [float(xhl)] - if isinstance(i.reactances[1], (int, float)): - transfo_creation_string += " XLT={}".format( - i.reactances[1] - ) - except: - logger.warning( - "Could not extract XLT from regulator {name}".format( - name=i.name - ) + except: + logger.warning( + "Could not extract XHL from regulator {name}".format( + name=i.name ) - pass - # XHT: - try: # probably an index error b/c cyme reader only has api_transformer.reactances = [float(xhl)] - if isinstance(i.reactances[2], (int, float)): - transfo_creation_string += " XHT={}".format( - i.reactances[2] - ) - except: - logger.warning( - "Could not extract XHT from regulator {name}".format( - name=i.name - ) + ) + pass + # XLT: + try: # probably an index error b/c cyme reader only has api_transformer.reactances = [float(xhl)] + if isinstance(i.reactances[1], (int, float)): + transfo_creation_string += " XLT={}".format( + i.reactances[1] ) - pass + except: + logger.warning( + "Could not extract XLT from regulator {name}".format( + name=i.name + ) + ) + pass + # XHT: + try: # probably an index error b/c cyme reader only has api_transformer.reactances = [float(xhl)] + if isinstance(i.reactances[2], (int, float)): + transfo_creation_string += " XHT={}".format( + i.reactances[2] + ) + except: + logger.warning( + "Could not extract XHT from regulator {name}".format( + name=i.name + ) + ) + pass - txt += " transformer={trans}".format(trans=transfo_name) + txt += " transformer={trans}".format(trans=transfo_name) - # Winding - if hasattr(i, "winding") and i.winding is not None: - txt += " winding={w}".format(w=i.winding) - else: - txt += " winding=2" + # Winding + if hasattr(i, "winding") and i.winding is not None: + txt += " winding={w}".format(w=i.winding) + else: + txt += " winding=2" - # CTprim - if hasattr(i, "ct_prim") and i.ct_prim is not None: - txt += " CTprim={CT}".format(CT=i.ct_prim) + # CTprim + if hasattr(i, "ct_prim") and i.ct_prim is not None: + txt += " CTprim={CT}".format(CT=i.ct_prim) - # noload_loss - if hasattr(i, "noload_loss") and i.noload_loss is not None: - txt += " %noLoadLoss={nL}".format(NL=i.noload_loss) + # noload_loss + if hasattr(i, "noload_loss") and i.noload_loss is not None: + txt += " %noLoadLoss={nL}".format(NL=i.noload_loss) - # Delay - if hasattr(i, "delay") and i.delay is not None: - txt += " delay={d}".format(d=i.delay) + # Delay + if hasattr(i, "delay") and i.delay is not None: + txt += " delay={d}".format(d=i.delay) - # highstep - # if hasattr(i, "highstep") and i.highstep is not None: - # txt += " maxtapchange={high}".format(high=i.highstep) + # highstep + # if hasattr(i, "highstep") and i.highstep is not None: + # txt += " maxtapchange={high}".format(high=i.highstep) - # lowstep (Not mapped) + # lowstep (Not mapped) - # pt ratio - if hasattr(i, "pt_ratio") and i.pt_ratio is not None: - txt += " ptratio={PT}".format(PT=i.pt_ratio) + # pt ratio + if hasattr(i, "pt_ratio") and i.pt_ratio is not None: + txt += " ptratio={PT}".format(PT=i.pt_ratio) - # ct ratio (Not mapped) + # ct ratio (Not mapped) - # phase shift (Not mapped) + # phase shift (Not mapped) - # ltc (Not mapped) + # ltc (Not mapped) - # bandwidth - if hasattr(i, "bandwidth") and i.bandwidth is not None: - txt += " band={b}".format( - b=i.bandwidth * 1.2 - ) # The bandwidth is operated at 120 V + # bandwidth + if hasattr(i, "bandwidth") and i.bandwidth is not None: + txt += " band={b}".format( + b=i.bandwidth * 1.2 + ) # The bandwidth is operated at 120 V - # band center - if hasattr(i, "bandcenter") and i.bandcenter is not None: - txt += " vreg={vreg}".format(vreg=i.bandcenter) + # band center + if hasattr(i, "bandcenter") and i.bandcenter is not None: + txt += " vreg={vreg}".format(vreg=i.bandcenter) - # Pt phase - if hasattr(i, "pt_phase") and i.pt_phase is not None: - txt += " Ptphase={PT}".format(PT=self.phase_mapping(i.pt_phase)) + # Pt phase + if hasattr(i, "pt_phase") and i.pt_phase is not None: + txt += " Ptphase={PT}".format(PT=self.phase_mapping(i.pt_phase)) - # Voltage limit - if hasattr(i, "voltage_limit") and i.voltage_limit is not None: - txt += " vlimit={vlim}".format(vlim=i.voltage_limit) + # Voltage limit + if hasattr(i, "voltage_limit") and i.voltage_limit is not None: + txt += " vlimit={vlim}".format(vlim=i.voltage_limit) - if hasattr(i, "setpoint") and i.setpoint is not None: - txt += " vreg = {setp}".format(setp=i.setpoint / 100.0 * 120) + if hasattr(i, "setpoint") and i.setpoint is not None: + txt += " vreg = {setp}".format(setp=i.setpoint / 100.0 * 120) - # X (Store in the Phase Windings of the transformer) - if i.name in self.compensator: - if "X" in self.compensator[i.name]: - if len(self.compensator[i.name]["X"]) == 1: - txt += " X={x}".format( - x=list(self.compensator[i.name]["X"])[0] - ) - else: - logger.warning( - """Compensator_x not the same for all windings of transformer {name}. - Using the first value for regControl {name2}.""".format( - name=i.connected_transformer, name2=i.name - ) - ) - txt += " X={x}".format( - x=list(self.compensator[i.name]["X"])[0] + # X (Store in the Phase Windings of the transformer) + if i.name in self.compensator: + if "X" in self.compensator[i.name]: + if len(self.compensator[i.name]["X"]) == 1: + txt += " X={x}".format( + x=list(self.compensator[i.name]["X"])[0] + ) + else: + logger.warning( + """Compensator_x not the same for all windings of transformer {name}. + Using the first value for regControl {name2}.""".format( + name=i.connected_transformer, name2=i.name ) + ) + txt += " X={x}".format( + x=list(self.compensator[i.name]["X"])[0] + ) - # R (Store in the Phase Windings of the transformer) - if i.name in self.compensator: - if "R" in self.compensator[i.name]: - if len(self.compensator[i.name]["R"]) == 1: - txt += " R={r}".format( - r=list(self.compensator[i.name]["R"])[0] - ) - else: - logger.warning( - """Compensator_r not the same for all windings of transformer {name}. - Using the first value for regControl {name2}.""".format( - name=i.connected_transformer, name2=i.name - ) - ) - txt += " R={r}".format( - r=list(self.compensator[i.name]["R"])[0] + # R (Store in the Phase Windings of the transformer) + if i.name in self.compensator: + if "R" in self.compensator[i.name]: + if len(self.compensator[i.name]["R"]) == 1: + txt += " R={r}".format( + r=list(self.compensator[i.name]["R"])[0] + ) + else: + logger.warning( + """Compensator_r not the same for all windings of transformer {name}. + Using the first value for regControl {name2}.""".format( + name=i.connected_transformer, name2=i.name ) + ) + txt += " R={r}".format( + r=list(self.compensator[i.name]["R"])[0] + ) - txt += "\n\n" - if len(transfo_creation_string) > 0: - transfo_creation_string += "\n\n" - feeder_text_map[substation_name + "_" + feeder_name] = txt - transfo_creation_string_map[ - substation_name + "_" + feeder_name - ] = transfo_creation_string + txt += "\n\n" + if len(transfo_creation_string) > 0: + transfo_creation_string += "\n\n" + feeder_text_map[substation_name + "_" + feeder_name] = txt + transfo_creation_string_map[ + substation_name + "_" + feeder_name + ] = transfo_creation_string for substation_name in substation_text_map: for feeder_name in substation_text_map[substation_name]: From b2fe2b6c26a200614f01e2e5fa28bef4b68ecadd Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 19:16:20 -0600 Subject: [PATCH 51/83] fix cyme reader for sections with regulators only was chaining single phase regulators together rather than keeping them in "parallel" as the should be --- ditto/readers/cyme/read.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 2ad717d8..227cd2fa 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -6468,9 +6468,9 @@ def fix_section_overlaps(self, model, **kwargs): for i,j in self.section_duplicates.items(): if len(j)>1: multiple_elements[i] = j + # multiple_elements has keys of sectionID and values as list of DiTTo objects for sectionID in multiple_elements: - connectors = [] regulators = [] transformers = [] lines = [] #Warning - if multiple lines are used the names will be the same @@ -6512,6 +6512,11 @@ def fix_section_overlaps(self, model, **kwargs): if from_element is None or to_element is None: # i.e. just loads, pvs and caps so no problem continue + # if all we have is (single phase) regulators between nodes no need to fix it + # (looks like parse_regulators creates a Regulator for each phase) + if all(len(el) == 0 for el in [transformers, lines, loads, bess, capacitors]): + continue + original_from_element = from_element original_from_node = model[from_element] intermediate_count = 0 From c75a0dd268d701b8bd016ca2321956c9b5ccef79 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 19:53:20 -0600 Subject: [PATCH 52/83] log system_structure_modifier job in cyme reader --- ditto/readers/cyme/read.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 227cd2fa..b2eb3c71 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -903,6 +903,7 @@ def parse(self, model, **kwargs): self.fix_section_overlaps(model) model.set_names() + logger.info("Setting node and line nominal voltages...") modifier = system_structure_modifier(model) modifier.set_nominal_voltages_recur() modifier.set_nominal_voltages_recur_line() From c38dc3b0b0cc88d347256ad7d6476b07f9750049 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 20:00:39 -0600 Subject: [PATCH 53/83] cyme reader define static methods --- ditto/readers/cyme/read.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index b2eb3c71..8701dbd7 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -406,7 +406,8 @@ def get_file_content(self, filename: str) -> None: self.content = iter(content_) - def phase_mapping(self, CYME_value): + @staticmethod + def phase_mapping(CYME_value): """ Maps the CYME phase value format to a list of ABC phases: @@ -453,7 +454,8 @@ def phase_mapping(self, CYME_value): else: return list(CYME_value) - def phase_to_num(self, phase): + @staticmethod + def phase_to_num(phase): """ Maps phase in 'A', 'B', 'C' format in 1, 2, 3 format. @@ -478,7 +480,8 @@ def phase_to_num(self, phase): else: return phase - def load_value_type_mapping(self, load_type, value1, value2): + @staticmethod + def load_value_type_mapping(load_type, value1, value2): """ CYME customer loads provide two values v1 and v2 as well as a load value type: This function takes these as inputs and outputs P and Q of the load. @@ -553,7 +556,8 @@ def load_value_type_mapping(self, load_type, value1, value2): ) ) - def capacitors_connection_mapping(self, conn): + @staticmethod + def capacitors_connection_mapping(conn): """ Maps the capacitors connection in CYME (CAP_CONN) to DiTTo connection_type. @@ -590,7 +594,8 @@ def capacitors_connection_mapping(self, conn): else: return conn - def connection_configuration_mapping(self, value): + @staticmethod + def connection_configuration_mapping(value): """ Map the connection configuration from CYME to DiTTo. From 7b93a61c0941f8df7318ec9411a8885e5ced59f9 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 20:11:38 -0600 Subject: [PATCH 54/83] minor formatting in cyme reader trying to increase legibility --- ditto/readers/cyme/read.py | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 8701dbd7..72491e22 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -872,43 +872,40 @@ def parse(self, model, **kwargs): """ Parse the CYME model to DiTTo. - :param model: DiTTo model - :type model: DiTTo model - :param verbose: Set the verbose mode. Optional. Default=True + :param model: DiTTo Store instance + :param verbose: Set the verbose mode. Optional. Default=False :type verbose: bool """ + self.verbose = False if "verbose" in kwargs and isinstance(kwargs["verbose"], bool): self.verbose = kwargs["verbose"] - else: - self.verbose = False - if self.verbose: - logger.info("Parsing the header...") + if self.verbose: logger.info("Parsing the header...") self.parse_header() - logger.info("Parsing the Headnodes...") + if self.verbose: logger.info("Parsing the Headnodes...") self.parse_head_nodes(model) - logger.info("Parsing the sections...") + if self.verbose: logger.info("Parsing the sections...") self.parse_sections(model) # Call parse method of abtract reader super(Reader, self).parse(model, **kwargs) - logger.info("Parsing the subnetwork connections...") + if self.verbose: logger.info("Parsing the subnetwork connections...") self.parse_subnetwork_connections(model) - logger.info("Parsing the sources...") + if self.verbose: logger.info("Parsing the sources...") self.parse_sources(model) - logger.info("Parsing the network equivalents...") + if self.verbose: logger.info("Parsing the network equivalents...") self.parse_network_equivalent(model) self.fix_section_overlaps(model) - + # overwriting 7_reg after this? yes. what is the point of set_names() ? it's called a bunch of times model.set_names() - logger.info("Setting node and line nominal voltages...") + if self.verbose: logger.info("Setting node and line nominal voltages...") modifier = system_structure_modifier(model) modifier.set_nominal_voltages_recur() modifier.set_nominal_voltages_recur_line() @@ -957,9 +954,7 @@ def parse_header(self): self.cyme_version = cyme_version if self.use_SI is None: - raise ValueError( - "Could not find [SI] or [IMPERIAL] unit system information. Unable to parse." - ) + raise ValueError("Could not find [SI] or [IMPERIAL] unit system information. Unable to parse.") def parse_subnetwork_connections(self, model): """Parse the subnetwork connections. @@ -980,9 +975,7 @@ def parse_subnetwork_connections(self, model): ) for key in self.subnetwork_connections: - model[ - self.subnetwork_connections[key]["nodeid"] - ].is_substation_connection = True + model[self.subnetwork_connections[key]["nodeid"]].is_substation_connection = True def parse_head_nodes(self, model): """ From 0cbe8ff46212dae70e473213b1195442bfb8820f Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 20:36:40 -0600 Subject: [PATCH 55/83] use Store.iter_models in opendss writer parse_loads --- ditto/writers/opendss/write.py | 377 ++++++++++++++++----------------- 1 file changed, 188 insertions(+), 189 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 32a5f687..ba7f01c8 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -1766,218 +1766,217 @@ def write_loads(self, model): substation_text_map = {} feeder_text_map = {} - for i in model.models: - if isinstance(i, Load): - if ( - self.separate_feeders - and hasattr(i, "feeder_name") - and i.feeder_name is not None - ): - feeder_name = i.feeder_name - else: - feeder_name = "DEFAULT" - if ( - self.separate_substations - and hasattr(i, "substation_name") - and i.substation_name is not None - ): - substation_name = i.substation_name - else: - substation_name = "DEFAULT" - - if not substation_name in substation_text_map: - substation_text_map[substation_name] = set([feeder_name]) - else: - substation_text_map[substation_name].add(feeder_name) + for i in model.iter_models(Load): + if ( + self.separate_feeders + and hasattr(i, "feeder_name") + and i.feeder_name is not None + ): + feeder_name = i.feeder_name + else: + feeder_name = "DEFAULT" + if ( + self.separate_substations + and hasattr(i, "substation_name") + and i.substation_name is not None + ): + substation_name = i.substation_name + else: + substation_name = "DEFAULT" - txt = "" - if substation_name + "_" + feeder_name in feeder_text_map: - txt = feeder_text_map[substation_name + "_" + feeder_name] + if not substation_name in substation_text_map: + substation_text_map[substation_name] = set([feeder_name]) + else: + substation_text_map[substation_name].add(feeder_name) - # Name - if hasattr(i, "name") and i.name is not None: - txt += "New Load." + i.name - else: - continue + txt = "" + if substation_name + "_" + feeder_name in feeder_text_map: + txt = feeder_text_map[substation_name + "_" + feeder_name] - # Connection type - if hasattr(i, "connection_type") and i.connection_type is not None: - if i.connection_type == "Y": - txt += " conn=wye" - elif i.connection_type == "D": - txt += " conn=delta" + # Name + if hasattr(i, "name") and i.name is not None: + txt += "New Load." + i.name + else: + continue - # Connecting element - if ( - hasattr(i, "connecting_element") - and i.connecting_element is not None - ): - txt += " bus1={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element)) - if hasattr(i, "phase_loads") and i.phase_loads is not None: - for phase_load in i.phase_loads: - if ( - hasattr(phase_load, "phase") - and phase_load.phase is not None - ): - txt += ".{p}".format( - p=self.phase_mapping(phase_load.phase) - ) + # Connection type + if hasattr(i, "connection_type") and i.connection_type is not None: + if i.connection_type == "Y": + txt += " conn=wye" + elif i.connection_type == "D": + txt += " conn=delta" - if i.connection_type == "D" and len(i.phase_loads) == 1: - if self.phase_mapping(i.phase_loads[0].phase) == 1: - txt += ".2" - if self.phase_mapping(i.phase_loads[0].phase) == 2: - txt += ".3" - if self.phase_mapping(i.phase_loads[0].phase) == 3: - txt += ".1" + # Connecting element + if ( + hasattr(i, "connecting_element") + and i.connecting_element is not None + ): + txt += " bus1={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element)) + if hasattr(i, "phase_loads") and i.phase_loads is not None: + for phase_load in i.phase_loads: + if ( + hasattr(phase_load, "phase") + and phase_load.phase is not None + ): + txt += ".{p}".format( + p=self.phase_mapping(phase_load.phase) + ) - # nominal voltage - if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: - if i.nominal_voltage < 300: - txt += " kV={volt}".format( - volt=i.nominal_voltage * math.sqrt(3) * 10 ** -3 - ) - else: - txt += " kV={volt}".format(volt=i.nominal_voltage * 10 ** -3) - if not substation_name + "_" + feeder_name in self._baseKV_feeders_: - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() - if i.nominal_voltage < 300: # Line-Neutral voltage for 120 V - self._baseKV_.add(i.nominal_voltage * math.sqrt(3) * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( - i.nominal_voltage * math.sqrt(3) * 10 ** -3 - ) - else: - self._baseKV_.add(i.nominal_voltage * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( - i.nominal_voltage * 10 ** -3 - ) + if i.connection_type == "D" and len(i.phase_loads) == 1: + if self.phase_mapping(i.phase_loads[0].phase) == 1: + txt += ".2" + if self.phase_mapping(i.phase_loads[0].phase) == 2: + txt += ".3" + if self.phase_mapping(i.phase_loads[0].phase) == 3: + txt += ".1" + + # nominal voltage + if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: + if i.nominal_voltage < 300: + txt += " kV={volt}".format( + volt=i.nominal_voltage * math.sqrt(3) * 10 ** -3 + ) + else: + txt += " kV={volt}".format(volt=i.nominal_voltage * 10 ** -3) + if not substation_name + "_" + feeder_name in self._baseKV_feeders_: + self._baseKV_feeders_[ + substation_name + "_" + feeder_name + ] = set() + if i.nominal_voltage < 300: # Line-Neutral voltage for 120 V + self._baseKV_.add(i.nominal_voltage * math.sqrt(3) * 10 ** -3) + self._baseKV_feeders_[substation_name + "_" + feeder_name].add( + i.nominal_voltage * math.sqrt(3) * 10 ** -3 + ) + else: + self._baseKV_.add(i.nominal_voltage * 10 ** -3) + self._baseKV_feeders_[substation_name + "_" + feeder_name].add( + i.nominal_voltage * 10 ** -3 + ) - # Vmin - if hasattr(i, "vmin") and i.vmin is not None: - txt += " Vminpu={vmin}".format(vmin=i.vmin) + # Vmin + if hasattr(i, "vmin") and i.vmin is not None: + txt += " Vminpu={vmin}".format(vmin=i.vmin) - # Vmax - if hasattr(i, "vmax") and i.vmax is not None: - txt += " Vmaxpu={vmax}".format(vmax=i.vmax) + # Vmax + if hasattr(i, "vmax") and i.vmax is not None: + txt += " Vmaxpu={vmax}".format(vmax=i.vmax) - # positions (Not mapped) + # positions (Not mapped) - # KW - total_P = 0 - if hasattr(i, "phase_loads") and i.phase_loads: - txt += " model={N}".format(N=i.phase_loads[0].model) + # KW + total_P = 0 + if hasattr(i, "phase_loads") and i.phase_loads: + txt += " model={N}".format(N=i.phase_loads[0].model) - for phase_load in i.phase_loads: - if hasattr(phase_load, "p") and phase_load.p is not None: - total_P += phase_load.p - txt += " kW={P}".format(P=total_P * 10 ** -3) + for phase_load in i.phase_loads: + if hasattr(phase_load, "p") and phase_load.p is not None: + total_P += phase_load.p + txt += " kW={P}".format(P=total_P * 10 ** -3) - # Kva - total_Q = 0 - if hasattr(i, "phase_loads") and i.phase_loads: - for phase_load in i.phase_loads: - if hasattr(phase_load, "q") and phase_load.q is not None: - total_Q += phase_load.q - txt += " kvar={Q}".format(Q=total_Q * 10 ** -3) + # Kva + total_Q = 0 + if hasattr(i, "phase_loads") and i.phase_loads: + for phase_load in i.phase_loads: + if hasattr(phase_load, "q") and phase_load.q is not None: + total_Q += phase_load.q + txt += " kvar={Q}".format(Q=total_Q * 10 ** -3) - # phase_loads - if hasattr(i, "phase_loads") and i.phase_loads: + # phase_loads + if hasattr(i, "phase_loads") and i.phase_loads: - # if i.connection_type=='Y': - txt += " Phases={N}".format(N=len(i.phase_loads)) - # elif i.connection_type=='D' and len(i.phase_loads)==3: - # fp.write(' Phases=3') - # elif i.connection_type=='D' and len(i.phase_loads)==2: - # fp.write(' Phases=1') + # if i.connection_type=='Y': + txt += " Phases={N}".format(N=len(i.phase_loads)) + # elif i.connection_type=='D' and len(i.phase_loads)==3: + # fp.write(' Phases=3') + # elif i.connection_type=='D' and len(i.phase_loads)==2: + # fp.write(' Phases=1') - for phase_load in i.phase_loads: + for phase_load in i.phase_loads: - # P - # if hasattr(phase_load, 'p') and phase_load.p is not None: - # fp.write(' kW={P}'.format(P=phase_load.p*10**-3)) + # P + # if hasattr(phase_load, 'p') and phase_load.p is not None: + # fp.write(' kW={P}'.format(P=phase_load.p*10**-3)) - # Q - # if hasattr(phase_load, 'q') and phase_load.q is not None: - # fp.write(' kva={Q}'.format(Q=phase_load.q*10**-3)) + # Q + # if hasattr(phase_load, 'q') and phase_load.q is not None: + # fp.write(' kva={Q}'.format(Q=phase_load.q*10**-3)) - # ZIP load model - if ( - hasattr(phase_load, "use_zip") - and phase_load.use_zip is not None - ): - if phase_load.use_zip: + # ZIP load model + if ( + hasattr(phase_load, "use_zip") + and phase_load.use_zip is not None + ): + if phase_load.use_zip: - # Get the coefficients - if ( - ( - hasattr(i, "ppercentimpedance") - and i.ppercentimpedance is not None - ) - and ( - hasattr(i, "qpercentimpedance") - and i.qpercentimpedance is not None - ) - and ( - hasattr(i, "ppercentcurrent") - and i.ppercentcurrent is not None - ) - and ( - hasattr(i, "qpercentcurrent") - and i.qpercentcurrent is not None - ) - and ( - hasattr(i, "ppercentpower") - and i.ppercentpower is not None - ) - and ( - hasattr(i, "qpercentpower") - and i.qpercentpower is not None - ) - ): + # Get the coefficients + if ( + ( + hasattr(i, "ppercentimpedance") + and i.ppercentimpedance is not None + ) + and ( + hasattr(i, "qpercentimpedance") + and i.qpercentimpedance is not None + ) + and ( + hasattr(i, "ppercentcurrent") + and i.ppercentcurrent is not None + ) + and ( + hasattr(i, "qpercentcurrent") + and i.qpercentcurrent is not None + ) + and ( + hasattr(i, "ppercentpower") + and i.ppercentpower is not None + ) + and ( + hasattr(i, "qpercentpower") + and i.qpercentpower is not None + ) + ): - txt += ( - " model=8 ZIPV=[%.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f]" - % ( - i.ppercentimpedance, - i.ppercentcurrent, - i.ppercentpower, - i.qpercentimpedance, - i.qpercentcurrent, - i.qpercentpower, - ) + txt += ( + " model=8 ZIPV=[%.3f, %.3f, %.3f, %.3f, %.3f, %.3f, %.3f]" + % ( + i.ppercentimpedance, + i.ppercentcurrent, + i.ppercentpower, + i.qpercentimpedance, + i.qpercentcurrent, + i.qpercentpower, ) + ) - # fp.write(' model=1') - - # timeseries object - if hasattr(i, "timeseries") and i.timeseries is not None: - for ts in i.timeseries: - substation = "DEFAULT" - feeder = "DEFAULT" - if ts.feeder_name is not None: - feeder = ts.feeder_name - if ts.substation_name is not None: - substation = ts.substation_name - if ( - hasattr(ts, "data_location") - and ts.data_label is not None - and ts.data_location is not None - ): - filename = self.timeseries_datasets[ - substation + "_" + feeder - ][ts.data_location] - txt += " {ts_format}={filename}".format( - ts_format=self.timeseries_format[filename], - filename=filename, - ) - else: - pass - # TODO: manage the data correctly when it is only in memory + # fp.write(' model=1') + + # timeseries object + if hasattr(i, "timeseries") and i.timeseries is not None: + for ts in i.timeseries: + substation = "DEFAULT" + feeder = "DEFAULT" + if ts.feeder_name is not None: + feeder = ts.feeder_name + if ts.substation_name is not None: + substation = ts.substation_name + if ( + hasattr(ts, "data_location") + and ts.data_label is not None + and ts.data_location is not None + ): + filename = self.timeseries_datasets[ + substation + "_" + feeder + ][ts.data_location] + txt += " {ts_format}={filename}".format( + ts_format=self.timeseries_format[filename], + filename=filename, + ) + else: + pass + # TODO: manage the data correctly when it is only in memory - txt += "\n\n" - feeder_text_map[substation_name + "_" + feeder_name] = txt + txt += "\n\n" + feeder_text_map[substation_name + "_" + feeder_name] = txt for substation_name in substation_text_map: for feeder_name in substation_text_map[substation_name]: From 2217310494458f92792b84a33174c3a273440c1a Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 22:59:37 -0600 Subject: [PATCH 56/83] minor formatting in cyme reader parse_loads --- ditto/readers/cyme/read.py | 45 +++++++++++++------------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 72491e22..3d93491a 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -5673,10 +5673,10 @@ def parse_loads(self, model): ) ) - duplicate_loads = set() + section_ids_with_more_than_one_load = set() for sectionID in self.customer_loads.keys(): if sectionID.endswith("*"): - duplicate_loads.add(sectionID.lower().strip("*")) + section_ids_with_more_than_one_load.add(sectionID.lower().strip("*")) for sectionID, settings in self.customer_loads.items(): @@ -5701,15 +5701,15 @@ def parse_loads(self, model): else: load_data = {} + connectedkva = None if "connectedkva" in settings: connectedkva = float(settings["connectedkva"]) - else: - connectedkva = None + value_type = None if "valuetype" in settings: value_type = int(settings["valuetype"]) - if "value1" in settings and "value2" in settings: + if "value1" in settings and "value2" in settings and value_type is not None: if ( float(settings["value1"]) == 0.0 and float(settings["value2"]) == 0.0 @@ -5761,19 +5761,17 @@ def parse_loads(self, model): if p >= 0 or q >= 0: # then a Load is created + phases = [] if "loadphase" in settings: phases = settings["loadphase"] - else: - phases = [] - fused = False - if sectionID in duplicate_loads: + if sectionID in section_ids_with_more_than_one_load: fusion = True - if sectionID in self._loads: + if sectionID in self._loads: # already created a Load for this sectionID api_load = self._loads[sectionID] fused = True - elif p != 0: + elif p != 0: # have not created a load yet api_load = Load(model) else: fusion = False @@ -5787,9 +5785,8 @@ def parse_loads(self, model): try: if fusion and sectionID in self._loads: - api_load.name += "_" + reduce( - lambda x, y: x + "_" + y, phases - ) + # add phases to name with underscores + api_load.name += "_" + reduce(lambda x, y: x + "_" + y, phases) else: api_load.name = ( "Load_" @@ -5803,18 +5800,12 @@ def parse_loads(self, model): try: if not (fusion and sectionID in self._loads): if connectedkva is not None: - api_load.transformer_connected_kva = ( - connectedkva * 10 ** 3 - ) # DiTTo in var + api_load.transformer_connected_kva = (connectedkva * 10 ** 3) # DiTTo in var elif connectedkva is not None: if api_load.transformer_connected_kva is None: - api_load.transformer_connected_kva = ( - connectedkva * 10 ** 3 - ) # DiTTo in var + api_load.transformer_connected_kva = (connectedkva * 10 ** 3) # DiTTo in var else: - api_load.transformer_connected_kva += ( - connectedkva * 10 ** 3 - ) # DiTTo in var + api_load.transformer_connected_kva += (connectedkva * 10 ** 3) # DiTTo in var except: pass @@ -5848,12 +5839,7 @@ def parse_loads(self, model): api_load.num_users = float(settings["numberofcustomer"]) for ph in phases: - try: - api_phase_load = PhaseLoad(model) - except: - raise ValueError( - "Unable to instanciate PhaseLoad DiTTo object." - ) + api_phase_load = PhaseLoad(model) try: api_phase_load.phase = ph @@ -5899,7 +5885,6 @@ def parse_loads(self, model): # if api_phase_load.p!=0 or api_phase_load.q!=0: api_load.phase_loads.append(api_phase_load) - self._loads[sectionID] = api_load if not sectionID in self.section_duplicates: self.section_duplicates[sectionID] = [] From 90336d1308282b87b819a021bbf3ad9099de1446 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 20 Sep 2022 23:01:58 -0600 Subject: [PATCH 57/83] implement unbalanced loads in opendss writer was summing loads across phases this is the first steps in getting IEEE13 feeder results to match before implementing this the IEEE13 dss model would only segfault --- ditto/writers/opendss/write.py | 281 +++++++++++++++++++-------------- 1 file changed, 164 insertions(+), 117 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index ba7f01c8..086a2366 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -125,7 +125,7 @@ def __init__(self, **kwargs): # Call super super(Writer, self).__init__(**kwargs) - self._baseKV_ = set() + self._baseKV_ = set() # should only be LL? self._baseKV_feeders_ = {} logger.info("DiTTo--->OpenDSS writer successfuly instanciated.") @@ -1763,126 +1763,160 @@ def write_loads(self, model): :returns: 1 for success, -1 for failure :rtype: int """ - substation_text_map = {} feeder_text_map = {} + + def append_time_series(i, txt): + for ts in i.timeseries: + substation = "DEFAULT" + feeder = "DEFAULT" + if ts.feeder_name is not None: + feeder = ts.feeder_name + if ts.substation_name is not None: + substation = ts.substation_name + if ( + hasattr(ts, "data_location") + and ts.data_label is not None + and ts.data_location is not None + ): + filename = self.timeseries_datasets[ + substation + "_" + feeder + ][ts.data_location] + txt += " {ts_format}={filename}".format( + ts_format=self.timeseries_format[filename], + filename=filename, + ) + # TODO: manage the data correctly when it is only in memory + return txt + for i in model.iter_models(Load): + + if not (hasattr(i, "name") and i.name is not None): + continue # gotta have a name + + if not (hasattr(i, "phase_loads") and i.phase_loads is not None): + continue # no loads + + if not (hasattr(i, "connecting_element") and i.connecting_element is not None): + continue # no Bus1 to place load + + feeder_name = "DEFAULT" if ( self.separate_feeders and hasattr(i, "feeder_name") and i.feeder_name is not None ): feeder_name = i.feeder_name - else: - feeder_name = "DEFAULT" + + substation_name = "DEFAULT" if ( self.separate_substations and hasattr(i, "substation_name") and i.substation_name is not None ): substation_name = i.substation_name - else: - substation_name = "DEFAULT" if not substation_name in substation_text_map: substation_text_map[substation_name] = set([feeder_name]) else: substation_text_map[substation_name].add(feeder_name) - txt = "" - if substation_name + "_" + feeder_name in feeder_text_map: - txt = feeder_text_map[substation_name + "_" + feeder_name] - - # Name - if hasattr(i, "name") and i.name is not None: - txt += "New Load." + i.name - else: + txt = "" # first Load in the loop for the substation and feeder + sub_fdr_key = substation_name + "_" + feeder_name + if sub_fdr_key in feeder_text_map: + txt = feeder_text_map[sub_fdr_key] + # we are appending to the text to be written (last entry should be \n\n) + + kws = [] + kvars = [] + for phase_load in i.phase_loads: + if hasattr(phase_load, "p") and phase_load.p is not None: + kws.append(phase_load.p * 10 ** -3) + if hasattr(phase_load, "q") and phase_load.q is not None: + kvars.append(phase_load.q * 10 ** -3) + + if len(kws) == 0 and len(kvars) == 0: continue + balanced_load = True + + if len(kws) > 1 and len(kvars) > 1: + p1, q1 = kws[0], kvars[0] + if any(p1 != p for p in kws[1:]): + balanced_load = False + if any(q1 != q for q in kvars[1:]): + balanced_load = False + + # define shared values # Connection type + conn_type_txt = "" if hasattr(i, "connection_type") and i.connection_type is not None: if i.connection_type == "Y": - txt += " conn=wye" + conn_type_txt = " conn=wye" elif i.connection_type == "D": - txt += " conn=delta" + conn_type_txt = " conn=delta" - # Connecting element - if ( - hasattr(i, "connecting_element") - and i.connecting_element is not None - ): - txt += " bus1={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element)) - if hasattr(i, "phase_loads") and i.phase_loads is not None: - for phase_load in i.phase_loads: - if ( - hasattr(phase_load, "phase") - and phase_load.phase is not None - ): - txt += ".{p}".format( - p=self.phase_mapping(phase_load.phase) - ) + bus1_txt = " bus1={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', i.connecting_element)) - if i.connection_type == "D" and len(i.phase_loads) == 1: - if self.phase_mapping(i.phase_loads[0].phase) == 1: - txt += ".2" - if self.phase_mapping(i.phase_loads[0].phase) == 2: - txt += ".3" - if self.phase_mapping(i.phase_loads[0].phase) == 3: - txt += ".1" - - # nominal voltage + nomimal_voltage_txt = "" + nominal_voltage = 0.0 if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: - if i.nominal_voltage < 300: - txt += " kV={volt}".format( - volt=i.nominal_voltage * math.sqrt(3) * 10 ** -3 - ) - else: - txt += " kV={volt}".format(volt=i.nominal_voltage * 10 ** -3) - if not substation_name + "_" + feeder_name in self._baseKV_feeders_: - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() + if i.nominal_voltage < 300: # Line-Neutral voltage for 120 V - self._baseKV_.add(i.nominal_voltage * math.sqrt(3) * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( - i.nominal_voltage * math.sqrt(3) * 10 ** -3 - ) + nominal_voltage = i.nominal_voltage * math.sqrt(3) * 10 ** -3 + nomimal_voltage_txt = f" kV={nominal_voltage}" else: - self._baseKV_.add(i.nominal_voltage * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( - i.nominal_voltage * 10 ** -3 - ) + nominal_voltage = i.nominal_voltage * 10 ** -3 + nomimal_voltage_txt = f" kV={nominal_voltage}" - # Vmin + if not sub_fdr_key in self._baseKV_feeders_: + self._baseKV_feeders_[sub_fdr_key] = set() + + self._baseKV_.add(nominal_voltage) + self._baseKV_feeders_[sub_fdr_key].add(nominal_voltage) + + vmin_txt = "" if hasattr(i, "vmin") and i.vmin is not None: - txt += " Vminpu={vmin}".format(vmin=i.vmin) + vmin_txt = " Vminpu={vmin}".format(vmin=i.vmin) - # Vmax + vmax_txt = "" if hasattr(i, "vmax") and i.vmax is not None: - txt += " Vmaxpu={vmax}".format(vmax=i.vmax) + vmax_txt = " Vmaxpu={vmax}".format(vmax=i.vmax) + + if balanced_load: # only need to define one New Load regardless of number of phases + + txt += "New Load." + i.name + txt += conn_type_txt + txt += bus1_txt - # positions (Not mapped) + # balanced_load case: + for phase_load in i.phase_loads: + # add all phases onto Bus1 + if hasattr(phase_load, "phase") and phase_load.phase is not None: + txt += f".{self.phase_mapping(phase_load.phase)}" - # KW - total_P = 0 - if hasattr(i, "phase_loads") and i.phase_loads: - txt += " model={N}".format(N=i.phase_loads[0].model) + # handle Delta connections + if i.connection_type == "D" and len(i.phase_loads) == 1: + if self.phase_mapping(i.phase_loads[0].phase) == 1: + txt += ".2" + if self.phase_mapping(i.phase_loads[0].phase) == 2: + txt += ".3" + if self.phase_mapping(i.phase_loads[0].phase) == 3: + txt += ".1" - for phase_load in i.phase_loads: - if hasattr(phase_load, "p") and phase_load.p is not None: - total_P += phase_load.p - txt += " kW={P}".format(P=total_P * 10 ** -3) + txt += nomimal_voltage_txt - # Kva - total_Q = 0 - if hasattr(i, "phase_loads") and i.phase_loads: - for phase_load in i.phase_loads: - if hasattr(phase_load, "q") and phase_load.q is not None: - total_Q += phase_load.q - txt += " kvar={Q}".format(Q=total_Q * 10 ** -3) + txt += vmin_txt - # phase_loads - if hasattr(i, "phase_loads") and i.phase_loads: + txt += vmax_txt + + # positions (Not mapped) + + txt += " model={N}".format(N=i.phase_loads[0].model) + + txt += " kW={P}".format(P=sum(kws)) + + txt += " kvar={Q}".format(Q=sum(kvars)) # if i.connection_type=='Y': txt += " Phases={N}".format(N=len(i.phase_loads)) @@ -1893,14 +1927,6 @@ def write_loads(self, model): for phase_load in i.phase_loads: - # P - # if hasattr(phase_load, 'p') and phase_load.p is not None: - # fp.write(' kW={P}'.format(P=phase_load.p*10**-3)) - - # Q - # if hasattr(phase_load, 'q') and phase_load.q is not None: - # fp.write(' kva={Q}'.format(Q=phase_load.q*10**-3)) - # ZIP load model if ( hasattr(phase_load, "use_zip") @@ -1948,39 +1974,60 @@ def write_loads(self, model): ) ) - # fp.write(' model=1') - - # timeseries object - if hasattr(i, "timeseries") and i.timeseries is not None: - for ts in i.timeseries: - substation = "DEFAULT" - feeder = "DEFAULT" - if ts.feeder_name is not None: - feeder = ts.feeder_name - if ts.substation_name is not None: - substation = ts.substation_name - if ( - hasattr(ts, "data_location") - and ts.data_label is not None - and ts.data_location is not None - ): - filename = self.timeseries_datasets[ - substation + "_" + feeder - ][ts.data_location] - txt += " {ts_format}={filename}".format( - ts_format=self.timeseries_format[filename], - filename=filename, - ) - else: - pass - # TODO: manage the data correctly when it is only in memory + # timeseries object + if hasattr(i, "timeseries") and i.timeseries is not None: + txt = append_time_series(i, txt) - txt += "\n\n" - feeder_text_map[substation_name + "_" + feeder_name] = txt + txt += "\n\n" + + else: # unbalanced load, need to define more than one New Load + for (n, phase_load) in enumerate(i.phase_loads): + txt += "New Load." + i.name + "_" + str(n+1) # need unique names + txt += conn_type_txt + txt += bus1_txt + + if hasattr(phase_load, "phase") and phase_load.phase is not None: + dss_phase = self.phase_mapping(phase_load.phase) + txt += f".{dss_phase}" + + # handle Delta connections + if i.connection_type == "D": + if dss_phase == 1: + txt += ".2" + if dss_phase == 2: + txt += ".3" + if dss_phase == 3: + txt += ".1" + + nomimal_voltage_txt = "" + if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: + nominal_voltage = i.nominal_voltage * 10 ** -3 + if not (i.connection_type == "D" or nominal_voltage < 300): + # single phase connections in openDSS should specify LN voltage + # if not delta connection or not already LN + nominal_voltage = round(nominal_voltage / math.sqrt(3), 4) + nomimal_voltage_txt = f" kV={nominal_voltage}" + + txt += nomimal_voltage_txt + txt += vmin_txt + txt += vmax_txt + # positions (Not mapped) + txt += " model={N}".format(N=phase_load.model) + txt += " kW={P}".format(P=kws[n]) + txt += " kvar={Q}".format(Q=kvars[n]) + txt += " Phases=1" + + # timeseries object + if hasattr(i, "timeseries") and i.timeseries is not None: + txt = append_time_series(i, txt) + + txt += "\n\n" + + feeder_text_map[sub_fdr_key] = txt for substation_name in substation_text_map: for feeder_name in substation_text_map[substation_name]: - txt = feeder_text_map[substation_name + "_" + feeder_name] + txt = feeder_text_map[sub_fdr_key] feeder_name = feeder_name.replace(">", "-") substation_name = substation_name.replace(">", "-") if txt != "": From 8a0b1ece6ab840c71f9fb0afb9f484771d189a7d Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 23 Sep 2022 13:31:15 -0600 Subject: [PATCH 58/83] add pandas to setup.py install_requires --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 2a9ac9b4..33a69249 100644 --- a/setup.py +++ b/setup.py @@ -109,7 +109,7 @@ def run(self): "Programming Language :: Python :: 3.6", ], test_suite="tests", - install_requires=["click", "future", "networkx", "six", "traitlets>=4.1", "json_tricks", "numpy"], + install_requires=["click", "future", "networkx", "six", "traitlets>=4.1", "json_tricks", "numpy", "pandas"], extras_require={ "all": extras_requires + opendss_requires From cbf894f5c0b387a1b36d3e525e3e1da944b1ed7a Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 23 Sep 2022 13:35:17 -0600 Subject: [PATCH 59/83] properly close Lines.dss in opendss writer --- ditto/writers/opendss/write.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 086a2366..970e6a24 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -3482,16 +3482,15 @@ def write_linecodes(self, list_of_lines, feeder_name=None, substation_name=None) ) ) - fp = open( + with open( os.path.join(output_folder, self.output_filenames["linecodes"]), "w", - ) - - for linecode_name, linecode_data in txt.items(): - fp.write("New Linecode.{name}".format(name=re.sub('[^0-9a-zA-Z]+', '_', linecode_name))) - for k, v in linecode_data.items(): - fp.write(" {k}={v}".format(k=k, v=v)) - fp.write("\n\n") + ) as fp: + for linecode_name, linecode_data in txt.items(): + fp.write("New Linecode.{name}".format(name=re.sub('[^0-9a-zA-Z]+', '_', linecode_name))) + for k, v in linecode_data.items(): + fp.write(" {k}={v}".format(k=k, v=v)) + fp.write("\n\n") return 1 From ca718a30375d68c32370b159d871ff2d243697f7 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Sat, 24 Sep 2022 10:34:59 -0600 Subject: [PATCH 60/83] fix opendss writer trfx for regulator base voltages - it appears that the openDSS set of voltage bases should be LL, not LN (deducting from IEEE openDSS examples). - the code was not handling the case of single phase regulators, which naturally have a LN `nominal_voltage` --- ditto/writers/opendss/write.py | 48 ++++++++++++---------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 970e6a24..c6ab6fd5 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2153,17 +2153,14 @@ def write_regulators(self, model): transfo_creation_string += transfo_name # Number of Phases + nphases = 0 if hasattr(i, "windings") and i.windings is not None: if ( hasattr(i.windings[0], "phase_windings") and i.windings[0].phase_windings is not None ): - try: - transfo_creation_string += " phases={}".format( - len(i.windings[0].phase_windings) - ) - except: - pass + nphases = len(i.windings[0].phase_windings) + transfo_creation_string += f" phases={nphases}" phases = [ self.phase_mapping(x.phase) for x in i.windings[0].phase_windings @@ -2189,7 +2186,8 @@ def write_regulators(self, model): and i.to_element is not None ): transfo_creation_string += " buses=({b1}.{p},{b2}.{p})".format( - b1=re.sub('[^0-9a-zA-Z]+', '_', i.from_element), b2=re.sub('[^0-9a-zA-Z]+', '_', i.to_element), p=phase_string + b1=re.sub('[^0-9a-zA-Z]+', '_', i.from_element), + b2=re.sub('[^0-9a-zA-Z]+', '_', i.to_element), p=phase_string ) # Conns @@ -2210,10 +2208,10 @@ def write_regulators(self, model): # kvs if hasattr(i, "windings") and i.windings is not None: kvs = " kvs=(" - for w, winding in enumerate(i.windings): - if hasattr(i.windings[w], "nominal_voltage"): + for winding in i.windings: + if hasattr(winding, "nominal_voltage"): kvs += ( - str(i.windings[w].nominal_voltage * 10 ** -3) + str(winding.nominal_voltage * 10 ** -3) + ", " ) if ( @@ -2224,29 +2222,17 @@ def write_regulators(self, model): substation_name + "_" + feeder_name ] = set() if ( - i.windings[w].nominal_voltage < 300 - ): # Line-Neutral voltage for 120 V - self._baseKV_.add( - i.windings[w].nominal_voltage - * math.sqrt(3) - * 10 ** -3 - ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 - ) + winding.nominal_voltage < 300 + or nphases == 1 + ): # Line-Neutral voltage for 120 V or single phase trfo + kVLL = round(winding.nominal_voltage * math.sqrt(3) * 10 ** -3, 3) else: - self._baseKV_.add( - i.windings[w].nominal_voltage * 10 ** -3 - ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add(winding.nominal_voltage * 10 ** -3) + kVLL = round(winding.nominal_voltage * 10 ** -3, 3) + + self._baseKV_.add(kVLL) + self._baseKV_feeders_[substation_name + "_" + feeder_name].add(kVLL) - kvs = kvs[:-2] + kvs = kvs[:-2] # drop the last ", " kvs += ")" transfo_creation_string += kvs From 3311865200db8b8cb2db322045ad00ae64d04678 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Sat, 24 Sep 2022 13:58:01 -0600 Subject: [PATCH 61/83] fix the order of Line objects in opendss writer hoping to fix so many seg faults by writing object settings in the correct order --- ditto/writers/opendss/write.py | 84 +++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 21 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index c6ab6fd5..5c4261a8 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2848,22 +2848,44 @@ def write_lines(self, model): else: continue - # Set the units in miles for comparison (IEEE 13 nodes feeder) - # TODO: Let the user specify the export units - txt += " Units=km" - - # Length - if hasattr(i, "length") and i.length is not None: - txt += " Length={length}".format( - length=max( - 0.001, self.convert_from_meters(np.real(i.length), u"km") - ) - ) - - # nominal_voltage (Not mapped) - - # line type (Not mapped) - + """ + order for opendss is: + (order matters b/c o.w. causes seg faults c.f https://sourceforge.net/p/electricdss/discussion/861977/thread/e29ef76db9/) + + bus1 + bus2 + Linecode + Length + Phases + R1 + X1 + R0 + X0 + C1 + C0 + B1 + B0 + Normamps + Emergamps + Faultrate + Pctperm + Repair + BaseFreq + Rmatrix + Xmatrix + Cmatrix + Switch + Rg + Xg + Rho + Geometry + EarthModel + Units + Seasons + Ratings + LineType + Like + """ # from_element if hasattr(i, "from_element") and i.from_element is not None: txt += " bus1={from_el}".format(from_el=re.sub('[^0-9a-zA-Z]+', '_', i.from_element)) @@ -2888,6 +2910,27 @@ def write_lines(self, model): ): txt += ".{p}".format(p=self.phase_mapping(wire.phase)) + geometry = "" + if i in lines_to_geometrify: + geometry = " geometry={g}".format(g=i.nameclass) # added later in the correct order + elif i in lines_to_linecodify: + txt += " Linecode={c}".format(c=re.sub('[^0-9a-zA-Z]+', '_', i.nameclass)) + + # Length + if hasattr(i, "length") and i.length is not None: + txt += " Length={length}".format( + length=max( + 0.001, self.convert_from_meters(np.real(i.length), u"km") + ) + ) + + # Set the units in miles for comparison (IEEE 13 nodes feeder) + # TODO: Let the user specify the export units + txt += " Units=km" + + # nominal_voltage (Not mapped) + + # line type (Not mapped) # is_switch or is_breaker if (hasattr(i, "is_switch") and i.is_switch == 1) or ( hasattr(i, "is_breaker") and i.is_breaker == 1 @@ -2910,6 +2953,10 @@ def write_lines(self, model): txt += " enabled=n" else: txt += " enabled=y" + # enabled is not in opendss manual for line objects? + + if len(geometry) > 1: + txt += geometry # is_fuse if hasattr(i, "is_fuse") and i.is_fuse == 1: @@ -2938,11 +2985,6 @@ def write_lines(self, model): phase_wires = [w for w in i.wires if w.phase in ["A", "B", "C"]] txt += " phases=" + str(len(phase_wires)) - if i in lines_to_geometrify: - txt += " geometry={g}".format(g=i.nameclass) - elif i in lines_to_linecodify: - txt += " Linecode={c}".format(c=re.sub('[^0-9a-zA-Z]+', '_', i.nameclass)) - txt += "\n\n" if fuse_line != "": txt += fuse_line From 2ba2ab6efb6a9b4d5f6c336a8ad775e28d21780d Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Sat, 24 Sep 2022 16:07:41 -0600 Subject: [PATCH 62/83] fix the order of regulator params in opendss writer --- ditto/writers/opendss/write.py | 117 ++++++++++++++++----------------- 1 file changed, 58 insertions(+), 59 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 5c4261a8..7d797461 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2306,38 +2306,17 @@ def write_regulators(self, model): txt += " transformer={trans}".format(trans=transfo_name) # Winding + txt += " winding=2" if hasattr(i, "winding") and i.winding is not None: txt += " winding={w}".format(w=i.winding) - else: - txt += " winding=2" - - # CTprim - if hasattr(i, "ct_prim") and i.ct_prim is not None: - txt += " CTprim={CT}".format(CT=i.ct_prim) - - # noload_loss - if hasattr(i, "noload_loss") and i.noload_loss is not None: - txt += " %noLoadLoss={nL}".format(NL=i.noload_loss) - # Delay - if hasattr(i, "delay") and i.delay is not None: - txt += " delay={d}".format(d=i.delay) - - # highstep - # if hasattr(i, "highstep") and i.highstep is not None: - # txt += " maxtapchange={high}".format(high=i.highstep) - - # lowstep (Not mapped) - - # pt ratio - if hasattr(i, "pt_ratio") and i.pt_ratio is not None: - txt += " ptratio={PT}".format(PT=i.pt_ratio) - - # ct ratio (Not mapped) - - # phase shift (Not mapped) + # band center + if hasattr(i, "bandcenter") and i.bandcenter is not None: + txt += " vreg={vreg}".format(vreg=i.bandcenter) - # ltc (Not mapped) + # TODO WHAT IS GOING ON HERE? 1. overwrites vreg; 2. spaces not allowd in parameter setting + # if hasattr(i, "setpoint") and i.setpoint is not None: + # txt += " vreg = {setp}".format(setp=i.setpoint / 100.0 * 120) # bandwidth if hasattr(i, "bandwidth") and i.bandwidth is not None: @@ -2345,57 +2324,78 @@ def write_regulators(self, model): b=i.bandwidth * 1.2 ) # The bandwidth is operated at 120 V - # band center - if hasattr(i, "bandcenter") and i.bandcenter is not None: - txt += " vreg={vreg}".format(vreg=i.bandcenter) - - # Pt phase - if hasattr(i, "pt_phase") and i.pt_phase is not None: - txt += " Ptphase={PT}".format(PT=self.phase_mapping(i.pt_phase)) + # Delay + if hasattr(i, "delay") and i.delay is not None: + txt += " delay={d}".format(d=i.delay) - # Voltage limit - if hasattr(i, "voltage_limit") and i.voltage_limit is not None: - txt += " vlimit={vlim}".format(vlim=i.voltage_limit) + # pt ratio + if hasattr(i, "pt_ratio") and i.pt_ratio is not None: + txt += " ptratio={PT}".format(PT=i.pt_ratio) - if hasattr(i, "setpoint") and i.setpoint is not None: - txt += " vreg = {setp}".format(setp=i.setpoint / 100.0 * 120) + # CTprim + if hasattr(i, "ct_prim") and i.ct_prim is not None: + txt += " CTprim={CT}".format(CT=i.ct_prim) - # X (Store in the Phase Windings of the transformer) + # R (Store in the Phase Windings of the transformer) if i.name in self.compensator: - if "X" in self.compensator[i.name]: - if len(self.compensator[i.name]["X"]) == 1: - txt += " X={x}".format( - x=list(self.compensator[i.name]["X"])[0] + if "R" in self.compensator[i.name]: + if len(self.compensator[i.name]["R"]) == 1: + txt += " R={r}".format( + r=list(self.compensator[i.name]["R"])[0] ) else: logger.warning( - """Compensator_x not the same for all windings of transformer {name}. + """Compensator_r not the same for all windings of transformer {name}. Using the first value for regControl {name2}.""".format( name=i.connected_transformer, name2=i.name ) ) - txt += " X={x}".format( - x=list(self.compensator[i.name]["X"])[0] + txt += " R={r}".format( + r=list(self.compensator[i.name]["R"])[0] ) - # R (Store in the Phase Windings of the transformer) + # X (Store in the Phase Windings of the transformer) if i.name in self.compensator: - if "R" in self.compensator[i.name]: - if len(self.compensator[i.name]["R"]) == 1: - txt += " R={r}".format( - r=list(self.compensator[i.name]["R"])[0] + if "X" in self.compensator[i.name]: + if len(self.compensator[i.name]["X"]) == 1: + txt += " X={x}".format( + x=list(self.compensator[i.name]["X"])[0] ) else: logger.warning( - """Compensator_r not the same for all windings of transformer {name}. + """Compensator_x not the same for all windings of transformer {name}. Using the first value for regControl {name2}.""".format( name=i.connected_transformer, name2=i.name ) ) - txt += " R={r}".format( - r=list(self.compensator[i.name]["R"])[0] + txt += " X={x}".format( + x=list(self.compensator[i.name]["X"])[0] ) + # Pt phase + if hasattr(i, "pt_phase") and i.pt_phase is not None: + txt += " Ptphase={PT}".format(PT=self.phase_mapping(i.pt_phase)) + + # noload_loss + if hasattr(i, "noload_loss") and i.noload_loss is not None: + txt += " %noLoadLoss={nL}".format(NL=i.noload_loss) + + # highstep + # if hasattr(i, "highstep") and i.highstep is not None: + # txt += " maxtapchange={high}".format(high=i.highstep) + + # lowstep (Not mapped) + + # ct ratio (Not mapped) + + # phase shift (Not mapped) + + # ltc (Not mapped) + + # Voltage limit + if hasattr(i, "voltage_limit") and i.voltage_limit is not None: + txt += " vlimit={vlim}".format(vlim=i.voltage_limit) + txt += "\n\n" if len(transfo_creation_string) > 0: transfo_creation_string += "\n\n" @@ -3390,10 +3390,9 @@ def write_linecodes(self, list_of_lines, feeder_name=None, substation_name=None) ) if i.nameclass is not None: + n_phases = "" if "nphases" in parsed_line: n_phases = str(parsed_line["nphases"]) - else: - n_phases = "" nameclass_phase = i.nameclass + "_" + n_phases if nameclass_phase not in self.all_linecodes: self.all_linecodes[nameclass_phase] = parsed_line @@ -3516,7 +3515,7 @@ def write_linecodes(self, list_of_lines, feeder_name=None, substation_name=None) ) as fp: for linecode_name, linecode_data in txt.items(): fp.write("New Linecode.{name}".format(name=re.sub('[^0-9a-zA-Z]+', '_', linecode_name))) - for k, v in linecode_data.items(): + for k, v in linecode_data.items(): # needs an order fp.write(" {k}={v}".format(k=k, v=v)) fp.write("\n\n") From 74250863a76bf52077d7019d4cf0c2aa5887e81b Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Sat, 24 Sep 2022 16:37:09 -0600 Subject: [PATCH 63/83] fix order of params for 2 winding trfx in opendss writer --- ditto/writers/opendss/write.py | 213 +++++++++++++++------------------ 1 file changed, 97 insertions(+), 116 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 7d797461..e665674d 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -440,8 +440,9 @@ def write_transformers(self, model): substation_text_map[substation_name].add(feeder_name) txt = "" - if substation_name + "_" + feeder_name in feeder_text_map: - txt = feeder_text_map[substation_name + "_" + feeder_name] + sub_fdr_key = substation_name + "_" + feeder_name + if sub_fdr_key in feeder_text_map: + txt = feeder_text_map[sub_fdr_key] if hasattr(i, "name") and i.name is not None: txt += "New Transformer." + i.name @@ -495,25 +496,11 @@ def write_transformers(self, model): # Emergency_power removed from powerTransformers and added to windings by Tarek # if hasattr(i, 'emergency_power') and i.emergency_power is not None: # fp.write(' EmergHKVA='+str(i.emergency_power*10**-3)) #OpenDSS in kWatts - - # Loadloss - if hasattr(i, "loadloss") and i.loadloss is not None: - txt += " %loadloss=" + str(i.loadloss) # OpenDSS in kWatts - - # install type (Not mapped) - - # noload_loss - if hasattr(i, "noload_loss") and i.noload_loss is not None: - txt += " %Noloadloss=" + str(i.noload_loss) - - # noload_loss - if hasattr(i, "normhkva") and i.normhkva is not None: - txt += " normhkva=" + str(i.normhkva) - # phase shift (Not mapped) # Assume that we only have two or three windings. Three are used for center-tap transformers. Other single or three phase transformers use 2 windings # For banked 3-phase transformers, separate single phase transformers are used + nphases = 3 if hasattr(i, "windings") and i.windings is not None: # set compensator_r and compensator_x @@ -522,12 +509,13 @@ def write_transformers(self, model): hasattr(winding, "phase_windings") and winding.phase_windings is not None ): - + nphases = 0 for phase_winding in winding.phase_windings: if ( hasattr(phase_winding, "compensator_r") and phase_winding.compensator_r is not None ): + nphases += 1 if not i.name in self.compensator: self.compensator[i.name] = {} self.compensator[i.name]["R"] = set( @@ -561,11 +549,49 @@ def write_transformers(self, model): ) if len(i.windings) == 2: - + emergency_power = None # openDSS expects one EmergHKVA value, but DiTTo has emergency_power for each phase_winding for cnt, winding in enumerate(i.windings): txt += " wdg={N}".format(N=cnt + 1) + # bus + if ( + hasattr(winding, "phase_windings") + and winding.phase_windings is not None + ): + + if buses is not None: + bus = buses[cnt] + txt += " bus={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', str(bus))) + + if len(winding.phase_windings) != 3: + + for phase_winding in winding.phase_windings: + + # Connection + if ( + hasattr(phase_winding, "phase") + and phase_winding.phase is not None + ): + txt += "." + str(self.phase_mapping(phase_winding.phase)) + + if ( + winding.connection_type == "D" + and len(winding.phase_windings) == 1 + ): + logger.warn( + "Warning - only one phase specified for a delta system - adding another connection" + ) + if self.phase_mapping(phase_winding.phase) == 1: + txt += ".2" + if self.phase_mapping(phase_winding.phase) == 2: + txt += ".3" + if self.phase_mapping(phase_winding.phase) == 3: + txt += ".1" + + if winding.is_grounded: + txt += ".0" + # Connection type if ( hasattr(winding, "connection_type") @@ -600,36 +626,19 @@ def write_transformers(self, model): txt += " Kv={kv}".format( kv=winding.nominal_voltage * 10 ** -3 ) # OpenDSS in kvolts - if ( - not substation_name + "_" + feeder_name - in self._baseKV_feeders_ - ): - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() + if (not sub_fdr_key in self._baseKV_feeders_): + self._baseKV_feeders_[sub_fdr_key] = set() if ( winding.nominal_voltage < 300 + or nphases == 1 ): # Line-Neutral voltage for 120 V - self._baseKV_.add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 - ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add( - winding.nominal_voltage - * math.sqrt(3) - * 10 ** -3 - ) + kVLL = round(winding.nominal_voltage * math.sqrt(3) * 10 ** -3, 3) else: - self._baseKV_.add( - winding.nominal_voltage * 10 ** -3 - ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add(winding.nominal_voltage * 10 ** -3) + kVLL = round(winding.nominal_voltage * 10 ** -3, 3) + + self._baseKV_.add(kVLL) + self._baseKV_feeders_[sub_fdr_key].add(kVLL) # rated power if ( @@ -640,72 +649,6 @@ def write_transformers(self, model): kva=winding.rated_power * 10 ** -3 ) - # emergency_power - # Was added to windings by Tarek - if ( - hasattr(winding, "emergency_power") - and winding.emergency_power is not None - ): - txt += " EmergHKVA={}".format( - winding.emergency_power * 10 ** -3 - ) # OpenDSS in kWatts - - # resistance - if ( - hasattr(winding, "resistance") - and winding.resistance is not None - ): - txt += " %R={R}".format(R=winding.resistance) - - # Voltage limit (Not mapped) - - # Reverse resistance (Not mapped) - - # Check if winding is grounded - # this check is done here so that it happens only for 2 windings - - # Phase windings - if ( - hasattr(winding, "phase_windings") - and winding.phase_windings is not None - ): - - if buses is not None: - bus = buses[cnt] - txt += " bus={bus}".format(bus=re.sub('[^0-9a-zA-Z]+', '_', str(bus))) - - if len(winding.phase_windings) != 3: - - for j, phase_winding in enumerate( - winding.phase_windings - ): - - # Connection - if ( - hasattr(phase_winding, "phase") - and phase_winding.phase is not None - ): - txt += "." + str( - self.phase_mapping(phase_winding.phase) - ) - - if ( - winding.connection_type == "D" - and len(winding.phase_windings) == 1 - ): - print( - "Warning - only one phase specified for a delta system - adding another connection" - ) - if self.phase_mapping(phase_winding.phase) == 1: - txt += ".2" - if self.phase_mapping(phase_winding.phase) == 2: - txt += ".3" - if self.phase_mapping(phase_winding.phase) == 3: - txt += ".1" - - if winding.is_grounded: - txt += ".0" - if ( hasattr(winding, "phase_windings") and winding.phase_windings is not None @@ -725,6 +668,27 @@ def write_transformers(self, model): tap=winding.phase_windings[0].tap_position ) + # resistance + if ( + hasattr(winding, "resistance") + and winding.resistance is not None + ): + txt += " %R={R}".format(R=winding.resistance) + + # emergency_power + # Was added to windings by Tarek + if ( + hasattr(winding, "emergency_power") + and winding.emergency_power is not None + and emergency_power is None + ): + emergency_power = " EmergHKVA={}".format( + winding.emergency_power * 10 ** -3 + ) # OpenDSS in kWatts + + # Voltage limit (Not mapped) + + # Reverse resistance (Not mapped) if hasattr(i, "reactances") and i.reactances is not None: # Since we are in the case of 2 windings, we should only have one reactance if isinstance(i.reactances, list): @@ -745,6 +709,8 @@ def write_transformers(self, model): name=i.name ) ) + if emergency_power is not None: + txt += emergency_power # This is used to represent center-tap transformers # As described in the documentation, if the R and X values are not known, the values described by default_r and default_x should be used @@ -839,11 +805,11 @@ def write_transformers(self, model): kv=winding.nominal_voltage * 10 ** -3 ) # OpenDSS in kvolts if ( - not substation_name + "_" + feeder_name + not sub_fdr_key in self._baseKV_feeders_ ): self._baseKV_feeders_[ - substation_name + "_" + feeder_name + sub_fdr_key ] = set() if ( winding.nominal_voltage < 300 @@ -854,7 +820,7 @@ def write_transformers(self, model): * 10 ** -3 ) self._baseKV_feeders_[ - substation_name + "_" + feeder_name + sub_fdr_key ].add( winding.nominal_voltage * math.sqrt(3) @@ -865,7 +831,7 @@ def write_transformers(self, model): winding.nominal_voltage * 10 ** -3 ) self._baseKV_feeders_[ - substation_name + "_" + feeder_name + sub_fdr_key ].add(winding.nominal_voltage * 10 ** -3) # rated power @@ -939,12 +905,27 @@ def write_transformers(self, model): default_x[2], ) + + # Loadloss + if hasattr(i, "loadloss") and i.loadloss is not None: + txt += " %loadloss=" + str(i.loadloss) # OpenDSS in kWatts + + # install type (Not mapped) + + # noload_loss + if hasattr(i, "noload_loss") and i.noload_loss is not None: + txt += " %Noloadloss=" + str(i.noload_loss) + + # noload_loss + if hasattr(i, "normhkva") and i.normhkva is not None: + txt += " normhkva=" + str(i.normhkva) + txt += "\n\n" - feeder_text_map[substation_name + "_" + feeder_name] = txt + feeder_text_map[sub_fdr_key] = txt for substation_name in substation_text_map: for feeder_name in substation_text_map[substation_name]: - txt = feeder_text_map[substation_name + "_" + feeder_name] + txt = feeder_text_map[sub_fdr_key] feeder_name = feeder_name.replace(">", "-") substation_name = substation_name.replace(">", "-") if txt != "": From d65ed7fb33d7f0f5fe8591cad376d551b3e97738 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Sat, 24 Sep 2022 17:10:12 -0600 Subject: [PATCH 64/83] fix order of load params in opendss writer --- ditto/writers/opendss/write.py | 51 +++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index e665674d..78072924 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -1867,11 +1867,9 @@ def append_time_series(i, txt): if balanced_load: # only need to define one New Load regardless of number of phases txt += "New Load." + i.name - txt += conn_type_txt txt += bus1_txt - # balanced_load case: - for phase_load in i.phase_loads: + for phase_load in i.phase_loads: # add the .phs1.phs2 etc # add all phases onto Bus1 if hasattr(phase_load, "phase") and phase_load.phase is not None: txt += f".{self.phase_mapping(phase_load.phase)}" @@ -1885,26 +1883,28 @@ def append_time_series(i, txt): if self.phase_mapping(i.phase_loads[0].phase) == 3: txt += ".1" + # if i.connection_type=='Y': + txt += " Phases={N}".format(N=len(i.phase_loads)) + # elif i.connection_type=='D' and len(i.phase_loads)==3: + # fp.write(' Phases=3') + # elif i.connection_type=='D' and len(i.phase_loads)==2: + # fp.write(' Phases=1') + txt += nomimal_voltage_txt + + txt += " kW={P}".format(P=sum(kws)) + + txt += " model={N}".format(N=i.phase_loads[0].model) + + txt += conn_type_txt + + txt += " kvar={Q}".format(Q=sum(kvars)) txt += vmin_txt txt += vmax_txt # positions (Not mapped) - - txt += " model={N}".format(N=i.phase_loads[0].model) - - txt += " kW={P}".format(P=sum(kws)) - - txt += " kvar={Q}".format(Q=sum(kvars)) - - # if i.connection_type=='Y': - txt += " Phases={N}".format(N=len(i.phase_loads)) - # elif i.connection_type=='D' and len(i.phase_loads)==3: - # fp.write(' Phases=3') - # elif i.connection_type=='D' and len(i.phase_loads)==2: - # fp.write(' Phases=1') for phase_load in i.phase_loads: @@ -1964,9 +1964,7 @@ def append_time_series(i, txt): else: # unbalanced load, need to define more than one New Load for (n, phase_load) in enumerate(i.phase_loads): txt += "New Load." + i.name + "_" + str(n+1) # need unique names - txt += conn_type_txt txt += bus1_txt - if hasattr(phase_load, "phase") and phase_load.phase is not None: dss_phase = self.phase_mapping(phase_load.phase) txt += f".{dss_phase}" @@ -1979,7 +1977,9 @@ def append_time_series(i, txt): txt += ".3" if dss_phase == 3: txt += ".1" - + + txt += " Phases=1" + nomimal_voltage_txt = "" if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: nominal_voltage = i.nominal_voltage * 10 ** -3 @@ -1990,13 +1990,18 @@ def append_time_series(i, txt): nomimal_voltage_txt = f" kV={nominal_voltage}" txt += nomimal_voltage_txt + + txt += " kW={P}".format(P=kws[n]) + + txt += " model={N}".format(N=phase_load.model) + + txt += conn_type_txt + + txt += " kvar={Q}".format(Q=kvars[n]) + txt += vmin_txt txt += vmax_txt # positions (Not mapped) - txt += " model={N}".format(N=phase_load.model) - txt += " kW={P}".format(P=kws[n]) - txt += " kvar={Q}".format(Q=kvars[n]) - txt += " Phases=1" # timeseries object if hasattr(i, "timeseries") and i.timeseries is not None: From ef62bcaa8e404d31fa966ded704b548103523400 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Wed, 28 Sep 2022 10:09:38 -0600 Subject: [PATCH 65/83] improve legibility in opendss write PVs --- ditto/writers/opendss/write.py | 65 +++++++++++++++------------------- 1 file changed, 28 insertions(+), 37 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 78072924..867487b9 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -1219,22 +1219,22 @@ def write_PVs(self, model): for i in model.models: if isinstance(i, Photovoltaic): # If is_sourcebus is set to 1, then the object represents a source and not a PV system + + feeder_name = "DEFAULT" if ( self.separate_feeders and hasattr(i, "feeder_name") and i.feeder_name is not None ): feeder_name = i.feeder_name - else: - feeder_name = "DEFAULT" + + substation_name = "DEFAULT" if ( self.separate_substations and hasattr(i, "substation_name") and i.substation_name is not None ): substation_name = i.substation_name - else: - substation_name = "DEFAULT" if not substation_name in substation_text_map: substation_text_map[substation_name] = set([feeder_name]) @@ -1243,23 +1243,25 @@ def write_PVs(self, model): txt = "" voltvar_nodes = set() voltwatt_nodes = set() - if substation_name + "_" + feeder_name in feeder_text_map: - txt = feeder_text_map[substation_name + "_" + feeder_name] + sub_fdr_key = substation_name + "_" + feeder_name + if sub_fdr_key in feeder_text_map: + txt = feeder_text_map[sub_fdr_key] - if substation_name + "_" + feeder_name in feeder_voltvar_map: - voltvar_nodes = feeder_voltvar_map[ - substation_name + "_" + feeder_name - ] + if sub_fdr_key in feeder_voltvar_map: + voltvar_nodes = feeder_voltvar_map[sub_fdr_key] - if substation_name + "_" + feeder_name in feeder_voltwatt_map: - voltwatt_nodes = feeder_voltwatt_map[ - substation_name + "_" + feeder_name - ] + if sub_fdr_key in feeder_voltwatt_map: + voltwatt_nodes = feeder_voltwatt_map[sub_fdr_key] # Name if hasattr(i, "name") and i.name is not None: txt += "New PVSystem.{name}".format(name=i.name) + # Phases + n_phases = 3 + if hasattr(i, "phases") and i.phases is not None: + n_phases = len(i.phases) + txt += f" phases={n_phases}" # connecting element if ( hasattr(i, "connecting_element") @@ -1273,9 +1275,6 @@ def write_PVs(self, model): txt += "." + str(self.phase_mapping(phase.default_value)) # Phases - if hasattr(i, "phases") and i.phases is not None: - txt += " phases={n_phases}".format(n_phases=len(i.phases)) - # nominal voltage if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: if i.nominal_voltage < 300: @@ -1286,20 +1285,18 @@ def write_PVs(self, model): txt += " kV={kV}".format( kV=i.nominal_voltage * 10 ** -3 ) # DiTTo in volts - if not substation_name + "_" + feeder_name in self._baseKV_feeders_: - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() + if not sub_fdr_key in self._baseKV_feeders_: + self._baseKV_feeders_[sub_fdr_key] = set() if ( i.nominal_voltage < 300 ): # Line-Neutral voltage for 120 V (i.e. 240V) self._baseKV_.add(i.nominal_voltage * math.sqrt(3) * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( + self._baseKV_feeders_[sub_fdr_key].add( i.nominal_voltage * 2 * 10 ** -3 ) else: self._baseKV_.add(i.nominal_voltage * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( + self._baseKV_feeders_[sub_fdr_key].add( i.nominal_voltage * 10 ** -3 ) else: @@ -1317,26 +1314,20 @@ def write_PVs(self, model): kV=parent.nominal_voltage * 10 ** -3 ) # DiTTo in volts if ( - not substation_name + "_" + feeder_name + not sub_fdr_key in self._baseKV_feeders_ ): - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() + self._baseKV_feeders_[sub_fdr_key] = set() if ( parent.nominal_voltage < 300 ): # Line-Line voltage for 120 V (i.e. 240V) self._baseKV_.add( parent.nominal_voltage * math.sqrt(3) * 10 ** -3 ) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add(parent.nominal_voltage * 2 * 10 ** -3) + self._baseKV_feeders_[sub_fdr_key].add(parent.nominal_voltage * 2 * 10 ** -3) else: self._baseKV_.add(parent.nominal_voltage * 10 ** -3) - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ].add(parent.nominal_voltage * 10 ** -3) + self._baseKV_feeders_[sub_fdr_key].add(parent.nominal_voltage * 10 ** -3) if hasattr(i, "active_rating") and i.active_rating is not None: pf_local = 1.0 @@ -1443,7 +1434,7 @@ def write_PVs(self, model): and os.path.isfile(os.path.join(self.output_path,ts.data_location)) ): filename = self.timeseries_datasets[ - substation_name + "_" + feeder_name + sub_fdr_key ][ts.data_location] txt += " {ts_format}={filename}".format( ts_format=self.timeseries_format[filename], @@ -1454,10 +1445,10 @@ def write_PVs(self, model): # TODO: manage the data correctly when it is only in memory txt += "\n" - feeder_text_map[substation_name + "_" + feeder_name] = txt - feeder_voltvar_map[substation_name + "_" + feeder_name] = voltvar_nodes + feeder_text_map[sub_fdr_key] = txt + feeder_voltvar_map[sub_fdr_key] = voltvar_nodes feeder_voltwatt_map[ - substation_name + "_" + feeder_name + sub_fdr_key ] = voltwatt_nodes for substation_name in substation_text_map: From 9ec6f940cfb72b9b4fc35b8514f371220fffbe75 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Thu, 29 Sep 2022 14:43:40 -0600 Subject: [PATCH 66/83] minor formatting in cyme reader --- ditto/readers/cyme/read.py | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 3d93491a..0a615e3e 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -3964,18 +3964,10 @@ def parse_capacitors(self, model): sectionID = sectionID.strip("*").lower() # Instanciate Capacitor DiTTo objects - try: - api_capacitor = Capacitor(model) - except: - raise ValueError( - "Unable to instanciate capacitor {id}".format(id=scap["sectionid"]) - ) + api_capacitor = Capacitor(model) # Set the name - try: - api_capacitor.name = "Cap_" + sectionID - except: - pass + api_capacitor.name = "Cap_" + sectionID # Set the connecting element (info is in the section) try: @@ -4016,12 +4008,11 @@ def parse_capacitors(self, model): pass # Get the device number + dev_num = None if "eqid" in settings: dev_num = settings["eqid"] elif "shuntcapacitorid" in settings: dev_num = settings["shuntcapacitorid"] - else: - dev_num = None capacitor_data = None if dev_num is not None: @@ -4074,7 +4065,7 @@ def parse_capacitors(self, model): "Capacitor {name} is monitoring phase {p} which is not in the section {id} phase list {lis}.".format( name=api_capacitor.name, p=api_capacitor.pt_phase, - id=scap["sectionid"], + id=sectionID, lis=phases, ) ) @@ -4083,12 +4074,7 @@ def parse_capacitors(self, model): for p in phases: # Instanciate a PhaseCapacitor DiTTo object - try: - api_phaseCapacitor = PhaseCapacitor(model) - except: - raise ValueError( - "Unable to instanciate PhaseCapacitor DiTTo object." - ) + api_phaseCapacitor = PhaseCapacitor(model) # Set the phase try: From 51646763d2bdb5503dae59666f44be1b0a46cda7 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Thu, 29 Sep 2022 14:45:14 -0600 Subject: [PATCH 67/83] improve opendss writer capacitor parsing legibility also a bug fix in a logger.error --- ditto/writers/opendss/write.py | 33 ++++++++++++++++----------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 867487b9..8e769605 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2472,30 +2472,30 @@ def write_capacitors(self, model): if isinstance(i, Capacitor): + feeder_name = "DEFAULT" if ( self.separate_feeders and hasattr(i, "feeder_name") and i.feeder_name is not None ): feeder_name = i.feeder_name - else: - feeder_name = "DEFAULT" + + substation_name = "DEFAULT" if ( self.separate_substations and hasattr(i, "substation_name") and i.substation_name is not None ): substation_name = i.substation_name - else: - substation_name = "DEFAULT" if not substation_name in substation_text_map: substation_text_map[substation_name] = set([feeder_name]) else: substation_text_map[substation_name].add(feeder_name) txt = "" - if substation_name + "_" + feeder_name in feeder_text_map: - txt = feeder_text_map[substation_name + "_" + feeder_name] + sub_fdr_key = substation_name + "_" + feeder_name + if sub_fdr_key in feeder_text_map: + txt = feeder_text_map[sub_fdr_key] # Name if hasattr(i, "name") and i.name is not None: @@ -2535,18 +2535,16 @@ def write_capacitors(self, model): txt += " Kv={volt}".format( volt=i.nominal_voltage * 10 ** -3 ) # OpenDSS in Kvolts - if not substation_name + "_" + feeder_name in self._baseKV_feeders_: - self._baseKV_feeders_[ - substation_name + "_" + feeder_name - ] = set() + if not sub_fdr_key in self._baseKV_feeders_: + self._baseKV_feeders_[sub_fdr_key] = set() if i.nominal_voltage < 300: # Line-Neutral voltage for 120 V self._baseKV_.add(i.nominal_voltage * math.sqrt(3) * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( + self._baseKV_feeders_[sub_fdr_key].add( i.nominal_voltage * math.sqrt(3) * 10 ** -3 ) else: self._baseKV_.add(i.nominal_voltage * 10 ** -3) - self._baseKV_feeders_[substation_name + "_" + feeder_name].add( + self._baseKV_feeders_[sub_fdr_key].add( i.nominal_voltage * 10 ** -3 ) @@ -2564,8 +2562,8 @@ def write_capacitors(self, model): ) # Rated kvar - # In DiTTo, this is splitted accross phase_capacitors - # We thus have to sum them up + # In DiTTo, this is split accross phase_capacitors + # We thus have to sum them up (3 phase kVaR is split equally by openDSS) total_var = 0 if hasattr(i, "phase_capacitors") and i.phase_capacitors is not None: for phase_capacitor in i.phase_capacitors: @@ -2578,7 +2576,7 @@ def write_capacitors(self, model): except: logger.error( "Cannot compute Var of capacitor {name}".format( - name=name + name=i.name ) ) pass @@ -2643,10 +2641,11 @@ def write_capacitors(self, model): txt += " PTPhase={PT}".format(PT=self.phase_mapping(i.pt_phase)) txt += "\n\n" - feeder_text_map[substation_name + "_" + feeder_name] = txt + feeder_text_map[sub_fdr_key] = txt + for substation_name in substation_text_map: for feeder_name in substation_text_map[substation_name]: - txt = feeder_text_map[substation_name + "_" + feeder_name] + txt = feeder_text_map[sub_fdr_key] feeder_name = feeder_name.replace(">", "-") substation_name = substation_name.replace(">", "-") if txt != "": From 1dbde595bcedd2ca425645361400282f27450584 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 30 Sep 2022 11:20:41 -0600 Subject: [PATCH 68/83] add vmin, vmax to Capacitor model these are necessary for capcontrol in ditto (Capacitor.low/high were being used for opendss vmin/vmax, but the low and high values should be used for the capcontrol on/offsetting) --- ditto/models/capacitor.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ditto/models/capacitor.py b/ditto/models/capacitor.py index 3761d2e5..560317e9 100644 --- a/ditto/models/capacitor.py +++ b/ditto/models/capacitor.py @@ -122,6 +122,14 @@ class Capacitor(DiTToHasTraits): help="""Flag that indicates wheter the element is inside a substation or not.""", default_value=False, ) + vmax = Float( + help="""Maximum voltage for control override OFF""", + default_value=None + ) + vmin = Float( + help="""Minimum voltage for control override ON""", + default_value=None + ) def build(self, model): self._model = model From c8db4a97206d364a479b82f5ef8f09896af7826b Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 30 Sep 2022 11:22:46 -0600 Subject: [PATCH 69/83] cyme reader parse more shunt capacitor settings for cap control --- ditto/readers/cyme/read.py | 172 ++++++++++++++++++++++--------------- 1 file changed, 104 insertions(+), 68 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 0a615e3e..96f36b05 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -3856,13 +3856,33 @@ def parse_capacitors(self, model): "switchedkvara": 13, "switchedkvarb": 14, "switchedkvarc": 15, + "voltageoverride": 20, + "voltageoverrideon": 21, + "voltageoverrideoff": 22, "kv": 24, + "control": 25, + "onvaluea": 26, + "onvalueb": 27, + "onvaluec": 28, + "offvaluea": 29, + "offvalueb": 30, + "offvaluec": 31, "controllingphase": 35, } mapp_serie_capacitor = {"id": 0, "reactance": 6} mapp_shunt_capacitor = {"id": 0, "kvar": 1, "kv": 2, "type": 6} self.settings = {} self.capacitors = {} + cap_control_map = { # cyme names in comments, None's don't have an equivalent in Ditto model + 0: None, # "Manual Control", + 1: "voltage", # "Voltage Control" + 2: "currentFlow", # "Current Control" + 3: None, # "Reactive Current Control", + 4: "activePower", # "Power Factor Control", + 5: None, # "Temperature Control", + 6: "timeScheduled", # "Time Control", + 7: "reactivePower", # "KVAR Control", + } ##################################################### # # @@ -3914,6 +3934,16 @@ def parse_capacitors(self, model): "switchedkvarc", "kv", "controllingphase", + "onvaluea", + "onvalueb", + "onvaluec", + "offvaluea", + "offvalueb", + "offvaluec", + "control", + "voltageoverride", + "voltageoverrideon", + "voltageoverrideoff", ], mapp_shunt_capacitor_settings, {"type": "shunt"}, @@ -3971,9 +4001,7 @@ def parse_capacitors(self, model): # Set the connecting element (info is in the section) try: - api_capacitor.connecting_element = self.section_phase_mapping[ - sectionID - ]["fromnodeid"] + api_capacitor.connecting_element = self.section_phase_mapping[sectionID]["fromnodeid"] except: pass @@ -4070,85 +4098,93 @@ def parse_capacitors(self, model): ) ) - # For each phase... + if "voltageoverride" in settings.keys() and settings["voltageoverride"]: + if "voltageoverrideon" in settings.keys(): + try: api_capacitor.vmin = float(settings["voltageoverrideon"]) + except: logger.warn(f"Could not set vmin for Capacitor {api_capacitor.name}") + if "voltageoverrideoff" in settings.keys(): + try: api_capacitor.vmax = float(settings["voltageoverrideoff"]) + except: logger.warn(f"Could not set vmax for Capacitor {api_capacitor.name}") + + # For each phase make a PhaseCapacitor and set its rating + on_values = [] + off_values = [] + control_mode_ints = [] for p in phases: - - # Instanciate a PhaseCapacitor DiTTo object api_phaseCapacitor = PhaseCapacitor(model) - - # Set the phase - try: - api_phaseCapacitor.phase = p - except: - pass + api_phaseCapacitor.phase = p # Set var value + fixed_key = "fixedkvar" + p.lower() # p is one of "A", "B", "C" + switched_key = "switchedkvar" + p.lower() if ( - "fixedkvara" in settings - and "fixedkvarb" in settings - and "fixedkvarc" in settings - and max( - float(settings["fixedkvara"]), - max( - float(settings["fixedkvarb"]), float(settings["fixedkvarc"]) - ), - ) - > 0 + fixed_key in settings.keys() and settings[fixed_key] is not None and + float(settings[fixed_key]) > 0 ): - try: - if p == "A": - api_phaseCapacitor.var = ( - float(settings["fixedkvara"]) * 10 ** 3 - ) # Ditto in var - if p == "B": - api_phaseCapacitor.var = ( - float(settings["fixedkvarb"]) * 10 ** 3 - ) # Ditto in var - if p == "C": - api_phaseCapacitor.var = ( - float(settings["fixedkvarc"]) * 10 ** 3 - ) # Ditto in var - except: - pass + api_phaseCapacitor.var = ( + float(settings[fixed_key]) * 10 ** 3 + ) # Ditto in var elif ( - "switchedkvara" in settings - and "switchedkvarb" in settings - and "switchedkvarc" in settings - and max( - float(settings["switchedkvara"]), - max( - float(settings["switchedkvarb"]), - float(settings["switchedkvarc"]), - ), - ) - > 0 + switched_key in settings.keys() and settings[switched_key] is not None and + float(settings[switched_key]) > 0 ): - try: - if p == "A": - api_phaseCapacitor.var = ( - float(settings["switchedkvara"]) * 10 ** 3 - ) # Ditto in var - if p == "B": - api_phaseCapacitor.var = ( - float(settings["switchedkvarb"]) * 10 ** 3 - ) # Ditto in var - if p == "C": - api_phaseCapacitor.var = ( - float(settings["switchedkvarc"]) * 10 ** 3 - ) # Ditto in var - except: - pass - - elif capacitor_data is not None: - try: + api_phaseCapacitor.var = ( + float(settings[switched_key]) * 10 ** 3 + ) # Ditto in var + api_phaseCapacitor.switch = True + elif capacitor_data is not None and "kvar" in capacitor_data.keys(): + if capacitor_data["kvar"] is not None: api_phaseCapacitor.var = ( float(capacitor_data["kvar"]) * 10 ** 3 ) # DiTTo in var - except: - pass + else: + continue # can't make a capacitor without a rating + + # check for on/off settings + on_key = "onvalue" + p.lower() + off_key = "offvalue" + p.lower() + + if on_key in settings.keys() and settings[on_key] is not None: + try: + if float(settings[on_key]) > 0: + on_values.append(float(settings[on_key])) + except ValueError: pass # empty string + + if off_key in settings.keys() and settings[off_key] is not None: + try: + if float(settings[off_key]) > 0: + off_values.append(float(settings[off_key])) + except ValueError: pass # empty string + + if "control" in settings.keys() and settings["control"] is not None: + try: + control_mode_ints.append(int(settings["control"])) + except ValueError: pass # empty string # Append the phase capacitor object to the capacitor + api_capacitor.measuring_element = sectionID # the line for control measurements api_capacitor.phase_capacitors.append(api_phaseCapacitor) + + if len(on_values) > 1: + if not all(x == on_values[0] for x in on_values[1:]): + logger.warn(f"Not all of the on values for capacitor in section {sectionID} are the same. Using the lowest value.") + api_capacitor.low = min(on_values) + elif len(on_values) == 1: + api_capacitor.low = on_values[0] + + if len(control_mode_ints) > 1: + if not all(x == control_mode_ints[0] for x in control_mode_ints[1:]): + logger.warn(f"Not all of the control modes for capacitor in section {sectionID} are the same. Using the first value.") + api_capacitor.mode = cap_control_map[control_mode_ints[0]] + elif len(control_mode_ints) == 1: + api_capacitor.mode = cap_control_map[control_mode_ints[0]] + + if len(off_values) > 1: + if not all(x == off_values[0] for x in off_values[1:]): + logger.warn(f"Not all of the off values for capacitor in section {sectionID} are the same. Using the highest value.") + api_capacitor.high = max(off_values) + elif len(off_values) == 1: + api_capacitor.high = off_values[0] self._capacitors.append(api_capacitor) if not sectionID in self.section_duplicates: From 775c33d4a68a795109f87c2a2868a846c7cf5e50 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 30 Sep 2022 11:24:22 -0600 Subject: [PATCH 70/83] ditto writer fix capcontrol definitions order of terms were incorrect and was not parsing all values that are available (also using vmax/min without off/on settings) --- ditto/writers/opendss/write.py | 124 ++++++++++++++++++++++----------- 1 file changed, 83 insertions(+), 41 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 8e769605..ecc8f2fd 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -1262,6 +1262,7 @@ def write_PVs(self, model): if hasattr(i, "phases") and i.phases is not None: n_phases = len(i.phases) txt += f" phases={n_phases}" + # connecting element if ( hasattr(i, "connecting_element") @@ -1274,7 +1275,6 @@ def write_PVs(self, model): for phase in i.phases: txt += "." + str(self.phase_mapping(phase.default_value)) - # Phases # nominal voltage if hasattr(i, "nominal_voltage") and i.nominal_voltage is not None: if i.nominal_voltage < 300: @@ -2583,63 +2583,105 @@ def write_capacitors(self, model): total_var *= 10 ** -3 # OpenDSS in Kvar txt += " Kvar={kvar}".format(kvar=total_var) - # We create a CapControl if we have valid input - # values that indicate that we should - create_capcontrol = False - if (hasattr(i, "name") and i.name is not None) and ( - (hasattr(i, "delay") and i.delay is not None) - or (hasattr(i, "mode") and i.mode is not None) - or (hasattr(i, "low") and i.low is not None) - or (hasattr(i, "high") and i.high is not None) - or (hasattr(i, "pt_ratio") and i.pt_ratio is not None) - or (hasattr(i, "ct_ratio") and i.ct_ratio is not None) - or (hasattr(i, "pt_phase") and i.pt_phase is not None) + """ CapControl Object + openDSS (DiTTo) values in order (some documentation copied here from June 2021 manual): + - Element (Capacitor.measuring_element) REQUIRED + - Capacitor (Capacitor.name) REQUIRED + - Type (Capacitor.mode) {Current | voltage | kvar | PF | time } REQUIRED Control type + - CTPhase + - Number of the phase being monitored for CURRENT control or one of {AVG | MAX | MIN} for all phases. Default=1. If delta or L-L connection, enter the first or the two phases being monitored [1-2, 2-3, 3-1]. Must be less than the number of phases. Does not apply to kvar control which uses all phases by default. + - CTratio + - Ratio of the CT from line amps to control ampere setting for current and kvar control type + - DeadTime + - time after capacitor is turned OFF before it can be turned back ON. default 300 seconds + - Delay (Capacitor.delay) + - time delay, in seconds, from when the control is armed before it sends out the switching command to turn ON. default 15 seconds + - DelayOFF (Capacitor.delay) + - default 15 seconds + - EventLog + - {Yes/True* | No/False} Default is YES for CapControl. Log control actions to Eventlog. + - NOTE event logging can significantly slow down solve times (esp. for time series) + - OFFsetting (Capacitor.low) + - Value at which the control arms to switch the capacitor OFF. + - ONsetting (Capacitor.high) + - Value at which the control arms to switch the capacitor ON (or ratchet up a step). Type of Control: + - Current: Line Amps / CTratio + - Voltage: Line-Neutral (or Line-Line for delta) Volts / PTratio + - kvar: Total kvar, all phases (3-phase for pos seq model). This is directional. + PF: Power Factor, Total power in monitored terminal. Negative for Leading. + Time: Hrs from Midnight as a floating point number (decimal). 7:30am would be entered as 7.5. + - PTPhase (Capacitor.pt_phase) + - Number of the phase being monitored for VOLTAGE control or one of {AVG | MAX | MIN} for all phases. Default=1. If delta or L-L connection, enter the first or the two phases being monitored [1-2, 2-3, 3-1]. Must be less than the number of phases. Does not apply to kvar control which uses all phases by default. + - PTratio (Capacitor.pt_ratio) + - Ratio of the PT that converts the monitored voltage to the control voltage. Default is 60. If the capacitor is Wye, the 1st phase line-to-neutral voltage is monitored. Else, the line-to-line voltage (1st - 2nd phase) is monitored. + - terminal + - Number of the terminal of the circuit element to which the CapControl is connected. 1 or 2, typically. Default is 1. + - VBus + - Name of bus to use for voltage override function. Default is bus at monitored terminal. Sometimes it is useful to monitor a bus in another location to emulate various DMS control algorithms. + - Vmax + - Maximum voltage, in volts. If the voltage across the capacitor divided by the PTRATIO is greater than this voltage, the capacitor will switch OFF regardless of other control settings. Default is 126 (goes with a PT ratio of 60 for 12.47 kV system). + - Vmin + - Minimum voltage, in volts. If the voltage across the capacitor divided by the PTRATIO is less than this voltage, the capacitor will switch ON regardless of other control settings. Default is 115 (goes with a PT ratio of 60 for 12.47 kV system). + - VoltOverride + - {Yes | No} Default is No. Switch to indicate whether VOLTAGE OVERRIDE is to be considered. Vmax and Vmin must be set to reasonable values if this property is Yes. + """ + if ( + i.name is not None and + i.measuring_element is not None and + i.mode is not None and + i.low is not None and + i.high is not None ): - create_capcontrol = True + txt += f"\n\nNew CapControl.{i.name} Element=Line.{i.measuring_element} Capacitor={i.name}" - # Create CapControl - if create_capcontrol: - txt += "\n\nNew CapControl.{name} Capacitor={name}".format( - name=i.name - ) + # mode (CONTROL) + txt += f" Type={self.mode_mapping(i.mode)}" - # Element (CONTROL) - if ( - hasattr(i, "measuring_element") - and i.measuring_element is not None - ): - txt += " Element=Line.{elt} Terminal=1".format( - elt=i.measuring_element - ) + # CTPhase not in Capacitor model + + # CTRatio (CONTROL) + if hasattr(i, "ct_ratio") and i.ct_ratio is not None: + txt += " Ctratio={CT}".format(CT=i.ct_ratio) + + # DeadTime not in Capacitor model # Delay (CONTROL) if hasattr(i, "delay") and i.delay is not None: txt += " delay={d}".format(d=i.delay) - # mode (CONTROL) - if hasattr(i, "mode") and i.mode is not None: - txt += " Type={m}".format(m=self.mode_mapping(i.mode)) + # DelayOFF not in Capacitor model + # EventLog not in Capacitor model - # Low (CONTROL) - if hasattr(i, "low") and i.low is not None: - txt += " Vmin={vmin}".format(vmin=i.low) + # OFFsetting + if i.high is not None: + txt += f" OFFsetting={i.high}" - # high (CONTROL) - if hasattr(i, "high") and i.high is not None: - txt += " Vmax={vmax}".format(vmax=i.high) + # ONsetting + if i.low is not None: + txt += f" ONsetting={i.low}" + + # Pt phase (CONTROL) + if hasattr(i, "pt_phase") and i.pt_phase is not None: + txt += " PTPhase={PT}".format(PT=self.phase_mapping(i.pt_phase)) # Pt ratio (CONTROL) if hasattr(i, "pt_ratio") and i.pt_ratio is not None: txt += " Ptratio={PT}".format(PT=i.pt_ratio) - # Ct ratio (CONTROL) - if hasattr(i, "ct_ratio") and i.ct_ratio is not None: - txt += " Ctratio={CT}".format(CT=i.ct_ratio) + # terminal not in Capacitor model + # VBus not in Capacitor model - # Pt phase (CONTROL) - if hasattr(i, "pt_phase") and i.pt_phase is not None: - txt += " PTPhase={PT}".format(PT=self.phase_mapping(i.pt_phase)) + # vmax override (CONTROL) + if i.vmax is not None: + txt += f" Vmax={i.vmax}" + + # vmin override (CONTROL) + if i.vmin is not None: + txt += f" Vmin={i.vmin}" + if i.vmax is not None or i.vmin is not None: + txt += " VoltOverride=Yes" + txt += "\n\n" feeder_text_map[sub_fdr_key] = txt From 43720ebc5ba8fbb91eed8b9f09870259b5b5d77f Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 30 Sep 2022 13:08:36 -0600 Subject: [PATCH 71/83] remove 1.2 scaling of regulator bandwidth in opendss writer --- ditto/writers/opendss/write.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index ecc8f2fd..115521b9 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2298,7 +2298,7 @@ def write_regulators(self, model): # bandwidth if hasattr(i, "bandwidth") and i.bandwidth is not None: txt += " band={b}".format( - b=i.bandwidth * 1.2 + b=i.bandwidth ) # The bandwidth is operated at 120 V # Delay From cdfc885a0646c42c65ff32839b7ab50a86cc2864 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Mon, 3 Oct 2022 09:34:55 -0600 Subject: [PATCH 72/83] rm some unnecessary try/except from cyme reader --- ditto/readers/cyme/read.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 96f36b05..2c95cb4a 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -4848,17 +4848,8 @@ def parse_transformers(self, model): # if "isltc" in transformer_data and transformer_data["isltc"]: # Instanciate a Regulator DiTTo object - try: - api_regulator = Regulator(model) - except: - raise ValueError( - "Unable to instanciate Regulator DiTTo object." - ) - - try: - api_regulator.name = "Reg_" + settings["sectionid"] - except: - pass + api_regulator = Regulator(model) + api_regulator.name = "Reg_" + settings["sectionid"] api_regulator.feeder_name = self.section_feeder_mapping[sectionID] try: From be3de3b0194ae906472d1e7f872ee51e4d88d596 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Mon, 3 Oct 2022 12:39:18 -0600 Subject: [PATCH 73/83] dss writer set default XHL=1 for regulator trfx --- ditto/writers/opendss/write.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 115521b9..a7754dae 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -2248,11 +2248,11 @@ def write_regulators(self, model): ) except: logger.warning( - "Could not extract XHL from regulator {name}".format( + "Could not extract XHL from regulator {name}. Setting XHL=1".format( name=i.name ) ) - pass + transfo_creation_string += " XHL=1" # XLT: try: # probably an index error b/c cyme reader only has api_transformer.reactances = [float(xhl)] if isinstance(i.reactances[1], (int, float)): From 90bf1cf0b14cb0849022a626366a5513ee0303aa Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Mon, 3 Oct 2022 12:50:06 -0600 Subject: [PATCH 74/83] cyme reader fix single phase trfx kV values to LN was setting kV to LL values --- ditto/readers/cyme/utils.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ditto/readers/cyme/utils.py b/ditto/readers/cyme/utils.py index d9994ff8..725d154b 100644 --- a/ditto/readers/cyme/utils.py +++ b/ditto/readers/cyme/utils.py @@ -181,13 +181,16 @@ def add_two_windings( # Set the nominal voltage try: + scaler = 1 + if len(phases) == 1: + scaler = 1/math.sqrt(3) if w == 0: api_winding.nominal_voltage = ( - float(trfx_data["kvllprim"]) * 10 ** 3 + float(trfx_data["kvllprim"]) * 10 ** 3 * scaler ) # DiTTo in volt if w == 1: api_winding.nominal_voltage = ( - float(trfx_data["kvllsec"]) * 10 ** 3 + float(trfx_data["kvllsec"]) * 10 ** 3 * scaler ) # DiTTo in volt except: pass From 1d18d48584a14f6ca50fac52c89e03e1d5b25a23 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 4 Oct 2022 18:32:54 -0600 Subject: [PATCH 75/83] add cyme reader test for load.nominal_voltage [FAILS] add same test to opendss reader, which passes --- tests/test_reader.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_reader.py b/tests/test_reader.py index 32bddf24..424fe468 100644 --- a/tests/test_reader.py +++ b/tests/test_reader.py @@ -9,6 +9,7 @@ import os import pytest as pt from ditto.store import Store +from ditto.models.load import Load current_directory = os.path.realpath(os.path.dirname(__file__)) @@ -44,6 +45,28 @@ def test_cyme_reader(): # TODO: Log properly print(">Cyme model {model} parsed.\n".format(model=model)) + if model == "ieee_13node": + """ + test load base voltage values + the load nominal voltages are set in the system_structure_modifier.set_nominal_voltages_recur + (which is only used in the cyme reader) + the test values are what is expected from the IEEE13 openDSS model + issues: + - perhaps the default load.connection_type should by "Y"? + - all connection_types are currently set to None in this test + - currently the nominal voltages are set to the nominal_voltage of the upline transformer, + but the upline transformer nominal_voltage is (usu.) the phase to phase voltage + which is not the correct voltage to use for single phase load connected between + phase and ground. + - can we use Load and PhaseLoad attributes to correctly define the load.nominal_voltage? + """ + for load in m.iter_models(Load): + nphases = len(load.phase_loads) + if load.connection_type in ("Y", None) and nphases == 1: + assert round(load.nominal_voltage, 3) in (2400, 277) + if load.connection_type == "D": + assert round(load.nominal_voltage, 3) == 4160 + # @pt.mark.skip("Segfault occurs") def test_opendss_reader(): @@ -69,6 +92,17 @@ def test_opendss_reader(): # TODO: Log properly print(">OpenDSS model {model} parsed.\n".format(model=model)) + if model == "ieee_13node": + """ + test load base voltage values match expected values + """ + for load in m.iter_models(Load): + nphases = len(load.phase_loads) + if load.connection_type in ("Y", None) and nphases == 1: + assert round(load.nominal_voltage, 3) in (2400, 277) + if load.connection_type == "D": + assert round(load.nominal_voltage, 3) == 4160 + def test_dew_reader(): """ From 6a7d233bfc04086405382b4ff53f971c729bfb4a Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 8 Nov 2022 12:35:13 -0700 Subject: [PATCH 76/83] add TODO to cyme reader for voltage reg --- ditto/readers/cyme/read.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ditto/readers/cyme/read.py b/ditto/readers/cyme/read.py index 2c95cb4a..40e29cf5 100644 --- a/ditto/readers/cyme/read.py +++ b/ditto/readers/cyme/read.py @@ -5022,6 +5022,7 @@ def parse_regulators(self, model): "phaseon": 9, "ct": 12, "pt": 13, + "settingoption": 14, # TODO map, "T" = terminal, i.e. control voltage at regulator terminal (secondary winding in OpenDSS, use bus= in RegControl) "vseta": 16, "vsetb": 17, "vsetc": 18, From f0c6c44004878c2e8ce9a2b2c75a27bb7a977173 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 8 Nov 2022 12:35:47 -0700 Subject: [PATCH 77/83] add note to set_nominal_voltages_recur --- ditto/modify/system_structure.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ditto/modify/system_structure.py b/ditto/modify/system_structure.py index e0a009ab..86c7d894 100644 --- a/ditto/modify/system_structure.py +++ b/ditto/modify/system_structure.py @@ -213,6 +213,7 @@ def set_feeder_headnodes(self): def set_nominal_voltages_recur(self, *args): """This function sets the nominal voltage of the elements in the network. + This function is only used in the CYME Reader. This is currently the fastest implementation available as of early January 2018. It uses a kind os message passing algorithm. A node passes its nominal voltage to its succesors but modify this value if there is a voltage transformation. From 221560f19d57ce39de120fdfc5560dcb57c6d3a0 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Tue, 8 Nov 2022 13:45:16 -0700 Subject: [PATCH 78/83] rm debug comments --- ditto/writers/opendss/write.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index a7754dae..70d40d76 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -3839,7 +3839,7 @@ def write_master_file(self, model): with open( os.path.join(self.output_path, self.output_filenames["master"]), "w" ) as fp: - fp.write("Clear\n\nNew Circuit.Full_Network ") # not reached for PPL model? it is but gets overwritten? + fp.write("Clear\n\nNew Circuit.Full_Network ") for obj in model.models: if ( isinstance(obj, PowerSource) and obj.is_sourcebus == 1 @@ -4044,7 +4044,7 @@ def write_master_file(self, model): ] ) else: - _baseKV_list_ = [] # getting here? + _baseKV_list_ = [] _baseKV_list_ = sorted(_baseKV_list_) fp.write("\nSet Voltagebases={}\n".format(_baseKV_list_)) From a98717d47c9d1457db53dc2aa553aec85dcbaf48 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Thu, 17 Nov 2022 13:49:12 -0700 Subject: [PATCH 79/83] rm obsolete base_voltage in opendss writer --- ditto/writers/opendss/write.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ditto/writers/opendss/write.py b/ditto/writers/opendss/write.py index 70d40d76..c225c826 100644 --- a/ditto/writers/opendss/write.py +++ b/ditto/writers/opendss/write.py @@ -610,14 +610,6 @@ def write_transformers(self, model): # Voltage type (Not mapped) - # volage basis - if ( - hasattr(winding, "base_voltage") - and winding.base_voltage is not None - and winding.base_voltage > 0 - ): - self._baseKV_.add(winding.base_voltage * 10 ** -3) - # Nominal voltage if ( hasattr(winding, "nominal_voltage") From 824c91912dcf9d57f0f3f58d419907b1cd61231b Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Fri, 18 Nov 2022 14:04:30 -0700 Subject: [PATCH 80/83] fix return type in cyme reader utils --- ditto/readers/cyme/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ditto/readers/cyme/utils.py b/ditto/readers/cyme/utils.py index 725d154b..87175b34 100644 --- a/ditto/readers/cyme/utils.py +++ b/ditto/readers/cyme/utils.py @@ -8,7 +8,7 @@ from six import string_types -def get_transformer_xhl_Rpercent(trfx: dict) -> tuple[float, float]: +def get_transformer_xhl_Rpercent(trfx: dict): # Resistance # Note: Imported from Julietta's code Z1 = float(trfx["z1"]) # TODO default values From 0a8199c5c5301027d008a737538215f15df9f68f Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Mon, 21 Nov 2022 10:09:29 -0700 Subject: [PATCH 81/83] fix some setup.py packages (and black auto-format) --- setup.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index d2831b03..bfbec2af 100644 --- a/setup.py +++ b/setup.py @@ -36,19 +36,26 @@ "ghp-import~=2.1.0", ] -numpy_dependency = "numpy~=1.20.1" +numpy_dependency = "numpy" -extras_requires = ["lxml~=4.9.1", "pandas~=1.3.5", "scipy~=1.7.3", numpy_dependency, "XlsxWriter~=3.0.3"] +extras_requires = [ + "lxml~=4.9.1", + "pandas", + "scipy~=1.7.3", + numpy_dependency, + "XlsxWriter~=3.0.3", +] -opendss_requires = ["OpenDSSDirect.py~=0.7.0", "pandas~=1.3.5", numpy_dependency] +opendss_requires = ["OpenDSSDirect.py~=0.7.0", "pandas", numpy_dependency] dew_requires = [numpy_dependency, "xlrd~=2.0.1"] gridlabd_requires = ["croniter~=1.3.7", numpy_dependency] cyme_requires = [numpy_dependency] -ephasor_requires = [numpy_dependency, "pandas~=1.3.5"] +ephasor_requires = [numpy_dependency, "pandas"] synergi_requires = [ numpy_dependency, "pandas_access~=0.0.1", ] # Need pandas_access to convert the MDB tables to Pandas DataFrame +windmil_requires = ["bokeh", "xmltodict"] class PostDevelopCommand(develop): @@ -109,7 +116,16 @@ def run(self): "Programming Language :: Python :: 3.6", ], test_suite="tests", - install_requires=["click~=8.0.4", "future~=0.18.2", "networkx~=2.5.1", "six~=1.16.0", "traitlets~=5.1.1", "json_tricks~=3.16.1", "pandas~=1.3.5", numpy_dependency], + install_requires=[ + "click~=8.0.4", + "future~=0.18.2", + "networkx~=2.5.1", + "six~=1.16.0", + "traitlets~=5.1.1", + "json_tricks~=3.16.1", + "pandas", + numpy_dependency, + ], extras_require={ "all": extras_requires + opendss_requires @@ -117,7 +133,8 @@ def run(self): + gridlabd_requires + ephasor_requires + cyme_requires - + synergi_requires, + + synergi_requires + + windmil_requires, "extras": extras_requires, "cyme": cyme_requires, "dew": dew_requires, @@ -125,6 +142,7 @@ def run(self): "synergi": synergi_requires, "gridlabd": gridlabd_requires, "opendss": opendss_requires, + "windmil": windmil_requires, "test": test_requires, "dev": test_requires + ["pypandoc", "black", "pre-commit"], }, From 1fc9a4c27b6661ce355c024cc9cb588bd92b234d Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Mon, 5 Dec 2022 12:19:15 -0700 Subject: [PATCH 82/83] Update .pre-commit-config.yaml --- .pre-commit-config.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dcda03b1..9d6c0873 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,4 +14,3 @@ repos: entry: python -m black types: [python] args: [--line-length=88, --safe] - python_version: python3.6 From 6cdd6484a3aaacab875b3aa43cc050b42e0f5a74 Mon Sep 17 00:00:00 2001 From: Nick Laws Date: Mon, 5 Dec 2022 12:23:54 -0700 Subject: [PATCH 83/83] change ~= to >= for python dependencies --- setup.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/setup.py b/setup.py index bfbec2af..f2d19a13 100644 --- a/setup.py +++ b/setup.py @@ -27,33 +27,33 @@ version = version.splitlines()[1].split()[2].strip('"').strip("'") test_requires = [ - "backports.tempfile~=1.0", - "pytest~=7.0.1", - "pytest-cov~=4.0.0", - "sphinx-rtd-theme~=1.1.1", - "nbsphinx~=0.8.10", - "sphinxcontrib-napoleon~=0.7", - "ghp-import~=2.1.0", + "backports.tempfile>=1.0", + "pytest>=7.0.1", + "pytest-cov>=4.0.0", + "sphinx-rtd-theme>=1.1.1", + "nbsphinx>=0.8.10", + "sphinxcontrib-napoleon>=0.7", + "ghp-import>=2.1.0", ] -numpy_dependency = "numpy" +numpy_dependency = "numpy>=1.20.0" extras_requires = [ - "lxml~=4.9.1", + "lxml>=4.9.1", "pandas", - "scipy~=1.7.3", + "scipy>=1.7.3", numpy_dependency, - "XlsxWriter~=3.0.3", + "XlsxWriter>=3.0.3", ] -opendss_requires = ["OpenDSSDirect.py~=0.7.0", "pandas", numpy_dependency] -dew_requires = [numpy_dependency, "xlrd~=2.0.1"] -gridlabd_requires = ["croniter~=1.3.7", numpy_dependency] +opendss_requires = ["OpenDSSDirect.py>=0.7.0", "pandas", numpy_dependency] +dew_requires = [numpy_dependency, "xlrd>=2.0.1"] +gridlabd_requires = ["croniter>=1.3.7", numpy_dependency] cyme_requires = [numpy_dependency] ephasor_requires = [numpy_dependency, "pandas"] synergi_requires = [ numpy_dependency, - "pandas_access~=0.0.1", + "pandas_access>=0.0.1", ] # Need pandas_access to convert the MDB tables to Pandas DataFrame windmil_requires = ["bokeh", "xmltodict"] @@ -117,12 +117,12 @@ def run(self): ], test_suite="tests", install_requires=[ - "click~=8.0.4", - "future~=0.18.2", - "networkx~=2.5.1", - "six~=1.16.0", - "traitlets~=5.1.1", - "json_tricks~=3.16.1", + "click>=8.0.4", + "future>=0.18.2", + "networkx>=2.5.1", + "six>=1.16.0", + "traitlets>=5.1.1", + "json_tricks>=3.16.1", "pandas", numpy_dependency, ],