' + package + ' Version ' + version + ' by SLAC GISMo
'
+ + ' '
+ + ' '
+ + ' '
+ + ''
+ + '
';
+var year = (new Date()).getFullYear();
+if ( year > 2019 )
+{
+ year = "2019-" + year;
+}
+var copyright = 'Copyright (C) ' + year + ', Regents of the Leland Stanford Junior University';
+var query = new URLSearchParams(window.location.search);
+
+function set_default(name, value)
+{
+ // console.info("set_default('" + name + "',value='" + value + "') --> cookie = '" + document.cookie + "'");
+ if ( document.cookie.indexOf("BLOCKED=") >= 0 )
+ {
+ return;
+ }
+ if ( value == null )
+ {
+ document.cookie = name + "=;";
+ }
+ else
+ {
+ document.cookie = name + "=" + value + ";path=/; expires=36524";
+ }
+}
+
+function get_default(name,deflt,asnull)
+{
+ // console.info("get_default('" + name + "',value='" + deflt + "')...");
+ var value = query.get(name);
+ var type = 'query';
+ if ( value == null )
+ {
+ var ca = document.cookie.split(';');
+ // console.info(" cookie = " + ca);
+ for ( var i = 0 ; i < ca.length ; i++ )
+ {
+ var c = ca[i].trim();
+ if ( c.indexOf(name+'=') == 0 )
+ {
+ value = c.substring(c.indexOf('=')+1, c.length);
+ type = 'cookie';
+ }
+ }
+ if ( value == null )
+ {
+ value = deflt;
+ type = 'default';
+ }
+ }
+ if ( asnull != null && value === asnull )
+ {
+ value = null;
+ type += ' asnull'
+ }
+ // console.info(" " + type + " --> " + name + " = '" + deflt + "'");
+ return value;
+}
+
+var host = get_default('host',default_host,null);
+var owner = get_default('owner',default_owner,null);
+var project = get_default('project',default_project,null);
+var branch = get_default('branch',default_branch,null);
+var doc = get_default('doc',default_doc,null);
+var folder = get_default('folder',"",null);
+var github_authorization_token = get_default('token',null,'');
+var search_keyword = get_default('search',"",null);
+
+function save_defaults()
+{
+ set_default("host",host)
+ set_default("owner",owner);
+ set_default("project",project);
+ set_default("branch",branch);
+ set_default("folder",folder);
+ set_default("doc",doc);
+}
+
+function run_query(query)
+{
+ // console.info("Running query '" + query + "'...")
+ var r = new XMLHttpRequest();
+ r.open('GET',query,false);
+ if ( github_authorization_token != null )
+ {
+ // console.info("API authorization token: ",github_authorization_token);
+ r.setRequestHeader("Authorization","token " + github_authorization_token);
+ }
+ r.send(null);
+ // console.info(" status -> " + r.status);
+ return r;
+}
+function reload_frameset()
+{
+ top.frames.document.location.href=top.frames.document.location.href;
+}
+
+function cookie_view()
+{
+ if ( document.cookie == "" )
+ alert("No data stored");
+ else if ( document.cookie.indexOf("BLOCKED=") >= 0 )
+ alert("The cookie is blocked. Use 'Clear' to unblock it.")
+ else
+ alert("Current data:\n\n" + document.cookie);
+}
+
+function cookie_clear(no_confirm)
+{
+ if ( no_confirm || confirm("Clearing the cookie will delete all current data and unblock the cookie.\n\nIs this ok? ") )
+ {
+ var cookies = document.cookie.split(";");
+ for (var i = 0; i < cookies.length; i++) {
+ var cookie = cookies[i];
+ var eqPos = cookie.indexOf("=");
+ var name = eqPos > -1 ? cookie.substr(0, eqPos) : cookie;
+ document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
+ }
+ }
+}
+
+function cookie_block()
+{
+ if ( confirm("Blocking the cookie will delete all current data.\n\nIs this ok?") )
+ {
+ cookie_clear(true);
+ document.cookie = "BLOCKED";
+ }
+}
diff --git a/cloud/websites/docs.gridlabd.us/_contents.html b/cloud/websites/docs.gridlabd.us/_contents.html
new file mode 100644
index 000000000..ae102045b
--- /dev/null
+++ b/cloud/websites/docs.gridlabd.us/_contents.html
@@ -0,0 +1,259 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Table of Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cloud/websites/docs.gridlabd.us/_defaults.js b/cloud/websites/docs.gridlabd.us/_defaults.js
new file mode 100644
index 000000000..6a50fe8a1
--- /dev/null
+++ b/cloud/websites/docs.gridlabd.us/_defaults.js
@@ -0,0 +1,20 @@
+// defaults.js
+// Copyright (C) 2019 Regents of the Leland Stanford Junior University
+//
+// This file is used to specify the defaults for a project. It should be
+// updated to match the project specifications when the docs-browser is
+// set up for a new project.
+//
+
+// configuration of repo docs
+default_host = "github.com";
+default_owner = "arras-energy";
+default_project = "gridlabd";
+default_branch = "master";
+default_doc = "/README.md";
+default_gethost = "raw.githubusercontent.com";
+
+// configuration of display
+top_panel_height = 40; // how high the topbar panel is (in pixels)
+left_panel_width = 320; // how wide the TOC panel is (in pixels)
+save_default_days = 30; // how long a cookie will be retained (in days)
diff --git a/cloud/websites/docs.gridlabd.us/_page.html b/cloud/websites/docs.gridlabd.us/_page.html
new file mode 100644
index 000000000..acafe731b
--- /dev/null
+++ b/cloud/websites/docs.gridlabd.us/_page.html
@@ -0,0 +1,332 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Host:
+
+
+
+
+
+ User/Org:
+
+
+
+
+
+ Project:
+
+
+
+
+
+
+
+ Branch:
+
+
+
+
+
+
+
+ Section:
+
+
+
+
+
+
+
+ Document:
+
+
+
+
+
+
+
+ Search:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/cloud/websites/docs.gridlabd.us/_theme.css b/cloud/websites/docs.gridlabd.us/_theme.css
new file mode 100644
index 000000000..3abe414f9
--- /dev/null
+++ b/cloud/websites/docs.gridlabd.us/_theme.css
@@ -0,0 +1,63 @@
+BODY.TOPBAR {
+ color: white;
+ background-color: #383838;
+ padding: 0px;
+}
+BODY.CONTENTS {
+ background-color: #f4f4f4;
+}
+BODY.PAGE {
+ background-color: #f4f4f4;
+}
+A:LINK,A:VISITED {
+ color: blue;
+ text-decoration: none;
+}
+A:ACTIVE {
+ color: blue;
+ text-decoration: underline;
+}
+A:BROKEN {
+ color: red;
+ text-decoration: none;
+}
+
+H1 {
+ border-bottom: 1px solid lightgrey;
+}
+CODE {
+ background-color: #f8f8f8;
+}
+
+PRE {
+ padding: 5px;
+
+ background-color: #f8f8f8;
+}
+DIV.HEADER {
+ font-size: small;
+}
+DIV.FOOTER {
+ font-size: small;
+}
+
+DIV.TOPBLOCK {
+ border: 1px solid lightgrey;
+}
+DIV.PAGE {
+ background-color: white;
+ border: 1px solid lightgrey;
+ padding: 20px;
+ border-radius: 10px;
+}
+DIV.CONTENTS {
+ background-color: white;
+ border: 1px solid grey;
+ padding: 20px;
+ border-radius: 10px;
+}
+
+INPUT.HEADER {
+ font-size: large;
+ font-weight: bold;
+}
\ No newline at end of file
diff --git a/cloud/websites/docs.gridlabd.us/_topbar.html b/cloud/websites/docs.gridlabd.us/_topbar.html
new file mode 100644
index 000000000..551f14b4d
--- /dev/null
+++ b/cloud/websites/docs.gridlabd.us/_topbar.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/cloud/websites/docs.gridlabd.us/index.html b/cloud/websites/docs.gridlabd.us/index.html
index b6157a329..9ed34b289 100644
--- a/cloud/websites/docs.gridlabd.us/index.html
+++ b/cloud/websites/docs.gridlabd.us/index.html
@@ -1,8 +1,15 @@
+
-
-
-
-
-Redirecting to http://docs.arras-energy.org/index.html?owner=arras-energy&project=gridlabd...
-
+
+
+
\ No newline at end of file
diff --git a/cloud/websites/docs.gridlabd.us/logo.png b/cloud/websites/docs.gridlabd.us/logo.png
new file mode 100644
index 000000000..0687f8088
Binary files /dev/null and b/cloud/websites/docs.gridlabd.us/logo.png differ
diff --git a/converters/mdb-cyme2glm.py b/converters/mdb-cyme2glm.py
index c63c22cb4..7f1e63233 100644
--- a/converters/mdb-cyme2glm.py
+++ b/converters/mdb-cyme2glm.py
@@ -32,7 +32,8 @@
import hashlib
import csv
import pprint
-pp = pprint.PrettyPrinter(indent=4,compact=True)
+
+pp = pprint.PrettyPrinter(indent=4, compact=True)
import traceback
from copy import copy
import numpy as np
@@ -42,7 +43,7 @@
#
app_command = os.path.abspath(sys.argv[0])
app_workdir = os.getenv("PWD")
-app_path = "/"+"/".join(app_command.split("/")[0:-1])
+app_path = "/" + "/".join(app_command.split("/")[0:-1])
# #
# # Git information
@@ -61,242 +62,326 @@
# Load user configuration
#
-config = pd.DataFrame({
- "GLM_NETWORK_PREFIX" : [""],
- "GLM_NETWORK_MATCHES" : [".*"],
- "GLM_NOMINAL_VOLTAGE" : [""],
- "GLM_INCLUDE" : [""],
- "GLM_DEFINE" : [""],
- "GLM_MODIFY" : [""],
- "GLM_ASSUMPTIONS" : ["include"],
- "GLM_VOLTAGE_FIX" : ["True"],
- "GLM_PHASE_FIX" : ["True"],
- "GLM_DISTRIBUTED_LOAD_CONFIG" : ["to"],
- "GLM_OUTPUT" : "/dev/stdout",
- "EXTRACT" : ["non-empty"],
- "ERROR_OUTPUT" : "/dev/stderr",
- "WARNING_OUTPUT" : "/dev/stderr",
- "WARNING" : ["True"],
- "DEBUG" : ["False"],
- "QUIET" : ["False"],
- "VERBOSE" : ["False"],
- }).transpose().set_axis(["value"],axis=1)
-config.index.name = "name"
-
-cyme_phase_name = {0:"ABCN", 1:"A", 2:"B", 3:"C", 4:"AB", 5:"AC", 6:"BC", 7:"ABC"} # CYME phase number -> phase names
-glm_phase_code = {"A":1, "B":2, "C":4, "AB":3, "AC":5, "BC":6, "ABC":7} # GLM phase name -> phase number
-glm_phase_name = {0:"ABCN", 1:"A",2:"B",3:"AB",4:"C",5:"AC",6:"BC",7:"ABC"} # GLM phase number -> phase name
-cyme_phase_name_delta = {1:"AB", 2:"BC", 3:"AC", 7:"ABC"} # CYME phase number -> phase names for delta connection
+config = (
+ pd.DataFrame(
+ {
+ "GLM_NETWORK_PREFIX": [""],
+ "GLM_NETWORK_MATCHES": [".*"],
+ "GLM_NOMINAL_VOLTAGE": [""],
+ "GLM_INCLUDE": [""],
+ "GLM_DEFINE": [""],
+ "GLM_MODIFY": [""],
+ "GLM_ASSUMPTIONS": ["include"],
+ "GLM_VOLTAGE_FIX": ["True"],
+ "GLM_PHASE_FIX": ["True"],
+ "GLM_OUTPUT": "/dev/stdout",
+ "GLM_INPUT_LINE_LENGTH_UNIT": ["m"],
+ "ERROR_OUTPUT": "/dev/stderr",
+ "WARNING_OUTPUT": "/dev/stderr",
+ "WARNING": ["True"],
+ "DEBUG": ["False"],
+ "QUIET": ["False"],
+ "VERBOSE": ["False"],
+ }
+ )
+ .transpose()
+ .set_axis(["value"], axis=1)
+)
+config.index.name = "name"
+
+cyme_phase_name = {
+ 0: "ABCN",
+ 1: "A",
+ 2: "B",
+ 3: "C",
+ 4: "AB",
+ 5: "AC",
+ 6: "BC",
+ 7: "ABC",
+} # CYME phase number -> phase names
+glm_phase_code = {
+ "A": 1,
+ "B": 2,
+ "C": 4,
+ "AB": 3,
+ "AC": 5,
+ "BC": 6,
+ "ABC": 7,
+} # GLM phase name -> phase number
+glm_phase_name = {
+ 0: "ABCN",
+ 1: "A",
+ 2: "B",
+ 3: "AB",
+ 4: "C",
+ 5: "AC",
+ 6: "BC",
+ 7: "ABC",
+} # GLM phase number -> phase name
+cyme_phase_name_delta = {
+ 1: "AB",
+ 2: "BC",
+ 3: "AC",
+ 7: "ABC",
+} # CYME phase number -> phase names for delta connection
#
# Device type mapping
#
cyme_devices = {
- 1 : "UndergroundLine",
- 2 : "OverheadLine",
- 3 : "OverheadByPhase",
- 4 : "Regulator",
- 5 : "Transformer",
- 6 : "Not used",
- 7 : "Not used",
- 8 : "Breaker",
- 9 : "LVCB",
- 10 : "Recloser",
- 11 : "Not used",
- 12 : "Sectionalizer",
- 13 : "Switch",
- 14 : "Fuse",
- 15 : "SeriesCapacitor",
- 16 : "SeriesReactor",
- 17 : "ShuntCapacitor",
- 18 : "ShuntReactor",
- 19 : "Not used",
- 20 : "SpotLoad",
- 21 : "DistributedLoad",
- 22 : "Miscellaneous",
- 23 : "OverheadLineUnbalanced",
- 24 : "ArcFurnace",
- 25 : "CTypeFilter",
- 26 : "DoubleTunedFilter",
- 27 : "HighPassFilter",
- 28 : "IdealConverter",
- 29 : "NonIdealConverter",
- 30 : "ShuntFrequencySource",
- 31 : "Not used",
- 32 : "SingleTunedFilter",
- 33 : "InductionMotor",
- 34 : "SynchronousMotor",
- 35 : "InductionGenerator",
- 36 : "SynchronousGenerator",
- 37 : "ElectronicConverterGenerator",
- 38 : "TransformerByPhase",
- 39 : "ThreeWindingTransformer",
- 40 : "NetworkEquivalent",
- 41 : "Wecs",
- 42 : "GroundingTransformer",
- 43 : "MicroTurbine",
- 44 : "Sofc",
- 45 : "Photovoltaic",
- 46 : "SeriesFrequencySource",
- 47 : "AutoTransformer",
- 48 : "ThreeWindingAutoTransformer",
- 80 : "battery"
+ 1: "UndergroundLine",
+ 2: "OverheadLine",
+ 3: "OverheadByPhase",
+ 4: "Regulator",
+ 5: "Transformer",
+ 6: "Not used",
+ 7: "Not used",
+ 8: "Breaker",
+ 9: "LVCB",
+ 10: "Recloser",
+ 11: "Not used",
+ 12: "Sectionalizer",
+ 13: "Switch",
+ 14: "Fuse",
+ 15: "SeriesCapacitor",
+ 16: "SeriesReactor",
+ 17: "ShuntCapacitor",
+ 18: "ShuntReactor",
+ 19: "Not used",
+ 20: "SpotLoad",
+ 21: "DistributedLoad",
+ 22: "Miscellaneous",
+ 23: "OverheadLineUnbalanced",
+ 24: "ArcFurnace",
+ 25: "CTypeFilter",
+ 26: "DoubleTunedFilter",
+ 27: "HighPassFilter",
+ 28: "IdealConverter",
+ 29: "NonIdealConverter",
+ 30: "ShuntFrequencySource",
+ 31: "Not used",
+ 32: "SingleTunedFilter",
+ 33: "InductionMotor",
+ 34: "SynchronousMotor",
+ 35: "InductionGenerator",
+ 36: "SynchronousGenerator",
+ 37: "ElectronicConverterGenerator",
+ 38: "TransformerByPhase",
+ 39: "ThreeWindingTransformer",
+ 40: "NetworkEquivalent",
+ 41: "Wecs",
+ 42: "GroundingTransformer",
+ 43: "MicroTurbine",
+ 44: "Sofc",
+ 45: "Photovoltaic",
+ 46: "SeriesFrequencySource",
+ 47: "AutoTransformer",
+ 48: "ThreeWindingAutoTransformer",
+ 80: "battery",
}
glm_devices = {
- 1 : "underground_line",
- 2 : "overhead_line",
- 3 : "overhead_line",
- 4 : "regulator",
- 5 : "transformer",
- 8 : "breaker",
- 10 : "recloser",
- # 12 : "sectionalizer",
- 13 : "switch",
- 14 : "fuse",
- 17 : "capacitor",
- 20 : "load",
- 21 : "load",
- 23 : "overhead_line",
- 38 : "single_transformer",
- 45 : "Photovoltaic",
+ 1: "underground_line",
+ 2: "overhead_line",
+ 3: "overhead_line",
+ 4: "regulator",
+ 5: "transformer",
+ 8: "breaker",
+ 10: "recloser",
+ # 12 : "sectionalizer",
+ 13: "switch",
+ 14: "fuse",
+ 17: "capacitor",
+ 20: "load",
+ 21: "load",
+ 23: "overhead_line",
+ 38: "single_transformer",
+ 45: "Photovoltaic",
}
#
# CYME database access tools
#
+
# find records in a table (exact field match only)
-def table_find(table,**kwargs):
- result = table
- for key,value in kwargs.items():
- result = result[result[key]==value]
- return result
+def table_find(table, **kwargs):
+ result = table
+ for key, value in kwargs.items():
+ result = result[result[key] == value]
+ return result
+
# get the value in a table using a certain id or index
-def table_get(table,id,column=None,id_column=None):
- if id_column == None or id_column == '*':
- if column == None or column == "*":
- return table.loc[id]
- else:
- return table.loc[id][column]
- else:
- for index, row in table.iterrows():
- if row[id_column] == id:
- if column == None or column == "*":
- return table.loc[index]
- else:
- return table.loc[index][column]
- return None
-
-def load_cals(load_type,load_phase,connection,load_power1,load_power2,load_voltage,value_type=None):
- phase_number=int(load_phase)
- # load_voltage in kV
- if connection == 2:
- # delta connection
- vol_real = float(load_voltage)*cos((1-phase_number)*pi*2.0/3.0+pi/6.0)*1000.0
- vol_imag = float(load_voltage)*sin((1-phase_number)*pi*2.0/3.0+pi/6.0)*1000.0
- line_phase_gain = sqrt(3.0)
- if len(cyme_phase_name_delta[phase_number].replace('N','')) == 2:
- load_scale = 1
- elif len(cyme_phase_name_delta[phase_number].replace('N','')) == 3:
- load_scale = 3
- else:
- error(f'wrong load phase {load_phase} for delta connection', 10)
- else:
- # wye connecttion
- vol_real = float(load_voltage)*cos((1-phase_number)*pi*2.0/3.0)*1000.0
- vol_imag = float(load_voltage)*sin((1-phase_number)*pi*2.0/3.0)*1000.0
- line_phase_gain = 1
- load_scale = len(cyme_phase_name[phase_number].replace('N',''))
- if load_scale < 0 or load_scale > 3:
- error(f'wrong load phase {load_phase} for wye connection', 11)
- if value_type == 0:
- load_real = load_power1 * 1000.0
- load_imag = load_power2 * 1000.0
- elif value_type == 1:
- if load_power2 > 0:
- load_real = load_power1 * load_power2/100 * 1000.0
- load_imag = load_power1 * sqrt(1 - (load_power2/100)**2) * 1000.0
- else:
- load_real = -load_power1 * load_power2/100 * 1000.0
- load_imag = -load_power1 * sqrt(1 - (load_power2/100)**2) * 1000.0
- else:
- load_real = load_power1 * 1000
- if load_power2 > 0.0 or load_power2 < 0.0:
- load_imag = load_real/(load_power2/100.0)*sqrt(1-abs(load_power2/100)**2)
- vol_mag = float(load_voltage)*1000.0
- vol_complex = vol_real+vol_imag*(1j)
- if load_type == "Z":
- if (load_real*load_real + load_imag*load_imag) > 0:
- load_cals_results = vol_mag*line_phase_gain*vol_mag*line_phase_gain/(load_real+load_imag*(1j))/load_scale
- return load_cals_results
- else:
- return 0+0*(1j)
- elif load_type == "I":
- load_cals_results = (load_real+load_imag*(1j))/(vol_complex*line_phase_gain)/load_scale
- return load_cals_results
- else:
- # for constant power load, the imag part is negative
- load_cals_results = (load_real-load_imag*(1j))/load_scale
- return load_cals_results
-
-def capacitor_phase_cals(KVARA,KVARB,KVARC):
- return int(KVARA > 0) + 2*int(KVARB > 0) + 3*int(KVARC > 0) + int((KVARA*KVARB > 0) or (KVARA*KVARC > 0) or (KVARB*KVARC > 0))
+def table_get(table, id, column=None, id_column=None):
+ if id_column == None or id_column == "*":
+ if column == None or column == "*":
+ return table.loc[id]
+ else:
+ return table.loc[id][column]
+ else:
+ for index, row in table.iterrows():
+ if row[id_column] == id:
+ if column == None or column == "*":
+ return table.loc[index]
+ else:
+ return table.loc[index][column]
+ return None
+
+
+def load_cals(
+ load_type,
+ load_phase,
+ connection,
+ load_power1,
+ load_power2,
+ load_voltage,
+ value_type=None,
+):
+ phase_number = int(load_phase)
+ # load_voltage in kV
+ if connection == 2:
+ # delta connection
+ vol_real = (
+ float(load_voltage)
+ * cos((1 - phase_number) * pi * 2.0 / 3.0 + pi / 6.0)
+ * 1000.0
+ )
+ vol_imag = (
+ float(load_voltage)
+ * sin((1 - phase_number) * pi * 2.0 / 3.0 + pi / 6.0)
+ * 1000.0
+ )
+ line_phase_gain = sqrt(3.0)
+ if len(cyme_phase_name_delta[phase_number].replace("N", "")) == 2:
+ load_scale = 1
+ elif len(cyme_phase_name_delta[phase_number].replace("N", "")) == 3:
+ load_scale = 3
+ else:
+ error(f"wrong load phase {load_phase} for delta connection", 10)
+ else:
+ # wye connecttion
+ vol_real = (
+ float(load_voltage) * cos((1 - phase_number) * pi * 2.0 / 3.0) * 1000.0
+ )
+ vol_imag = (
+ float(load_voltage) * sin((1 - phase_number) * pi * 2.0 / 3.0) * 1000.0
+ )
+ line_phase_gain = 1
+ load_scale = len(cyme_phase_name[phase_number].replace("N", ""))
+ if load_scale < 0 or load_scale > 3:
+ error(f"wrong load phase {load_phase} for wye connection", 11)
+ if value_type == 0:
+ load_real = load_power1 * 1000.0
+ load_imag = load_power2 * 1000.0
+ elif value_type == 1:
+ if load_power2 > 0:
+ load_real = load_power1 * load_power2 / 100 * 1000.0
+ load_imag = load_power1 * sqrt(1 - (load_power2 / 100) ** 2) * 1000.0
+ else:
+ load_real = -load_power1 * load_power2 / 100 * 1000.0
+ load_imag = -load_power1 * sqrt(1 - (load_power2 / 100) ** 2) * 1000.0
+ else:
+ load_real = load_power1 * 1000
+ if load_power2 > 0.0 or load_power2 < 0.0:
+ load_imag = (
+ load_real
+ / (load_power2 / 100.0)
+ * sqrt(1 - abs(load_power2 / 100) ** 2)
+ )
+ vol_mag = float(load_voltage) * 1000.0
+ vol_complex = vol_real + vol_imag * (1j)
+ if load_type == "Z":
+ if (load_real * load_real + load_imag * load_imag) > 0:
+ load_cals_results = (
+ vol_mag
+ * line_phase_gain
+ * vol_mag
+ * line_phase_gain
+ / (load_real + load_imag * (1j))
+ / load_scale
+ )
+ return load_cals_results
+ else:
+ return 0 + 0 * (1j)
+ elif load_type == "I":
+ load_cals_results = (
+ (load_real + load_imag * (1j))
+ / (vol_complex * line_phase_gain)
+ / load_scale
+ )
+ return load_cals_results
+ else:
+ # for constant power load, the imag part is negative
+ load_cals_results = (load_real - load_imag * (1j)) / load_scale
+ return load_cals_results
+
+
+def capacitor_phase_cals(KVARA, KVARB, KVARC):
+ return (
+ int(KVARA > 0)
+ + 2 * int(KVARB > 0)
+ + 3 * int(KVARC > 0)
+ + int((KVARA * KVARB > 0) or (KVARA * KVARC > 0) or (KVARB * KVARC > 0))
+ )
+
# Function that replaces characters not allowed in name with '_'
def fix_name(name):
- name = name.replace(' ', '_')
- name = name.replace('.','_')
- name = name.replace('-','_')
- name = name.replace('\\','_')
- name = name.replace('/','_')
- name = name.replace(':','_')
- name = name.replace('\'','_')
- name = name.replace('~','_')
- return name
+ name = name.replace(" ", "_")
+ name = name.replace(".", "_")
+ name = name.replace("-", "_")
+ name = name.replace("\\", "_")
+ name = name.replace("/", "_")
+ name = name.replace(":", "_")
+ name = name.replace("'", "_")
+ name = name.replace("~", "_")
+ return name
+
def arrangeString(string):
- MAX_CHAR = 26
- char_count = [0] * MAX_CHAR
- s = 0
-
- for i in range(len(string)):
- if string[i] >= "A" and string[i] <= "Z":
- char_count[ord(string[i]) - ord("A")] += 1
- else:
- s += ord(string[i]) - ord("0")
- res = ""
-
- for i in range(MAX_CHAR):
- ch = chr(ord("A") + i)
- while char_count[i]:
- res += ch
- char_count[i] -= 1
- if s > 0:
- res += str(s)
-
- return res
+ MAX_CHAR = 26
+ char_count = [0] * MAX_CHAR
+ s = 0
+
+ for i in range(len(string)):
+ if string[i] >= "A" and string[i] <= "Z":
+ char_count[ord(string[i]) - ord("A")] += 1
+ else:
+ s += ord(string[i]) - ord("0")
+ res = ""
+
+ for i in range(MAX_CHAR):
+ ch = chr(ord("A") + i)
+ while char_count[i]:
+ res += ch
+ char_count[i] -= 1
+ if s > 0:
+ res += str(s)
+
+ return res
+
def clean_phases(phases):
- p = ''
- if 'A' in phases:
- p = p + 'A'
- if 'B' in phases:
- p = p + 'B'
- if 'C' in phases:
- p = p + 'C'
- return p
+ p = ""
+ if "A" in phases:
+ p = p + "A"
+ if "B" in phases:
+ p = p + "B"
+ if "C" in phases:
+ p = p + "C"
+ return p
+
# map CYME phases to GLM phases
cyme_phase_map = {
- 1:'A',
- 2:'B',
- 3:'C',
- 4:'AB',
- 5:'AC',
- 6:'BC',
- 7:'ABC',
- }
+ 1: "A",
+ 2: "B",
+ 3: "C",
+ 4: "AB",
+ 5: "AC",
+ 6: "BC",
+ 7: "ABC",
+}
# global variable tracking
data_folder = None
@@ -320,6 +405,7 @@ def clean_phases(phases):
VERBOSE = False
voltage_check_fix = False
phase_check_fix = False
+line_length_unit = "m"
error_count = 0
warning_count = 0
GLM_error_file = sys.stderr
@@ -329,2045 +415,3016 @@ def clean_phases(phases):
# table for solar: "CYMDGGENERATIONMODEL", "CYMCONVERTER"
cyme_tables_required = [
- "CYMNETWORK","CYMHEADNODE","CYMNODE","CYMSECTION","CYMSECTIONDEVICE",
- "CYMOVERHEADBYPHASE","CYMOVERHEADLINEUNBALANCED","CYMEQCONDUCTOR",
- "CYMEQGEOMETRICALARRANGEMENT","CYMEQOVERHEADLINEUNBALANCED","CYMEQFUSE",
- "CYMSWITCH","CYMCUSTOMERLOAD","CYMLOAD","CYMSHUNTCAPACITOR","CYMFUSE",
- "CYMTRANSFORMER","CYMEQTRANSFORMER","CYMREGULATOR","CYMEQREGULATOR",
- "CYMOVERHEADLINE","CYMUNDERGROUNDLINE","CYMNODETAG","CYMEQCABLE",
- "CYMBREAKER","CYMCAPACITOREXTLTD","CYMCONSUMERCLASS",
- "CYMCTYPEFILTER","CYMTRANSFORMERBYPHASE","CYMRECLOSER","CYMEQOVERHEADLINE",
- "CYMSOURCE","CYMEQSHUNTCAPACITOR","CYMPHOTOVOLTAIC","CYMEQAVERAGEGEOARRANGEMENT","CYMSCHEMAVERSION"]
+ "CYMNETWORK",
+ "CYMHEADNODE",
+ "CYMNODE",
+ "CYMSECTION",
+ "CYMSECTIONDEVICE",
+ "CYMOVERHEADBYPHASE",
+ "CYMOVERHEADLINEUNBALANCED",
+ "CYMEQCONDUCTOR",
+ "CYMEQGEOMETRICALARRANGEMENT",
+ "CYMEQOVERHEADLINEUNBALANCED",
+ "CYMEQFUSE",
+ "CYMSWITCH",
+ "CYMCUSTOMERLOAD",
+ "CYMLOAD",
+ "CYMSHUNTCAPACITOR",
+ "CYMFUSE",
+ "CYMTRANSFORMER",
+ "CYMEQTRANSFORMER",
+ "CYMREGULATOR",
+ "CYMEQREGULATOR",
+ "CYMOVERHEADLINE",
+ "CYMUNDERGROUNDLINE",
+ "CYMNODETAG",
+ "CYMEQCABLE",
+ "CYMBREAKER",
+ "CYMCAPACITOREXTLTD",
+ "CYMCONSUMERCLASS",
+ "CYMCTYPEFILTER",
+ "CYMTRANSFORMERBYPHASE",
+ "CYMRECLOSER",
+ "CYMEQOVERHEADLINE",
+ "CYMSOURCE",
+ "CYMEQSHUNTCAPACITOR",
+ "CYMPHOTOVOLTAIC",
+ "CYMEQAVERAGEGEOARRANGEMENT",
+ "CYMSCHEMAVERSION",
+]
#
# Warning/error/verbose handling
#
+
def verbose(msg):
- print(f"VERBOSE [mdb-cyme2glm]: {msg}",flush=True)
+ print(f"VERBOSE [mdb-cyme2glm]: {msg}", flush=True)
+
def warning(msg):
- global warning_count
- warning_count += 1
- if WARNING:
- print(f"WARNING [mdb-cyme2glm]: {msg}",file=GLM_warning_file,flush=True)
- if VERBOSE:
- verbose(msg)
-
-def error(msg,code=None):
- global error_count
- error_count += 1
- if DEBUG:
- raise Exception(msg)
- if not QUIET:
- print(f"ERROR [mdb-cyme2glm]: {msg}",file=GLM_error_file,flush=True)
- if type(code) is int:
- exit(code)
+ global warning_count
+ warning_count += 1
+ if WARNING:
+ print(f"WARNING [mdb-cyme2glm]: {msg}", file=GLM_warning_file, flush=True)
+ if VERBOSE:
+ verbose(msg)
+
+
+def error(msg, code=None):
+ global error_count
+ error_count += 1
+ if DEBUG:
+ raise Exception(msg)
+ if not QUIET:
+ print(f"ERROR [mdb-cyme2glm]: {msg}", file=GLM_error_file, flush=True)
+ if type(code) is int:
+ exit(code)
+
def debug(msg):
- if DEBUG:
- print(f"DEBUG [mdb-cyme2glm]: {msg}",file=GLM_output_file,flush=True)
+ if DEBUG:
+ print(f"DEBUG [mdb-cyme2glm]: {msg}", file=GLM_output_file, flush=True)
+
def glm_output_print(msg):
- print(f"GLM_OUTPUT [mdb-cyme2glm]: {msg}",file=GLM_output_file,flush=True)
- if VERBOSE:
- verbose(msg)
-
-def format_exception(errmsg,ref=None,data=None):
- tb = str(traceback.format_exc().replace('\n','\n '))
- dd = str(pp.pformat(data).replace('\n','\n '))
- return "\n " + tb + "'" + ref + "' =\n "+ dd
-
-
-def mdb2csv(input_file,output_dir,tables,extract_option):
- if os.path.exists(data_folder):
- os.system(f"rm -rf {data_folder}")
- os.system(f"mkdir -p {data_folder}")
- for table in tables:
- csvname = table[3:].lower()
- os.system(f"mdb-export {input_file} {table} > {output_dir}/{csvname}.csv")
- row_count = os.popen(f"wc -l {output_dir}/{csvname}.csv").read()
- if (int(row_count.strip().split(" ")[0]) <= 1) and extract_option != "all":
- os.remove(f"{output_dir}/{csvname}.csv")
- cyme_table = {}
- for filename in glob.iglob(f"{output_dir}/*.csv"):
- data = pd.read_csv(filename, dtype=str)
- name = os.path.basename(filename)[0:-4].lower()
- cyme_table[name] = data
- return cyme_table
+ print(f"GLM_OUTPUT [mdb-cyme2glm]: {msg}", file=GLM_output_file, flush=True)
+ if VERBOSE:
+ verbose(msg)
+
+
+def format_exception(errmsg, ref=None, data=None):
+ tb = str(traceback.format_exc().replace("\n", "\n "))
+ dd = str(pp.pformat(data).replace("\n", "\n "))
+ return "\n " + tb + "'" + ref + "' =\n " + dd
+
+
+def mdb2csv(input_file, output_dir, tables, extract_option):
+ if os.path.exists(data_folder):
+ os.system(f"rm -rf {data_folder}")
+ os.system(f"mkdir -p {data_folder}")
+ for table in tables:
+ csvname = table[3:].lower()
+ os.system(f"mdb-export {input_file} {table} > {output_dir}/{csvname}.csv")
+ row_count = os.popen(f"wc -l {output_dir}/{csvname}.csv").read()
+ if (int(row_count.strip().split(" ")[0]) <= 1) and extract_option != "all":
+ os.remove(f"{output_dir}/{csvname}.csv")
+ cyme_table = {}
+ for filename in glob.iglob(f"{output_dir}/*.csv"):
+ data = pd.read_csv(filename, dtype=str)
+ name = os.path.basename(filename)[0:-4].lower()
+ cyme_table[name] = data
+ return cyme_table
+
def is_float(element):
- try:
- float(element)
- return True
- except ValueError:
- return False
-
-def fix_unit(string,output_unit):
- if output_unit == "V":
- scale = 1.0
- elif output_unit == "kV":
- scale = 0.001
- else:
- error(f"cannot convert string {string} with unit {output_unit}.", 71)
- if "kV" in string:
- scale = scale * 1000.0
- value_string = string.replace("kV","")
- elif "V" in string:
- scale = scale * 1.0
- value_string = string.replace("V","")
- try:
- value = float(value_string) * scale
- return "%.4g" % value
- except:
- error(f"cannot convert string {string}.", 72)
-
-def feeder_voltage_find(network_id,cyme_table):
- ## set up feeder nominal voltage
- feeder_kVLN = None
- if os.path.exists(os.path.join(input_folder,'feeder_map_2020.csv')): ## feeder_map_2020.csv should be provided from NG
- df_feeder_master = pd.read_csv(os.path.join(input_folder,'feeder_map_2020.csv'))
- df_feeder_select = df_feeder_master[df_feeder_master['GIS CDF'] == network_id].copy()
- if "source" in cyme_table.keys():
- for index, source in cyme_table["source"].iterrows():
- if source['NetworkId'] == network_id:
- if 'DesiredVoltage' in source.keys() and is_float(source['DesiredVoltage']) and float(source['DesiredVoltage'])>0:
- feeder_kVLN = float(source['DesiredVoltage'])/sqrt(3)
- elif 'EquipmentId' in source.keys() and is_float(source['EquipmentId'].split('_')[-1]) and float(source['EquipmentId'].split('_')[-1])>0:
- feeder_kVLN = float(source['EquipmentId'].split('_')[-1])/sqrt(3)
- elif os.path.exists(os.path.join(input_folder,'feeder_map_2020.csv')) and is_float(df_feeder_select['APS Voltage (kV)']) and float(df_feeder_select['APS Voltage (kV)'])>0:
- feeder_kVLN = float(df_feeder_select['APS Voltage (kV)'].iloc[0])/sqrt(3)
- # elif "OperatingVoltageA" in source.keys() and "OperatingVoltageB" in source.keys() and "OperatingVoltageC" in source.keys():
- # feeder_kVLN = float(source['OperatingVoltageA'])/sqrt(3)
- else:
- return feeder_kVLN
- break
- return "%.4g" % feeder_kVLN
- return feeder_kVLN
+ try:
+ float(element)
+ return True
+ except ValueError:
+ return False
+
+
+def fix_unit(string, output_unit):
+ if output_unit == "V":
+ scale = 1.0
+ elif output_unit == "kV":
+ scale = 0.001
+ else:
+ error(f"cannot convert string {string} with unit {output_unit}.", 71)
+ if "kV" in string:
+ scale = scale * 1000.0
+ value_string = string.replace("kV", "")
+ elif "V" in string:
+ scale = scale * 1.0
+ value_string = string.replace("V", "")
+ try:
+ value = float(value_string) * scale
+ return "%.4f" % value
+ except:
+ error(f"cannot convert string {string}.", 72)
+
+
+def fix_unit_line_length(string: str, output_unit: str):
+ if output_unit == "m":
+ scale = 1.0
+ elif output_unit == "km":
+ scale = 1000.0
+ else:
+ error(f"cannot convert string {string} with unit {output_unit}.", 71)
+ try:
+ value = float(string) * scale
+ return "%.4f" % value
+ except:
+ error(f"cannot convert string {string}.", 72)
+
+
+def feeder_voltage_find(network_id, cyme_table):
+ ## set up feeder nominal voltage
+ feeder_kVLN = None
+ if os.path.exists(
+ os.path.join(input_folder, "feeder_map_2020.csv")
+ ): ## feeder_map_2020.csv should be provided from NG
+ df_feeder_master = pd.read_csv(
+ os.path.join(input_folder, "feeder_map_2020.csv")
+ )
+ df_feeder_select = df_feeder_master[
+ df_feeder_master["GIS CDF"] == network_id
+ ].copy()
+ if "source" in cyme_table.keys():
+ for index, source in cyme_table["source"].iterrows():
+ if source["NetworkId"] == network_id:
+ if (
+ "DesiredVoltage" in source.keys()
+ and is_float(source["DesiredVoltage"])
+ and float(source["DesiredVoltage"]) > 0
+ ):
+ feeder_kVLN = float(source["DesiredVoltage"]) / sqrt(3)
+ elif (
+ "EquipmentId" in source.keys()
+ and is_float(source["EquipmentId"].split("_")[-1])
+ and float(source["EquipmentId"].split("_")[-1]) > 0
+ ):
+ feeder_kVLN = float(source["EquipmentId"].split("_")[-1]) / sqrt(3)
+ elif (
+ os.path.exists(os.path.join(input_folder, "feeder_map_2020.csv"))
+ and is_float(df_feeder_select["APS Voltage (kV)"])
+ and float(df_feeder_select["APS Voltage (kV)"]) > 0
+ ):
+ feeder_kVLN = float(
+ df_feeder_select["APS Voltage (kV)"].iloc[0]
+ ) / sqrt(3)
+ # elif "OperatingVoltageA" in source.keys() and "OperatingVoltageB" in source.keys() and "OperatingVoltageC" in source.keys():
+ # feeder_kVLN = float(source['OperatingVoltageA'])/sqrt(3)
+ else:
+ return feeder_kVLN
+ break
+ return "%.4f" % feeder_kVLN
+ return feeder_kVLN
+
#
# GLM file builder
#
class GLM:
-
- prefix = {
- # known powerflow class in gridlabd
- "billdump" : "BD_",
- "capacitor" : "CA_",
- "currdump" : "CD_",
- "emissions" : "EM_",
- "fault_check" : "FC_",
- "frequency_gen" : "FG_",
- "fuse" : "FS_",
- "impedance_dump" : "ID_",
- "inverter" : "IN_",
- "line" : "LN_",
- "line_configuration" : "LC_",
- "line_sensor" : "LS_",
- "line_spacing" : "LG_",
- "link" : "LK_",
- "load" : "LD_",
- "load_tracker" : "LT_",
- "meter" : "ME_",
- "motor" : "MO_",
- "node" : "ND_",
- "overhead_line" : "OL_",
- "overhead_line_conductor" : "OC_",
- "photovoltaic" : "PV_",
- "pole" : "PO_",
- "pole_configuration" : "PC_",
- "power_metrics" : "PM_",
- "powerflow_library" : "PL_",
- "powerflow_object" : "PO_",
- "pqload" : "PQ_",
- "recloser" : "RE_",
- "regulator" : "RG_",
- "regulator_configuration" : "RC_",
- "restoration" : "RS_",
- "sectionalizer" : "SE_",
- "series_reactor" : "SR_",
- "substation" : "SS_",
- "switch" : "SW_",
- "switch_coordinator" : "SC_",
- "transformer" : "TF_",
- "transformer_configuration" : "TC_",
- "triplex_line" : "XL_",
- "triplex_line_conductor" : "XC_",
- "triplex_line_configuration" : "XG_",
- "triplex_load" : "XD_",
- "triplex_meter" : "XM_",
- "triplex_node" : "XN_",
- "underground_line" : "UL_",
- "underground_line_conductor" : "UC_",
- "vfd" : "VF_",
- "volt_var_control" : "VV_",
- "voltdump" : "VD_",
- }
-
- def __init__(self,file,mode="w"):
-
- self.filename = file
- self.fh = open(file,mode)
- self.objects = {}
- self.assumptions = []
- self.refcount = {}
-
- # def __del__(self):
- # if self.object:
- # self.error("glm object was deleted before objects were output")
-
- def name(self,name,oclass=None):
- if type(name) is list: # composite name
- # name = "_".join(name).replace(".","").replace(":","")[0:63] # disallow special name characters
- name = "_".join(name)[0:63] # disallow special name characters
- if oclass: # name prefix based on class
- if not oclass in self.prefix.keys(): # name prefix not found
- prefix = f"Z{len(self.prefix.keys())}_"
- self.prefix[oclass] = prefix
- warning(f"{cyme_mdbname}@{network_id}: class '{oclass}' is not a known gridlabd powerflow class, using prefix '{prefix}' for names")
- else:
- prefix = self.prefix[oclass]
- name = prefix + name
- elif "0" <= name[0] <= "9": # fix names that start with digits
- name = "_" + name
- # return name.replace(" ","_").replace("-","_").replace(".","")[0:63] # remove white spaces from names
- return name[0:63] # remove white spaces from names
-
- def write(self,line):
- print(line,file=self.fh)
-
- def blank(self):
- self.write("")
-
- def print(self,message):
- self.write(f"#print {message}")
-
- def warning(self,message):
- self.write(f"#warning {message}")
-
- def error(self,message):
- self.write(f"#error {message}")
-
- def comment(self,*lines):
- for line in lines:
- self.write(f"// {line}")
-
- def set(self,name,value):
- self.write(f"#set {name}={value}")
-
- def define(self,name,value):
- self.write(f"#define {name}={value}")
-
- def include(self,name,brackets="\"\""):
- self.write(f"#include {brackets[0]}{name}{brackets[1]}")
-
- def module(self, name, parameters = {}):
- if not parameters:
- self.write(f"module {name};")
- else:
- self.write(f"module {name}")
- self.write("{")
- for tag, value in parameters.items():
- if type(value) is str:
- self.write(f"\t{tag} \"{value}\";")
- else:
- self.write(f"\t{tag} {value};")
- self.write("}")
-
- def clock(self, parameters = {}):
- if not parameters:
- error(f"clock needs parameters", 1)
- else:
- self.write(f"clock")
- self.write("{")
- for tag, value in parameters.items():
- if tag in ["timezone","starttime","stoptime"]:
- self.write(f"\t{tag} \"{value}\";")
- else:
- raise Exception(f"module clock not support parameter {tag}")
- self.write("}")
-
- def ifdef(self, name, call):
- self.write(f"#ifdef {name}")
- call()
- self.write("#endif")
-
- def ifndef(self, name, call):
- self.write(f"#ifndef {name}")
- call()
- self.write("#endif")
-
- def ifexist(self, name, call):
- self.write(f"#ifexist {name}")
- call()
- self.write("#endif")
-
- def object(self, oclass, name, parameters,overwrite=True):
- if name not in self.objects.keys():
- obj = {"name" : name}
- self.objects[name] = obj
- else:
- obj = self.objects[name]
- if "class" in obj.keys() and obj["class"] == "link" and oclass in ["fuse","underground_line","switch","overhead_line","transformer","single_transformer","regulator"]:
- # if obj is created based on a link object
- if oclass == "single_transformer":
- new_name = self.name(name+f"_{parameters['phases']}", "transformer") # new name
- oclass = "transformer"
- else:
- new_name = self.name(name, oclass) # new name
- new_obj = {"name" : new_name}
- self.objects[new_name] = new_obj
- for key, value in obj.items():
- if key != "name":
- new_obj[key] = value
- for key, value in parameters.items():
- if not overwrite and key in new_obj.keys() and new_obj[key] != value:
- error(f"object property '{key}={new_obj[key]}' merge conflicts with '{key}={value}'", 2)
- if value == None and key in new_obj.keys():
- del new_obj[key]
- else:
- new_obj[key] = value
- new_obj["class"] = oclass
- if "nominal_voltage" in new_obj.keys() and (new_obj["class"] == "underground_line" or new_obj["class"] == "overhead_line"):
- del new_obj["nominal_voltage"]
- if "phases" in new_obj.keys() and "N" not in new_obj["phases"]:
- new_obj["phases"] = new_obj["phases"] + "N"
- if new_name in self.refcount.keys():
- self.refcount[new_name] += 1
- else:
- self.refcount[new_name] = 1
- return new_obj
- else:
- for key, value in parameters.items():
- if not overwrite and key in obj.keys() and obj[key] != value:
- error(f"object property '{key}={obj[key]}' merge conflicts with '{key}={value}'", 3)
- if value == None and key in obj.keys():
- del obj[key]
- else:
- obj[key] = value
- obj["class"] = oclass
- if name in self.refcount.keys():
- self.refcount[name] += 1
- else:
- self.refcount[name] = 1
- return obj
-
-
- def delete(self,name):
- if self.refcount[name] == 1:
- del self.objects[name]
- elif self.refcount[name] > 1:
- self.refcount[name] -= 1
-
-
- def modify(self,object,property,value,comment=""):
- if comment:
- comment = " // " + str(comment)
- elif not type(comment) is str:
- comment = ""
- if type(value) is str:
- self.write(f"modify {object}.{property} \"{value}\";{comment}")
- else:
- self.write(f"modify {object}.{property} {value};{comment}")
-
- def assume(self,objname,propname,value,remark=""):
- self.assumptions.append([fix_name(objname),propname,value,remark])
-
- def close(self):
-
- # objects
- if self.objects:
- for name, parameters in self.objects.items():
- self.write(f"object {parameters['class']}")
- self.write("{")
- for tag, value in parameters.items():
- if tag != "class":
- if type(value) is str:
- self.write(f"\t{tag} \"{value}\";")
- else:
- self.write(f"\t{tag} {value};")
- self.write("}")
- self.objects = {}
-
- # assumptions
- if self.assumptions:
- if settings["GLM_ASSUMPTIONS"] == "save":
- filename = f"{settings['GLM_NETWORK_PREFIX']}{cyme_mdbname}_{network_id}_assumptions.glm"
- with open(f"{output_folder}/{filename}","w") as fh:
- print("// Assumptions for GLM conversion from database {cyme_mdbname} network {network_id}",file=fh)
- for row in self.assumptions:
- print(f"modify {fix_name(row[0])}.{row[1]} \"{row[2]}\"; // {row[3]}",file=fh)
- elif settings["GLM_ASSUMPTIONS"] == "include":
- self.blank()
- self.comment("","Assumptions","")
- for row in self.assumptions:
- self.modify(row[0],row[1],row[2],row[3])
- elif settings["GLM_ASSUMPTIONS"] == "warn":
- filename = f"{output_folder}/{cyme_mdbname}_{network_id}_assumptions.csv"
- warning(f"{cyme_mdbname}@{network_id}: {len(self.assumptions)} assumptions made, see '{filename}' for details")
- pd.DataFrame(self.assumptions).to_csv(filename,header=["object_name","property_name","value","remark"],index=False)
- elif settings["GLM_ASSUMPTIONS"] != "ignore":
- warning(f"GLM_ASSUMPTIONS={settings['GLM_ASSUMPTIONS']} is not valid (must be one of 'save','ignore','warn','include')")
-
- # modifications
- for modify in settings["GLM_MODIFY"].split():
- self.blank()
- self.comment("",f"Modifications from '{modify}'","")
- try:
- with open(f"{input_folder}/{modify}","r") as fh:
- reader = csv.reader(fh)
- for row in reader:
- if len(row) == 0:
- warning(f"No modifications from {modify}")
- elif 0 < len(row) < 3:
- warning(f"{modify}: row '{','.join(list(row))}' is missing one or more required fields")
- elif len(row) > 3:
- warning(f"{modify}: row '{','.join(list(row))}' has extra fields that will be ignored")
- self.modify(*row[0:3])
- else:
- self.modify(*row)
- except:
- pass
-
- # general glm model add function
- def add(self,oclass,device_id,data,version,**kwargs):
- try:
- call = getattr(self,"add_"+oclass)
- return call(device_id,data,version=version,**kwargs)
- except Exception as errmsg:
- warning(f"{cyme_mdbname}@{network_id}: unable to add gridlabd class '{oclass}' using CYME device '{device_id}': {errmsg} {format_exception(errmsg,device_id,data.to_dict())}")
- pass
-
- # add a link to glm file
- def add_link(self,section_id,section,version,**kwargs):
- phase = int(section["Phase"])
- from_node_id = section["FromNodeId"]
- to_node_id = section["ToNodeId"]
- device_dict = {}
- for index, device in table_find(cyme_table["sectiondevice"],SectionId=section_id).iterrows():
- device_id = device["DeviceNumber"]
- device_type = int(device["DeviceType"])
- if device_type in glm_devices.keys():
- device_name = self.name(device_id,"link")
- device_dict[device_id] = self.object("link", device_name , {
- "phases" : cyme_phase_name[phase],
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- "from" : self.name(from_node_id,"node"),
- "to" : self.name(to_node_id,"node"),
- })
- if from_node_id not in kwargs["node_links"].keys():
- kwargs["node_links"][from_node_id] = []
- kwargs["node_links"][from_node_id].append(device_id)
- if to_node_id not in kwargs["node_links"].keys():
- kwargs["node_links"][to_node_id] = []
- kwargs["node_links"][to_node_id].append(device_id)
- else:
- warning(f"{cyme_mdbname}@{network_id}: {cyme_devices[device_type]} on section {section_id} has no corresponding GLM object")
- # print(device_dict)
- return device_dict
-
- # add node to glm file
- def add_node(self,node_id,node_links,device_dict,version,**kwargs):
- phase = 0
- if geodata_file:
- node_geodata_id = f"{node_id}_{network_id}"
- if node_geodata_id not in node_geodata.keys():
- all_node = kwargs["node_info"]["all_node"]
- try:
- node_X = float(all_node[all_node["NodeId"] == node_id]["X"].values)
- node_Y = float(all_node[all_node["NodeId"] == node_id]["Y"].values)
- except:
- warning(f'{cyme_mdbname}@{network_id}: cannot add coordinates for node_id')
- node_X = np.nan
- node_Y = np.nan
- node_geodata[node_geodata_id] = {
- "NotworkID" : network_id,
- "node" : node_id,
- "x" : node_X,
- "y" : node_Y,
- }
- else:
- raise Exception(f"{cyme_mdbname}@{network_id}: multiple definition for {node_id}")
- for device_id in node_links[node_id]:
- phase |= glm_phase_code[device_dict[device_id]["phases"]]
- obj = self.object("node", self.name(node_id,"node"), {
- "phases" : glm_phase_name[phase]+"N",
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- })
- if node_id == table_get(cyme_table["headnode"],network_id,"NodeId","NetworkId"):
- obj["bustype"] = "SWING"
- else:
- obj["bustype"] = "PQ"
- return obj
-
- # add an overhead line based on a link
- def add_overhead_line(self,line_id,line,version):
- line_name = self.name(line_id,"link")
- length = float(line["Length"])
- if length == 0.0:
- length = 0.01
- line_conductor_id = line["LineId"]
- line_conductor = None
- if 'eqconductor' in cyme_equipment_table.keys():
- line_conductor = table_get(cyme_equipment_table['eqoverheadline'],line_conductor_id,None,'EquipmentId')
- # elif 'csvundergroundcable' in cyme_equipment_table.keys():
- # ## TODO
- elif 'eqconductor' in cyme_table.keys():
- line_conductor = table_get(cyme_table['eqoverheadline'],line_conductor_id,None,'EquipmentId')
- if line_conductor is None:
- warning(f'{cyme_mdbname}@{network_id}: OH cable conductor "{line_conductor_id}" of line "{line_id}" is missing in CYME model.Use default settings.')
- line_conductor = {
- "PhaseConductorId" : "DEFAULT",
- "NeutralConductorId" : "DEFAULT",
- "ConductorSpacingId" : "DEFAULT",
- }
- conductorABC_id = line_conductor["PhaseConductorId"]
- conductorN_id = line_conductor["NeutralConductorId"]
- self.add_overhead_line_conductors([conductorABC_id,conductorN_id],version)
- spacing_id = line_conductor["ConductorSpacingId"]
- self.add_line_spacing(spacing_id,version)
- configuration_name = self.add_line_configuration([conductorABC_id,conductorABC_id,conductorABC_id,conductorN_id,spacing_id],version)
- return self.object("overhead_line", line_name, {
- "length" : "%.2f m"%length,
- "configuration" : configuration_name,
- })
-
- # add an overhead line by phase based on a link
- def add_overhead_line_phase(self,line_id,line,version):
- line_name = self.name(line_id,"link")
- length = float(line["Length"])
- if length == 0.0:
- length = 0.01
- conductorA_id = line["PhaseConductorIdA"]
- conductorB_id = line["PhaseConductorIdB"]
- conductorC_id = line["PhaseConductorIdC"]
- conductorN_id = line["NeutralConductorId"]
- self.add_overhead_line_conductors([conductorA_id,conductorB_id,conductorC_id,conductorN_id],version)
- spacing_id = line["ConductorSpacingId"]
- self.add_line_spacing(spacing_id,version)
- configuration_name = self.add_line_configuration([conductorA_id,conductorB_id,conductorC_id,conductorN_id,spacing_id],version)
- return self.object("overhead_line", line_name, {
- "length" : "%.2f m"%length,
- "configuration" : configuration_name,
- })
-
- # add an unbalanced overhead line based on a link
- def add_overhead_line_unbalanced(self,line_id,line,version):
- line_name = self.name(line_id,"link")
- configuration_id = line["LineId"]
- configuration_name = self.name(configuration_id,"line_configuration")
- length = float(line["Length"])
- if length == 0.0:
- length = 0.01
- if not configuration_name in self.objects.keys():
- configuration = table_get(cyme_table['eqoverheadlineunbalanced'],configuration_id,None,'EquipmentId')
- conductorA_id = configuration["PhaseConductorIdA"]
- conductorB_id = configuration["PhaseConductorIdB"]
- conductorC_id = configuration["PhaseConductorIdC"]
- conductorN_id = configuration["NeutralConductorId"]
- conductor_names = self.add_overhead_line_conductors([conductorA_id,conductorB_id,conductorC_id,conductorN_id],version)
- spacing_id = configuration["ConductorSpacingId"]
- spacing_name = self.add_line_spacing(spacing_id,version)
- self.object("line_configuration",configuration_name,{
- "conductor_A" : conductor_names[0],
- "conductor_B" : conductor_names[1],
- "conductor_C" : conductor_names[2],
- "conductor_N" : conductor_names[3],
- "spacing" : spacing_name,
- })
- return self.object("overhead_line", line_name, {
- "length" : "%.2f m"%length,
- "configuration" : configuration_name,
- })
-
- # add an underground line based on a link
- def add_underground_line(self,line_id,line,version):
- line_name = self.name(line_id,"link")
- if version == 5020:
-
- ## TODO
- if float(line["Length"]) < 0.05:
- ## SCE feeder UG line length unit is km
- warning(f"{cyme_mdbname}@{network_id}: length of line '{line_id}' may in km.")
- length = float(line["Length"])
- else:
- length = float(line["Length"])
- if length == 0.0:
- length = 0.01
- cable_conductor_id = line["CableId"]
- conductor_name = self.name(cable_conductor_id,"underground_line_conductor")
- if 'eqconductor' in cyme_equipment_table.keys():
- cable_conductor = table_get(cyme_equipment_table['eqconductor'],cable_conductor_id,None,'EquipmentId')
- # elif 'csvundergroundcable' in cyme_equipment_table.keys():
- # ## TODO
- elif 'eqconductor' in cyme_table.keys():
- cable_conductor = table_get(cyme_table['eqconductor'],cable_conductor_id,None,'EquipmentId')
- if not conductor_name in self.objects.keys():
- if cable_conductor is None:
- warning(f"{cyme_mdbname}@{network_id}: UG cable conductor {cable_conductor_id} of line '{line_id}' is missing in CYME model, use default settings instead.")
- # only use default settings for now
- self.object("underground_line_conductor",conductor_name,{
- "outer_diameter" : "0.968 cm",
- "conductor_gmr" : "0.0319 cm",
- "conductor_diameter" : "0.968 cm",
- "conductor_resistance" : "0.139 Ohm/km",
- "neutral_gmr" : "0.00208 cm",
- "neutral_resistance" : "14.8722 Ohm/km",
- "neutral_diameter" : "0.0641 cm",
- "neutral_strands" : "16",
- "rating.summer.continuous" : "500 A",
- })
- else:
- gmr = float(cable_conductor["GMR"])
- r25 = float(cable_conductor["R25"])
- diameter = float(cable_conductor["Diameter"])
- nominal_rating = float(cable_conductor["FirstRating"])
- if nominal_rating == 0:
- nominal_rating = 1000
- if r25 == 0:
- r25 = 0.00001
- if gmr == 0:
- gmr = 0.01
- obj = self.object("underground_line_conductor",conductor_name,{
- "outer_diameter" : "%.2f cm" % diameter,
- "conductor_gmr" : "%.2f cm" % gmr,
- "conductor_diameter" : "%.2f cm" % diameter,
- "conductor_resistance" : "%.5f Ohm/km" % r25,
- "neutral_gmr" : "0.00208 cm",
- "neutral_resistance" : "14.8722 Ohm/km",
- "neutral_diameter" : "0.0641 cm",
- "neutral_strands" : "16",
- "rating.summer.continuous" : "%.1f A" % nominal_rating,
- })
- try:
- line_phases = self.objects[line_name]['phases']
- except:
- error(f'cannot find the link objects for underground line {line_id}', 20)
- if "N" not in line_phases:
- line_phases = line_phases + "N"
- spacing_name = self.name(f'UL_{line_id}_{line_phases}',"line_spacing")
- if not spacing_name in self.objects.keys():
- # only use default settings for now
- UL_spacings = {}
- if 'A' in line_phases and 'B' in line_phases:
- UL_spacings['distance_AB'] = "0.1 m"
- if 'B' in line_phases and 'C' in line_phases:
- UL_spacings['distance_BC'] = "0.1 m"
- if 'A' in line_phases and 'C' in line_phases:
- UL_spacings['distance_AC'] = "0.1 m"
- if 'A' in line_phases:
- UL_spacings['distance_AN'] = "0.0477 m"
- if 'B' in line_phases:
- UL_spacings['distance_BN'] = "0.0477 m"
- if 'C' in line_phases:
- UL_spacings['distance_CN'] = "0.0477 m"
- self.object("line_spacing",spacing_name,UL_spacings)
- configuration_name = self.name(f'UL_{line_id}_{line_phases}',"line_configuration")
- if not configuration_name in self.objects.keys():
- UL_configs = {}
- if 'A' in line_phases:
- UL_configs['conductor_A'] = conductor_name
- if 'B' in line_phases:
- UL_configs['conductor_B'] = conductor_name
- if 'C' in line_phases:
- UL_configs['conductor_C'] = conductor_name
- if 'N' in line_phases:
- UL_configs['conductor_N'] = conductor_name
- UL_configs['spacing'] = spacing_name
- self.object("line_configuration",configuration_name,UL_configs)
- return self.object("underground_line", line_name, {
- "length" : "%.2f m"%length,
- "configuration" : configuration_name,
- })
-
- # add overhead line conductor library entry
- def add_overhead_line_conductors(self,conductors,version):
- conductor_names = []
- for conductor_id in conductors:
- conductor_name = self.name(conductor_id,"overhead_line_conductor")
- conductor = None
- if not conductor_name in self.objects.keys():
- if 'eqconductor' in cyme_equipment_table.keys():
- conductor = table_get(cyme_equipment_table['eqconductor'],conductor_id,None,'EquipmentId')
- elif 'eqconductor' in cyme_table.keys():
- conductor = table_get(cyme_table['eqconductor'],conductor_id,None,'EquipmentId')
- else:
- error(f"cannot add cable conductor {conductor_name} for version {version}", 21)
- if conductor is None:
- warning(f"{cyme_mdbname}@{network_id}: OH cable conductor {conductor_id} is missing in CYME model, use default settings instead.")
- # use default settings.
- obj = self.object("overhead_line_conductor",conductor_name,{
- "geometric_mean_radius" : "0.1 cm",
- "resistance" : "0.5 Ohm/km",
- "diameter" : "1 cm",
- "rating.summer.continuous" : "1000 A",
- "rating.winter.continuous" : "1000 A",
- "rating.summer.emergency" : "1000 A",
- "rating.winter.emergency" : "1000 A",
- })
- else:
- gmr = float(conductor["GMR"])
- r25 = float(conductor["R25"])
- diameter = float(conductor["Diameter"])
- nominal_rating = float(conductor["NominalRating"])
- # should set up NONE conductor rating and resistance as non-zero value
- # cannot use modify.csv to change the ratings fior OC_NONE
- if nominal_rating == 0:
- nominal_rating = 1000
- if r25 == 0:
- r25 = 0.00001
- if gmr == 0:
- gmr = 0.01
- obj = self.object("overhead_line_conductor",conductor_name,{
- "geometric_mean_radius" : "%.2f cm" % gmr,
- "resistance" : "%.5f Ohm/km" % r25,
- "diameter" : "%.2f cm" % diameter,
- "rating.summer.continuous" : "%.1f A" % nominal_rating,
- "rating.winter.continuous" : "%.1f A" % nominal_rating,
- "rating.summer.emergency" : "%.1f A" % nominal_rating,
- "rating.winter.emergency" : "%.1f A" % nominal_rating,
- })
- conductor_names.append(conductor_name)
- return conductor_names
-
- # line spacing library object
- def add_line_spacing(self,spacing_id,version):
- spacing_name = self.name(spacing_id,"line_spacing")
- if not spacing_name in self.objects.keys():
- spacing = None
- if 'eqgeometricalarrangement' in cyme_equipment_table.keys():
- spacing = table_get(cyme_equipment_table['eqgeometricalarrangement'],spacing_id,None,'EquipmentId')
- if spacing is None and 'eqaveragegeoarrangement' in cyme_equipment_table.keys():
- spacing = table_get(cyme_equipment_table['eqaveragegeoarrangement'],spacing_id,None,'EquipmentId')
- if spacing is None:
- warning(f"cannot add cable spacing {spacing_id} for version {version}, use default settings")
- spacing = table_get(cyme_equipment_table['eqgeometricalarrangement'],"DEFAULT",None,'EquipmentId')
- elif 'eqgeometricalarrangement' in cyme_table.keys():
- spacing = table_get(cyme_table['eqgeometricalarrangement'],spacing_id,None,'EquipmentId')
- if spacing is None and 'eqaveragegeoarrangement' in cyme_table.keys():
- spacing = table_get(cyme_table['eqaveragegeoarrangement'],spacing_id,None,'EquipmentId')
- if spacing is None:
- warning(f"cannot add cable spacing {spacing_id} for version {version}, use default settings")
- spacing = table_get(cyme_table['eqgeometricalarrangement'],"DEFAULT",None,'EquipmentId')
- else:
- error(f"table 'eqgeometricalarrangement' for cable spacing is missing", 23)
- if spacing is None:
- error(f"cannot add cable spacing {spacing_id} for version {version}", 24)
- elif "GMDPhaseToPhase" in spacing.index:
- ABC2ABC = float(spacing["GMDPhaseToPhase"])
- ABC2N = float(spacing["GMDPhaseToNeutral"])
- ABC2E = float(spacing["AveragePhaseConductorHeight"])
- N2E = float(spacing["AverageNeutralConductorHeight"])
- self.object("line_spacing",spacing_name,{
- "distance_AB" : "%.2f m"%ABC2ABC,
- "distance_AC" : "%.2f m"%ABC2ABC,
- "distance_BC" : "%.2f m"%ABC2ABC,
- "distance_AN" : "%.2f m"%ABC2N,
- "distance_BN" : "%.2f m"%ABC2N,
- "distance_CN" : "%.2f m"%ABC2N,
- "distance_AE" : "%.2f m"%ABC2E,
- "distance_BE" : "%.2f m"%ABC2E,
- "distance_CE" : "%.2f m"%ABC2E,
- "distance_NE" : "%.2f m"%N2E,
- })
- elif "ConductorA_Horizontal" in spacing.index:
- Ax = float(spacing["ConductorA_Horizontal"])
- Ay = float(spacing["ConductorA_Vertical"])
- Bx = float(spacing["ConductorB_Horizontal"])
- By = float(spacing["ConductorB_Vertical"])
- Cx = float(spacing["ConductorC_Horizontal"])
- Cy = float(spacing["ConductorC_Vertical"])
- Nx = float(spacing["NeutralConductor_Horizontal"])
- Ny = float(spacing["NeutralConductor_Vertical"])
- ABx = Ax-Bx; ABy = Ay-By
- ACx = Ax-Cx; ACy = Ay-Cy
- BCx = Bx-Cx; BCy = By-Cy
- ANx = Ax-Nx; ANy = Ay-Ny
- BNx = Bx-Nx; BNy = By-Ny
- CNx = Cx-Nx; CNy = Cy-Ny
- self.object("line_spacing",spacing_name,{
- "distance_AB" : "%.2f m"%sqrt(ABx*ABx+ABy*ABy),
- "distance_AC" : "%.2f m"%sqrt(ACx*ACx+ACy*ACy),
- "distance_BC" : "%.2f m"%sqrt(BCx*BCx+BCy*BCy),
- "distance_AN" : "%.2f m"%sqrt(ANx*ANx+ANy*ANy),
- "distance_BN" : "%.2f m"%sqrt(BNx*BNx+BNy*BNy),
- "distance_CN" : "%.2f m"%sqrt(CNx*CNx+CNy*CNy),
- "distance_AE" : "%.2f m"%Ay,
- "distance_BE" : "%.2f m"%By,
- "distance_CE" : "%.2f m"%Cy,
- "distance_NE" : "%.2f m"%Ny,
- })
- else:
- error(f"data is missing for cable spacing {spacing_id}", 25)
- return spacing_name
-
- # line configuration library object
- def add_line_configuration(self,items,version):
- configuration_id = "_".join(items)
- configuration_name = self.name(configuration_id,"line_configuration")
- if not configuration_name in self.objects.keys():
- self.object("line_configuration",configuration_name,{
- "conductor_A" : self.name(items[0],"overhead_line_conductor"),
- "conductor_B" : self.name(items[1],"overhead_line_conductor"),
- "conductor_C" : self.name(items[2],"overhead_line_conductor"),
- "conductor_N" : self.name(items[3],"overhead_line_conductor"),
- "spacing" : self.name(items[4],"line_spacing")
- })
- return configuration_name
-
- # add a switch based on a link
- def add_switch(self,switch_id,switch,version):
- switch_name = self.name(switch_id,"link")
- phases = cyme_phase_name[int(switch["ClosedPhase"])]
- switch_config = {
- "operating_mode" : "BANKED"
- }
- for phase in phases:
- if phase != "N":
- switch_config[f'phase_{phase}_state'] = "CLOSED"
- return self.object("switch", switch_name, switch_config,overwrite=False)
-
- # add a breaker based on a link and a switch object
- def add_breaker(self,breaker_id,breaker,version):
- breaker_name = self.name(breaker_id,"link")
- phases = cyme_phase_name[int(breaker["ClosedPhase"])]
- breaker_config = {
- "operating_mode" : "BANKED"
- }
- for phase in phases:
- if phase != "N":
- breaker_config[f'phase_{phase}_state'] = "CLOSED"
- return self.object("switch", breaker_name, breaker_config,overwrite=False)
-
- # add a recloser based on a link and a switch object
- def add_recloser(self,recloser_id,recloser,version):
- recloser_name = self.name(recloser_id,"link")
- phases = cyme_phase_name[int(recloser["ClosedPhase"])]
- recloser_config = {
- "operating_mode" : "BANKED"
- }
- for phase in phases:
- if phase != "N":
- recloser_config[f'phase_{phase}_state'] = "CLOSED"
- return self.object("switch", recloser_name, recloser_config,overwrite=False)
-
- # add a fuse based on a link
- def add_fuse(self,fuse_id,fuse,version):
- fuse_name = self.name(fuse_id,"link")
- equipment_id = fuse["EquipmentId"]
- equipment = None
- if 'eqfuse' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table['eqfuse'],equipment_id,None,'EquipmentId')
- elif 'eqfuse' in cyme_table.keys():
- equipment = table_get(cyme_table['eqfuse'],equipment_id,None,'EquipmentId')
- if equipment is None:
- # use default settings
- current_limit = "50000.0"
- else:
- current_limit = equipment["FirstRatedCurrent"]
- fuse_dict = {
- "current_limit" : f"{current_limit} A",
- "mean_replacement_time" : "3600.0",
- }
- return self.object("fuse", fuse_name, fuse_dict,overwrite=False)
-
- # add a load
- def add_load(self,load_id,load,version,**kwargs):
- section = kwargs["node_info"]["load_section"].squeeze()
- connection_type = kwargs["node_info"]["connection_type"]
- all_node = kwargs["node_info"]["all_node"]
- default_load_voltage = kwargs["node_info"]["load_voltage"]
- device_type = int(load["DeviceType"])
- value_type = int(load["LoadValueType"])
- if device_type == 20: # spot load is attached at from node of section
- parent_name = self.name(section["FromNodeId"],"node")
- elif device_type == 21: # distributed load is attached at to node of section
- parent_name = self.name(section["ToNodeId"],"node")
- else:
- error(f"CYME device type {device_type} is not supported as a load", 30)
-
- if parent_name not in self.objects.keys():
- # Definition for node "parent_name" is missing
- device_dict = kwargs["node_info"]["Device_Dicts"]
- node_links = kwargs["node_info"]["Node_Links"]
- self.add_node(parent_name[3:],node_links,device_dict,version,node_info={"all_node":all_node})
-
- # customer_id = load["CustomerNumber"]
- link_name = self.name(load_id,"link")
- if link_name in self.objects.keys(): # link is no longer needed
- self.delete(link_name)
- load_name = self.name(load_id,"load")
- device_type = int(load["DeviceType"])
- phase = cyme_phase_name[int(load["Phase"])]
-
- if load_name in self.objects.keys() and "phases" in self.objects[load_name]:
- phases = self.objects[load_name]["phases"] + phase
- else:
- phases = phase
- if device_type in glm_devices.keys():
- ConsumerClassId = load["ConsumerClassId"]
- # the default load unit in gridlabd is Volt-Amperes, or Amperes or Ohms
- load_value1 = float(load["LoadValue1"])
- load_value2 = float(load["LoadValue2"])
- # from the mdb file, type for constant power load is defined as PQ
- load_types = {"Z":"constant_impedance","I":"constant_current","PQ":"constant_power"}
- if ConsumerClassId in load_types.keys():
- load_cals_complex = load_cals(ConsumerClassId,load["Phase"],connection_type,\
- load_value1,load_value2,default_load_voltage,value_type)
- load_value1 = load_cals_complex.real
- load_value2 = -load_cals_complex.imag
- if (load_value1*load_value1 + load_value2*load_value2) > 0:
- load_dict = {
- "parent" : parent_name,
- "phases" : arrangeString(phases),
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- }
- for i_phase in phase:
- load_dict[f"{load_types[ConsumerClassId]}_{i_phase}"] = "%.4g%+.4gj" % (load_value1,load_value2)
- return self.object("load",load_name,load_dict)
- elif ConsumerClassId in ["PV","SWING","SWINGPQ"]:
- # GLM bus types allowed
- load_cals_complex = load_cals("Z",load["Phase"],connection_type,\
- load_value1,load_value2,default_load_voltage,value_type)
- load_value1 = load_cals_complex.real
- load_value2 = -load_cals_complex.imag
- if (load_value1*load_value1 + load_value2*load_value2) > 0:
- load_dict = {
- "parent" : parent_name,
- "phases" : arrangeString(phases),
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- "bustype" : ConsumerClassId,
- }
- for i_phase in phase:
- load_dict[f"constant_impedance_{i_phase}"] = "%.4g%+.4gj" % (load_value1,load_value2)
- return self.object("load",load_name,load_dict)
- elif ConsumerClassId in ["CGSUB","Other","Industrial","Residential","NONE"]:
- # GLM bus types allowed
- load_cals_complex = load_cals("PQ",load["Phase"],connection_type,\
- load_value1,load_value2,default_load_voltage,value_type)
- load_value1 = load_cals_complex.real
- load_value2 = -load_cals_complex.imag
- if (load_value1*load_value1 + load_value2*load_value2) > 0:
- load_dict = {
- "parent" : parent_name,
- "phases" : arrangeString(phases),
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- }
- for i_phase in phase:
- # load_dict[f"constant_power_{i_phase}"] = "%.4g%+.4gj" % (load_value1,load_value2)
- load_dict[f"constant_power_{i_phase}"] = "%.4g%+.4gj" % (0.01,0.01)
- return self.object("load",load_name,load_dict)
- else:
- warning(f"{cyme_mdbname}@{network_id}: load '{load_id}' on phase '{phase}' dropped because '{ConsumerClassId}' is not a supported CYME load type")
- else:
- warning(f"{cyme_mdbname}@{network_id}: load '{load_id}' on phase '{phase}' dropped because '{cyme_devices[device_type]}' is not a supported CYME device type")
-
- # add a capacitor
- def add_capacitor(self,capacitor_id,capacitor,version,**kwargs):
- section_id = table_get(cyme_table["sectiondevice"],capacitor_id,"SectionId","DeviceNumber")
- section = table_get(cyme_table["section"],section_id,None,"SectionId")
- section = kwargs["node_info"]["cap_section"].squeeze()
- from_name = self.name(section["FromNodeId"],"node")
- to_name = self.name(section["ToNodeId"],"node")
- equipment_id = capacitor["EquipmentId"]
- if 'eqtransformer' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table["eqshuntcapacitor"],equipment_id,None,"EquipmentId")
- link_name = self.name(capacitor_id,"link")
- if link_name in self.objects.keys(): # link is no longer needed
- self.delete(link_name)
- KVARA = float(capacitor["KVARA"])
- if "SwitchedKVARA" in capacitor.keys(): # for NG MDB files
- KVARA = KVARA + float(capacitor["SwitchedKVARA"])
- KVARB = float(capacitor["KVARB"])
- if "SwitchedKVARB" in capacitor.keys(): # for NG MDB files
- KVARB = KVARB + float(capacitor["SwitchedKVARB"])
- KVARC = float(capacitor["KVARC"])
- if "SwitchedKVARC" in capacitor.keys(): # for NG MDB files
- KVARC = KVARC + float(capacitor["SwitchedKVARC"])
- if not KVARA + KVARB + KVARC > 0.0:
- warning(f"{cyme_mdbname}@{network_id}: capacitor {capacitor_id} has zero capacitance for all phases.")
- return
- KVLN = float(capacitor["KVLN"])
- ConnectionConfig = int(capacitor["ConnectionConfiguration"]) # 2 for delta and else for wye
- capacitor_name = self.name(capacitor_id,"capacitor")
- control = "MANUAL"
- self.assume(capacitor_name,"control",control,f"capacitor {fix_name(capacitor_id)} does not specify a control strategy, valid options are 'CURRENT', 'VARVOLT', 'VOLT', 'VAR', or 'MANUAL'")
-
- if "Phase" in capacitor.keys():
- phase = cyme_phase_name[int(capacitor["Phase"])]
- elif "ByPhase" in capacitor.keys():
- phase = cyme_phase_name[int(capacitor["ByPhase"])]
- else:
- warning(f"{cyme_mdbname}@{network_id}: capacitor {capacitor_id} does not specify, phase will be specified based on capacitance data")
- phase = cyme_phase_name[capacitor_phase_cals(KVARA,KVARB,KVARC)]
-
- capacitor_dict = {
- "parent" : from_name,
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- }
- phase = ""
- if KVARA > 0.0:
- CAPACITOR_A = f"{KVARA} kVA"
- SWITCH_A = "CLOSED"
- if ConnectionConfig == 2:
- phase = phase + "AB"
- else:
- phase = phase + "A"
- else:
- CAPACITOR_A = f"0 kVA"
- SWITCH_A = "OPEN"
- if KVARB > 0.0:
- CAPACITOR_B = f"{KVARB} kVA"
- SWITCH_B = "CLOSED"
- if ConnectionConfig == 2:
- phase = phase + "BC"
- else:
- phase = phase + "B"
- else:
- CAPACITOR_B = f"0 kVA"
- SWITCH_B = "OPEN"
- if KVARC > 0.0:
- CAPACITOR_C = f"{KVARC} kVA"
- SWITCH_C = "CLOSED"
- if ConnectionConfig == 2:
- phase = phase + "AC"
- else:
- phase = phase + "C"
- else:
- CAPACITOR_C = f"0 kVA"
- SWITCH_C = "OPEN"
- phase = clean_phases(phase)
- if ConnectionConfig == 0 and "N" not in phase:
- phase = phase + "N"
- elif ConnectionConfig > 0 and "N" in phase:
- phase.replace("N","")
- capacitor_dict["phases"] = phase
- capacitor_dict["capacitor_A"] = CAPACITOR_A
- capacitor_dict["capacitor_B"] = CAPACITOR_B
- capacitor_dict["capacitor_C"] = CAPACITOR_C
- capacitor_dict["switchA"] = SWITCH_A
- capacitor_dict["switchB"] = SWITCH_B
- capacitor_dict["switchC"] = SWITCH_C
- capacitor_dict["control"] = "MANUAL"
- capacitor_dict["control"] = "MANUAL"
- return self.object("capacitor",capacitor_name,capacitor_dict)
-
- # add a transformer
- def add_transformer(self,transformer_id, transformer,version):
- DeviceType = int(transformer["DeviceType"])
- equipment_id = transformer["EquipmentId"]
- equipment = None
- if 'eqtransformer' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table["eqtransformer"],equipment_id,None,"EquipmentId")
- elif 'eqtransformer' in cyme_table.keys():
- equipment = table_get(cyme_table["eqtransformer"],equipment_id,None,"EquipmentId")
- else:
- warning(f"{cyme_mdbname}@{network_id}: cannot find cyme table 'eqtransformer'.")
- if equipment is None:
- warning(f"{cyme_mdbname}@{network_id}: equipment {equipment_id} of transformer '{transformer_id}' is missing in CYME model, use default settings instead.")
- if 'eqtransformer' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table["eqtransformer"],"DEFAULT",None,"EquipmentId")
- elif 'eqtransformer' in cyme_table.keys():
- equipment = table_get(cyme_table["eqtransformer"],"DEFAULT",None,"EquipmentId")
- NominalRatingKVA = float(equipment["NominalRatingKVA"])
- PosSeqImpedancePercent = float(equipment["PosSeqImpedancePercent"])
- XRRatio = float(equipment["XRRatio"])
- try:
- PrimarySecondaryVoltag= equipment_id.split("_")[1]
- PrimaryVoltageKVLL = float(PrimarySecondaryVoltag.split("/")[0])
- if PrimaryVoltageKVLL > 50:
- PrimaryVoltageKVLL = PrimaryVoltageKVLL/1000
- SecondaryVoltageKVLL = float(PrimarySecondaryVoltag.split("/")[1])
- if SecondaryVoltageKVLL > 50:
- SecondaryVoltageKVLL = SecondaryVoltageKVLL/1000
- primary_voltage = "%.4gkV" % (PrimaryVoltageKVLL/sqrt(3.0))
- secondary_voltage = "%.4gkV" % (SecondaryVoltageKVLL/sqrt(3.0))
- except:
- warning(f"{cyme_mdbname}@{network_id}: Connot get the PrimaryVoltageKVLL/SecondaryVoltageKVLL from the name of equipment {equipment_id}. Use default settings instead.")
- PrimaryVoltageKVLL = float(equipment["PrimaryVoltageKVLL"])
- SecondaryVoltageKVLL = float(equipment["SecondaryVoltageKVLL"])
- else:
- NominalRatingKVA = float(equipment["NominalRatingKVA"])
- PrimaryVoltageKVLL = float(equipment["PrimaryVoltageKVLL"])
- SecondaryVoltageKVLL = float(equipment["SecondaryVoltageKVLL"])
- PosSeqImpedancePercent = float(equipment["PosSeqImpedancePercent"])
- XRRatio = float(equipment["XRRatio"])
- primary_voltage = "%.4gkV" % (PrimaryVoltageKVLL/sqrt(3.0))
- secondary_voltage = "%.4gkV" % (SecondaryVoltageKVLL/sqrt(3.0))
- r = XRRatio / 100.0 / sqrt(1+XRRatio**2)
- x = r * XRRatio
- nominal_rating = "%.4gkVA" % (NominalRatingKVA)
- configuration_name = self.name([nominal_rating,primary_voltage,secondary_voltage,"R%.4g"%(r),"X%4g"%(x)], "transformer_configuration")
- if primary_voltage == secondary_voltage:
- secondary_voltage = "%.4gkV" % ((SecondaryVoltageKVLL+0.001)/sqrt(3.0))
- self.assume(configuration_name,"secondary_voltage",secondary_voltage,f"transformer {fix_name(transformer_id)} primary voltage is the same as secondary voltage")
- if r == 0.0:
- r = 0.000333
- x = 0.00222
- self.assume(configuration_name,"resistance",r,f"transformer {fix_name(transformer_id)} XRRatio is zero")
- self.assume(configuration_name,"reactance",x,f"transformer {fix_name(transformer_id)} XRRatio is zero")
-
- connect_type = "WYE_WYE"
- self.assume(configuration_name,"connect_type",connect_type,f"transformer '{fix_name(transformer_id)}' does not specify connection type")
- install_type = "PADMOUNT"
- self.assume(configuration_name,"install_type",install_type,f"transformer '{fix_name(transformer_id)}' does not specify install type")
-
- self.object("transformer_configuration", configuration_name, {
- "connect_type" : "WYE_WYE",
- "install_type" : "PADMOUNT",
- "power_rating" : "%.4gkVA" % (NominalRatingKVA),
- "primary_voltage" : primary_voltage,
- "secondary_voltage" : secondary_voltage,
- "resistance" : r,
- "reactance" : x,
- })
- # add a transformer based on a link
- link_name = self.name(transformer_id,"link")
- return self.object("transformer", link_name, {
- "nominal_voltage" : None,
- "phases" : "".join(sorted(set(self.objects[link_name]["phases"] + "N"))),
- "configuration" : configuration_name,
- })
-
- # add a single phase transformer
- def add_single_transformer(self,transformer_id, transformer,version):
- for n in range(1,4):
- equipment_id = transformer[f"PhaseTransformerID{n}"]
- if isinstance(equipment_id, str):
- if 'eqtransformer' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table["eqtransformer"],equipment_id,None,"EquipmentId")
- elif 'eqtransformer' in cyme_table.keys():
- equipment = table_get(cyme_table["eqtransformer"],equipment_id,None,"EquipmentId")
- else:
- warning(f"{cyme_mdbname}@{network_id}: equipment {equipment_id} of transformer '{transformer_id}' is missing in CYME model, use default settings instead.")
- if 'eqtransformer' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table["eqtransformer"],"DEFAULT",None,"EquipmentId")
- elif 'eqtransformer' in cyme_table.keys():
- equipment = table_get(cyme_table["eqtransformer"],"DEFAULT",None,"EquipmentId")
- else:
- error(f"cannot add single transformer.", 40)
- NominalRatingKVA = float(equipment["NominalRatingKVA"])
- PrimaryVoltageKVLL = float(equipment["PrimaryVoltageKVLL"])
- SecondaryVoltageKVLL = float(equipment["SecondaryVoltageKVLL"])
- PosSeqImpedancePercent = float(equipment["PosSeqImpedancePercent"])
- XRRatio = float(equipment["XRRatio"])
- r = XRRatio / 100.0 / sqrt(1+XRRatio**2)
- x = r * XRRatio
- nominal_rating = "%.4gkVA" % (NominalRatingKVA)
- primary_voltage = "%.4gkV" % (PrimaryVoltageKVLL/sqrt(3.0))
- secondary_voltage = "%.4gkV" % (SecondaryVoltageKVLL/sqrt(3.0))
- configuration_name = self.name([nominal_rating,primary_voltage,secondary_voltage,"R%.4g"%(r),"X%4g"%(x),cyme_phase_name[n]], "transformer_configuration")
- if primary_voltage == secondary_voltage:
- secondary_voltage = "%.4gkV" % ((SecondaryVoltageKVLL+0.001)/sqrt(3.0))
- self.assume(configuration_name,"secondary_voltage",secondary_voltage,f"transformer {fix_name(transformer_id)} primary voltage is the same as secondary voltage")
- if r == 0.0:
- r = 0.000333
- x = 0.00222
- self.assume(configuration_name,"resistance",r,f"transformer {fix_name(transformer_id)} XRRatio is zero")
- self.assume(configuration_name,"reactance",x,f"transformer {fix_name(transformer_id)} XRRatio is zero")
- connect_type = "SINGLE_PHASE"
- self.assume(configuration_name,"connect_type",connect_type,f"transformer '{fix_name(transformer_id)}' does not specify connection type")
- install_type = "PADMOUNT"
- self.assume(configuration_name,"install_type",install_type,f"transformer '{fix_name(transformer_id)}' does not specify install type")
-
- self.object("transformer_configuration", configuration_name, {
- "connect_type" : connect_type,
- "install_type" : install_type,
- "power_rating" : "%.4gkVA" % (NominalRatingKVA),
- "primary_voltage" : primary_voltage,
- "secondary_voltage" : secondary_voltage,
- "resistance" : r,
- "reactance" : x,
- })
- link_name = self.name(transformer_id,"link")
- self.object("single_transformer", link_name, {
- "nominal_voltage" : None,
- "phases" : "".join(sorted(set(cyme_phase_name[n] + "N"))),
- "configuration" : configuration_name,
- })
-
- # add a regulator
- def add_regulator(self, regulator_id, regulator, version):
- equipment_id = regulator["EquipmentId"]
- equipment = None
- if 'eqregulator' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table["eqregulator"],equipment_id,None,"EquipmentId")
- elif 'eqregulator' in cyme_table.keys():
- equipment = table_get(cyme_table["eqregulator"],equipment_id,None,"EquipmentId")
- else:
- error(f"cannot find cyme table 'eqtransformer'.", 50)
- CTPrimaryRating = float(regulator["CTPrimaryRating"])
- PTRatio = float(regulator["PTRatio"])
- try:
- BandWidth = float(regulator["BandWidth"])
- except KeyError as err:
- warning(f"Regulator '{regulator_id}' doesn't define {err}, default value will be used")
- BandWidth = 2.0
- BoostPercent = float(regulator["BoostPercent"])
- BuckPercent = float(regulator["BuckPercent"])
- TapPositionA = float(regulator["TapPositionA"])
- TapPositionB = float(regulator["TapPositionB"])
- TapPositionC = float(regulator["TapPositionC"])
- ControlStatus = float(regulator["ControlStatus"])
- ReverseSensingMode = float(regulator["ReverseSensingMode"])
- ReverseThreshold = float(regulator["ReverseThreshold"])
- X = float(regulator["X"])
- Y = float(regulator["Y"])
- Status = int(regulator["Status"])
- Reversible = int(regulator["Reversible"])
-
- if equipment is None:
- if 'eqregulator' in cyme_equipment_table.keys():
- equipment = table_get(cyme_equipment_table["eqregulator"],"DEFAULT",None,"EquipmentId")
- elif 'eqregulator' in cyme_table.keys():
- equipment = table_get(cyme_table["eqregulator"],"DEFAULT",None,"EquipmentId")
- RatedKVA = float(equipment["RatedKVA"])
- RatedKVLN = float(equipment["RatedKVLN"])
- NumberOfTaps = int(equipment["NumberOfTaps"])
-
- connect_type = "WYE_WYE"
- Control = "OUTPUT_VOLTAGE"
- time_delay = "30s"
- band_center = "${GLM_NOMINAL_VOLTAGE}"
- band_width = "%.1gV" % (BandWidth)
-
- configuration_name = self.name([regulator_id,band_width,time_delay],"regulator_configuration")
- self.assume(configuration_name,"connect_type",connect_type,f"regulator '{fix_name(regulator_id)}' does not specify connection type")
- self.assume(configuration_name,"Control",Control,f"regulator '{fix_name(regulator_id)}' does not specify control type")
- self.assume(configuration_name,"time_delay",time_delay,f"regulator '{fix_name(regulator_id)}' does not specify time delay")
- self.assume(configuration_name,"band_center",band_center,f"regulator '{fix_name(regulator_id)}' does not specify band center")
-
- self.object("regulator_configuration", configuration_name, {
- "connect_type" : connect_type,
- "band_center" : band_center,
- "band_width" : band_width,
- "time_delay" : time_delay,
- "raise_taps" : "%.0f" % float(NumberOfTaps/2),
- "lower_taps" : "%.0f" % float(NumberOfTaps/2),
- "current_transducer_ratio" : "%.0f" % CTPrimaryRating,
- "power_transducer_ratio" : "%.0f" % PTRatio,
- "regulation" : "%.4f%%" % (BandWidth / (RatedKVLN*1000) * 100),
- "tap_pos_A" : "%.0f" % (TapPositionA),
- "tap_pos_B" : "%.0f" % (TapPositionB),
- "tap_pos_C" : "%.0f" % (TapPositionC),
- "Control" : Control
- })
-
- link_name = self.name(regulator_id,"link")
- regulator_name = self.name(link_name,"regulator")
- regulator_name = fix_name(regulator_name)
- sense_node = self.objects[link_name]["to"]
- self.assume(regulator_name,"sense_node",sense_node,f"regulator '{fix_name(regulator_id)}' does not specify sense node")
- return self.object("regulator", self.name(regulator_id,"link"), {
- "configuration" : configuration_name,
- "sense_node" : sense_node,
- })
-
- # add a PV system including PV panel, inverter, and meter
- def add_photovoltaic(self, photovoltaic_id, photovoltaic, version, **kwargs):
- section = kwargs["node_info"]["pv_section"].squeeze()
- all_node = kwargs["node_info"]["all_node"]
- parent_name = self.name(section["FromNodeId"],"node")
- inverter = table_get(cyme_table["dggenerationmodel"],photovoltaic_id,None,"DeviceNumber")
- rated_power = float(inverter["ActiveGeneration"])
- power_factor = float(inverter["PowerFactor"])/100
- if power_factor > 1:
- error(f"power factor for solar inverter {photovoltaic_id} is greater than 1.0", 70)
- efficiency = float(table_get(cyme_table["converter"],photovoltaic_id,"Efficiency","DeviceNumber"))/100.0
- if efficiency > 1:
- error(f"converter efficiency for solar inverter {photovoltaic_id} is greater than 1.0", 71)
- if parent_name not in self.objects.keys():
- # Definition of node "parent_name" is missing
- device_dict = kwargs["node_info"]["Device_Dicts"]
- node_links = kwargs["node_info"]["Node_Links"]
- self.add_node(parent_name[3:],node_links,device_dict,version,node_info={"all_node":all_node})
-
- pv_name = self.name(photovoltaic_id,"photovoltaic")
- inverter_name = self.name(photovoltaic_id,"inverter")
- meter_name = self.name(photovoltaic_id,"meter")
- phases = cyme_phase_name[int(photovoltaic["Phase"])]
- panel_efficiency = 0.2
- panel_area = math.ceil(13.3*rated_power/panel_efficiency) # got from NG converter, double-check is needed
- pv_dict = {
- "parent" : inverter_name,
- "generator_status" : "ONLINE",
- "generator_mode" : "SUPPLY_DRIVEN",
- "panel_type" : "MULTI_CRYSTAL_SILICON",
- "efficiency" : panel_efficiency,
- "area" : panel_area,
- "tilt_angle" : "45",
- "orientation_azimuth" : "180",
- "orientation" : "FIXED_AXIS",
- }
- self.object("solar", pv_name, pv_dict,overwrite=False)
- inverter_dict = {
- "parent" : meter_name,
- "generator_status" : "ONLINE",
- "generator_mode" : "CONSTANT_PF",
- "inverter_type" : "PWM",
- "inverter_efficiency" : efficiency,
- "phases" : phases,
- "power_factor" : power_factor,
- "rated_power" : rated_power*1000,
- }
- self.object("inverter", inverter_name, inverter_dict,overwrite=False)
- meter_dict = {
- "phases" : phases,
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- }
- self.object("meter", meter_name, meter_dict,overwrite=False)
-
- line_name = self.name(photovoltaic_id,"overhead_line")
- conductorABC_id = "DEFAULT"
- conductorN_id = "DEFAULT"
- self.add_overhead_line_conductors([conductorABC_id,conductorN_id],version)
- spacing_id = "DEFAULT"
- self.add_line_spacing(spacing_id,version)
- configuration_name = self.add_line_configuration([conductorABC_id,conductorABC_id,conductorABC_id,conductorN_id,spacing_id],version)
- return self.object("overhead_line", line_name, {
- "phases" : phases,
- "from" : parent_name,
- "to" : meter_name,
- "length" : "1 m",
- "configuration" : configuration_name,
- })
-
- def node_checks(self, node_dict, node_links, device_dict, version,**kwargs): # check node objects
- list_of_from = []
- list_of_to = []
- list_of_parent = []
- all_node = kwargs["node_info"]["all_node"]
- for name in list(self.objects.keys()): # missing node: if a node object is used in link object but not been difined
- data = self.objects[name]
- if 'from' in data.keys():
- if data["from"] not in self.objects.keys():
- node_dict[data["from"]] = self.add_node(data["from"][3:], node_links, device_dict, version, node_info={"all_node":all_node})
- if data["from"] not in list_of_from:
- list_of_from.append(data["from"])
- if 'to' in data.keys():
- if data["to"] not in self.objects.keys():
- node_dict[data["to"]] = self.add_node(data["to"][3:], node_links, device_dict, version, node_info={"all_node":all_node})
- if data["to"] not in list_of_to:
- list_of_to.append(data["to"])
- if 'parent' in data.keys():
- if data["parent"] not in self.objects.keys():
- node_dict[data["parent"]] = self.add_node(data["parent"][3:], node_links, device_dict, version, node_info={"all_node":all_node})
- if data["parent"] not in list_of_parent:
- list_of_parent.append(data["parent"])
- for name in list(self.objects.keys()):
- data = self.objects[name]
- if "class" in data.keys() and data["class"] == "node":
- if data["name"] not in list_of_from and data["name"] not in list_of_to and data["name"] not in list_of_parent: # islanded nodes
- warning(f'{cyme_mdbname}@{network_id}: node {data["name"]} is islanded.')
- self.delete(name)
- if "parent" in data.keys() and data["name"] == data["parent"]["name"]: # the object's parent is itself
- warning(f"{cyme_mdbname}@{network_id}: {name}'parent is itself.")
- self.delete(name)
- if 'from' in data.keys() and 'to' in data.keys() and data["from"] == data["to"]: # section loops back to itself
- warning(f'{cyme_mdbname}@{network_id}: section {name} loops back to itself.')
- self.delete(name)
- return node_dict
-
- def link_checks(self): # check link objects
- done = False
- while not done:
- done = True
- for name in list(self.objects.keys()):
- try:
- data = self.objects[name]
- if "class" in data.keys() and data["class"] == "link": # link needs to be collapse
- self.delete(name)
- done = False
- break
- elif "class" in data.keys() and data["class"] in ["node","load"] and "parent" in data.keys(): # don't allow grandchild cyme_table["node"]
- parent_name = data["parent"]
- parent_data = self.objects[parent_name]
- if "class" in parent_data.keys() and parent_data["class"] in ["node","load"] and "parent" in parent_data.keys():
- grandparent = parent_data["parent"]
- data["parent"] = grandparent
- done = False
- break
- except Exception as exc:
- warning(format_exception("link removal failed",name,self.objects[name]))
- self.delete(name)
- pass
-
- def section_checks(self): # remove parallel section between two nodes
- multi_g = nx.MultiGraph()
- for name in list(self.objects.keys()):
- try:
- data = self.objects[name]
- if "from" in data.keys() and "to" in data.keys():
- if data["from"] not in multi_g:
- multi_g.add_node(data["from"])
- if data["to"] not in multi_g:
- multi_g.add_node(data["to"])
- multi_g.add_edge(data["from"],data["to"],edge_name=name,edge_phase=data["phases"].replace("N",""))
- except Exception as exc:
- warning(format_exception("connection removal failed",name,self.objects[name]))
- self.delete(name)
- pass
- for u in multi_g.nodes():
- for neighbor in multi_g.neighbors(u):
- if multi_g.number_of_edges(u,neighbor)>1:
- edge_data = {}
- for edge_id in multi_g[u][neighbor].keys():
- if multi_g[u][neighbor][edge_id]["edge_name"][0:2] not in edge_data.keys():
- edge_data[multi_g[u][neighbor][edge_id]["edge_name"][0:2]] = edge_id
- else:
- warning(f"{cyme_mdbname}@{network_id}: multiple {multi_g[u][neighbor][edge_id]['edge_name'][0:2]} devices connected between {u} and {neighbor}.")
- object_name = multi_g[u][neighbor][edge_id]["edge_name"]
- if object_name in self.objects.keys():
- glm_output_print(f"object_name is deleted {object_name}.")
- self.delete(object_name)
- # RG > TF > SW > FS > OL = UL
- if "RG" in edge_data.keys(): # one of the multi-edges is regulator
- if "OL" in edge_data.keys() or "UL" in edge_data.keys(): # add a node to handle both RG and OL/UL
- name_node_added = multi_g[u][neighbor][edge_data["RG"]]["edge_name"]
- self.object("node",self.name(name_node_added,"node"),{
- "phases" : multi_g[u][neighbor][edge_data["RG"]]["edge_phase"] + "N",
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- "bustype" : "PQ",
- })
- RG_object_name = multi_g[u][neighbor][edge_data["RG"]]["edge_name"]
- self.objects[RG_object_name]["to"] = self.name(name_node_added,"node")
- if "OL" in edge_data.keys():
- line_object_name = multi_g[u][neighbor][edge_data["OL"]]["edge_name"]
- else:
- line_object_name = multi_g[u][neighbor][edge_data["UL"]]["edge_name"]
- self.objects[line_object_name]["from"] = self.name(name_node_added,"node")
- for key in edge_data.keys():
- if key != "RG" and key != "OL" and key != "UL":
- object_name = multi_g[u][neighbor][edge_data[key]]["edge_name"]
- if object_name in self.objects.keys():
- self.delete(object_name)
- elif "TF" in edge_data.keys(): # one of the multi-edges is transformer
- if "OL" in edge_data.keys() or "UL" in edge_data.keys(): # add a node to handle both RG and OL/UL
- name_node_added = multi_g[u][neighbor][edge_data["TF"]]["edge_name"]
- TF_object_name = multi_g[u][neighbor][edge_data["TF"]]["edge_name"]
- TF_config_name = self.objects[TF_object_name]["configuration"]
- self.object("node",self.name(name_node_added,"node"),{
- "phases" : multi_g[u][neighbor][edge_data["TF"]]["edge_phase"] + "N",
- "nominal_voltage" : f"{self.objects[TF_config_name]['secondary_voltage']}",
- "bustype" : "PQ",
- })
-
- self.objects[TF_object_name]["to"] = self.name(name_node_added,"node")
- if "OL" in edge_data.keys():
- line_object_name = multi_g[u][neighbor][edge_data["OL"]]["edge_name"]
- else:
- line_object_name = multi_g[u][neighbor][edge_data["UL"]]["edge_name"]
- self.objects[line_object_name]["from"] = self.name(name_node_added,"node")
- for key in edge_data.keys():
- if key != "TF" and key != "OL" and key != "UL":
- object_name = multi_g[u][neighbor][edge_data[key]]["edge_name"]
- if object_name in self.objects.keys():
- self.delete(object_name)
- elif "SW" in edge_data.keys():
- # one of the multi-edges is switch
- if "OL" in edge_data.keys() or "UL" in edge_data.keys(): # add a node to handle both RG and OL/UL
- name_node_added = multi_g[u][neighbor][edge_data["SW"]]["edge_name"]
- self.object("node",self.name(name_node_added,"node"),{
- "phases" : multi_g[u][neighbor][edge_data["SW"]]["edge_phase"] + "N",
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- "bustype" : "PQ",
- })
- SW_object_name = multi_g[u][neighbor][edge_data["SW"]]["edge_name"]
- self.objects[SW_object_name]["to"] = self.name(name_node_added,"node")
- if "OL" in edge_data.keys():
- line_object_name = multi_g[u][neighbor][edge_data["OL"]]["edge_name"]
- else:
- line_object_name = multi_g[u][neighbor][edge_data["UL"]]["edge_name"]
- self.objects[line_object_name]["from"] = self.name(name_node_added,"node")
- for key in edge_data.keys():
- if key != "SW" and key != "OL" and key != "UL":
- object_name = multi_g[u][neighbor][edge_data[key]]["edge_name"]
- if object_name in self.objects.keys():
- self.delete(object_name)
- elif "FS" in edge_data.keys():
- # one of the multi-edges is fuse
- if "OL" in edge_data.keys() or "UL" in edge_data.keys(): # add a node to handle both RG and OL/UL
- name_node_added = multi_g[u][neighbor][edge_data["FS"]]["edge_name"]
- self.object("node",self.name(name_node_added,"node"),{
- "phases" : multi_g[u][neighbor][edge_data["FS"]]["edge_phase"] + "N",
- "nominal_voltage" : "${GLM_NOMINAL_VOLTAGE}",
- "bustype" : "PQ",
- })
- FS_object_name = multi_g[u][neighbor][edge_data["FS"]]["edge_name"]
- self.objects[FS_object_name]["to"] = self.name(name_node_added,"node")
- if "OL" in edge_data.keys():
- line_object_name = multi_g[u][neighbor][edge_data["OL"]]["edge_name"]
- else:
- line_object_name = multi_g[u][neighbor][edge_data["UL"]]["edge_name"]
- self.objects[line_object_name]["from"] = self.name(name_node_added,"node")
- for key in edge_data.keys():
- if key != "FS" and key != "OL" and key != "UL":
- object_name = multi_g[u][neighbor][edge_data[key]]["edge_name"]
- if object_name in self.objects.keys():
- self.delete(object_name)
- elif "OL" in edge_data.keys() or "UL" in edge_data.keys():
- for key in edge_data.keys():
- if "OL" in edge_data.keys() and key != "OL":
- object_name = multi_g[u][neighbor][edge_data[key]]["edge_name"]
- if object_name in self.objects.keys():
- self.delete(object_name)
- elif "UL" in edge_data.keys() and key != "UL":
- object_name = multi_g[u][neighbor][edge_data[key]]["edge_name"]
- if object_name in self.objects.keys():
- self.delete(object_name)
- else:
- for key in edge_data.keys():
- object_name = multi_g[u][neighbor][edge_data[key]]["edge_name"]
- print(self.objects[object_name])
- error(f"CYME model has unsupported duplicate connections between {u} and {neighbor}", 60)
-
- def phase_checks(self): # check phase dismatch
- check_done = False
- while not check_done:
- check_done = True
- for name in list(self.objects.keys()):
- data = self.objects[name]
- target_node_name = None
- target_device_name = None
- if "from" in data.keys() and "to" in data.keys():
- target_device_name = data["name"]
- elif "parent" in data.keys() and data["class"] != "solar":
- target_node_name = data["parent"]
- if target_device_name:
- if len(self.objects[data["from"]]["phases"]) < len(self.objects[data["to"]]["phases"]):
- for phase in self.objects[data["from"]]["phases"].replace("N",""):
- if phase not in self.objects[data["to"]]["phases"]:
- warning(f"{cyme_mdbname}@{network_id} phase dismatch: {data['from']} has {self.objects[data['from']]['phases']} \
- but {data['to']} has {self.objects[data['to']]['phases']}")
- else:
- for phase in self.objects[data["to"]]["phases"].replace("N",""):
- if phase not in self.objects[data["from"]]["phases"]:
- warning(f"{cyme_mdbname}@{network_id} phase dismatch: {data['from']} has {self.objects[data['from']]['phases']} \
- but {data['to']} has {self.objects[data['to']]['phases']}")
- break
- for phase in data["phases"].replace("N",""):
- if phase not in self.objects[data["from"]]["phases"] and phase not in self.objects[data["to"]]["phases"]:
- warning(f"{cyme_mdbname}@{network_id} phase dismatch: section {data['name']} has {data['phases']} \
- but {data['to']} has {self.objects[data['to']]['phases']} and {data['from']} has {self.objects[data['from']]['phases']}")
- break
- if target_node_name:
- target_node = self.objects[target_node_name]
- target_node_phases = target_node["phases"].replace("N","")
- for phase in data["phases"].replace("N",""):
- if phase not in target_node_phases:
- warning(f"{cyme_mdbname}@{network_id} phase dismatch: parent {target_node_name} has {target_node_phases} but child {name} has {data['phases']}")
- if phase_check_fix:
- self.objects[name]["phases"] = self.objects[name]["phases"].replace(phase,"")
- if len(self.objects[name]["phases"]) == 0:
- warning(f"{cyme_mdbname}@{network_id} phase problem: {name} phases is None")
-
- def voltage_checks(self, nominal_voltage): # Check transformer primary/secondary voltage
- check_done = False
- check_done_count = 0
- while not check_done:
- check_done = True
- check_done_count += 1
- if check_done_count > 1e8:
- error(f"the voltage_checks may suck in a dead loop.", 65)
- for name, data in self.objects.items():
- if "class" in data.keys() and (data["class"] == "transformer" or data["class"] == "single_transformer"):
- config_name = data["configuration"]
- config_data = self.objects[config_name]
- from_node_name = data["from"]
- to_node_name = data["to"]
- from_node_voltage = self.objects[from_node_name]["nominal_voltage"]
- to_node_voltage = self.objects[to_node_name]["nominal_voltage"]
- primary_voltage = config_data["primary_voltage"]
- secondary_voltage = config_data["secondary_voltage"]
- if from_node_voltage == "${GLM_NOMINAL_VOLTAGE}" and primary_voltage.replace("kV","") != nominal_voltage:
- warning(f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has primary voltage as {primary_voltage} but node {from_node_name} nominal voltage is {nominal_voltage}kV.")
- if voltage_check_fix:
- check_done = False
- self.objects[from_node_name]["nominal_voltage"] = primary_voltage
- elif from_node_voltage != "${GLM_NOMINAL_VOLTAGE}" and float(primary_voltage.replace("kV","")) != float(from_node_voltage.replace("kV","")):
- warning(f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has primary voltage as {primary_voltage} but node {from_node_name} nominal voltage is {from_node_voltage}.")
- if voltage_check_fix:
- check_done = False
- self.objects[from_node_name]["nominal_voltage"] = primary_voltage
- if to_node_voltage == "${GLM_NOMINAL_VOLTAGE}" and secondary_voltage.replace("kV","") != nominal_voltage:
- warning(f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has secondary voltage as {secondary_voltage} but node {to_node_name} nominal voltage is {nominal_voltage}kV.")
- if voltage_check_fix:
- check_done = False
- self.objects[to_node_name]["nominal_voltage"] = secondary_voltage
- elif to_node_voltage != "${GLM_NOMINAL_VOLTAGE}" and float(secondary_voltage.replace("kV","")) != float(to_node_voltage.replace("kV","")):
- warning(f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has secondary voltage as {secondary_voltage} but node {to_node_name} nominal voltage is {to_node_voltage}.")
- if voltage_check_fix:
- check_done = False
- self.objects[to_node_name]["nominal_voltage"] = secondary_voltage
- elif "from" in data.keys() and "to" in data.keys():
- from_node_name = data["from"]
- to_node_name = data["to"]
- from_node_voltage = self.objects[from_node_name]["nominal_voltage"]
- to_node_voltage = self.objects[to_node_name]["nominal_voltage"]
- if from_node_voltage != to_node_voltage:
- warning(f"{cyme_mdbname}@{network_id} voltage mismatch: node {from_node_name} is {from_node_voltage} but node {to_node_name} is {to_node_voltage}.")
- if voltage_check_fix:
- check_done = False
- self.objects[to_node_name]["nominal_voltage"] = self.objects[from_node_name]["nominal_voltage"]
- elif "parent" in data.keys() and data["class"] not in ["solar", "inverter"]:
- parent_name = data["parent"]
- parent_data = self.objects[parent_name]
- if data["nominal_voltage"] != parent_data["nominal_voltage"]:
- warning(f"{cyme_mdbname}@{network_id} voltage mismatch: parent {parent_name} has voltage as {parent_data['nominal_voltage']} but child {name} has {data['nominal_voltage']}.")
- if voltage_check_fix:
- check_done = False
- old_voltage = self.objects[name]["nominal_voltage"]
- self.objects[name]["nominal_voltage"] = parent_data["nominal_voltage"]
- if data["class"] == "load":
- warning(f"{cyme_mdbname}@{network_id}: load {fix_name(name)} nominal_voltage changes from {old_voltage} to {parent_data['nominal_voltage']}.")
-
-
- def object_checks(self): # Check conversion
- del_nom_volt_list = ['overhead_line', 'underground_line', 'regulator', 'transformer', 'switch', 'fuse', 'ZIPload', 'diesel_dg','triplex_line_conductor','recorder','inverter','solar','triplex_line']
- for name, data in self.objects.items():
- if not "name" in data.keys():
- warning("%s: object does not have a name, object data [%s]" % (self.filename,data))
- elif not "class" in data.keys():
- warning("%s: object '%s' does not have a class" % (self.filename,data["name"]))
- elif data["class"] in ["link","powerflow_object","line"]:
- print(self.objects[name])
- warning("%s: object '%s' uses abstract-only class '%s'" % (self.filename,data["name"],data["class"]))
- elif data["class"] in del_nom_volt_list and 'nominal_voltage' in data.keys():
- del self.objects[name]["nominal_voltage"]
-
- def name_check(self): # Check object name
- for name_compared, data_compared in self.objects.items():
- for name, data in self.objects.items():
- if name != name_compared:
- for key in data.keys():
- if data[key] == name_compared:
- data[key] = fix_name(name_compared)
- data_compared['name'] = fix_name(name_compared)
-
-def cyme_extract_9(network_id,network,conversion_info):
-
- output_file_name = conversion_info['output_file_name']
- version = conversion_info['version']
-
- creation_time = int(network["CreationTime"])
- last_change = int(network["LastChange"])
- load_factor = float(network["LoadFactor"])
- if single_file:
- glmname = os.path.abspath(f"{output_folder}/{output_file_name}.glm")
- else:
- glmname = os.path.abspath(f"{output_folder}/{output_file_name}_{network_id}.glm")
-
- glm = GLM(glmname,"w")
- # glm.comment(f"Automatically generated by {git_project}/postproc/write_glm.py")
-
- # glm.blank()
- # glm.comment("","Application information","")
- # glm.define("APP_COMMAND",app_command)
- # glm.define("APP_VERSION",app_version)
-
- # glm.blank()
- # glm.comment("","Git information","")
- # glm.define("GIT_PROJECT",git_project)
- # glm.define("GIT_COMMIT",git_commit)
- # glm.define("GIT_BRANCH",git_branch)
-
- # glm.blank()
- # glm.comment("","GLM creation context","")
- # glm.define("GLM_PATHNAME",glmname)
- # glm.define("GLM_CREATED",dt.datetime.utcnow().isoformat())
- # glm.define("GLM_USER",os.getenv("USER"))
- # glm.define("GLM_WORKDIR",os.getenv("PWD"))
- # glm.define("GLM_LANG",os.getenv("LANG"))
-
- # # settings from model
- # glm.blank()
- # glm.comment("","CYME model information","")
- # glm.define("CYME_MDBNAME",cyme_mdbname)
- # glm.define("CYME_VERSION",version)
- # glm.define("CYME_CREATED",dt.datetime.fromtimestamp(creation_time).isoformat())
- # glm.define("CYME_MODIFIED",dt.datetime.fromtimestamp(last_change).isoformat())
- # glm.define("CYME_LOADFACTOR",load_factor)
- # glm.define("CYME_NETWORKID",network_id)
-
- # settings from config.csv
- glm.blank()
- glm.comment("","Settings from 'config.csv'","")
- define = settings["GLM_DEFINE"].split("=")
- if type(define) is list and len(define) > 1:
- glm.define(define[0].strip(),"=".join(define[1:]).strip())
- feeder_kVLN = feeder_voltage_find(network_id,cyme_table)
- if feeder_kVLN is not None:
- glm.comment("GLM_NOMINAL_VOLTAGE found in network MDB is used")
- settings["GLM_NOMINAL_VOLTAGE"] = feeder_kVLN + ' kV'
- glm.define("GLM_NOMINAL_VOLTAGE",settings["GLM_NOMINAL_VOLTAGE"])
- elif settings["GLM_NOMINAL_VOLTAGE"]:
- glm.comment("GLM_NOMINAL_VOLTAGE found in config.csv is used")
- glm.define("GLM_NOMINAL_VOLTAGE",settings["GLM_NOMINAL_VOLTAGE"])
- feeder_kVLN = fix_unit(settings["GLM_NOMINAL_VOLTAGE"], "kV")
- else:
- if settings["GLM_INCLUDE"]: # cannot verify setting in GLM_INCLUDE until run in gridlabd
- glm.ifndef("GLM_NOMINAL_VOLTAGE",lambda:glm.error("GLM_NOMINAL_VOLTAGE must be defined in either 'config.csv' or the GLM_INCLUDE file"))
- else:
- error("GLM_NOMINAL_VOLTAGE must be defined in either 'config.csv' or the GLM_INCLUDE file")
- default_load_voltage = re.match("\d+[\.]?[\d+]*", settings["GLM_NOMINAL_VOLTAGE"]).group(0)
- if settings["GLM_INCLUDE"]:
- for include in settings["GLM_INCLUDE"].split():
- glm.include(include.strip())
- else:
- glm.blank()
- glm.comment("","default clock settings","")
- glm.clock({"timezone":"PST+8PDT", "starttime":"2020-01-01T00:00:00+08:00", "stoptime":"2020-01-01T00:05:00+08:00"})
-
- glm.blank()
- glm.comment("","Modules","")
- glm.module("powerflow",{"solver_method":"NR"})
- glm.module("generators")
-
- node_dict = {}
- device_dict = {}
- node_links = {}
-
- all_section_device = table_find(cyme_table["sectiondevice"],NetworkId=network_id)
- all_section = table_find(cyme_table["section"],NetworkId=network_id)
- all_load = table_find(cyme_table["load"],NetworkId=network_id)
- all_node = table_find(cyme_table["node"],NetworkId=network_id)
-
- # node graph data
- if "nodetag" in cyme_table.keys():
- for index, node in table_find(cyme_table["nodetag"],NetworkId=network_id).iterrows():
- node_id = node['NodeId']
- node_dict[node_id] = [] # node dictionary
- for node_id, node in table_find(cyme_table["node"],NetworkId=network_id).iterrows():
- node_id = node['NodeId']
- node_links[node_id] = [] # incident links
- else:
- for index, node in table_find(cyme_table["node"],NetworkId=network_id).iterrows():
- node_id = node['NodeId']
- node_links[node_id] = [] # incident links
- node_dict[node_id] = [] # node dictionary
-
- glm.blank()
- glm.comment("","Objects","")
-
- # links
- for index, section in table_find(cyme_table["section"],NetworkId=network_id).iterrows():
- section_id = section['SectionId']
- links = glm.add("link",section_id,section, version=5020, node_links=node_links)
- if links:
- device_dict.update(links)
-
- # cyme_table["node"]
- for node_id in node_dict.keys():
- # only network node and substantiation will be added
- if table_find(cyme_table["node"],NodeId=node_id).iloc[0]["ComponentMask"] != "0":
- node_dict[node_id] = glm.add_node(node_id, node_links, device_dict, version=5020, node_info={"all_node":all_node})
-
- # overhead lines
- try:
- for cyme_id, cyme_data in table_find(cyme_table["overheadbyphase"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("overhead_line_phase", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "overheadbyphase".')
-
- # unbalanced overhead lines
- try:
- for cyme_id, cyme_data in table_find(cyme_table["overheadlineunbalanced"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("overhead_line_unbalanced", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table \'overheadlineunbalanced\'.')
-
- # overhead lines
- try:
- for cyme_id, cyme_data in table_find(cyme_table["overheadline"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("overhead_line", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "overheadline".')
-
- # underground lines
- try:
- for cyme_id, cyme_data in table_find(cyme_table["undergroundline"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("underground_line", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "undergroundline".')
-
- # load
- try:
- for cyme_id, cyme_data in table_find(cyme_table["customerload"],NetworkId=network_id).iterrows():
- section_id = all_section_device[all_section_device["DeviceNumber"] == cyme_data['DeviceNumber']]["SectionId"].values
- load_section = all_section[all_section["SectionId"] == section_id[0]]
- connection_type = int(all_load[all_load["DeviceNumber"] == cyme_data['DeviceNumber']]['ConnectionConfiguration'])
- cyme_id = cyme_data['DeviceNumber']
- glm.add("load", cyme_id, cyme_data, version=5200, node_info={"Node_Links":node_links, "Device_Dicts": device_dict, "load_section": load_section, "connection_type": connection_type, "all_node": all_node,"load_voltage" : default_load_voltage})
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "customerload".')
-
- # transformer
- try:
- for cyme_id, cyme_data in table_find(cyme_table["transformer"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("transformer", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "transformer".')
-
- # transformerbyphase
- try:
- for cyme_id, cyme_data in table_find(cyme_table["transformerbyphase"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("single_transformer", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "transformerbyphase".')
-
- # regulator
- try:
- for cyme_id, cyme_data in table_find(cyme_table["regulator"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("regulator", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "regulator".')
-
- # capacitor
- try:
- for cyme_id, cyme_data in table_find(cyme_table["shuntcapacitor"],NetworkId=network_id).iterrows():
- section_id = all_section_device[all_section_device["DeviceNumber"] == cyme_data['DeviceNumber']]["SectionId"].values
- cap_section = all_section[all_section["SectionId"] == section_id[0]]
- cyme_id = cyme_data['DeviceNumber']
- glm.add("capacitor", cyme_id, cyme_data, version=5020,node_info={"Node_Links":node_links, "Device_Dicts": device_dict, "cap_section": cap_section})
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "shuntcapacitor".')
-
- # switches
- try:
- for cyme_id, cyme_data in table_find(cyme_table["switch"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("switch", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "switch".')
-
- # breaker
- try:
- for cyme_id, cyme_data in table_find(cyme_table["breaker"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("breaker", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "breaker".')
-
- # recloser
- try:
- for cyme_id, cyme_data in table_find(cyme_table["recloser"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("recloser", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "recloser".')
-
- # fuse
- try:
- for cyme_id, cyme_data in table_find(cyme_table["fuse"],NetworkId=network_id).iterrows():
- cyme_id = cyme_data['DeviceNumber']
- glm.add("fuse", cyme_id, cyme_data, version=5020)
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "fuse".')
-
- # photovoltaic
- try:
- for cyme_id, cyme_data in table_find(cyme_table["photovoltaic"],NetworkId=network_id).iterrows():
- section_id = all_section_device[all_section_device["DeviceNumber"] == cyme_data['DeviceNumber']]["SectionId"].values
- pv_section = all_section[all_section["SectionId"] == section_id[0]]
- cyme_id = cyme_data['DeviceNumber']
- glm.add("photovoltaic", cyme_id, cyme_data, version=5020, node_info={"Node_Links":node_links, "Device_Dicts": device_dict, "pv_section": pv_section, "all_node": all_node})
- except Exception as err:
- exception_type = type(err).__name__
- warning(f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "photovoltaic".')
-
- # network senity checks
- node_dict = glm.node_checks(node_dict,node_links,device_dict,version=5200,node_info={"all_node":all_node})
- glm.link_checks()
- glm.section_checks()
- glm.phase_checks()
- glm.object_checks()
- glm.voltage_checks(feeder_kVLN)
- glm.name_check()
-
- # generate coordinate file
- if geodata_file:
- df_node = pd.DataFrame.from_dict(node_geodata)
- df_node = df_node.T
- df_node.drop(df_node[df_node[:]["NotworkID"] != network_id].index,inplace=True)
- df_node = df_node.drop(["NotworkID"], axis=1)
- df_node.to_csv(f'{output_folder}/{geodata_file}', index = False, header=True)
-
- glm.close()
-
-
-def convert(input_file: str ,output_file: str =None, options: dict[str, Any]={}):
- """Convert a CYME MDB file to GLM
-
- Parameters:
- input_file (str) input MDB file name
- output_file (str) output GLM file name
- options (dict) options to define as globals in model
- """
-
- global data_folder
- global input_folder
- global output_folder
- global network_select
- global equipment_file
- global config_file
- global modify_file
- global settings
- global cyme_table
- global cyme_equipment_table
- global default_load_voltage
- global network_id
- global cyme_mdbname
- global geodata_file
- global single_file
- global WARNING
- global DEBUG
- global QUIET
- global VERBOSE
- global voltage_check_fix
- global phase_check_fix
- global error_count
- global GLM_error_file
- global GLM_warning_file
- global GLM_output_file
- global node_geodata
-
-
- # Configure options and paths
-
- if options:
- for opt, arg in options.items():
- if opt in ("c","config"):
- if arg and arg != True:
- config_file = arg.strip()
- else:
- print(config)
- elif opt in ("t","cyme-tables"):
- print(" ".join(cyme_tables_required))
- sys.exit(0)
- elif opt in ("d", "data-dir"):
- data_folder = arg.strip()
- elif opt in ("m", "modify"):
- modify_file = arg.strip()
- elif opt in ("n", "network-ID"):
- # only extract the selected network
- network_select = arg.split(" ")
- elif opt in ("e", "equipment-mdb"):
- equipment_file = arg.strip()
- elif opt in ("C", "coordinateCSV"):
- geodata_file = arg.strip()
- else:
- error(f"{opt}={arg} is not a valid option", 5);
- input_folder = os.path.dirname(os.path.abspath(input_file))
- input_file_name = os.path.basename(input_file).split('.')[0]
- if output_file:
- output_folder = os.path.dirname(os.path.abspath(output_file))
- output_file_name = os.path.basename(output_file).split('.')[0]
- else:
- output_file_name = input_file_name
- output_folder = input_folder
- output_file = os.path.join(output_folder,output_file_name+'.glm')
- if not network_select:
- single_file = True
-
- # converter input MBD to CSV tables
- if not data_folder:
- data_folder = f"/tmp/gridlabd/mdb-cyme2glm/{input_file_name}"
- cyme_table = mdb2csv(input_file,data_folder,cyme_tables_required,'non-empty')
- cyme_mdbname = data_folder.split("/")[-1]
- if equipment_file != None: # if equipment MDB is provided
- equipment_data_folder = f'{data_folder}/cyme_equipment_tables'
- cyme_equipment_table = mdb2csv(equipment_file,equipment_data_folder,cyme_tables_required,'non-empty')
- else:
- cyme_equipment_table = {}
-
- # Load user configuration
- if config_file:
- settings = pd.read_csv(config_file, dtype=str,
- names=["name","value"],
- comment = "#",
- ).set_index("name")
- else:
- default_config_data = {
- "name" : ["GLM_NOMINAL_VOLTAGE", "GLM_MODIFY", "GLM_WARNINGS"],
- "value" : ["2.40178 kV", "modify.csv", "stdout"]
- }
- settings = pd.DataFrame(data=default_config_data).set_index("name")
- warning(f"No configuration file, will use default configurations")
- for name, values in settings.iterrows():
- if name in config.index:
- config["value"][name] = values[0]
- settings = config["value"]
-
- GLM_output_file = open(settings["GLM_OUTPUT"],"w")
- GLM_error_file = open(settings["ERROR_OUTPUT"],"a")
- GLM_warning_file = open(settings["WARNING_OUTPUT"],"a")
- default_load_voltage = None
- voltage_check_fix = True if settings["GLM_VOLTAGE_FIX"].lower() == "true" else False
- phase_check_fix = True if settings["GLM_PHASE_FIX"].lower() == "true" else False
- WARNING = True if settings["WARNING"].lower() == "true" else False
- DEBUG = True if settings["DEBUG"].lower() == "true" else False
- QUIET = True if settings["QUIET"].lower() == "true" else False
- VERBOSE = True if settings["VERBOSE"].lower() == "true" else False
-
-
- # Prepare Cyme converter settings
- cyme_extracter = {
- "90000" : cyme_extract_9, # CYME version 9 database
- "82000" : cyme_extract_9, # CYME version 8 database
- "50401" : cyme_extract_9, # CYME version 5 database
- }
- cyme_extracter["default"] = cyme_extracter["90000"]
-
- version = cyme_table["schemaversion"].loc[0]['Version']
- network_count = 0
- conversion_info = {
- "input_folder" : input_folder,
- "output_file_name" : output_file_name,
- 'version' : version,
- }
-
- #
- for index, network in cyme_table["network"].iterrows():
- network_id = network['NetworkId']
- if not re.match(settings["GLM_NETWORK_MATCHES"],network_id):
- continue
- else:
- network_count += 1
- if network_select != None and network_id not in network_select:
- pass
- else:
- found = False
- for key, extractor in cyme_extracter.items():
- if key[0] == version[0]:
- extractor(network_id,network,conversion_info)
- found = True
- if not found:
- raise Exception(f"CYME model version {version} is not supported")
-
-
-
-if __name__ == '__main__':
- testfile = 'autotest/IEEE-123-cyme.mdb'
- if os.path.exists(testfile):
- config.annotated_model = True
- convert(testfile)
+ prefix = {
+ # known powerflow class in gridlabd
+ "billdump": "BD_",
+ "capacitor": "CA_",
+ "currdump": "CD_",
+ "emissions": "EM_",
+ "fault_check": "FC_",
+ "frequency_gen": "FG_",
+ "fuse": "FS_",
+ "impedance_dump": "ID_",
+ "inverter": "IN_",
+ "line": "LN_",
+ "line_configuration": "LC_",
+ "line_sensor": "LS_",
+ "line_spacing": "LG_",
+ "link": "LK_",
+ "load": "LD_",
+ "load_tracker": "LT_",
+ "meter": "ME_",
+ "motor": "MO_",
+ "node": "ND_",
+ "overhead_line": "OL_",
+ "overhead_line_conductor": "OC_",
+ "photovoltaic": "PV_",
+ "pole": "PO_",
+ "pole_configuration": "PC_",
+ "power_metrics": "PM_",
+ "powerflow_library": "PL_",
+ "powerflow_object": "PO_",
+ "pqload": "PQ_",
+ "recloser": "RE_",
+ "regulator": "RG_",
+ "regulator_configuration": "RC_",
+ "restoration": "RS_",
+ "sectionalizer": "SE_",
+ "series_reactor": "SR_",
+ "substation": "SS_",
+ "switch": "SW_",
+ "switch_coordinator": "SC_",
+ "transformer": "TF_",
+ "transformer_configuration": "TC_",
+ "triplex_line": "XL_",
+ "triplex_line_conductor": "XC_",
+ "triplex_line_configuration": "XG_",
+ "triplex_load": "XD_",
+ "triplex_meter": "XM_",
+ "triplex_node": "XN_",
+ "underground_line": "UL_",
+ "underground_line_conductor": "UC_",
+ "vfd": "VF_",
+ "volt_var_control": "VV_",
+ "voltdump": "VD_",
+ }
+
+ def __init__(self, file, mode="w"):
+ self.filename = file
+ self.fh = open(file, mode)
+ self.objects = {}
+ self.assumptions = []
+ self.refcount = {}
+
+ # def __del__(self):
+ # if self.object:
+ # self.error("glm object was deleted before objects were output")
+
+ def name(self, name, oclass=None):
+ if type(name) is list: # composite name
+ # name = "_".join(name).replace(".","").replace(":","")[0:63] # disallow special name characters
+ name = "_".join(name)[0:63] # disallow special name characters
+ if oclass: # name prefix based on class
+ if not oclass in self.prefix.keys(): # name prefix not found
+ prefix = f"Z{len(self.prefix.keys())}_"
+ self.prefix[oclass] = prefix
+ warning(
+ f"{cyme_mdbname}@{network_id}: class '{oclass}' is not a known gridlabd powerflow class, using prefix '{prefix}' for names"
+ )
+ else:
+ prefix = self.prefix[oclass]
+ name = prefix + name
+ elif "0" <= name[0] <= "9": # fix names that start with digits
+ name = "_" + name
+ # return name.replace(" ","_").replace("-","_").replace(".","")[0:63] # remove white spaces from names
+ return name[0:63] # remove white spaces from names
+
+ def write(self, line):
+ print(line, file=self.fh)
+
+ def blank(self):
+ self.write("")
+
+ def print(self, message):
+ self.write(f"#print {message}")
+
+ def warning(self, message):
+ self.write(f"#warning {message}")
+
+ def error(self, message):
+ self.write(f"#error {message}")
+
+ def comment(self, *lines):
+ for line in lines:
+ self.write(f"// {line}")
+
+ def set(self, name, value):
+ self.write(f"#set {name}={value}")
+
+ def define(self, name, value):
+ self.write(f"#define {name}={value}")
+
+ def include(self, name, brackets='""'):
+ self.write(f"#include {brackets[0]}{name}{brackets[1]}")
+
+ def module(self, name, parameters={}):
+ if not parameters:
+ self.write(f"module {name};")
+ else:
+ self.write(f"module {name}")
+ self.write("{")
+ for tag, value in parameters.items():
+ if type(value) is str:
+ self.write(f'\t{tag} "{value}";')
+ else:
+ self.write(f"\t{tag} {value};")
+ self.write("}")
+
+ def clock(self, parameters={}):
+ if not parameters:
+ error(f"clock needs parameters", 1)
+ else:
+ self.write(f"clock")
+ self.write("{")
+ for tag, value in parameters.items():
+ if tag in ["timezone", "starttime", "stoptime"]:
+ self.write(f'\t{tag} "{value}";')
+ else:
+ raise Exception(f"module clock not support parameter {tag}")
+ self.write("}")
+
+ def ifdef(self, name, call):
+ self.write(f"#ifdef {name}")
+ call()
+ self.write("#endif")
+
+ def ifndef(self, name, call):
+ self.write(f"#ifndef {name}")
+ call()
+ self.write("#endif")
+
+ def ifexist(self, name, call):
+ self.write(f"#ifexist {name}")
+ call()
+ self.write("#endif")
+
+ def object(self, oclass, name, parameters, overwrite=True):
+ if name not in self.objects.keys():
+ obj = {"name": name}
+ self.objects[name] = obj
+ else:
+ obj = self.objects[name]
+ if (
+ "class" in obj.keys()
+ and obj["class"] == "link"
+ and oclass
+ in [
+ "fuse",
+ "underground_line",
+ "switch",
+ "overhead_line",
+ "transformer",
+ "single_transformer",
+ "regulator",
+ ]
+ ):
+ # if obj is created based on a link object
+ if oclass == "single_transformer":
+ new_name = self.name(
+ name + f"_{parameters['phases']}", "transformer"
+ ) # new name
+ oclass = "transformer"
+ else:
+ new_name = self.name(name, oclass) # new name
+ new_obj = {"name": new_name}
+ self.objects[new_name] = new_obj
+ for key, value in obj.items():
+ if key != "name":
+ new_obj[key] = value
+ for key, value in parameters.items():
+ if not overwrite and key in new_obj.keys() and new_obj[key] != value:
+ error(
+ f"object property '{key}={new_obj[key]}' merge conflicts with '{key}={value}'",
+ 2,
+ )
+ if value == None and key in new_obj.keys():
+ del new_obj[key]
+ else:
+ new_obj[key] = value
+ new_obj["class"] = oclass
+ if "nominal_voltage" in new_obj.keys() and (
+ new_obj["class"] == "underground_line"
+ or new_obj["class"] == "overhead_line"
+ ):
+ del new_obj["nominal_voltage"]
+ if "phases" in new_obj.keys() and "N" not in new_obj["phases"]:
+ new_obj["phases"] = new_obj["phases"] + "N"
+ if new_name in self.refcount.keys():
+ self.refcount[new_name] += 1
+ else:
+ self.refcount[new_name] = 1
+ return new_obj
+ else:
+ for key, value in parameters.items():
+ if not overwrite and key in obj.keys() and obj[key] != value:
+ error(
+ f"object property '{key}={obj[key]}' merge conflicts with '{key}={value}'",
+ 3,
+ )
+ if value == None and key in obj.keys():
+ del obj[key]
+ else:
+ obj[key] = value
+ obj["class"] = oclass
+ if name in self.refcount.keys():
+ self.refcount[name] += 1
+ else:
+ self.refcount[name] = 1
+ return obj
+
+ def delete(self, name):
+ if self.refcount[name] == 1:
+ del self.objects[name]
+ elif self.refcount[name] > 1:
+ self.refcount[name] -= 1
+
+ def modify(self, object, property, value, comment=""):
+ if comment:
+ comment = " // " + str(comment)
+ elif not type(comment) is str:
+ comment = ""
+ if type(value) is str:
+ self.write(f'modify {object}.{property} "{value}";{comment}')
+ else:
+ self.write(f"modify {object}.{property} {value};{comment}")
+
+ def assume(self, objname, propname, value, remark=""):
+ self.assumptions.append([fix_name(objname), propname, value, remark])
+
+ def close(self):
+ # objects
+ if self.objects:
+ for name, parameters in self.objects.items():
+ self.write(f"object {parameters['class']}")
+ self.write("{")
+ for tag, value in parameters.items():
+ if tag != "class":
+ if type(value) is str:
+ self.write(f'\t{tag} "{value}";')
+ else:
+ self.write(f"\t{tag} {value};")
+ self.write("}")
+ self.objects = {}
+
+ # assumptions
+ if self.assumptions:
+ if settings["GLM_ASSUMPTIONS"] == "save":
+ filename = f"{settings['GLM_NETWORK_PREFIX']}{cyme_mdbname}_{network_id}_assumptions.glm"
+ with open(f"{output_folder}/{filename}", "w") as fh:
+ print(
+ "// Assumptions for GLM conversion from database {cyme_mdbname} network {network_id}",
+ file=fh,
+ )
+ for row in self.assumptions:
+ print(
+ f'modify {fix_name(row[0])}.{row[1]} "{row[2]}"; // {row[3]}',
+ file=fh,
+ )
+ elif settings["GLM_ASSUMPTIONS"] == "include":
+ self.blank()
+ self.comment("", "Assumptions", "")
+ for row in self.assumptions:
+ self.modify(row[0], row[1], row[2], row[3])
+ elif settings["GLM_ASSUMPTIONS"] == "warn":
+ filename = (
+ f"{output_folder}/{cyme_mdbname}_{network_id}_assumptions.csv"
+ )
+ warning(
+ f"{cyme_mdbname}@{network_id}: {len(self.assumptions)} assumptions made, see '{filename}' for details"
+ )
+ pd.DataFrame(self.assumptions).to_csv(
+ filename,
+ header=["object_name", "property_name", "value", "remark"],
+ index=False,
+ )
+ elif settings["GLM_ASSUMPTIONS"] != "ignore":
+ warning(
+ f"GLM_ASSUMPTIONS={settings['GLM_ASSUMPTIONS']} is not valid (must be one of 'save','ignore','warn','include')"
+ )
+
+ # modifications
+ for modify in settings["GLM_MODIFY"].split():
+ self.blank()
+ self.comment("", f"Modifications from '{modify}'", "")
+ try:
+ with open(f"{input_folder}/{modify}", "r") as fh:
+ reader = csv.reader(fh)
+ for row in reader:
+ if len(row) == 0:
+ warning(f"No modifications from {modify}")
+ elif 0 < len(row) < 3:
+ warning(
+ f"{modify}: row '{','.join(list(row))}' is missing one or more required fields"
+ )
+ elif len(row) > 3:
+ warning(
+ f"{modify}: row '{','.join(list(row))}' has extra fields that will be ignored"
+ )
+ self.modify(*row[0:3])
+ else:
+ self.modify(*row)
+ except:
+ pass
+
+ # general glm model add function
+ def add(self, oclass, device_id, data, version, **kwargs):
+ try:
+ call = getattr(self, "add_" + oclass)
+ return call(device_id, data, version=version, **kwargs)
+ except Exception as errmsg:
+ warning(
+ f"{cyme_mdbname}@{network_id}: unable to add gridlabd class '{oclass}' using CYME device '{device_id}': {errmsg} {format_exception(errmsg,device_id,data.to_dict())}"
+ )
+ pass
+
+ # add a link to glm file
+ def add_link(self, section_id, section, version, **kwargs):
+ phase = int(section["Phase"])
+ from_node_id = section["FromNodeId"]
+ to_node_id = section["ToNodeId"]
+ device_dict = {}
+ for index, device in table_find(
+ cyme_table["sectiondevice"], SectionId=section_id
+ ).iterrows():
+ device_id = device["DeviceNumber"]
+ device_type = int(device["DeviceType"])
+ if device_type in glm_devices.keys():
+ device_name = self.name(device_id, "link")
+ device_dict[device_id] = self.object(
+ "link",
+ device_name,
+ {
+ "phases": cyme_phase_name[phase],
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ "from": self.name(from_node_id, "node"),
+ "to": self.name(to_node_id, "node"),
+ },
+ )
+ if from_node_id not in kwargs["node_links"].keys():
+ kwargs["node_links"][from_node_id] = []
+ kwargs["node_links"][from_node_id].append(device_id)
+ if to_node_id not in kwargs["node_links"].keys():
+ kwargs["node_links"][to_node_id] = []
+ kwargs["node_links"][to_node_id].append(device_id)
+ else:
+ warning(
+ f"{cyme_mdbname}@{network_id}: {cyme_devices[device_type]} on section {section_id} has no corresponding GLM object"
+ )
+ # print(device_dict)
+ return device_dict
+
+ # add node to glm file
+ def add_node(self, node_id, node_links, device_dict, version, **kwargs):
+ phase = 0
+ if geodata_file:
+ node_geodata_id = f"{node_id}_{network_id}"
+ if node_geodata_id not in node_geodata.keys():
+ all_node = kwargs["node_info"]["all_node"]
+ try:
+ node_X = float(all_node[all_node["NodeId"] == node_id]["X"].values)
+ node_Y = float(all_node[all_node["NodeId"] == node_id]["Y"].values)
+ except:
+ warning(
+ f"{cyme_mdbname}@{network_id}: cannot add coordinates for node_id"
+ )
+ node_X = np.nan
+ node_Y = np.nan
+ node_geodata[node_geodata_id] = {
+ "NotworkID": network_id,
+ "node": node_id,
+ "x": node_X,
+ "y": node_Y,
+ }
+ else:
+ raise Exception(
+ f"{cyme_mdbname}@{network_id}: multiple definition for {node_id}"
+ )
+ for device_id in node_links[node_id]:
+ phase |= glm_phase_code[device_dict[device_id]["phases"]]
+ obj = self.object(
+ "node",
+ self.name(node_id, "node"),
+ {
+ "phases": glm_phase_name[phase] + "N",
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ },
+ )
+ if node_id == table_get(
+ cyme_table["headnode"], network_id, "NodeId", "NetworkId"
+ ):
+ obj["bustype"] = "SWING"
+ else:
+ obj["bustype"] = "PQ"
+ return obj
+
+ # add an overhead line based on a link
+ def add_overhead_line(self, line_id, line, version):
+ line_name = self.name(line_id, "link")
+ length = float(fix_unit_line_length(line["Length"], line_length_unit))
+ if length == 0.0:
+ length = 1.0
+ warning(
+ f"{cyme_mdbname}@{network_id}: line {line_id} has zero length, using 1.0 m"
+ )
+ line_conductor_id = line["LineId"]
+ line_conductor = None
+ if "eqconductor" in cyme_equipment_table.keys():
+ line_conductor = table_get(
+ cyme_equipment_table["eqoverheadline"],
+ line_conductor_id,
+ None,
+ "EquipmentId",
+ )
+ # elif 'csvundergroundcable' in cyme_equipment_table.keys():
+ # ## TODO
+ elif "eqconductor" in cyme_table.keys():
+ line_conductor = table_get(
+ cyme_table["eqoverheadline"], line_conductor_id, None, "EquipmentId"
+ )
+ if line_conductor is None:
+ warning(
+ f'{cyme_mdbname}@{network_id}: OH cable conductor "{line_conductor_id}" of line "{line_id}" is missing in CYME model.Use default settings.'
+ )
+ line_conductor = {
+ "PhaseConductorId": "DEFAULT",
+ "NeutralConductorId": "DEFAULT",
+ "ConductorSpacingId": "DEFAULT",
+ }
+ conductorABC_id = line_conductor["PhaseConductorId"]
+ conductorN_id = line_conductor["NeutralConductorId"]
+ self.add_overhead_line_conductors([conductorABC_id, conductorN_id], version)
+ spacing_id = line_conductor["ConductorSpacingId"]
+ self.add_line_spacing(spacing_id, version)
+ configuration_name = self.add_line_configuration(
+ [
+ conductorABC_id,
+ conductorABC_id,
+ conductorABC_id,
+ conductorN_id,
+ spacing_id,
+ ],
+ version,
+ )
+ return self.object(
+ "overhead_line",
+ line_name,
+ {
+ "length": "%.2f m" % length,
+ "configuration": configuration_name,
+ },
+ )
+
+ # add an overhead line by phase based on a link
+ def add_overhead_line_phase(self, line_id, line, version):
+ line_name = self.name(line_id, "link")
+ length = float(fix_unit_line_length(line["Length"], line_length_unit))
+ if length == 0.0:
+ length = 1.0
+ warning(
+ f"{cyme_mdbname}@{network_id}: line {line_id} has zero length, using 1.0 m"
+ )
+ conductorA_id = line["PhaseConductorIdA"]
+ conductorB_id = line["PhaseConductorIdB"]
+ conductorC_id = line["PhaseConductorIdC"]
+ conductorN_id = line["NeutralConductorId"]
+ self.add_overhead_line_conductors(
+ [conductorA_id, conductorB_id, conductorC_id, conductorN_id], version
+ )
+ spacing_id = line["ConductorSpacingId"]
+ self.add_line_spacing(spacing_id, version)
+ configuration_name = self.add_line_configuration(
+ [conductorA_id, conductorB_id, conductorC_id, conductorN_id, spacing_id],
+ version,
+ )
+ return self.object(
+ "overhead_line",
+ line_name,
+ {
+ "length": "%.2f m" % length,
+ "configuration": configuration_name,
+ },
+ )
+
+ # add an unbalanced overhead line based on a link
+ def add_overhead_line_unbalanced(self, line_id, line, version):
+ line_name = self.name(line_id, "link")
+ configuration_id = line["LineId"]
+ configuration_name = self.name(configuration_id, "line_configuration")
+ length = float(fix_unit_line_length(line["Length"], line_length_unit))
+ if length == 0.0:
+ length = 1.0
+ warning(
+ f"{cyme_mdbname}@{network_id}: line {line_id} has zero length, using 1.0 m"
+ )
+ if not configuration_name in self.objects.keys():
+ configuration = table_get(
+ cyme_table["eqoverheadlineunbalanced"],
+ configuration_id,
+ None,
+ "EquipmentId",
+ )
+ conductorA_id = configuration["PhaseConductorIdA"]
+ conductorB_id = configuration["PhaseConductorIdB"]
+ conductorC_id = configuration["PhaseConductorIdC"]
+ conductorN_id = configuration["NeutralConductorId"]
+ conductor_names = self.add_overhead_line_conductors(
+ [conductorA_id, conductorB_id, conductorC_id, conductorN_id], version
+ )
+ spacing_id = configuration["ConductorSpacingId"]
+ spacing_name = self.add_line_spacing(spacing_id, version)
+ self.object(
+ "line_configuration",
+ configuration_name,
+ {
+ "conductor_A": conductor_names[0],
+ "conductor_B": conductor_names[1],
+ "conductor_C": conductor_names[2],
+ "conductor_N": conductor_names[3],
+ "spacing": spacing_name,
+ },
+ )
+ return self.object(
+ "overhead_line",
+ line_name,
+ {
+ "length": "%.2f m" % length,
+ "configuration": configuration_name,
+ },
+ )
+
+ # add an underground line based on a link
+ def add_underground_line(self, line_id, line, version):
+ line_name = self.name(line_id, "link")
+ length = float(fix_unit_line_length(line["Length"], line_length_unit))
+ if length < 1.0:
+ warning(
+ f"{cyme_mdbname}@{network_id}: length of line '{line_id}' may be in km."
+ )
+ if length == 0.0:
+ length = 1.0
+ warning(
+ f"{cyme_mdbname}@{network_id}: line {line_id} has zero length, using 1.0 m"
+ )
+ cable_conductor_id = line["CableId"]
+ conductor_name = self.name(cable_conductor_id, "underground_line_conductor")
+ if "eqconductor" in cyme_equipment_table.keys():
+ cable_conductor = table_get(
+ cyme_equipment_table["eqconductor"],
+ cable_conductor_id,
+ None,
+ "EquipmentId",
+ )
+ # elif 'csvundergroundcable' in cyme_equipment_table.keys():
+ # ## TODO
+ elif "eqconductor" in cyme_table.keys():
+ cable_conductor = table_get(
+ cyme_table["eqconductor"], cable_conductor_id, None, "EquipmentId"
+ )
+ if not conductor_name in self.objects.keys():
+ if cable_conductor is None:
+ warning(
+ f"{cyme_mdbname}@{network_id}: UG cable conductor {cable_conductor_id} of line '{line_id}' is missing in CYME model, use default settings instead."
+ )
+ # only use default settings for now
+ self.object(
+ "underground_line_conductor",
+ conductor_name,
+ {
+ "outer_diameter": "0.968 cm",
+ "conductor_gmr": "0.0319 cm",
+ "conductor_diameter": "0.968 cm",
+ "conductor_resistance": "0.139 Ohm/km",
+ "neutral_gmr": "0.00208 cm",
+ "neutral_resistance": "14.8722 Ohm/km",
+ "neutral_diameter": "0.0641 cm",
+ "neutral_strands": "16",
+ "rating.summer.continuous": "500 A",
+ },
+ )
+ else:
+ gmr = float(cable_conductor["GMR"])
+ r25 = float(cable_conductor["R25"])
+ diameter = float(cable_conductor["Diameter"])
+ nominal_rating = float(cable_conductor["FirstRating"])
+ if nominal_rating == 0:
+ nominal_rating = 1000
+ if r25 == 0:
+ r25 = 0.00001
+ if gmr == 0:
+ gmr = 0.01
+ obj = self.object(
+ "underground_line_conductor",
+ conductor_name,
+ {
+ "outer_diameter": "%.2f cm" % diameter,
+ "conductor_gmr": "%.2f cm" % gmr,
+ "conductor_diameter": "%.2f cm" % diameter,
+ "conductor_resistance": "%.5f Ohm/km" % r25,
+ "neutral_gmr": "0.00208 cm",
+ "neutral_resistance": "14.8722 Ohm/km",
+ "neutral_diameter": "0.0641 cm",
+ "neutral_strands": "16",
+ "rating.summer.continuous": "%.1f A" % nominal_rating,
+ },
+ )
+ try:
+ line_phases = self.objects[line_name]["phases"]
+ except:
+ error(f"cannot find the link objects for underground line {line_id}", 20)
+ if "N" not in line_phases:
+ line_phases = line_phases + "N"
+ spacing_name = self.name(f"UL_{line_id}_{line_phases}", "line_spacing")
+ if not spacing_name in self.objects.keys():
+ # only use default settings for now
+ UL_spacings = {}
+ if "A" in line_phases and "B" in line_phases:
+ UL_spacings["distance_AB"] = "0.1 m"
+ if "B" in line_phases and "C" in line_phases:
+ UL_spacings["distance_BC"] = "0.1 m"
+ if "A" in line_phases and "C" in line_phases:
+ UL_spacings["distance_AC"] = "0.1 m"
+ if "A" in line_phases:
+ UL_spacings["distance_AN"] = "0.0477 m"
+ if "B" in line_phases:
+ UL_spacings["distance_BN"] = "0.0477 m"
+ if "C" in line_phases:
+ UL_spacings["distance_CN"] = "0.0477 m"
+ self.object("line_spacing", spacing_name, UL_spacings)
+ configuration_name = self.name(
+ f"UL_{line_id}_{line_phases}", "line_configuration"
+ )
+ if not configuration_name in self.objects.keys():
+ UL_configs = {}
+ if "A" in line_phases:
+ UL_configs["conductor_A"] = conductor_name
+ if "B" in line_phases:
+ UL_configs["conductor_B"] = conductor_name
+ if "C" in line_phases:
+ UL_configs["conductor_C"] = conductor_name
+ if "N" in line_phases:
+ UL_configs["conductor_N"] = conductor_name
+ UL_configs["spacing"] = spacing_name
+ self.object("line_configuration", configuration_name, UL_configs)
+ return self.object(
+ "underground_line",
+ line_name,
+ {
+ "length": "%.2f m" % length,
+ "configuration": configuration_name,
+ },
+ )
+
+ # add overhead line conductor library entry
+ def add_overhead_line_conductors(self, conductors, version):
+ conductor_names = []
+ for conductor_id in conductors:
+ conductor_name = self.name(conductor_id, "overhead_line_conductor")
+ conductor = None
+ if not conductor_name in self.objects.keys():
+ if "eqconductor" in cyme_equipment_table.keys():
+ conductor = table_get(
+ cyme_equipment_table["eqconductor"],
+ conductor_id,
+ None,
+ "EquipmentId",
+ )
+ elif "eqconductor" in cyme_table.keys():
+ conductor = table_get(
+ cyme_table["eqconductor"], conductor_id, None, "EquipmentId"
+ )
+ else:
+ error(
+ f"cannot add cable conductor {conductor_name} for version {version}",
+ 21,
+ )
+ if conductor is None:
+ warning(
+ f"{cyme_mdbname}@{network_id}: OH cable conductor {conductor_id} is missing in CYME model, use default settings instead."
+ )
+ # use default settings.
+ obj = self.object(
+ "overhead_line_conductor",
+ conductor_name,
+ {
+ "geometric_mean_radius": "0.1 cm",
+ "resistance": "0.5 Ohm/km",
+ "diameter": "1 cm",
+ "rating.summer.continuous": "1000 A",
+ "rating.winter.continuous": "1000 A",
+ "rating.summer.emergency": "1000 A",
+ "rating.winter.emergency": "1000 A",
+ },
+ )
+ else:
+ gmr = float(conductor["GMR"])
+ r25 = float(conductor["R25"])
+ diameter = float(conductor["Diameter"])
+ nominal_rating = float(conductor["NominalRating"])
+ # should set up NONE conductor rating and resistance as non-zero value
+ # cannot use modify.csv to change the ratings fior OC_NONE
+ if nominal_rating == 0:
+ nominal_rating = 1000
+ if r25 == 0:
+ r25 = 0.00001
+ if gmr == 0:
+ gmr = 0.01
+ obj = self.object(
+ "overhead_line_conductor",
+ conductor_name,
+ {
+ "geometric_mean_radius": "%.2f cm" % gmr,
+ "resistance": "%.5f Ohm/km" % r25,
+ "diameter": "%.2f cm" % diameter,
+ "rating.summer.continuous": "%.1f A" % nominal_rating,
+ "rating.winter.continuous": "%.1f A" % nominal_rating,
+ "rating.summer.emergency": "%.1f A" % nominal_rating,
+ "rating.winter.emergency": "%.1f A" % nominal_rating,
+ },
+ )
+ conductor_names.append(conductor_name)
+ return conductor_names
+
+ # line spacing library object
+ def add_line_spacing(self, spacing_id, version):
+ spacing_name = self.name(spacing_id, "line_spacing")
+ if not spacing_name in self.objects.keys():
+ spacing = None
+ if "eqgeometricalarrangement" in cyme_equipment_table.keys():
+ spacing = table_get(
+ cyme_equipment_table["eqgeometricalarrangement"],
+ spacing_id,
+ None,
+ "EquipmentId",
+ )
+ if (
+ spacing is None
+ and "eqaveragegeoarrangement" in cyme_equipment_table.keys()
+ ):
+ spacing = table_get(
+ cyme_equipment_table["eqaveragegeoarrangement"],
+ spacing_id,
+ None,
+ "EquipmentId",
+ )
+ if spacing is None:
+ warning(
+ f"cannot add cable spacing {spacing_id} for version {version}, use default settings"
+ )
+ spacing = table_get(
+ cyme_equipment_table["eqgeometricalarrangement"],
+ "DEFAULT",
+ None,
+ "EquipmentId",
+ )
+ elif "eqgeometricalarrangement" in cyme_table.keys():
+ spacing = table_get(
+ cyme_table["eqgeometricalarrangement"],
+ spacing_id,
+ None,
+ "EquipmentId",
+ )
+ if spacing is None and "eqaveragegeoarrangement" in cyme_table.keys():
+ spacing = table_get(
+ cyme_table["eqaveragegeoarrangement"],
+ spacing_id,
+ None,
+ "EquipmentId",
+ )
+ if spacing is None:
+ warning(
+ f"cannot add cable spacing {spacing_id} for version {version}, use default settings"
+ )
+ spacing = table_get(
+ cyme_table["eqgeometricalarrangement"],
+ "DEFAULT",
+ None,
+ "EquipmentId",
+ )
+ else:
+ error(
+ f"table 'eqgeometricalarrangement' for cable spacing is missing", 23
+ )
+ if spacing is None:
+ error(
+ f"cannot add cable spacing {spacing_id} for version {version}", 24
+ )
+ elif "GMDPhaseToPhase" in spacing.index:
+ ABC2ABC = float(spacing["GMDPhaseToPhase"])
+ ABC2N = float(spacing["GMDPhaseToNeutral"])
+ ABC2E = float(spacing["AveragePhaseConductorHeight"])
+ N2E = float(spacing["AverageNeutralConductorHeight"])
+ self.object(
+ "line_spacing",
+ spacing_name,
+ {
+ "distance_AB": "%.2f m" % ABC2ABC,
+ "distance_AC": "%.2f m" % ABC2ABC,
+ "distance_BC": "%.2f m" % ABC2ABC,
+ "distance_AN": "%.2f m" % ABC2N,
+ "distance_BN": "%.2f m" % ABC2N,
+ "distance_CN": "%.2f m" % ABC2N,
+ "distance_AE": "%.2f m" % ABC2E,
+ "distance_BE": "%.2f m" % ABC2E,
+ "distance_CE": "%.2f m" % ABC2E,
+ "distance_NE": "%.2f m" % N2E,
+ },
+ )
+ elif "ConductorA_Horizontal" in spacing.index:
+ Ax = float(spacing["ConductorA_Horizontal"])
+ Ay = float(spacing["ConductorA_Vertical"])
+ Bx = float(spacing["ConductorB_Horizontal"])
+ By = float(spacing["ConductorB_Vertical"])
+ Cx = float(spacing["ConductorC_Horizontal"])
+ Cy = float(spacing["ConductorC_Vertical"])
+ Nx = float(spacing["NeutralConductor_Horizontal"])
+ Ny = float(spacing["NeutralConductor_Vertical"])
+ ABx = Ax - Bx
+ ABy = Ay - By
+ ACx = Ax - Cx
+ ACy = Ay - Cy
+ BCx = Bx - Cx
+ BCy = By - Cy
+ ANx = Ax - Nx
+ ANy = Ay - Ny
+ BNx = Bx - Nx
+ BNy = By - Ny
+ CNx = Cx - Nx
+ CNy = Cy - Ny
+ self.object(
+ "line_spacing",
+ spacing_name,
+ {
+ "distance_AB": "%.2f m" % sqrt(ABx * ABx + ABy * ABy),
+ "distance_AC": "%.2f m" % sqrt(ACx * ACx + ACy * ACy),
+ "distance_BC": "%.2f m" % sqrt(BCx * BCx + BCy * BCy),
+ "distance_AN": "%.2f m" % sqrt(ANx * ANx + ANy * ANy),
+ "distance_BN": "%.2f m" % sqrt(BNx * BNx + BNy * BNy),
+ "distance_CN": "%.2f m" % sqrt(CNx * CNx + CNy * CNy),
+ "distance_AE": "%.2f m" % Ay,
+ "distance_BE": "%.2f m" % By,
+ "distance_CE": "%.2f m" % Cy,
+ "distance_NE": "%.2f m" % Ny,
+ },
+ )
+ else:
+ error(f"data is missing for cable spacing {spacing_id}", 25)
+ return spacing_name
+
+ # line configuration library object
+ def add_line_configuration(self, items, version):
+ configuration_id = "_".join(items)
+ configuration_name = self.name(configuration_id, "line_configuration")
+ if not configuration_name in self.objects.keys():
+ self.object(
+ "line_configuration",
+ configuration_name,
+ {
+ "conductor_A": self.name(items[0], "overhead_line_conductor"),
+ "conductor_B": self.name(items[1], "overhead_line_conductor"),
+ "conductor_C": self.name(items[2], "overhead_line_conductor"),
+ "conductor_N": self.name(items[3], "overhead_line_conductor"),
+ "spacing": self.name(items[4], "line_spacing"),
+ },
+ )
+ return configuration_name
+
+ # add a switch based on a link
+ def add_switch(self, switch_id, switch, version):
+ switch_name = self.name(switch_id, "link")
+ phases = cyme_phase_name[int(switch["ClosedPhase"])]
+ switch_config = {"operating_mode": "BANKED"}
+ for phase in phases:
+ if phase != "N":
+ switch_config[f"phase_{phase}_state"] = "CLOSED"
+ return self.object("switch", switch_name, switch_config, overwrite=False)
+
+ # add a breaker based on a link and a switch object
+ def add_breaker(self, breaker_id, breaker, version):
+ breaker_name = self.name(breaker_id, "link")
+ phases = cyme_phase_name[int(breaker["ClosedPhase"])]
+ breaker_config = {"operating_mode": "BANKED"}
+ for phase in phases:
+ if phase != "N":
+ breaker_config[f"phase_{phase}_state"] = "CLOSED"
+ return self.object("switch", breaker_name, breaker_config, overwrite=False)
+
+ # add a recloser based on a link and a switch object
+ def add_recloser(self, recloser_id, recloser, version):
+ recloser_name = self.name(recloser_id, "link")
+ phases = cyme_phase_name[int(recloser["ClosedPhase"])]
+ recloser_config = {"operating_mode": "BANKED"}
+ for phase in phases:
+ if phase != "N":
+ recloser_config[f"phase_{phase}_state"] = "CLOSED"
+ return self.object("switch", recloser_name, recloser_config, overwrite=False)
+
+ # add a fuse based on a link
+ def add_fuse(self, fuse_id, fuse, version):
+ fuse_name = self.name(fuse_id, "link")
+ equipment_id = fuse["EquipmentId"]
+ equipment = None
+ if "eqfuse" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqfuse"], equipment_id, None, "EquipmentId"
+ )
+ elif "eqfuse" in cyme_table.keys():
+ equipment = table_get(
+ cyme_table["eqfuse"], equipment_id, None, "EquipmentId"
+ )
+ if equipment is None:
+ # use default settings
+ current_limit = "50000.0"
+ else:
+ current_limit = equipment["FirstRatedCurrent"]
+ fuse_dict = {
+ "current_limit": f"{current_limit} A",
+ "mean_replacement_time": "3600.0",
+ }
+ return self.object("fuse", fuse_name, fuse_dict, overwrite=False)
+
+ # add a load
+ def add_load(self, load_id, load, version, **kwargs):
+ section = kwargs["node_info"]["load_section"].squeeze()
+ connection_type = kwargs["node_info"]["connection_type"]
+ all_node = kwargs["node_info"]["all_node"]
+ default_load_voltage = kwargs["node_info"]["load_voltage"]
+ device_type = int(load["DeviceType"])
+ value_type = int(load["LoadValueType"])
+ if device_type == 20: # spot load is attached at from node of section
+ parent_name = self.name(section["FromNodeId"], "node")
+ elif device_type == 21: # distributed load is attached at to node of section
+ parent_name = self.name(section["ToNodeId"], "node")
+ else:
+ error(f"CYME device type {device_type} is not supported as a load", 30)
+
+ if parent_name not in self.objects.keys():
+ # Definition for node "parent_name" is missing
+ device_dict = kwargs["node_info"]["Device_Dicts"]
+ node_links = kwargs["node_info"]["Node_Links"]
+ self.add_node(
+ parent_name[3:],
+ node_links,
+ device_dict,
+ version,
+ node_info={"all_node": all_node},
+ )
+
+ # customer_id = load["CustomerNumber"]
+ link_name = self.name(load_id, "link")
+ if link_name in self.objects.keys(): # link is no longer needed
+ self.delete(link_name)
+ load_name = self.name(load_id, "load")
+ device_type = int(load["DeviceType"])
+ phase = cyme_phase_name[int(load["Phase"])]
+
+ if load_name in self.objects.keys() and "phases" in self.objects[load_name]:
+ phases = self.objects[load_name]["phases"] + phase
+ else:
+ phases = phase
+ if device_type in glm_devices.keys():
+ ConsumerClassId = load["ConsumerClassId"]
+ # the default load unit in gridlabd is Volt-Amperes, or Amperes or Ohms
+ load_value1 = float(load["LoadValue1"])
+ load_value2 = float(load["LoadValue2"])
+ # from the mdb file, type for constant power load is defined as PQ
+ load_types = {
+ "Z": "constant_impedance",
+ "I": "constant_current",
+ "PQ": "constant_power",
+ }
+ if ConsumerClassId in load_types.keys():
+ load_cals_complex = load_cals(
+ ConsumerClassId,
+ load["Phase"],
+ connection_type,
+ load_value1,
+ load_value2,
+ default_load_voltage,
+ value_type,
+ )
+ load_value1 = load_cals_complex.real
+ load_value2 = -load_cals_complex.imag
+ if (load_value1 * load_value1 + load_value2 * load_value2) > 0:
+ load_dict = {
+ "parent": parent_name,
+ "phases": arrangeString(phases),
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ }
+ for i_phase in phase:
+ load_dict[
+ f"{load_types[ConsumerClassId]}_{i_phase}"
+ ] = "%.4f%+.4fj" % (load_value1, load_value2)
+ return self.object("load", load_name, load_dict)
+ elif ConsumerClassId in ["PV", "SWING", "SWINGPQ"]:
+ # GLM bus types allowed
+ load_cals_complex = load_cals(
+ "Z",
+ load["Phase"],
+ connection_type,
+ load_value1,
+ load_value2,
+ default_load_voltage,
+ value_type,
+ )
+ load_value1 = load_cals_complex.real
+ load_value2 = -load_cals_complex.imag
+ if (load_value1 * load_value1 + load_value2 * load_value2) > 0:
+ load_dict = {
+ "parent": parent_name,
+ "phases": arrangeString(phases),
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ "bustype": ConsumerClassId,
+ }
+ for i_phase in phase:
+ load_dict[f"constant_impedance_{i_phase}"] = "%.4f%+.4fj" % (
+ load_value1,
+ load_value2,
+ )
+ return self.object("load", load_name, load_dict)
+ elif ConsumerClassId in [
+ "CGSUB",
+ "Other",
+ "Industrial",
+ "Residential",
+ "NONE",
+ ]:
+ # GLM bus types allowed
+ load_cals_complex = load_cals(
+ "PQ",
+ load["Phase"],
+ connection_type,
+ load_value1,
+ load_value2,
+ default_load_voltage,
+ value_type,
+ )
+ load_value1 = load_cals_complex.real
+ load_value2 = -load_cals_complex.imag
+ if (load_value1 * load_value1 + load_value2 * load_value2) > 0:
+ load_dict = {
+ "parent": parent_name,
+ "phases": arrangeString(phases),
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ }
+ for i_phase in phase:
+ # load_dict[f"constant_power_{i_phase}"] = "%.4f%+.4fj" % (load_value1,load_value2)
+ load_dict[f"constant_power_{i_phase}"] = "%.4f%+.4fj" % (
+ 0.01,
+ 0.01,
+ )
+ return self.object("load", load_name, load_dict)
+ else:
+ warning(
+ f"{cyme_mdbname}@{network_id}: load '{load_id}' on phase '{phase}' dropped because '{ConsumerClassId}' is not a supported CYME load type"
+ )
+ else:
+ warning(
+ f"{cyme_mdbname}@{network_id}: load '{load_id}' on phase '{phase}' dropped because '{cyme_devices[device_type]}' is not a supported CYME device type"
+ )
+
+ # add a capacitor
+ def add_capacitor(self, capacitor_id, capacitor, version, **kwargs):
+ section_id = table_get(
+ cyme_table["sectiondevice"], capacitor_id, "SectionId", "DeviceNumber"
+ )
+ section = table_get(cyme_table["section"], section_id, None, "SectionId")
+ section = kwargs["node_info"]["cap_section"].squeeze()
+ from_name = self.name(section["FromNodeId"], "node")
+ to_name = self.name(section["ToNodeId"], "node")
+ equipment_id = capacitor["EquipmentId"]
+ if "eqtransformer" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqshuntcapacitor"],
+ equipment_id,
+ None,
+ "EquipmentId",
+ )
+ link_name = self.name(capacitor_id, "link")
+ if link_name in self.objects.keys(): # link is no longer needed
+ self.delete(link_name)
+ KVARA = float(capacitor["KVARA"])
+ if "SwitchedKVARA" in capacitor.keys(): # for NG MDB files
+ KVARA = KVARA + float(capacitor["SwitchedKVARA"])
+ KVARB = float(capacitor["KVARB"])
+ if "SwitchedKVARB" in capacitor.keys(): # for NG MDB files
+ KVARB = KVARB + float(capacitor["SwitchedKVARB"])
+ KVARC = float(capacitor["KVARC"])
+ if "SwitchedKVARC" in capacitor.keys(): # for NG MDB files
+ KVARC = KVARC + float(capacitor["SwitchedKVARC"])
+ if not KVARA + KVARB + KVARC > 0.0:
+ warning(
+ f"{cyme_mdbname}@{network_id}: capacitor {capacitor_id} has zero capacitance for all phases."
+ )
+ return
+ KVLN = float(capacitor["KVLN"])
+ ConnectionConfig = int(
+ capacitor["ConnectionConfiguration"]
+ ) # 2 for delta and else for wye
+ capacitor_name = self.name(capacitor_id, "capacitor")
+ control = "MANUAL"
+ self.assume(
+ capacitor_name,
+ "control",
+ control,
+ f"capacitor {fix_name(capacitor_id)} does not specify a control strategy, valid options are 'CURRENT', 'VARVOLT', 'VOLT', 'VAR', or 'MANUAL'",
+ )
+
+ if "Phase" in capacitor.keys():
+ phase = cyme_phase_name[int(capacitor["Phase"])]
+ elif "ByPhase" in capacitor.keys():
+ phase = cyme_phase_name[int(capacitor["ByPhase"])]
+ else:
+ warning(
+ f"{cyme_mdbname}@{network_id}: capacitor {capacitor_id} does not specify, phase will be specified based on capacitance data"
+ )
+ phase = cyme_phase_name[capacitor_phase_cals(KVARA, KVARB, KVARC)]
+
+ capacitor_dict = {
+ "parent": from_name,
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ }
+ phase = ""
+ if KVARA > 0.0:
+ CAPACITOR_A = f"{KVARA} kVA"
+ SWITCH_A = "CLOSED"
+ if ConnectionConfig == 2:
+ phase = phase + "AB"
+ else:
+ phase = phase + "A"
+ else:
+ CAPACITOR_A = f"0 kVA"
+ SWITCH_A = "OPEN"
+ if KVARB > 0.0:
+ CAPACITOR_B = f"{KVARB} kVA"
+ SWITCH_B = "CLOSED"
+ if ConnectionConfig == 2:
+ phase = phase + "BC"
+ else:
+ phase = phase + "B"
+ else:
+ CAPACITOR_B = f"0 kVA"
+ SWITCH_B = "OPEN"
+ if KVARC > 0.0:
+ CAPACITOR_C = f"{KVARC} kVA"
+ SWITCH_C = "CLOSED"
+ if ConnectionConfig == 2:
+ phase = phase + "AC"
+ else:
+ phase = phase + "C"
+ else:
+ CAPACITOR_C = f"0 kVA"
+ SWITCH_C = "OPEN"
+ phase = clean_phases(phase)
+ if ConnectionConfig == 0 and "N" not in phase:
+ phase = phase + "N"
+ elif ConnectionConfig > 0 and "N" in phase:
+ phase.replace("N", "")
+ capacitor_dict["phases"] = phase
+ capacitor_dict["capacitor_A"] = CAPACITOR_A
+ capacitor_dict["capacitor_B"] = CAPACITOR_B
+ capacitor_dict["capacitor_C"] = CAPACITOR_C
+ capacitor_dict["switchA"] = SWITCH_A
+ capacitor_dict["switchB"] = SWITCH_B
+ capacitor_dict["switchC"] = SWITCH_C
+ capacitor_dict["control"] = "MANUAL"
+ capacitor_dict["control"] = "MANUAL"
+ return self.object("capacitor", capacitor_name, capacitor_dict)
+
+ # add a transformer
+ def add_transformer(self, transformer_id, transformer, version):
+ DeviceType = int(transformer["DeviceType"])
+ equipment_id = transformer["EquipmentId"]
+ equipment = None
+ if "eqtransformer" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqtransformer"], equipment_id, None, "EquipmentId"
+ )
+ elif "eqtransformer" in cyme_table.keys():
+ equipment = table_get(
+ cyme_table["eqtransformer"], equipment_id, None, "EquipmentId"
+ )
+ else:
+ warning(
+ f"{cyme_mdbname}@{network_id}: cannot find cyme table 'eqtransformer'."
+ )
+ if equipment is None:
+ warning(
+ f"{cyme_mdbname}@{network_id}: equipment {equipment_id} of transformer '{transformer_id}' is missing in CYME model, use default settings instead."
+ )
+ if "eqtransformer" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqtransformer"],
+ "DEFAULT",
+ None,
+ "EquipmentId",
+ )
+ elif "eqtransformer" in cyme_table.keys():
+ equipment = table_get(
+ cyme_table["eqtransformer"], "DEFAULT", None, "EquipmentId"
+ )
+ NominalRatingKVA = float(equipment["NominalRatingKVA"])
+ PosSeqImpedancePercent = float(equipment["PosSeqImpedancePercent"])
+ XRRatio = float(equipment["XRRatio"])
+ try:
+ PrimarySecondaryVoltag = equipment_id.split("_")[1]
+ PrimaryVoltageKVLL = float(PrimarySecondaryVoltag.split("/")[0])
+ if PrimaryVoltageKVLL > 50:
+ PrimaryVoltageKVLL = PrimaryVoltageKVLL / 1000
+ SecondaryVoltageKVLL = float(PrimarySecondaryVoltag.split("/")[1])
+ if SecondaryVoltageKVLL > 50:
+ SecondaryVoltageKVLL = SecondaryVoltageKVLL / 1000
+ primary_voltage = "%.4fkV" % (PrimaryVoltageKVLL / sqrt(3.0))
+ secondary_voltage = "%.4fkV" % (SecondaryVoltageKVLL / sqrt(3.0))
+ except:
+ warning(
+ f"{cyme_mdbname}@{network_id}: Connot get the PrimaryVoltageKVLL/SecondaryVoltageKVLL from the name of equipment {equipment_id}. Use default settings instead."
+ )
+ PrimaryVoltageKVLL = float(equipment["PrimaryVoltageKVLL"])
+ SecondaryVoltageKVLL = float(equipment["SecondaryVoltageKVLL"])
+ else:
+ NominalRatingKVA = float(equipment["NominalRatingKVA"])
+ PrimaryVoltageKVLL = float(equipment["PrimaryVoltageKVLL"])
+ SecondaryVoltageKVLL = float(equipment["SecondaryVoltageKVLL"])
+ PosSeqImpedancePercent = float(equipment["PosSeqImpedancePercent"])
+ XRRatio = float(equipment["XRRatio"])
+ primary_voltage = "%.4fkV" % (PrimaryVoltageKVLL / sqrt(3.0))
+ secondary_voltage = "%.4fkV" % (SecondaryVoltageKVLL / sqrt(3.0))
+ r = XRRatio / 100.0 / sqrt(1 + XRRatio**2)
+ x = r * XRRatio
+ nominal_rating = "%.4fkVA" % (NominalRatingKVA)
+ configuration_name = self.name(
+ [
+ nominal_rating,
+ primary_voltage,
+ secondary_voltage,
+ "R%.4f" % (r),
+ "X%4f" % (x),
+ ],
+ "transformer_configuration",
+ )
+ if primary_voltage == secondary_voltage:
+ secondary_voltage = "%.4fkV" % ((SecondaryVoltageKVLL + 0.001) / sqrt(3.0))
+ self.assume(
+ configuration_name,
+ "secondary_voltage",
+ secondary_voltage,
+ f"transformer {fix_name(transformer_id)} primary voltage is the same as secondary voltage",
+ )
+ if r == 0.0:
+ r = 0.000333
+ x = 0.00222
+ self.assume(
+ configuration_name,
+ "resistance",
+ r,
+ f"transformer {fix_name(transformer_id)} XRRatio is zero",
+ )
+ self.assume(
+ configuration_name,
+ "reactance",
+ x,
+ f"transformer {fix_name(transformer_id)} XRRatio is zero",
+ )
+
+ connect_type = "WYE_WYE"
+ self.assume(
+ configuration_name,
+ "connect_type",
+ connect_type,
+ f"transformer '{fix_name(transformer_id)}' does not specify connection type",
+ )
+ install_type = "PADMOUNT"
+ self.assume(
+ configuration_name,
+ "install_type",
+ install_type,
+ f"transformer '{fix_name(transformer_id)}' does not specify install type",
+ )
+
+ self.object(
+ "transformer_configuration",
+ configuration_name,
+ {
+ "connect_type": "WYE_WYE",
+ "install_type": "PADMOUNT",
+ "power_rating": "%.4fkVA" % (NominalRatingKVA),
+ "primary_voltage": primary_voltage,
+ "secondary_voltage": secondary_voltage,
+ "resistance": r,
+ "reactance": x,
+ },
+ )
+ # add a transformer based on a link
+ link_name = self.name(transformer_id, "link")
+ return self.object(
+ "transformer",
+ link_name,
+ {
+ "nominal_voltage": None,
+ "phases": "".join(sorted(set(self.objects[link_name]["phases"] + "N"))),
+ "configuration": configuration_name,
+ },
+ )
+
+ # add a single phase transformer
+ def add_single_transformer(self, transformer_id, transformer, version):
+ for n in range(1, 4):
+ equipment_id = transformer[f"PhaseTransformerID{n}"]
+ if isinstance(equipment_id, str):
+ if "eqtransformer" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqtransformer"],
+ equipment_id,
+ None,
+ "EquipmentId",
+ )
+ elif "eqtransformer" in cyme_table.keys():
+ equipment = table_get(
+ cyme_table["eqtransformer"], equipment_id, None, "EquipmentId"
+ )
+ else:
+ warning(
+ f"{cyme_mdbname}@{network_id}: equipment {equipment_id} of transformer '{transformer_id}' is missing in CYME model, use default settings instead."
+ )
+ if "eqtransformer" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqtransformer"],
+ "DEFAULT",
+ None,
+ "EquipmentId",
+ )
+ elif "eqtransformer" in cyme_table.keys():
+ equipment = table_get(
+ cyme_table["eqtransformer"], "DEFAULT", None, "EquipmentId"
+ )
+ else:
+ error(f"cannot add single transformer.", 40)
+ NominalRatingKVA = float(equipment["NominalRatingKVA"])
+ PrimaryVoltageKVLL = float(equipment["PrimaryVoltageKVLL"])
+ SecondaryVoltageKVLL = float(equipment["SecondaryVoltageKVLL"])
+ PosSeqImpedancePercent = float(equipment["PosSeqImpedancePercent"])
+ XRRatio = float(equipment["XRRatio"])
+ r = XRRatio / 100.0 / sqrt(1 + XRRatio**2)
+ x = r * XRRatio
+ nominal_rating = "%.4fkVA" % (NominalRatingKVA)
+ primary_voltage = "%.4fkV" % (PrimaryVoltageKVLL / sqrt(3.0))
+ secondary_voltage = "%.4fkV" % (SecondaryVoltageKVLL / sqrt(3.0))
+ configuration_name = self.name(
+ [
+ nominal_rating,
+ primary_voltage,
+ secondary_voltage,
+ "R%.4f" % (r),
+ "X%.4f" % (x),
+ cyme_phase_name[n],
+ ],
+ "transformer_configuration",
+ )
+ if primary_voltage == secondary_voltage:
+ secondary_voltage = "%.4fkV" % (
+ (SecondaryVoltageKVLL + 0.001) / sqrt(3.0)
+ )
+ self.assume(
+ configuration_name,
+ "secondary_voltage",
+ secondary_voltage,
+ f"transformer {fix_name(transformer_id)} primary voltage is the same as secondary voltage",
+ )
+ if r == 0.0:
+ r = 0.000333
+ x = 0.00222
+ self.assume(
+ configuration_name,
+ "resistance",
+ r,
+ f"transformer {fix_name(transformer_id)} XRRatio is zero",
+ )
+ self.assume(
+ configuration_name,
+ "reactance",
+ x,
+ f"transformer {fix_name(transformer_id)} XRRatio is zero",
+ )
+ connect_type = "SINGLE_PHASE"
+ self.assume(
+ configuration_name,
+ "connect_type",
+ connect_type,
+ f"transformer '{fix_name(transformer_id)}' does not specify connection type",
+ )
+ install_type = "PADMOUNT"
+ self.assume(
+ configuration_name,
+ "install_type",
+ install_type,
+ f"transformer '{fix_name(transformer_id)}' does not specify install type",
+ )
+
+ self.object(
+ "transformer_configuration",
+ configuration_name,
+ {
+ "connect_type": connect_type,
+ "install_type": install_type,
+ "power_rating": "%.4fkVA" % (NominalRatingKVA),
+ "primary_voltage": primary_voltage,
+ "secondary_voltage": secondary_voltage,
+ "resistance": r,
+ "reactance": x,
+ },
+ )
+ link_name = self.name(transformer_id, "link")
+ self.object(
+ "single_transformer",
+ link_name,
+ {
+ "nominal_voltage": None,
+ "phases": "".join(sorted(set(cyme_phase_name[n] + "N"))),
+ "configuration": configuration_name,
+ },
+ )
+
+ # add a regulator
+ def add_regulator(self, regulator_id, regulator, version):
+ equipment_id = regulator["EquipmentId"]
+ equipment = None
+ if "eqregulator" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqregulator"], equipment_id, None, "EquipmentId"
+ )
+ elif "eqregulator" in cyme_table.keys():
+ equipment = table_get(
+ cyme_table["eqregulator"], equipment_id, None, "EquipmentId"
+ )
+ else:
+ error(f"cannot find cyme table 'eqtransformer'.", 50)
+ CTPrimaryRating = float(regulator["CTPrimaryRating"])
+ PTRatio = float(regulator["PTRatio"])
+ try:
+ BandWidth = float(regulator["BandWidth"])
+ except KeyError as err:
+ warning(
+ f"Regulator '{regulator_id}' doesn't define {err}, default value will be used"
+ )
+ BandWidth = 2.0
+ BoostPercent = float(regulator["BoostPercent"])
+ BuckPercent = float(regulator["BuckPercent"])
+ TapPositionA = float(regulator["TapPositionA"])
+ TapPositionB = float(regulator["TapPositionB"])
+ TapPositionC = float(regulator["TapPositionC"])
+ ControlStatus = float(regulator["ControlStatus"])
+ ReverseSensingMode = float(regulator["ReverseSensingMode"])
+ ReverseThreshold = float(regulator["ReverseThreshold"])
+ X = float(regulator["X"])
+ Y = float(regulator["Y"])
+ Status = int(regulator["Status"])
+ Reversible = int(regulator["Reversible"])
+
+ if equipment is None:
+ if "eqregulator" in cyme_equipment_table.keys():
+ equipment = table_get(
+ cyme_equipment_table["eqregulator"], "DEFAULT", None, "EquipmentId"
+ )
+ elif "eqregulator" in cyme_table.keys():
+ equipment = table_get(
+ cyme_table["eqregulator"], "DEFAULT", None, "EquipmentId"
+ )
+ RatedKVA = float(equipment["RatedKVA"])
+ RatedKVLN = float(equipment["RatedKVLN"])
+ NumberOfTaps = int(equipment["NumberOfTaps"])
+
+ connect_type = "WYE_WYE"
+ Control = "OUTPUT_VOLTAGE"
+ time_delay = "30s"
+ band_center = "${GLM_NOMINAL_VOLTAGE}"
+ band_width = "%.1gV" % (BandWidth)
+
+ configuration_name = self.name(
+ [regulator_id, band_width, time_delay], "regulator_configuration"
+ )
+ self.assume(
+ configuration_name,
+ "connect_type",
+ connect_type,
+ f"regulator '{fix_name(regulator_id)}' does not specify connection type",
+ )
+ self.assume(
+ configuration_name,
+ "Control",
+ Control,
+ f"regulator '{fix_name(regulator_id)}' does not specify control type",
+ )
+ self.assume(
+ configuration_name,
+ "time_delay",
+ time_delay,
+ f"regulator '{fix_name(regulator_id)}' does not specify time delay",
+ )
+ self.assume(
+ configuration_name,
+ "band_center",
+ band_center,
+ f"regulator '{fix_name(regulator_id)}' does not specify band center",
+ )
+
+ self.object(
+ "regulator_configuration",
+ configuration_name,
+ {
+ "connect_type": connect_type,
+ "band_center": band_center,
+ "band_width": band_width,
+ "time_delay": time_delay,
+ "raise_taps": "%.0f" % float(NumberOfTaps / 2),
+ "lower_taps": "%.0f" % float(NumberOfTaps / 2),
+ "current_transducer_ratio": "%.0f" % CTPrimaryRating,
+ "power_transducer_ratio": "%.0f" % PTRatio,
+ "regulation": "%.4f%%" % (BandWidth / (RatedKVLN * 1000) * 100),
+ "tap_pos_A": "%.0f" % (TapPositionA),
+ "tap_pos_B": "%.0f" % (TapPositionB),
+ "tap_pos_C": "%.0f" % (TapPositionC),
+ "Control": Control,
+ },
+ )
+
+ link_name = self.name(regulator_id, "link")
+ regulator_name = self.name(link_name, "regulator")
+ regulator_name = fix_name(regulator_name)
+ sense_node = self.objects[link_name]["to"]
+ self.assume(
+ regulator_name,
+ "sense_node",
+ sense_node,
+ f"regulator '{fix_name(regulator_id)}' does not specify sense node",
+ )
+ return self.object(
+ "regulator",
+ self.name(regulator_id, "link"),
+ {
+ "configuration": configuration_name,
+ "sense_node": sense_node,
+ },
+ )
+
+ # add a PV system including PV panel, inverter, and meter
+ def add_photovoltaic(self, photovoltaic_id, photovoltaic, version, **kwargs):
+ section = kwargs["node_info"]["pv_section"].squeeze()
+ all_node = kwargs["node_info"]["all_node"]
+ parent_name = self.name(section["FromNodeId"], "node")
+ inverter = table_get(
+ cyme_table["dggenerationmodel"], photovoltaic_id, None, "DeviceNumber"
+ )
+ rated_power = float(inverter["ActiveGeneration"])
+ power_factor = float(inverter["PowerFactor"]) / 100
+ if power_factor > 1:
+ error(
+ f"power factor for solar inverter {photovoltaic_id} is greater than 1.0",
+ 70,
+ )
+ efficiency = (
+ float(
+ table_get(
+ cyme_table["converter"],
+ photovoltaic_id,
+ "Efficiency",
+ "DeviceNumber",
+ )
+ )
+ / 100.0
+ )
+ if efficiency > 1:
+ error(
+ f"converter efficiency for solar inverter {photovoltaic_id} is greater than 1.0",
+ 71,
+ )
+ if parent_name not in self.objects.keys():
+ # Definition of node "parent_name" is missing
+ device_dict = kwargs["node_info"]["Device_Dicts"]
+ node_links = kwargs["node_info"]["Node_Links"]
+ self.add_node(
+ parent_name[3:],
+ node_links,
+ device_dict,
+ version,
+ node_info={"all_node": all_node},
+ )
+
+ pv_name = self.name(photovoltaic_id, "photovoltaic")
+ inverter_name = self.name(photovoltaic_id, "inverter")
+ meter_name = self.name(photovoltaic_id, "meter")
+ phases = cyme_phase_name[int(photovoltaic["Phase"])]
+ panel_efficiency = 0.2
+ panel_area = math.ceil(
+ 13.3 * rated_power / panel_efficiency
+ ) # got from NG converter, double-check is needed
+ pv_dict = {
+ "parent": inverter_name,
+ "generator_status": "ONLINE",
+ "generator_mode": "SUPPLY_DRIVEN",
+ "panel_type": "MULTI_CRYSTAL_SILICON",
+ "efficiency": panel_efficiency,
+ "area": panel_area,
+ "tilt_angle": "45",
+ "orientation_azimuth": "180",
+ "orientation": "FIXED_AXIS",
+ }
+ self.object("solar", pv_name, pv_dict, overwrite=False)
+ inverter_dict = {
+ "parent": meter_name,
+ "generator_status": "ONLINE",
+ "generator_mode": "CONSTANT_PF",
+ "inverter_type": "PWM",
+ "inverter_efficiency": efficiency,
+ "phases": phases,
+ "power_factor": power_factor,
+ "rated_power": rated_power * 1000,
+ }
+ self.object("inverter", inverter_name, inverter_dict, overwrite=False)
+ meter_dict = {
+ "phases": phases,
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ }
+ self.object("meter", meter_name, meter_dict, overwrite=False)
+
+ line_name = self.name(photovoltaic_id, "overhead_line")
+ conductorABC_id = "DEFAULT"
+ conductorN_id = "DEFAULT"
+ self.add_overhead_line_conductors([conductorABC_id, conductorN_id], version)
+ spacing_id = "DEFAULT"
+ self.add_line_spacing(spacing_id, version)
+ configuration_name = self.add_line_configuration(
+ [
+ conductorABC_id,
+ conductorABC_id,
+ conductorABC_id,
+ conductorN_id,
+ spacing_id,
+ ],
+ version,
+ )
+ return self.object(
+ "overhead_line",
+ line_name,
+ {
+ "phases": phases,
+ "from": parent_name,
+ "to": meter_name,
+ "length": "1 m",
+ "configuration": configuration_name,
+ },
+ )
+
+ def node_checks(
+ self, node_dict, node_links, device_dict, version, **kwargs
+ ): # check node objects
+ list_of_from = []
+ list_of_to = []
+ list_of_parent = []
+ all_node = kwargs["node_info"]["all_node"]
+ for name in list(
+ self.objects.keys()
+ ): # missing node: if a node object is used in link object but not been difined
+ data = self.objects[name]
+ if "from" in data.keys():
+ if data["from"] not in self.objects.keys():
+ node_dict[data["from"]] = self.add_node(
+ data["from"][3:],
+ node_links,
+ device_dict,
+ version,
+ node_info={"all_node": all_node},
+ )
+ if data["from"] not in list_of_from:
+ list_of_from.append(data["from"])
+ if "to" in data.keys():
+ if data["to"] not in self.objects.keys():
+ node_dict[data["to"]] = self.add_node(
+ data["to"][3:],
+ node_links,
+ device_dict,
+ version,
+ node_info={"all_node": all_node},
+ )
+ if data["to"] not in list_of_to:
+ list_of_to.append(data["to"])
+ if "parent" in data.keys():
+ if data["parent"] not in self.objects.keys():
+ node_dict[data["parent"]] = self.add_node(
+ data["parent"][3:],
+ node_links,
+ device_dict,
+ version,
+ node_info={"all_node": all_node},
+ )
+ if data["parent"] not in list_of_parent:
+ list_of_parent.append(data["parent"])
+ for name in list(self.objects.keys()):
+ data = self.objects[name]
+ if "class" in data.keys() and data["class"] == "node":
+ if (
+ data["name"] not in list_of_from
+ and data["name"] not in list_of_to
+ and data["name"] not in list_of_parent
+ ): # islanded nodes
+ warning(
+ f'{cyme_mdbname}@{network_id}: node {data["name"]} is islanded.'
+ )
+ self.delete(name)
+ if (
+ "parent" in data.keys() and data["name"] == data["parent"]["name"]
+ ): # the object's parent is itself
+ warning(f"{cyme_mdbname}@{network_id}: {name}'parent is itself.")
+ self.delete(name)
+ if (
+ "from" in data.keys()
+ and "to" in data.keys()
+ and data["from"] == data["to"]
+ ): # section loops back to itself
+ warning(
+ f"{cyme_mdbname}@{network_id}: section {name} loops back to itself."
+ )
+ self.delete(name)
+ return node_dict
+
+ def link_checks(self): # check link objects
+ done = False
+ while not done:
+ done = True
+ for name in list(self.objects.keys()):
+ try:
+ data = self.objects[name]
+ if (
+ "class" in data.keys() and data["class"] == "link"
+ ): # link needs to be collapse
+ self.delete(name)
+ done = False
+ break
+ elif (
+ "class" in data.keys()
+ and data["class"] in ["node", "load"]
+ and "parent" in data.keys()
+ ): # don't allow grandchild cyme_table["node"]
+ parent_name = data["parent"]
+ parent_data = self.objects[parent_name]
+ if (
+ "class" in parent_data.keys()
+ and parent_data["class"] in ["node", "load"]
+ and "parent" in parent_data.keys()
+ ):
+ grandparent = parent_data["parent"]
+ data["parent"] = grandparent
+ done = False
+ break
+ except Exception as exc:
+ warning(
+ format_exception(
+ "link removal failed", name, self.objects[name]
+ )
+ )
+ self.delete(name)
+ pass
+
+ def section_checks(self): # remove parallel section between two nodes
+ multi_g = nx.MultiGraph()
+ for name in list(self.objects.keys()):
+ try:
+ data = self.objects[name]
+ if "from" in data.keys() and "to" in data.keys():
+ if data["from"] not in multi_g:
+ multi_g.add_node(data["from"])
+ if data["to"] not in multi_g:
+ multi_g.add_node(data["to"])
+ multi_g.add_edge(
+ data["from"],
+ data["to"],
+ edge_name=name,
+ edge_phase=data["phases"].replace("N", ""),
+ )
+ except Exception as exc:
+ warning(
+ format_exception(
+ "connection removal failed", name, self.objects[name]
+ )
+ )
+ self.delete(name)
+ pass
+ for u in multi_g.nodes():
+ for neighbor in multi_g.neighbors(u):
+ if multi_g.number_of_edges(u, neighbor) > 1:
+ edge_data = {}
+ for edge_id in multi_g[u][neighbor].keys():
+ if (
+ multi_g[u][neighbor][edge_id]["edge_name"][0:2]
+ not in edge_data.keys()
+ ):
+ edge_data[
+ multi_g[u][neighbor][edge_id]["edge_name"][0:2]
+ ] = edge_id
+ else:
+ warning(
+ f"{cyme_mdbname}@{network_id}: multiple {multi_g[u][neighbor][edge_id]['edge_name'][0:2]} devices connected between {u} and {neighbor}."
+ )
+ object_name = multi_g[u][neighbor][edge_id]["edge_name"]
+ if object_name in self.objects.keys():
+ glm_output_print(
+ f"object_name is deleted {object_name}."
+ )
+ self.delete(object_name)
+ # RG > TF > SW > FS > OL = UL
+ if "RG" in edge_data.keys(): # one of the multi-edges is regulator
+ if (
+ "OL" in edge_data.keys() or "UL" in edge_data.keys()
+ ): # add a node to handle both RG and OL/UL
+ name_node_added = multi_g[u][neighbor][edge_data["RG"]][
+ "edge_name"
+ ]
+ self.object(
+ "node",
+ self.name(name_node_added, "node"),
+ {
+ "phases": multi_g[u][neighbor][edge_data["RG"]][
+ "edge_phase"
+ ]
+ + "N",
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ "bustype": "PQ",
+ },
+ )
+ RG_object_name = multi_g[u][neighbor][edge_data["RG"]][
+ "edge_name"
+ ]
+ self.objects[RG_object_name]["to"] = self.name(
+ name_node_added, "node"
+ )
+ if "OL" in edge_data.keys():
+ line_object_name = multi_g[u][neighbor][
+ edge_data["OL"]
+ ]["edge_name"]
+ else:
+ line_object_name = multi_g[u][neighbor][
+ edge_data["UL"]
+ ]["edge_name"]
+ self.objects[line_object_name]["from"] = self.name(
+ name_node_added, "node"
+ )
+ for key in edge_data.keys():
+ if key != "RG" and key != "OL" and key != "UL":
+ object_name = multi_g[u][neighbor][edge_data[key]][
+ "edge_name"
+ ]
+ if object_name in self.objects.keys():
+ self.delete(object_name)
+ elif (
+ "TF" in edge_data.keys()
+ ): # one of the multi-edges is transformer
+ if (
+ "OL" in edge_data.keys() or "UL" in edge_data.keys()
+ ): # add a node to handle both RG and OL/UL
+ name_node_added = multi_g[u][neighbor][edge_data["TF"]][
+ "edge_name"
+ ]
+ TF_object_name = multi_g[u][neighbor][edge_data["TF"]][
+ "edge_name"
+ ]
+ TF_config_name = self.objects[TF_object_name][
+ "configuration"
+ ]
+ self.object(
+ "node",
+ self.name(name_node_added, "node"),
+ {
+ "phases": multi_g[u][neighbor][edge_data["TF"]][
+ "edge_phase"
+ ]
+ + "N",
+ "nominal_voltage": f"{self.objects[TF_config_name]['secondary_voltage']}",
+ "bustype": "PQ",
+ },
+ )
+
+ self.objects[TF_object_name]["to"] = self.name(
+ name_node_added, "node"
+ )
+ if "OL" in edge_data.keys():
+ line_object_name = multi_g[u][neighbor][
+ edge_data["OL"]
+ ]["edge_name"]
+ else:
+ line_object_name = multi_g[u][neighbor][
+ edge_data["UL"]
+ ]["edge_name"]
+ self.objects[line_object_name]["from"] = self.name(
+ name_node_added, "node"
+ )
+ for key in edge_data.keys():
+ if key != "TF" and key != "OL" and key != "UL":
+ object_name = multi_g[u][neighbor][edge_data[key]][
+ "edge_name"
+ ]
+ if object_name in self.objects.keys():
+ self.delete(object_name)
+ elif "SW" in edge_data.keys():
+ # one of the multi-edges is switch
+ if (
+ "OL" in edge_data.keys() or "UL" in edge_data.keys()
+ ): # add a node to handle both RG and OL/UL
+ name_node_added = multi_g[u][neighbor][edge_data["SW"]][
+ "edge_name"
+ ]
+ self.object(
+ "node",
+ self.name(name_node_added, "node"),
+ {
+ "phases": multi_g[u][neighbor][edge_data["SW"]][
+ "edge_phase"
+ ]
+ + "N",
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ "bustype": "PQ",
+ },
+ )
+ SW_object_name = multi_g[u][neighbor][edge_data["SW"]][
+ "edge_name"
+ ]
+ self.objects[SW_object_name]["to"] = self.name(
+ name_node_added, "node"
+ )
+ if "OL" in edge_data.keys():
+ line_object_name = multi_g[u][neighbor][
+ edge_data["OL"]
+ ]["edge_name"]
+ else:
+ line_object_name = multi_g[u][neighbor][
+ edge_data["UL"]
+ ]["edge_name"]
+ self.objects[line_object_name]["from"] = self.name(
+ name_node_added, "node"
+ )
+ for key in edge_data.keys():
+ if key != "SW" and key != "OL" and key != "UL":
+ object_name = multi_g[u][neighbor][edge_data[key]][
+ "edge_name"
+ ]
+ if object_name in self.objects.keys():
+ self.delete(object_name)
+ elif "FS" in edge_data.keys():
+ # one of the multi-edges is fuse
+ if (
+ "OL" in edge_data.keys() or "UL" in edge_data.keys()
+ ): # add a node to handle both RG and OL/UL
+ name_node_added = multi_g[u][neighbor][edge_data["FS"]][
+ "edge_name"
+ ]
+ self.object(
+ "node",
+ self.name(name_node_added, "node"),
+ {
+ "phases": multi_g[u][neighbor][edge_data["FS"]][
+ "edge_phase"
+ ]
+ + "N",
+ "nominal_voltage": "${GLM_NOMINAL_VOLTAGE}",
+ "bustype": "PQ",
+ },
+ )
+ FS_object_name = multi_g[u][neighbor][edge_data["FS"]][
+ "edge_name"
+ ]
+ self.objects[FS_object_name]["to"] = self.name(
+ name_node_added, "node"
+ )
+ if "OL" in edge_data.keys():
+ line_object_name = multi_g[u][neighbor][
+ edge_data["OL"]
+ ]["edge_name"]
+ else:
+ line_object_name = multi_g[u][neighbor][
+ edge_data["UL"]
+ ]["edge_name"]
+ self.objects[line_object_name]["from"] = self.name(
+ name_node_added, "node"
+ )
+ for key in edge_data.keys():
+ if key != "FS" and key != "OL" and key != "UL":
+ object_name = multi_g[u][neighbor][edge_data[key]][
+ "edge_name"
+ ]
+ if object_name in self.objects.keys():
+ self.delete(object_name)
+ elif "OL" in edge_data.keys() or "UL" in edge_data.keys():
+ for key in edge_data.keys():
+ if "OL" in edge_data.keys() and key != "OL":
+ object_name = multi_g[u][neighbor][edge_data[key]][
+ "edge_name"
+ ]
+ if object_name in self.objects.keys():
+ self.delete(object_name)
+ elif "UL" in edge_data.keys() and key != "UL":
+ object_name = multi_g[u][neighbor][edge_data[key]][
+ "edge_name"
+ ]
+ if object_name in self.objects.keys():
+ self.delete(object_name)
+ else:
+ for key in edge_data.keys():
+ object_name = multi_g[u][neighbor][edge_data[key]][
+ "edge_name"
+ ]
+ print(self.objects[object_name])
+ error(
+ f"CYME model has unsupported duplicate connections between {u} and {neighbor}",
+ 60,
+ )
+
+ def phase_checks(self): # check phase dismatch
+ check_done = False
+ while not check_done:
+ check_done = True
+ for name in list(self.objects.keys()):
+ data = self.objects[name]
+ target_node_name = None
+ target_device_name = None
+ if "from" in data.keys() and "to" in data.keys():
+ target_device_name = data["name"]
+ elif "parent" in data.keys() and data["class"] != "solar":
+ target_node_name = data["parent"]
+ if target_device_name:
+ if len(self.objects[data["from"]]["phases"]) < len(
+ self.objects[data["to"]]["phases"]
+ ):
+ for phase in self.objects[data["from"]]["phases"].replace(
+ "N", ""
+ ):
+ if phase not in self.objects[data["to"]]["phases"]:
+ warning(
+ f"{cyme_mdbname}@{network_id} phase dismatch: {data['from']} has {self.objects[data['from']]['phases']} \
+ but {data['to']} has {self.objects[data['to']]['phases']}"
+ )
+ else:
+ for phase in self.objects[data["to"]]["phases"].replace(
+ "N", ""
+ ):
+ if phase not in self.objects[data["from"]]["phases"]:
+ warning(
+ f"{cyme_mdbname}@{network_id} phase dismatch: {data['from']} has {self.objects[data['from']]['phases']} \
+ but {data['to']} has {self.objects[data['to']]['phases']}"
+ )
+ break
+ for phase in data["phases"].replace("N", ""):
+ if (
+ phase not in self.objects[data["from"]]["phases"]
+ and phase not in self.objects[data["to"]]["phases"]
+ ):
+ warning(
+ f"{cyme_mdbname}@{network_id} phase dismatch: section {data['name']} has {data['phases']} \
+ but {data['to']} has {self.objects[data['to']]['phases']} and {data['from']} has {self.objects[data['from']]['phases']}"
+ )
+ break
+ if target_node_name:
+ target_node = self.objects[target_node_name]
+ target_node_phases = target_node["phases"].replace("N", "")
+ for phase in data["phases"].replace("N", ""):
+ if phase not in target_node_phases:
+ warning(
+ f"{cyme_mdbname}@{network_id} phase dismatch: parent {target_node_name} has {target_node_phases} but child {name} has {data['phases']}"
+ )
+ if phase_check_fix:
+ self.objects[name]["phases"] = self.objects[name][
+ "phases"
+ ].replace(phase, "")
+ if len(self.objects[name]["phases"]) == 0:
+ warning(
+ f"{cyme_mdbname}@{network_id} phase problem: {name} phases is None"
+ )
+
+ def voltage_checks(
+ self, nominal_voltage
+ ): # Check transformer primary/secondary voltage
+ check_done = False
+ check_done_count = 0
+ check_done_limit = len(self.objects.items())
+ while not check_done:
+ check_done = True
+ check_done_count += 1
+ print()
+ if check_done_count > check_done_limit:
+ warning(
+ f"converter was unable to reconcile voltage mismatches in {cyme_mdbname}@{network_id}."
+ )
+ self.write(
+ f"// Converter's voltage checks were unable to reconcile voltage mismatches in {cyme_mdbname}@{network_id} with current GLM_NOMINAL_VOLTAGE."
+ )
+ break
+ for name, data in self.objects.items():
+ if "class" in data.keys() and (
+ data["class"] == "transformer"
+ or data["class"] == "single_transformer"
+ ):
+ config_name = data["configuration"]
+ config_data = self.objects[config_name]
+ from_node_name = data["from"]
+ to_node_name = data["to"]
+ from_node_voltage = self.objects[from_node_name]["nominal_voltage"]
+ to_node_voltage = self.objects[to_node_name]["nominal_voltage"]
+ primary_voltage = config_data["primary_voltage"]
+ secondary_voltage = config_data["secondary_voltage"]
+ if (
+ from_node_voltage == "${GLM_NOMINAL_VOLTAGE}"
+ and primary_voltage.replace("kV", "") != nominal_voltage
+ ):
+ warning(
+ f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has primary voltage as {primary_voltage} but node {from_node_name} nominal voltage is {nominal_voltage}kV."
+ )
+ if voltage_check_fix:
+ check_done = False
+ self.objects[from_node_name][
+ "nominal_voltage"
+ ] = primary_voltage
+ elif from_node_voltage != "${GLM_NOMINAL_VOLTAGE}" and float(
+ primary_voltage.replace("kV", "")
+ ) != float(from_node_voltage.replace("kV", "")):
+ warning(
+ f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has primary voltage as {primary_voltage} but node {from_node_name} nominal voltage is {from_node_voltage}."
+ )
+ if voltage_check_fix:
+ check_done = False
+ self.objects[from_node_name][
+ "nominal_voltage"
+ ] = primary_voltage
+ if (
+ to_node_voltage == "${GLM_NOMINAL_VOLTAGE}"
+ and secondary_voltage.replace("kV", "") != nominal_voltage
+ ):
+ warning(
+ f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has secondary voltage as {secondary_voltage} but node {to_node_name} nominal voltage is {nominal_voltage}kV."
+ )
+ if voltage_check_fix:
+ check_done = False
+ self.objects[to_node_name][
+ "nominal_voltage"
+ ] = secondary_voltage
+ elif to_node_voltage != "${GLM_NOMINAL_VOLTAGE}" and float(
+ secondary_voltage.replace("kV", "")
+ ) != float(to_node_voltage.replace("kV", "")):
+ warning(
+ f"{cyme_mdbname}@{network_id} voltage mismatch: transformer {name} has secondary voltage as {secondary_voltage} but node {to_node_name} nominal voltage is {to_node_voltage}."
+ )
+ if voltage_check_fix:
+ check_done = False
+ self.objects[to_node_name][
+ "nominal_voltage"
+ ] = secondary_voltage
+ elif "from" in data.keys() and "to" in data.keys():
+ from_node_name = data["from"]
+ to_node_name = data["to"]
+ from_node_voltage = self.objects[from_node_name]["nominal_voltage"]
+ to_node_voltage = self.objects[to_node_name]["nominal_voltage"]
+ if from_node_voltage != to_node_voltage:
+ warning(
+ f"{cyme_mdbname}@{network_id} voltage mismatch: node {from_node_name} is {from_node_voltage} but node {to_node_name} is {to_node_voltage}."
+ )
+ if voltage_check_fix:
+ check_done = False
+ self.objects[to_node_name][
+ "nominal_voltage"
+ ] = self.objects[from_node_name]["nominal_voltage"]
+ elif "parent" in data.keys() and data["class"] not in [
+ "solar",
+ "inverter",
+ ]:
+ parent_name = data["parent"]
+ parent_data = self.objects[parent_name]
+ if data["nominal_voltage"] != parent_data["nominal_voltage"]:
+ warning(
+ f"{cyme_mdbname}@{network_id} voltage mismatch: parent {parent_name} has voltage as {parent_data['nominal_voltage']} but child {name} has {data['nominal_voltage']}."
+ )
+ if voltage_check_fix:
+ check_done = False
+ old_voltage = self.objects[name]["nominal_voltage"]
+ self.objects[name]["nominal_voltage"] = parent_data[
+ "nominal_voltage"
+ ]
+ if data["class"] == "load":
+ warning(
+ f"{cyme_mdbname}@{network_id}: load {fix_name(name)} nominal_voltage changes from {old_voltage} to {parent_data['nominal_voltage']}."
+ )
+
+ def object_checks(self): # Check conversion
+ del_nom_volt_list = [
+ "overhead_line",
+ "underground_line",
+ "regulator",
+ "transformer",
+ "switch",
+ "fuse",
+ "ZIPload",
+ "diesel_dg",
+ "triplex_line_conductor",
+ "recorder",
+ "inverter",
+ "solar",
+ "triplex_line",
+ ]
+ for name, data in self.objects.items():
+ if not "name" in data.keys():
+ warning(
+ "%s: object does not have a name, object data [%s]"
+ % (self.filename, data)
+ )
+ elif not "class" in data.keys():
+ warning(
+ "%s: object '%s' does not have a class"
+ % (self.filename, data["name"])
+ )
+ elif data["class"] in ["link", "powerflow_object", "line"]:
+ print(self.objects[name])
+ warning(
+ "%s: object '%s' uses abstract-only class '%s'"
+ % (self.filename, data["name"], data["class"])
+ )
+ elif (
+ data["class"] in del_nom_volt_list and "nominal_voltage" in data.keys()
+ ):
+ del self.objects[name]["nominal_voltage"]
+
+ def name_check(self): # Check object name
+ for name_compared, data_compared in self.objects.items():
+ for name, data in self.objects.items():
+ if name != name_compared:
+ for key in data.keys():
+ if data[key] == name_compared:
+ data[key] = fix_name(name_compared)
+ data_compared["name"] = fix_name(name_compared)
+
+
+def cyme_extract_9(network_id, network, conversion_info):
+ output_file_name = conversion_info["output_file_name"]
+ version = conversion_info["version"]
+
+ creation_time = int(network["CreationTime"])
+ last_change = int(network["LastChange"])
+ load_factor = float(network["LoadFactor"])
+ if single_file:
+ glmname = os.path.abspath(f"{output_folder}/{output_file_name}.glm")
+ else:
+ glmname = os.path.abspath(
+ f"{output_folder}/{output_file_name}_{network_id}.glm"
+ )
+
+ glm = GLM(glmname, "w")
+ # glm.comment(f"Automatically generated by {git_project}/postproc/write_glm.py")
+
+ # glm.blank()
+ # glm.comment("","Application information","")
+ # glm.define("APP_COMMAND",app_command)
+ # glm.define("APP_VERSION",app_version)
+
+ # glm.blank()
+ # glm.comment("","Git information","")
+ # glm.define("GIT_PROJECT",git_project)
+ # glm.define("GIT_COMMIT",git_commit)
+ # glm.define("GIT_BRANCH",git_branch)
+
+ # glm.blank()
+ # glm.comment("","GLM creation context","")
+ # glm.define("GLM_PATHNAME",glmname)
+ # glm.define("GLM_CREATED",dt.datetime.utcnow().isoformat())
+ # glm.define("GLM_USER",os.getenv("USER"))
+ # glm.define("GLM_WORKDIR",os.getenv("PWD"))
+ # glm.define("GLM_LANG",os.getenv("LANG"))
+
+ # # settings from model
+ # glm.blank()
+ # glm.comment("","CYME model information","")
+ # glm.define("CYME_MDBNAME",cyme_mdbname)
+ # glm.define("CYME_VERSION",version)
+ # glm.define("CYME_CREATED",dt.datetime.fromtimestamp(creation_time).isoformat())
+ # glm.define("CYME_MODIFIED",dt.datetime.fromtimestamp(last_change).isoformat())
+ # glm.define("CYME_LOADFACTOR",load_factor)
+ # glm.define("CYME_NETWORKID",network_id)
+
+ # settings from config.csv
+ glm.blank()
+ glm.comment("", "Settings from 'config.csv'", "")
+ define = settings["GLM_DEFINE"].split("=")
+ if type(define) is list and len(define) > 1:
+ glm.define(define[0].strip(), "=".join(define[1:]).strip())
+ feeder_kVLN = feeder_voltage_find(network_id, cyme_table)
+ if feeder_kVLN is not None:
+ glm.comment("GLM_NOMINAL_VOLTAGE found in network MDB is used")
+ settings["GLM_NOMINAL_VOLTAGE"] = feeder_kVLN + " kV"
+ glm.define("GLM_NOMINAL_VOLTAGE", settings["GLM_NOMINAL_VOLTAGE"])
+ elif settings["GLM_NOMINAL_VOLTAGE"]:
+ glm.comment("GLM_NOMINAL_VOLTAGE found in config.csv is used")
+ glm.define("GLM_NOMINAL_VOLTAGE", settings["GLM_NOMINAL_VOLTAGE"])
+ feeder_kVLN = fix_unit(settings["GLM_NOMINAL_VOLTAGE"], "kV")
+ else:
+ if settings[
+ "GLM_INCLUDE"
+ ]: # cannot verify setting in GLM_INCLUDE until run in gridlabd
+ glm.ifndef(
+ "GLM_NOMINAL_VOLTAGE",
+ lambda: glm.error(
+ "GLM_NOMINAL_VOLTAGE must be defined in either 'config.csv' or the GLM_INCLUDE file"
+ ),
+ )
+ else:
+ error(
+ "GLM_NOMINAL_VOLTAGE must be defined in either 'config.csv' or the GLM_INCLUDE file"
+ )
+ default_load_voltage = re.match(
+ "\d+[\.]?[\d+]*", settings["GLM_NOMINAL_VOLTAGE"]
+ ).group(0)
+ if settings["GLM_INCLUDE"]:
+ for include in settings["GLM_INCLUDE"].split():
+ glm.include(include.strip())
+ else:
+ # glm.blank()
+ # glm.comment("","default clock settings","")
+ # glm.clock({"timezone":"PST+8PDT", "starttime":"2020-01-01T00:00:00+08:00", "stoptime":"2020-01-01T00:05:00+08:00"})
+ pass
+
+ node_dict = {}
+ device_dict = {}
+ node_links = {}
+
+ all_section_device = table_find(cyme_table["sectiondevice"], NetworkId=network_id)
+ all_section = table_find(cyme_table["section"], NetworkId=network_id)
+ all_load = table_find(cyme_table["load"], NetworkId=network_id)
+ all_node = table_find(cyme_table["node"], NetworkId=network_id)
+
+ # node graph data
+ if "nodetag" in cyme_table.keys():
+ for index, node in table_find(
+ cyme_table["nodetag"], NetworkId=network_id
+ ).iterrows():
+ node_id = node["NodeId"]
+ node_dict[node_id] = [] # node dictionary
+ for node_id, node in table_find(
+ cyme_table["node"], NetworkId=network_id
+ ).iterrows():
+ node_id = node["NodeId"]
+ node_links[node_id] = [] # incident links
+ else:
+ for index, node in table_find(
+ cyme_table["node"], NetworkId=network_id
+ ).iterrows():
+ node_id = node["NodeId"]
+ node_links[node_id] = [] # incident links
+ node_dict[node_id] = [] # node dictionary
+
+ # links
+ for index, section in table_find(
+ cyme_table["section"], NetworkId=network_id
+ ).iterrows():
+ section_id = section["SectionId"]
+ links = glm.add(
+ "link", section_id, section, version=5020, node_links=node_links
+ )
+ if links:
+ device_dict.update(links)
+
+ # cyme_table["node"]
+ for node_id in node_dict.keys():
+ # only network node and substantiation will be added
+ if (
+ table_find(cyme_table["node"], NodeId=node_id).iloc[0]["ComponentMask"]
+ != "0"
+ ):
+ node_dict[node_id] = glm.add_node(
+ node_id,
+ node_links,
+ device_dict,
+ version=5020,
+ node_info={"all_node": all_node},
+ )
+
+ # overhead lines
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["overheadbyphase"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("overhead_line_phase", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "overheadbyphase".'
+ )
+
+ # unbalanced overhead lines
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["overheadlineunbalanced"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("overhead_line_unbalanced", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f"{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table 'overheadlineunbalanced'."
+ )
+
+ # overhead lines
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["overheadline"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("overhead_line", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "overheadline".'
+ )
+
+ # underground lines
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["undergroundline"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("underground_line", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "undergroundline".'
+ )
+
+ # transformer
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["transformer"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("transformer", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "transformer".'
+ )
+
+ # transformerbyphase
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["transformerbyphase"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("single_transformer", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "transformerbyphase".'
+ )
+
+ # load
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["customerload"], NetworkId=network_id
+ ).iterrows():
+ section_id = all_section_device[
+ all_section_device["DeviceNumber"] == cyme_data["DeviceNumber"]
+ ]["SectionId"].values
+ load_section = all_section[all_section["SectionId"] == section_id[0]]
+ connection_type = int(
+ all_load[all_load["DeviceNumber"] == cyme_data["DeviceNumber"]][
+ "ConnectionConfiguration"
+ ]
+ )
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add(
+ "load",
+ cyme_id,
+ cyme_data,
+ version=5200,
+ node_info={
+ "Node_Links": node_links,
+ "Device_Dicts": device_dict,
+ "load_section": load_section,
+ "connection_type": connection_type,
+ "all_node": all_node,
+ "load_voltage": default_load_voltage,
+ },
+ )
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "customerload".'
+ )
+
+ # regulator
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["regulator"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("regulator", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "regulator".'
+ )
+
+ # capacitor
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["shuntcapacitor"], NetworkId=network_id
+ ).iterrows():
+ section_id = all_section_device[
+ all_section_device["DeviceNumber"] == cyme_data["DeviceNumber"]
+ ]["SectionId"].values
+ cap_section = all_section[all_section["SectionId"] == section_id[0]]
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add(
+ "capacitor",
+ cyme_id,
+ cyme_data,
+ version=5020,
+ node_info={
+ "Node_Links": node_links,
+ "Device_Dicts": device_dict,
+ "cap_section": cap_section,
+ },
+ )
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "shuntcapacitor".'
+ )
+
+ # switches
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["switch"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("switch", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "switch".'
+ )
+
+ # breaker
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["breaker"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("breaker", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "breaker".'
+ )
+
+ # recloser
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["recloser"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("recloser", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "recloser".'
+ )
+
+ # fuse
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["fuse"], NetworkId=network_id
+ ).iterrows():
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add("fuse", cyme_id, cyme_data, version=5020)
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "fuse".'
+ )
+
+ # photovoltaic
+ try:
+ for cyme_id, cyme_data in table_find(
+ cyme_table["photovoltaic"], NetworkId=network_id
+ ).iterrows():
+ section_id = all_section_device[
+ all_section_device["DeviceNumber"] == cyme_data["DeviceNumber"]
+ ]["SectionId"].values
+ pv_section = all_section[all_section["SectionId"] == section_id[0]]
+ cyme_id = cyme_data["DeviceNumber"]
+ glm.add(
+ "photovoltaic",
+ cyme_id,
+ cyme_data,
+ version=5020,
+ node_info={
+ "Node_Links": node_links,
+ "Device_Dicts": device_dict,
+ "pv_section": pv_section,
+ "all_node": all_node,
+ },
+ )
+ except Exception as err:
+ exception_type = type(err).__name__
+ warning(
+ f'{cyme_mdbname}@{network_id}: ({exception_type}: {err}) cannot add GLM objects from cyme_table "photovoltaic".'
+ )
+
+ # network senity checks
+ node_dict = glm.node_checks(
+ node_dict,
+ node_links,
+ device_dict,
+ version=5200,
+ node_info={"all_node": all_node},
+ )
+ glm.link_checks()
+ glm.section_checks()
+ glm.phase_checks()
+ glm.object_checks()
+ glm.voltage_checks(feeder_kVLN)
+ glm.name_check()
+
+ glm.blank()
+ glm.comment("", "Modules", "")
+ glm.module("powerflow", {"solver_method": "NR"})
+ glm.module("generators")
+
+ glm.blank()
+ glm.comment("", "Objects", "")
+
+ # generate coordinate file
+ if geodata_file:
+ df_node = pd.DataFrame.from_dict(node_geodata)
+ df_node = df_node.T
+ df_node.drop(df_node[df_node[:]["NotworkID"] != network_id].index, inplace=True)
+ df_node = df_node.drop(["NotworkID"], axis=1)
+ df_node.to_csv(f"{output_folder}/{geodata_file}", index=False, header=True)
+
+ glm.close()
+
+
+def convert(input_file: str, output_file: str = None, options: dict[str, Any] = {}):
+ """Convert a CYME MDB file to GLM
+
+ Parameters:
+ input_file (str) input MDB file name
+ output_file (str) output GLM file name
+ options (dict) options to define as globals in model
+ """
+
+ global data_folder
+ global input_folder
+ global output_folder
+ global network_select
+ global equipment_file
+ global config_file
+ global modify_file
+ global settings
+ global cyme_table
+ global cyme_equipment_table
+ global default_load_voltage
+ global network_id
+ global cyme_mdbname
+ global geodata_file
+ global single_file
+ global WARNING
+ global DEBUG
+ global QUIET
+ global VERBOSE
+ global voltage_check_fix
+ global phase_check_fix
+ global line_length_unit
+ global error_count
+ global GLM_error_file
+ global GLM_warning_file
+ global GLM_output_file
+ global node_geodata
+
+ # Configure options and paths
+
+ if options:
+ for opt, arg in options.items():
+ if opt in ("c", "config"):
+ if arg and arg != True:
+ config_file = arg.strip()
+ else:
+ print(config)
+ elif opt in ("t", "cyme-tables"):
+ print(" ".join(cyme_tables_required))
+ sys.exit(0)
+ elif opt in ("d", "data-dir"):
+ data_folder = arg.strip()
+ elif opt in ("m", "modify"):
+ modify_file = arg.strip()
+ elif opt in ("n", "network-ID"):
+ # only extract the selected network
+ network_select = arg.split(" ")
+ elif opt in ("e", "equipment-mdb"):
+ equipment_file = arg.strip()
+ elif opt in ("C", "coordinateCSV"):
+ geodata_file = arg.strip()
+ else:
+ error(f"{opt}={arg} is not a valid option", 5)
+ input_folder = os.path.dirname(os.path.abspath(input_file))
+ input_file_name = os.path.basename(input_file).split(".")[0]
+ if output_file:
+ output_folder = os.path.dirname(os.path.abspath(output_file))
+ output_file_name = os.path.basename(output_file).split(".")[0]
+ else:
+ output_file_name = input_file_name
+ output_folder = input_folder
+ output_file = os.path.join(output_folder, output_file_name + ".glm")
+ if not network_select:
+ single_file = True
+
+ # converter input MBD to CSV tables
+ if not data_folder:
+ data_folder = f"/tmp/gridlabd/mdb-cyme2glm/{input_file_name}"
+ cyme_table = mdb2csv(input_file, data_folder, cyme_tables_required, "non-empty")
+ cyme_mdbname = data_folder.split("/")[-1]
+ if equipment_file != None: # if equipment MDB is provided
+ equipment_data_folder = f"{data_folder}/cyme_equipment_tables"
+ cyme_equipment_table = mdb2csv(
+ equipment_file, equipment_data_folder, cyme_tables_required, "non-empty"
+ )
+ else:
+ cyme_equipment_table = {}
+
+ # Load user configuration
+ if config_file:
+ settings = pd.read_csv(
+ config_file,
+ dtype=str,
+ names=["name", "value"],
+ comment="#",
+ ).set_index("name")
+ else:
+ default_config_data = {
+ "name": ["GLM_NOMINAL_VOLTAGE", "GLM_MODIFY", "GLM_WARNINGS"],
+ "value": ["2.40178 kV", "modify.csv", "stdout"],
+ }
+ settings = pd.DataFrame(data=default_config_data).set_index("name")
+ warning(f"No configuration file, will use default configurations")
+ for name, values in settings.iterrows():
+ if name in config.index:
+ config["value"][name] = values[0]
+ settings = config["value"]
+ GLM_output_file = open(settings["GLM_OUTPUT"], "w")
+ GLM_error_file = open(settings["ERROR_OUTPUT"], "a")
+ GLM_warning_file = open(settings["WARNING_OUTPUT"], "a")
+ default_load_voltage = None
+ voltage_check_fix = True if settings["GLM_VOLTAGE_FIX"].lower() == "true" else False
+ phase_check_fix = True if settings["GLM_PHASE_FIX"].lower() == "true" else False
+ line_length_unit = (
+ "m" if settings["GLM_INPUT_LINE_LENGTH_UNIT"].lower() == "m" else "km"
+ )
+ WARNING = True if settings["WARNING"].lower() == "true" else False
+ DEBUG = True if settings["DEBUG"].lower() == "true" else False
+ QUIET = True if settings["QUIET"].lower() == "true" else False
+ VERBOSE = True if settings["VERBOSE"].lower() == "true" else False
+
+ # Prepare Cyme converter settings
+ cyme_extracter = {
+ "90000": cyme_extract_9, # CYME version 9 database
+ "82000": cyme_extract_9, # CYME version 8 database
+ "50401": cyme_extract_9, # CYME version 5 database
+ }
+ cyme_extracter["default"] = cyme_extracter["90000"]
+
+ version = cyme_table["schemaversion"].loc[0]["Version"]
+ network_count = 0
+ conversion_info = {
+ "input_folder": input_folder,
+ "output_file_name": output_file_name,
+ "version": version,
+ }
+
+ #
+ for index, network in cyme_table["network"].iterrows():
+ network_id = network["NetworkId"]
+ if not re.match(settings["GLM_NETWORK_MATCHES"], network_id):
+ continue
+ else:
+ network_count += 1
+ if network_select != None and network_id not in network_select:
+ pass
+ else:
+ found = False
+ for key, extractor in cyme_extracter.items():
+ if key[0] == version[0]:
+ extractor(network_id, network, conversion_info)
+ found = True
+ if not found:
+ raise Exception(f"CYME model version {version} is not supported")
+
+
+if __name__ == "__main__":
+ testfile = "autotest/IEEE-123-cyme.mdb"
+ if os.path.exists(testfile):
+ config.annotated_model = True
+ convert(testfile)
diff --git a/docker/packer/prod.pkr.hcl b/docker/packer/prod.pkr.hcl
index f65377efc..c0ecc8c47 100644
--- a/docker/packer/prod.pkr.hcl
+++ b/docker/packer/prod.pkr.hcl
@@ -8,6 +8,7 @@ packer {
}
}
+// variables must be passed in the command line. Used for the github actions workflows.
variable "aws_access_key" {
type = string
description = "AWS access key"
@@ -39,6 +40,7 @@ variable "tagname" {
}
source "amazon-ebs" "ubuntu-22-04" {
+ force_deregister = true // If AMI of same name exists and is owned, we can replace it with this option
source_ami = "ami-014d05e6b24240371"
region = var.aws_region
instance_type = "t2.micro"
diff --git a/docker/packer/templateDevelop.pkr.hcl b/docker/packer/templateDevelop.pkr.hcl
index 37c8885d6..620f66832 100644
--- a/docker/packer/templateDevelop.pkr.hcl
+++ b/docker/packer/templateDevelop.pkr.hcl
@@ -8,6 +8,7 @@ packer {
}
}
+// variables must be passed in the command line. Used for the github actions workflows.
variable "aws_access_key" {
type = string
description = "AWS access key"
@@ -39,6 +40,7 @@ variable "tagname" {
}
source "amazon-ebs" "ubuntu-22-04" {
+ force_deregister = true // If AMI of same name exists and is owned, we can replace it with this option
source_ami = "ami-014d05e6b24240371"
region = var.aws_region
instance_type = "t2.micro"
diff --git a/docs/Converters/Import/Mdb_files.md b/docs/Converters/Import/Mdb_files.md
index cb986d09f..c21903b29 100644
--- a/docs/Converters/Import/Mdb_files.md
+++ b/docs/Converters/Import/Mdb_files.md
@@ -58,6 +58,111 @@ Options:
- `chunksize=INTEGER`: specify the chunksize for processing data (default 1000)
+## Config.csv Options
+
+Settings in the `config.csv` file that affect the converter include:
+
+~~~
+,
+,
+...
+,
+~~~
+
+### `GLM_NETWORK_PREFIX`
+
+Each network in the CYME database will be output in a separate GLM file using the name of the network with the network prefix.
+
+### `GLM_NETWORK_MATCHES`
+
+The network pattern matching uses POSIX regular expressions to match network names starting with the first character of the network name. Here are some useful examples:
+
+ - `abc`: match all network names that start with the string "abc"
+ - `abc$`: match the network name "abc" only
+ - `.*`: match all network names
+ - `.*abc`: match all networks containing the string "abc"
+ - `.*abc$`: match all networks ending with the string "abc"
+ - `[0-9]`: match all networks starting with the digits 0 through 9
+ - `.*[0-9]`: match all networks containing the digits 0 through 9
+ - `.*[0-9]$`: match all networks ending with the digits 0 through 9
+
+For details on POSIX pattern matching see [POSIX Regular Expression Documentation](https://en.wikibooks.org/wiki/Regular_Expressions/POSIX_Basic_Regular_Expressions).
+
+### `GLM_NOMINAL_VOLTAGE`
+
+The nominal voltage must be specified either in the `config.csv` or in the included GLM file.
+
+### `GLM_INCLUDE`
+
+A single `#include` macro may be added after the `#define` specified by `GLM_DEFINE`. This allows the define statement to control the behavior of the include file.
+
+### `GLM_DEFINE`
+
+A single `#define` may be specified to alter the behavior of the include file, object definitions, and modify statements.
+
+### `GLM_MODIFY`
+
+A single CSV file may be processed after the GLM objects are created to enable modification of object properties, if desired. The format of the modification file is as follows:
+
+~~~
+,,
+,,
+...
+,,
+~~~
+
+### `GLM_ASSUMPTIONS`
+
+During the conversion process, some assumption may be made to generate a working GridLAB-D model. These assumptions will be disposed of according to setting of `GLM_ASSUMPTIONS`:
+
+ - `ignore`: ignore the assumptions generated
+ - `include`: add the assumptions to the GLM file so they can be modified manually
+ - `save`: save the assumption to GLM file for later processig
+ - `warn`: save the assumptions to a CSV file and generate a warning message
+
+### `GLM_VOLTAGE_FIX`
+
+Once conversion of the mdb file is finished the converter can attempt to fix the nominal voltages across the network such that connections have the same voltage at each connection. `True` or `False`
+
+### `GLM_PHASE_FIX`
+
+Once conversion of the mdb file is finished the converter can attempt to fix the phases across the network such that connections have the phase at each connection. `True` or `False`
+
+### `GLM_INPUT_LINE_LENGTH_UNIT`
+
+Set the line length unit that is used within the input MDB file. Options are `m` or `km`. Default `m`.
+
+### `GLM_OUTPUT`
+
+Sets the path for any outputs. Defaults to `stdout`
+
+
+
+### `ERROR_OUTPUT`
+
+Sets the path for any errors. Defaults to `stderr`
+
+### `WARNING_OUTPUT`
+
+Sets the path for any warnings. Defaults to `stderr`
+
+### `WARNING`
+
+Option to display any warnings during the conversion. Defaults to `True`
+
+### `DEBUG`
+
+Option to display any debug outputs during the conversion. Defaults to `False`
+
+### `QUIET`
+
+Option to run converter without any error messages. Defaults to `False`
+
+### `VERBOSE`
+
+Option to run converter with verbose output messages. Defaults to `False`
+
+
# Example
@@ -78,3 +183,9 @@ The `MDB` converters require the `mdbtools` be installed on the host system.
# See also
* [[/Converters/Input/Csv_files]]
+
+
+
+
+
+
diff --git a/docs/Module/Generators/Diesel_dg.md b/docs/Module/Generators/Diesel_dg.md
index fb85dbed3..bc0549ccd 100644
--- a/docs/Module/Generators/Diesel_dg.md
+++ b/docs/Module/Generators/Diesel_dg.md
@@ -17,7 +17,7 @@ GLM:
speed " 1/min";
cylinders "";
stroke "";
- torque " N";
+ torque " N.m";
pressure " N/m^2";
time_operation " min";
fuel " kg";
@@ -388,7 +388,7 @@ Category of internal combustion engines
### `torque`
~~~
- double torque[N];
+ double torque[N.m];
~~~
Net brake load
diff --git a/docs/Utilities/Metar2glm.md b/docs/Utilities/Metar2glm.md
new file mode 100644
index 000000000..1f3b676bd
--- /dev/null
+++ b/docs/Utilities/Metar2glm.md
@@ -0,0 +1,41 @@
+[[/Utilities/Metar2glm]] - METAR to GLM realtime weather
+
+# Synopsis
+
+Shell:
+
+~~~
+ $ gridlabd python -m metar2glm -m metar2glm [--index|STATION ...] [-c|-class CLASSNAME]
+~~~
+
+GLM:
+
+~~~
+ #python -m metar2glm -m metar2glm [--index|STATION ...] [-c|-class CLASSNAME]
+~~~
+
+Options:
+
+- `-h|--help|help`: Get this help information (exits with code 0)
+
+- `-c|--class CLASSNAME`: Name the GLM class to use for the weather data class
+
+- `--index`: Output a list of valid METAR stations
+
+# Description
+
+The `metar2glm` tool creates an object that represents the realtime weather at the specifies weather stations.
+
+# Example
+
+The following outputs the realtime weather for San Francisco airport.
+
+~~~
+$ gridlabd python -m metar2glm KSFO
+~~~
+
+# See also
+
+* [https://github.com/python-metar/python-metar]
+* [https://www.ucar.edu]
+
diff --git a/module/connection/json_link/bin/json_link_tester.py b/module/connection/json_link/bin/json_link_tester.py
index c7e04ba3a..258cb9b34 100644
--- a/module/connection/json_link/bin/json_link_tester.py
+++ b/module/connection/json_link/bin/json_link_tester.py
@@ -2,7 +2,7 @@
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-"""
+__doc__="""
****************
json_link_tester
****************
diff --git a/module/connection/json_link_demo.py b/module/connection/json_link_demo.py
index 7b33b164e..d5be07851 100644
--- a/module/connection/json_link_demo.py
+++ b/module/connection/json_link_demo.py
@@ -2,7 +2,7 @@
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-"""
+__doc__="""
json_link_demo
~~~~~~~~~~~~~~
diff --git a/module/generators/autotest/test_diesel_dg.glm b/module/generators/autotest/test_diesel_dg.glm
index 8b8d9ff86..cc51172a6 100644
--- a/module/generators/autotest/test_diesel_dg.glm
+++ b/module/generators/autotest/test_diesel_dg.glm
@@ -129,7 +129,7 @@ object meter:671 {
speed 360 1/min;
- torque 680 N;
+ torque 680 N*m;
time_operation 20 min;
diff --git a/module/generators/diesel_dg.cpp b/module/generators/diesel_dg.cpp
index 324bffe50..1f6b0d8ca 100644
--- a/module/generators/diesel_dg.cpp
+++ b/module/generators/diesel_dg.cpp
@@ -55,7 +55,7 @@ diesel_dg::diesel_dg(MODULE *module)
PT_double, "speed[1/min]", PADDR(speed),PT_DESCRIPTION,"speed of an engine",
PT_double, "cylinders", PADDR(cylinders),PT_DESCRIPTION,"Total number of cylinders in a diesel engine",
PT_double, "stroke", PADDR(stroke),PT_DESCRIPTION,"category of internal combustion engines",
- PT_double, "torque[N]", PADDR(torque),PT_DESCRIPTION,"Net brake load",
+ PT_double, "torque[N*m]", PADDR(torque),PT_DESCRIPTION,"Net brake load",
PT_double, "pressure[N/m^2]", PADDR(pressure),PT_DESCRIPTION,"",
PT_double, "time_operation[min]", PADDR(time_operation),PT_DESCRIPTION,"",
PT_double, "fuel[kg]", PADDR(fuel),PT_DESCRIPTION,"fuel consumption",
diff --git a/module/resilience/docs/elevation.py b/module/resilience/docs/elevation.py
index 9b0fc8431..9eacdc734 100644
--- a/module/resilience/docs/elevation.py
+++ b/module/resilience/docs/elevation.py
@@ -2,7 +2,7 @@
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-"""Global elevation data acquisition
+__doc__="""Global elevation data acquisition
REQUIREMENTS
diff --git a/python/extras/gridlabd_library.py b/python/extras/gridlabd_library.py
index 1f23bf7c8..958b5e58b 100755
--- a/python/extras/gridlabd_library.py
+++ b/python/extras/gridlabd_library.py
@@ -2,7 +2,7 @@
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-"""Syntax: gridlabd_library ...
+__doc__="""Syntax: gridlabd_library ...
Subcommands:
help
"""
diff --git a/source/version.h b/source/version.h
index 62f381c30..27552ba76 100644
--- a/source/version.h
+++ b/source/version.h
@@ -11,7 +11,7 @@
#define REV_MAJOR 4
#define REV_MINOR 3
-#define REV_PATCH 1
+#define REV_PATCH 2
#ifdef HAVE_CONFIG_H
#include "config.h"
diff --git a/subcommands/gridlabd-building b/subcommands/gridlabd-building
index 9dcbded16..01a00bd4b 100644
--- a/subcommands/gridlabd-building
+++ b/subcommands/gridlabd-building
@@ -1,9 +1,10 @@
-#!/usr/bin/python3
-"""GridLAB-D powerflow building load model
+''':'
+exec "$GLD_BIN/python3" "$0" "$@"
+:' '''
+# Syntax: building [OPTIONS ...]
+__doc__="""GridLAB-D powerflow building load model
-Syntax:
-
- $ gridlabd building [OPTIONS ...]
+Syntax: gridlabd building [OPTIONS ...]
Options:
diff --git a/subcommands/gridlabd-check b/subcommands/gridlabd-check
index fbe448da8..d218122b0 100755
--- a/subcommands/gridlabd-check
+++ b/subcommands/gridlabd-check
@@ -1,8 +1,9 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
+# Syntax: check [OPTIONS] FILE ...
-"""GridLAB-D model check routines
+__doc__="""GridLAB-D model check routines
Syntax: gridlabd check [OPTIONS] FILE ...
diff --git a/subcommands/gridlabd-compare b/subcommands/gridlabd-compare
index 02cdbfa0f..767bfe1ff 100755
--- a/subcommands/gridlabd-compare
+++ b/subcommands/gridlabd-compare
@@ -1,8 +1,8 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
-"""gridlabd-compare subcommand
+# Syntax: compare [OPTIONS] FILE1 FILE2
+__doc__="""gridlabd-compare subcommand
"""
import sys, getopt
diff --git a/subcommands/gridlabd-contributors b/subcommands/gridlabd-contributors
index a88a8252b..1b23174cf 100755
--- a/subcommands/gridlabd-contributors
+++ b/subcommands/gridlabd-contributors
@@ -1,7 +1,7 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
+# Syntax: contributors []
import sys, os, github
E_NONE = None
diff --git a/subcommands/gridlabd-convert b/subcommands/gridlabd-convert
index 38c5a5a03..52e70748c 100755
--- a/subcommands/gridlabd-convert
+++ b/subcommands/gridlabd-convert
@@ -1,8 +1,8 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
-"""General GridLAB-D file format converter for command line and macro usage.
+# Syntax: convert [[-i|--input] FILE1] [[-o|--output] FILE2] [-v|--verbose] [-d|--debug] [-f|--from TYPE1] [-t|--to TYPE2] [OPTIONS]
+__doc__="""General GridLAB-D file format converter for command line and macro usage.
"""
import sys, os
import importlib
diff --git a/subcommands/gridlabd-geodata b/subcommands/gridlabd-geodata
index b73348bd7..7b38db37c 100755
--- a/subcommands/gridlabd-geodata
+++ b/subcommands/gridlabd-geodata
@@ -1,8 +1,8 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
-"""Syntax: gridlabd geodata OPTIONS DIRECTIVE [ARGUMENTS]
+# Syntax: geodata OPTIONS DIRECTIVE [ARGUMENTS]
+__doc__="""Syntax: gridlabd geodata OPTIONS DIRECTIVE [ARGUMENTS]
The geodata command gathers and joins geographic data. The geodata subcommand
uses directives that are documented in the DIRECTIVES section below.
diff --git a/subcommands/gridlabd-help b/subcommands/gridlabd-help
index 6eadf8cb2..52548d24e 100755
--- a/subcommands/gridlabd-help
+++ b/subcommands/gridlabd-help
@@ -13,15 +13,18 @@ if [ $# -gt 0 ]; then
echo "ERROR: subcommand or tool '$1' not found"
fi
else
- echo "Syntax: gridlabd command [options] ..."
-
- echo "Subcommands:"
+ gridlabd --help
+ echo ""
+ echo "Subcommands"
+ echo "-----------"
for F in $( cd $GLD_BIN ; ls -1d gridlabd-* ) ; do
- head -n 2 $GLD_BIN/$F | grep '^# Syntax: ' | sed -e 's/^# Syntax: / /'
+ head -n 4 $GLD_BIN/$F | grep '^# Syntax: ' | sed -e 's/^# Syntax: / /'
done
-
- echo "Tools:"
+ echo ""
+ echo "Tools"
+ echo "-----"
for F in $( cd $GLD_ETC ; ls -1d *.py | grep -v '^gridlabd-' ) ; do
- head -n 1 $GLD_ETC/$F | grep '^# ' | sed -e 's/^# / /'
+ head -n 4 $GLD_ETC/$F | grep '^# Syntax: ' | sed -e 's/^# Syntax: / /'
done
+
fi
\ No newline at end of file
diff --git a/subcommands/gridlabd-job b/subcommands/gridlabd-job
index 26d102046..2ff077432 100755
--- a/subcommands/gridlabd-job
+++ b/subcommands/gridlabd-job
@@ -1,7 +1,7 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
+# Syntax: job [OPTIONS ...] FILES...
#
# This script runs multiple jobs as specified by the controlfile.csv,
# where the columns are the globals and rows are the job number.
@@ -12,7 +12,17 @@ import subprocess
from multiprocessing import pool, freeze_support
import curses
-SYNTAX="Syntax: gridlabd job [-v|--verbose] [-q|--quiet] [-d|--debug] [-j|--jobfile JOBFILE] [-w|--workdir FOLDER] [-c|--configfile CONFIG] [-m|--modifyfile MODIFY] [-T|--threadcount NTHREADS] FILE1 ..."
+SYNTAX="""Syntax: gridlabd job [OPTIONS ...] FILES...
+Options:
+ -v|--verbose enable verbose output
+ -q|--quiet disable error messages
+ -d|--debug output debuggin messages
+ -j|--jobfile JOBFILE select job file
+ -w|--workdir FOLDER select work directory
+ -c|--configfile CONFIG select configuration file
+ -m|--modifyfile MODIFY select modification file
+ -T|--threadcount NTHREADS select number of threads
+"""
VERBOSE=False
DEBUG=False
QUIET=False
diff --git a/subcommands/gridlabd-json-get b/subcommands/gridlabd-json-get
index 39a2e1c79..10e8f9adf 100755
--- a/subcommands/gridlabd-json-get
+++ b/subcommands/gridlabd-json-get
@@ -1,7 +1,7 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
+# Syntax: json-get [keys ...] [-k|--keys] [-j|--json|-r|--raw|-r|--csv] [-i|--input INTPUT] [-o|--output OUTPUT]
import sys
import json
diff --git a/subcommands/gridlabd-loaddata b/subcommands/gridlabd-loaddata
index 784ae186a..e9cd99bbf 100755
--- a/subcommands/gridlabd-loaddata
+++ b/subcommands/gridlabd-loaddata
@@ -1,8 +1,8 @@
''':'
exec "${GLD_BIN:-/usr/local/opt/gridlabd/current}/python3" "$0" "$@"
:' '''
-
-__doc__ = """Syntax: gridlabd loaddata [OPTIONS]
+# Syntax: loaddata [OPTIONS ...]
+__doc__ = """Syntax: gridlabd loaddata [OPTIONS ...]
Downloads building load data from the NREL ResStock and ComStock data lakes.
diff --git a/subcommands/gridlabd-lock b/subcommands/gridlabd-lock
index e926ea87b..1f983978f 100644
--- a/subcommands/gridlabd-lock
+++ b/subcommands/gridlabd-lock
@@ -1,4 +1,5 @@
# enable locking of the current executable to ensure only one runs at a time
+# Syntax: lock PIDFILE
PIDFILE="/tmp/$(basename $0).pid"
trap "rm -f ${PIDFILE}; exit" INT TERM EXIT
if [ -e $PIDFILE ]; then
diff --git a/subcommands/gridlabd-matrix b/subcommands/gridlabd-matrix
index cb921c9e8..a954b5dcd 100644
--- a/subcommands/gridlabd-matrix
+++ b/subcommands/gridlabd-matrix
@@ -1,8 +1,8 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
-"""GridLAB-D matrix subcommand
+# Syntax: matrix [OPTIONS ...] FUNCTION [ARGUMENTS ...]
+__doc__="""GridLAB-D matrix subcommand
SYNTAX
diff --git a/subcommands/gridlabd-openfido b/subcommands/gridlabd-openfido
index cfb33d151..97b360d0c 100755
--- a/subcommands/gridlabd-openfido
+++ b/subcommands/gridlabd-openfido
@@ -1,4 +1,5 @@
#!/bin/bash
+# Syntax: openfido [OPTIONS ...] FUNCTION [ARGUMENTS ...]
if [ ! -x $GLD_BIN/openfido ]; then
curl -sL https://raw.githubusercontent.com/openfido/cli/main/install.sh | bash
fi
diff --git a/subcommands/gridlabd-pandas b/subcommands/gridlabd-pandas
index 09021ae86..098b087b5 100644
--- a/subcommands/gridlabd-pandas
+++ b/subcommands/gridlabd-pandas
@@ -1,8 +1,8 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
-"""GridLAB-D pandas subcommand
+# Syntax: pandas [-i INPUTFILE] [-o OUTPUTFILE]
+__doc__="""GridLAB-D pandas subcommand
SYNTAX
diff --git a/subcommands/gridlabd-plot b/subcommands/gridlabd-plot
index 907139293..332be4645 100755
--- a/subcommands/gridlabd-plot
+++ b/subcommands/gridlabd-plot
@@ -1,8 +1,8 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
-"""GridLAB-D plot subcommand
+# Syntax: plot [OPTIONS ...]
+__doc__="""GridLAB-D plot subcommand
SYNOPSIS
diff --git a/subcommands/gridlabd-python b/subcommands/gridlabd-python
index aed09bb35..d51b8ee9f 100755
--- a/subcommands/gridlabd-python
+++ b/subcommands/gridlabd-python
@@ -1,5 +1,5 @@
#!/bin/bash
-
+# Syntax: python [OPTIONS ...]
if [ "$1" == "help" ]; then
echo "Syntax: gridlabd python"
${GLD_BIN:-/opt/gridlabd/bin}/python3 --help | head -n 1 | cut -f3- -d' '
diff --git a/subcommands/gridlabd-server b/subcommands/gridlabd-server
index 96b57906f..313a20c5e 100644
--- a/subcommands/gridlabd-server
+++ b/subcommands/gridlabd-server
@@ -1,4 +1,5 @@
#!/bin/bash
+# Syntax: server [OPTIONS ...] COMMAND [ARGS ...]
## GridLAB-D server control
##
## Syntax: gridlabd server COMMAND
diff --git a/subcommands/gridlabd-timezone b/subcommands/gridlabd-timezone
index daf5b7242..8dfb05259 100644
--- a/subcommands/gridlabd-timezone
+++ b/subcommands/gridlabd-timezone
@@ -1,8 +1,8 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
-"""Syntax: gridlabd timezone [OPTIONS...] LOCATION
+# Syntax: timezone [OPTIONS ...] LOCATION
+__doc__="""Syntax: gridlabd timezone [OPTIONS ...] LOCATION
The gridlabd timezone subcommand obtains the timezone for the location given
by LOCATION, which may be in the form `LATITUDE,LONGITUDE`, `local`, or an IP
diff --git a/tools/Makefile.mk b/tools/Makefile.mk
index 5bf8e9201..dfa7366d0 100644
--- a/tools/Makefile.mk
+++ b/tools/Makefile.mk
@@ -1,3 +1,4 @@
+dist_pkgdata_DATA += tools/create_ductbank.py
dist_pkgdata_DATA += tools/create_filter.py
dist_pkgdata_DATA += tools/create_player.py
dist_pkgdata_DATA += tools/create_meters.py
diff --git a/tools/create_ductbank.py b/tools/create_ductbank.py
index b075f12dd..879e99c01 100644
--- a/tools/create_ductbank.py
+++ b/tools/create_ductbank.py
@@ -1,3 +1,4 @@
+# Syntax: create_ductbank [-i|--input]=INPUT.csv [-o|--output]=OUTPUT.glm [--PARAMETER=VALUE ...]
"""Create ductbank
Syntax:
@@ -16,7 +17,7 @@
sys.argv.append("--name=test")
if len(sys.argv) == 1:
- print("Syntax: gridlabd create_ductbank [-[-input|i]=INPUT.csv]] [-[-output|o]=OUTPUT.glm] [PARAMETER=VALUE ...]",file=sys.stderr)
+ print("Syntax: gridlabd create_ductbank [-i|--input]=INPUT.csv [-o|--output]=OUTPUT.glm [--PARAMETER=VALUE ...]",file=sys.stderr)
exit(E_SYNTAX)
INPUT = sys.stdin
diff --git a/tools/create_filter.py b/tools/create_filter.py
index 7f678a8cd..e5ed5ac53 100644
--- a/tools/create_filter.py
+++ b/tools/create_filter.py
@@ -1,4 +1,4 @@
-# create_filter NAME NUMERATOR DENOMINATOR [OPTIONS ...]
+# Syntax: create_filter NAME NUMERATOR DENOMINATOR [OPTIONS ...]
"""[[/Utilities/Create_filter]] - Create a filter from a transfer function
SYNOPSIS
diff --git a/tools/create_meters.py b/tools/create_meters.py
index 24405e3e6..e9c14b598 100644
--- a/tools/create_meters.py
+++ b/tools/create_meters.py
@@ -1,4 +1,4 @@
-# create_meters [--with-ami] INPUTFILE [OUTPUTFILE]
+# Syntax: create_meters [--with-ami] INPUTFILE [OUTPUTFILE]
"""Add meters to a MODEL
SYNOPSIS
diff --git a/tools/create_player.py b/tools/create_player.py
index 641a7e053..51317f7b8 100644
--- a/tools/create_player.py
+++ b/tools/create_player.py
@@ -1,4 +1,4 @@
-# create_player [OPTIONS ...]
+# Syntax: create_player [OPTIONS ...]
"""Player object creation tool
SYNOPSIS
diff --git a/tools/create_poles.py b/tools/create_poles.py
index 00608c17a..997f4c7b6 100644
--- a/tools/create_poles.py
+++ b/tools/create_poles.py
@@ -1,4 +1,4 @@
-# create_poles INPUTFILE OPTIONS ...
+# Syntax: create_poles INPUTFILE OPTIONS ...
"""Create poles for a GridLAB-D model
SYNTAX
diff --git a/tools/create_schedule.py b/tools/create_schedule.py
index 096f6ff33..11ecc358f 100644
--- a/tools/create_schedule.py
+++ b/tools/create_schedule.py
@@ -1,3 +1,4 @@
+# Syntax: create_schedule CSVFILE [-o|--output=GLMFILE] [OPTIONS ...]
"""Syntax: gridlabd create_schedule CSVFILE [-o|--output=GLMFILE] [OPTIONS ...]
Options
diff --git a/tools/find_location.py b/tools/find_location.py
index 2d91d78ad..62f1216f3 100644
--- a/tools/find_location.py
+++ b/tools/find_location.py
@@ -1,4 +1,4 @@
-# gridlabd find_location [OPTIONS] KEY
+# Syntax: find_location [OPTIONS] KEY
"""Find_location -- find the latitude and longitude of a location
SYNOPSIS
diff --git a/tools/fire_danger.py b/tools/fire_danger.py
index eb596fe07..41087577e 100644
--- a/tools/fire_danger.py
+++ b/tools/fire_danger.py
@@ -1,4 +1,4 @@
-# gridlabd fire_danger [OPTIONS ...]
+# Syntax: fire_danger [OPTIONS ...]
"""Fire danger forecast data tool
SYNTAX
diff --git a/tools/fire_report.py b/tools/fire_report.py
index ce8425f7e..8e72f8c96 100644
--- a/tools/fire_report.py
+++ b/tools/fire_report.py
@@ -1,4 +1,4 @@
-# fire_report [OPTIONS ...]
+# Syntax: fire_report [OPTIONS ...]
"""Fire Incident Report Tool
SYNOPSIS
diff --git a/tools/fit_filter.py b/tools/fit_filter.py
index d2c5af0ff..50d58fc86 100644
--- a/tools/fit_filter.py
+++ b/tools/fit_filter.py
@@ -1,4 +1,4 @@
-# fit_filter [OPTIONS ...]
+# Syntax: fit_filter [OPTIONS ...]
"""Create filters from data
SYNTAX
diff --git a/tools/insights.py b/tools/insights.py
index 54ee99938..2655a8f79 100644
--- a/tools/insights.py
+++ b/tools/insights.py
@@ -1,4 +1,4 @@
-# insights CATEGORY [OPTIONS ...]
+# Syntax: insights CATEGORY [OPTIONS ...]
"""Gather data on gridlabd usage
SYNTAX
diff --git a/tools/install.py b/tools/install.py
index 673de1137..c18ba4c11 100644
--- a/tools/install.py
+++ b/tools/install.py
@@ -1,3 +1,4 @@
+# Syntax: install [OPTIONS ...] ORGANIZATION/REPOSITORY ...
"""GridLAB-D tool installer
Syntax:
diff --git a/tools/market_data.py b/tools/market_data.py
index 493f2fabb..3e92ff115 100644
--- a/tools/market_data.py
+++ b/tools/market_data.py
@@ -1,4 +1,4 @@
-# market_data [OPTIONS ...]
+# Syntax: market_data [OPTIONS ...]
"""ISO Market Data tool
SYNOPSIS
diff --git a/tools/market_model.py b/tools/market_model.py
index 071bf4bfd..dc1c80a0a 100644
--- a/tools/market_model.py
+++ b/tools/market_model.py
@@ -1,4 +1,4 @@
-# market_model [OPTIONS ...]
+# Syntax: market_model [OPTIONS ...]
"""Create market model from ISO data
SYNTAX
diff --git a/tools/mdb_info.py b/tools/mdb_info.py
index 7f469eb19..a8125b469 100644
--- a/tools/mdb_info.py
+++ b/tools/mdb_info.py
@@ -1,4 +1,4 @@
-# mdb_info MDBFILE COMMAND [ARGS ...
+# Syntax: mdb_info MDBFILE COMMAND [ARGS ...
"""Convert MDB table to a CSV player
SYNOPSIS
diff --git a/tools/metar2glm.py b/tools/metar2glm.py
index 99caa0557..cc68d50a7 100755
--- a/tools/metar2glm.py
+++ b/tools/metar2glm.py
@@ -1,7 +1,7 @@
''':'
exec "$GLD_BIN/python3" "$0" "$@"
:' '''
-
+# Syntax: metar2glm [--index|STATION ...]
import sys
sys.path.append(sys.argv[0].split()[0:-1])
import ucar_weather
@@ -9,7 +9,8 @@
station_list = []
station_data = ucar_weather.stations()
-syntax = "python3 -m metar2glm [--index|STATION ...]"
+syntax = "python3 -m metar2glm [--index|STATION ...] [-c|-class CLASSNAME]"
+classname = "weather"
n = 1
while n < len(sys.argv):
@@ -19,6 +20,9 @@
elif sys.argv[n] in ["--index"]:
print("\n".join(station_data.keys()))
exit(0)
+ elif sys.argv[n] in ["-c","--class"]:
+ classname = sys.argv[n+1]
+ n = n+1
else:
station_list.append(sys.argv[n])
n += 1
@@ -34,9 +38,9 @@
"deg" : "deg",
}
print(f"// generated by metar2glm.py on {datetime.datetime.utcnow().isoformat()}Z")
-print("""
-class weather
-{
+print(f"""
+class {classname}
+{{
char8 country;
char8 region;
char32 station;
@@ -50,14 +54,13 @@ class weather
double pressure[mbar];
char256 clouds;
char1024 metar;
- on_init "python:metar_weather.weather_init";
-}
+}}
""")
for station in station_list:
if not station in station_data.keys():
raise Exception(f"station '{station}' not found in metar index")
weather = ucar_weather.get(station)
- print("object weather")
+ print(f"object {classname}")
print("{")
for attr,data in station_data[station].items():
print(f" {attr} \"{data}\";");
diff --git a/tools/meteostat_weather.py b/tools/meteostat_weather.py
index a5175981c..5bf4e4803 100644
--- a/tools/meteostat_weather.py
+++ b/tools/meteostat_weather.py
@@ -1,15 +1,15 @@
-# meteostat.py [OPTIONS ...]
+# Syntax: meteostat_weather.py [OPTIONS ...]
"""Meteostat weather access
SYNOPSIS
Shell:
- $ python3 meteostat.py [OPTIONS ...]
+ $ python3 meteostat_weather.py [OPTIONS ...]
GLM:
- #python3 meteostat.py [OPTIONS ...]
+ #python3 meteostat_weather.py [OPTIONS ...]
Options:
diff --git a/tools/noaa_forecast.py b/tools/noaa_forecast.py
index c6787b133..17a11bbc5 100644
--- a/tools/noaa_forecast.py
+++ b/tools/noaa_forecast.py
@@ -1,4 +1,4 @@
-# noaa_forecast [OPTIONS ...]
+# Syntax: noaa_forecast [OPTIONS ...]
"""NOAA weather forecast tool
SYNOPSIS
diff --git a/tools/nsrdb_weather.py b/tools/nsrdb_weather.py
index 0358601c1..b23c99983 100644
--- a/tools/nsrdb_weather.py
+++ b/tools/nsrdb_weather.py
@@ -1,4 +1,4 @@
-# nsrdb_weather [OPTIONS ...]
+# Syntax: nsrdb_weather [OPTIONS ...]
"""NSRDB weather data tool
SYNOPSIS
diff --git a/tools/pole_analysis.py b/tools/pole_analysis.py
index fed73d694..f0060d644 100644
--- a/tools/pole_analysis.py
+++ b/tools/pole_analysis.py
@@ -1,4 +1,4 @@
-# pole_analysis INPUTFILE [OPTIONS ...]
+# Syntax: pole_analysis INPUTFILE [OPTIONS ...]
"""poles loading analysis for a GridLAB-D model
SYNTAX
diff --git a/tools/read_dlp.py b/tools/read_dlp.py
index 04526dc37..59f37f5a8 100644
--- a/tools/read_dlp.py
+++ b/tools/read_dlp.py
@@ -1,4 +1,4 @@
-# read_dlp FILENAME.DLP [OPTIONS ...]
+# Syntax: read_dlp FILENAME.DLP [OPTIONS ...]
"""Read Dynamic Load Profile
Syntax: python3 -m read_dlp FILENAME.DLP [OPTIONS ...]
diff --git a/tools/ucar_weather.py b/tools/ucar_weather.py
index ebb35aab2..b2a89a46a 100644
--- a/tools/ucar_weather.py
+++ b/tools/ucar_weather.py
@@ -1,3 +1,4 @@
+"""UCAR weather access module"""
import requests
import re
from metar import Metar
@@ -35,8 +36,17 @@ def get(station):
raise Exception("request error (no observation)")
else:
raise Exception(r"request error (no observation),, query='{query}',' response='{r.text}'")
- result = obs.to_dict()
- result["metar"] = data
+ result = {
+ "station" : station,
+ "temperature" : f"{obs.temp.value()} degC",
+ "dew_point" : f"{obs.dewpt.value()} degC",
+ "wind_speed" : f"{obs.wind_speed.value()} knot",
+ "wind_dir" : f"{obs.wind_dir.value()} deg",
+ "visibility" : f"{obs.vis.value()} mile",
+ "pressure" : f"{obs.press.value()} inH2O",
+ "clouds" : obs.sky_conditions(),
+ }
+ result["metar"] = data.replace("\n"," ")
return result
def stations():