Skip to content

Commit

Permalink
Creating a way to add loads with meters (#177)
Browse files Browse the repository at this point in the history
Signed-off-by: Alyona Teyber <Ivanova.alyona5@gmail.com>
  • Loading branch information
aivanova5 authored Mar 13, 2024
1 parent 8485300 commit bc15edb
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 5 deletions.
38 changes: 33 additions & 5 deletions converters/csv-ami2glm-player.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,19 @@ def write_player(file, obj, node_ID, phase) :
file.write('object player {\n')
file.write('\tparent "' + obj + '";\n')
file.write('\tfile "' + os.path.join(folder_name,str(node_ID)) + '.csv";\n')
file.write(f'\tproperty constant_power_{phase};\n')
for p in phase :
file.write(f'\tproperty constant_power_{p};\n')
file.write('}\n')

def filter_dict_by_min_value(input_dict, patterns):
result_dict = {}
for pattern in patterns:
pattern_dictionary = {key: value for key, value in input_dict.items() if pattern in key}
min_value = min(pattern_dictionary.values())
min_dict = {key: value for key, value in pattern_dictionary.items() if value == min_value}
result_dict.update(min_dict)
return list(result_dict.keys())

def convert(input_files, output_file, options={}):

if type(input_files) is dict:
Expand Down Expand Up @@ -106,6 +116,8 @@ def convert(input_files, output_file, options={}):
df.to_csv(os.path.join(folder_name,os.path.basename(output_file)), index=False)
elif os.path.splitext(output_file)[1]=='.glm' :
phase_dict = {}
load_list = {}
load_list_filtered = {}
with open(output_file, mode='w') as file :
file.write('module tape;\n')

Expand All @@ -114,11 +126,27 @@ def convert(input_files, output_file, options={}):
continue
for obj,val in network["objects"].items() :
if "load" in val["class"] and node_ID in obj:
node_phase = ''.join([x for x in 'ABC' if x in val['phases']])
volts = float(val['nominal_voltage'].split(' ')[0])
if 'k' in val['nominal_voltage'].split(' ')[1] :
load_list[obj] = volts*1000
elif 'M' in val['nominal_voltage'].split(' ')[1] :
load_list[obj] = volts*1000000
else :
load_list[obj] = volts
load_phase = ''.join([x for x in 'ABC' if x in val['phases']])
phase_dict[node_ID]=load_phase


# Grabbing only loads on the low side of the Transformer
load_list_filtered = filter_dict_by_min_value(load_list,node_ID_set)
for load_ID in load_list_filtered :
for obj, val in network["objects"].items() :
if load_ID==obj :
load_phase = ''.join([x for x in 'ABC' if x in val['phases']])
parent = val["parent"]
phase_dict[node_ID]=node_phase
for p in node_phase :
write_player(file, obj, node_ID, p)
for node_ID in node_ID_set :
if node_ID in load_ID :
write_player(file, obj, node_ID, load_phase)

new_column_names = {
'reading_dttm': 'timestamp',
Expand Down
1 change: 1 addition & 0 deletions tools/Makefile.mk
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
dist_pkgdata_DATA += tools/create_childs.py
dist_pkgdata_DATA += tools/create_ductbank.py
dist_pkgdata_DATA += tools/create_filter.py
dist_pkgdata_DATA += tools/create_metered_loads.py
dist_pkgdata_DATA += tools/create_meters.py
dist_pkgdata_DATA += tools/create_player.py
dist_pkgdata_DATA += tools/create_poles.py
Expand Down
236 changes: 236 additions & 0 deletions tools/create_metered_loads.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# Syntax: create_metered_loads [-i|--input=INPUTFILE] [-o|--output=GLMFILE] [OPTIONS ...]
"""Syntax: create_metered_loads [-i|--input=INPUTFILE] [-o|--output=GLMFILE] [OPTIONS ...]
Options
-------
-P|--parents=NAME:VALUE,... specify parent property pattern to match (required)
-C|--childs=NAME:VALUE,... specify child property list to assign (required)
-N|--names=STRING specify object naming convention (default is '{class}_{name}')
-M|--modules=NAME,... specify module names to use (defaults to those found)
-L|--link=NAME:VALUE,.... specify link types (required)
Description
-----------
The `create_metered_loads` tool adds loads with meter objects to all objects that match the
parent object pattern specified.
Parent patterns and child properties as specified as a comma-separate list of
`NAME:VALUE` strings, e.g., `class:node` or `nominal_voltage:2.4kV`. Parent
patterns use `regex` pattern matching. Child properties may include `{NAME}`
format strings where `NAME` is a property of the parent object. This
allows copying of values from the parent object. This formatting also can be
applied to the naming string, e.g., `-N='{name}_L' to append '_L' to the
parent object name.
Example
-------
The following creates a GLM file containing a `triplex_load` objects attached
to `triplex_node` objects with names starting as `N_` in the file `my-network.json`:
~~~
$ gridlabd create_metered_loads -i=my-network.json -o=loads.glm -P='class:triplex_node,name:^N_' -C='class:triplex_load,nominal_voltage:{nominal_voltage},phases:{phases},constant_power_B:1.2+0.1jkVA'
~~~
The following creates a GLM file containing a `triplex_load` objects attached
to `triplex_node` objects with names starting as `N_` in the file `my-network.json` without specifying the power phase. The power is captured from the upstream node/triplex_node:
~~~
$ gridlabd create_metered_loads -i=my-network.json -o=loads.glm -P='class:triplex_node,name:^N_' -C='class:triplex_load,nominal_voltage:{nominal_voltage},phases:{phases},constant_power_B:1.2+0.1jkVA'
~~~
"""

import sys, os
import json
import re
import datetime
import subprocess
import random

EXENAME = os.path.splitext(os.path.basename(sys.argv[0]))[0]

DEBUG = False
WARNING = True
QUIET = False
VERBOSE = False

E_OK = 0
E_INVALID = 1
E_FAILED = 2
E_SYNTAX = 8
E_EXCEPTION = 9
EXITCODE = E_OK

class GldException(Exception):
pass

def error(msg,code=None):
if type(code) is int:
global EXITCODE
EXITCODE = code
if DEBUG:
raise GldException(msg)
print("ERROR [create_metered_loads]:",msg,file=sys.stderr)
exit(code)


def load():

if not INPUTFILE.endswith(".json"):
tmpfile = "."
while os.path.exists(tmpfile):
tmpfile = f"tmp{hex(random.randint(1e30,1e31))[2:]}.json"
try:
result = subprocess.run(["gridlabd","-C",INPUTFILE,"-o",tmpfile])
assert(result.returncode==0)
with open(tmpfile,"r") as fh:
model = json.load(fh)
except:
raise
finally:
os.remove(tmpfile)
pass
else:
with open(INPUTFILE,"r") as fh:
model = json.load(fh)
return model

def save(fh):
print(f"// generated by '{' '.join(sys.argv)}' at {datetime.datetime.now()}",file=fh)
for name in MODULES:
print(f"module {name};",file=fh)
classname = CHILDS["class"]
link_class = LINK["class"]
if classname == "load" :
meter_class = "meter"
elif classname == "triplex_load" :
meter_class = "triplex_meter"
for obj,data in OBJECTS.items():
if "meter" in obj :
print(f"object {meter_class} {{",file=fh)
elif "link" in obj :
print(f"object {link_class} {{",file=fh)
else :
print(f"object {classname} {{",file=fh)
for prop,value in data.items():
print(f" {prop} \"{value}\";",file=fh)
print("}",file=fh)

def main():

PATTERN = {}
for name,pattern in PARENTS.items():
PATTERN[name] = re.compile(pattern)

if "class" not in CHILDS:
error("you must include a class name in the child properties",E_INVALID)
classname = CHILDS["class"]
model = load()
assert(model['application']=='gridlabd')
global MODULES
if not MODULES:
MODULES = list(model['modules'])

for obj,data in model['objects'].items():
data['name'] = obj
ok = True
for name,pattern in PATTERN.items():
if not pattern.match(data[name]):
ok = False
break
if ok:
name = f"{classname}_{obj}" if NAMING is None else NAMING.format(**data)
name_meter = f"meter_{obj}"
name_link = f"link_{obj}"
OBJECTS[name_meter] = dict(name=name_meter)
OBJECTS[name] = dict(parent=name_meter,name=name)
OBJECTS[name_link] = dict(name=name_link, to=name_meter)
OBJECTS[name_link]["from"] = obj

for prop,value in CHILDS.items():
if prop in ["phases"]:
load_phase = ''.join([x for x in 'ABC' if x in value.format(**data)])

for prop,value in CHILDS.items():
if not prop in ["class"] and "constant_" not in prop:
OBJECTS[name][prop] = value.format(**data)
if prop in ["phases"]:
OBJECTS[name_meter][prop] = value.format(**data)
OBJECTS[name_link][prop] = value.format(**data)
if prop in ["nominal_voltage"]:
OBJECTS[name_meter][prop] = value.format(**data)
if "constant_" in prop:
if "_A" in prop or "_B" in prop or "_C" in prop:
OBJECTS[name][prop] = value.format(**data)

else :
for i in load_phase :
OBJECTS[name][prop+'_'+i] = value.format(**data)

for prop,value in LINK.items():
if not prop in ["class"]:
OBJECTS[name_link][prop] = value.format(**data)



if OUTPUTFILE.endswith(".glm"):
with open(OUTPUTFILE,"w") as fh:
save(fh)
else:
error("invalid output file format")

return E_OK

INPUTFILE = "/dev/stdin"
OUTPUTFILE = "/dev/stdout"
PARENTS = None
CHILDS = None
NAMING = None
OBJECTS = {}
MODULES = []

if __name__ == "__main__":

if len(sys.argv) == 1:
print(__doc__.split('\n')[0],file=sys.stderr)
exit(E_SYNTAX)

for arg in sys.argv[1:]:
spec = arg.split("=")
if len(spec) == 1:
tag = arg
value = None
else:
tag = spec[0]
value = '='.join(spec[1:])

if tag in ["-h","--help","help"]:
print(__doc__)
exit(E_OK)
if tag in ["-i","--input"]:
INPUTFILE = value if value else "/dev/stdin"
elif tag in ["-o","--output"]:
OUTPUTFILE = value if value else "/dev/stdout"
elif tag in ["-P","--parent"]:
PARENTS = dict([x.split(":") for x in value.split(",")])
elif tag in ["-C","--childs"]:
CHILDS = dict([x.split(":") for x in value.split(",")])
elif tag in ["-L","--link"]:
LINK = dict([x.split(":") for x in value.split(",")])
elif tag in ["-N","--names"]:
NAMING = value
elif tag in ["-M","--modules"]:
MODULES = value.split(",")
else:
error(f"option '{arg}' is invalid",E_INVALID)

if PARENTS is None:
error("you must specify the parent patterns to match")
if CHILDS is None:
error("you must specify the child properties to define")

EXITCODE = main()
exit(EXITCODE)

0 comments on commit bc15edb

Please sign in to comment.