Skip to content
This repository has been archived by the owner on Sep 27, 2023. It is now read-only.

Add create_childs tool #1335

Merged
merged 9 commits into from
Sep 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion geodata/geodata_powerline.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ def TODO(value=float('nan')):
}

default_config = {
"cabletype_file" : f"{GLD_ETC}/gridlabd/geodata_powerline_cabletypes.csv",
"cabletype_file" : f"{GLD_ETC}/geodata_powerline_cabletypes.csv",
}

OPTIONS = default_options
Expand Down
15 changes: 8 additions & 7 deletions tools/Makefile.mk
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
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_player.py
dist_pkgdata_DATA += tools/create_meters.py
dist_pkgdata_DATA += tools/create_player.py
dist_pkgdata_DATA += tools/create_poles.py
dist_pkgdata_DATA += tools/create_schedule.py
dist_pkgdata_DATA += tools/eia_recs.py
dist_pkgdata_DATA += tools/find_location.py
dist_pkgdata_DATA += tools/fire_report.py
dist_pkgdata_DATA += tools/fire_danger.py
dist_pkgdata_DATA += tools/fire_report.py
dist_pkgdata_DATA += tools/fit_filter.py
dist_pkgdata_DATA += tools/gldserver.py
dist_pkgdata_DATA += tools/gridlabd-editor.png
dist_pkgdata_DATA += tools/gridlabd-editor.py
dist_pkgdata_DATA += tools/group.py
dist_pkgdata_DATA += tools/insights.py
dist_pkgdata_DATA += tools/install.py
dist_pkgdata_DATA += tools/isone.py
dist_pkgdata_DATA += tools/market_data.py
dist_pkgdata_DATA += tools/mdb_info.py
dist_pkgdata_DATA += tools/market_model.py
dist_pkgdata_DATA += tools/meteostat_weather.py
dist_pkgdata_DATA += tools/mdb_info.py
dist_pkgdata_DATA += tools/metar2glm.py
dist_pkgdata_DATA += tools/read_dlp.py
dist_pkgdata_DATA += tools/gldserver.py
dist_pkgdata_DATA += tools/meteostat_weather.py
dist_pkgdata_DATA += tools/noaa_forecast.py
dist_pkgdata_DATA += tools/nsrdb_weather.py
dist_pkgdata_DATA += tools/ucar_weather.py
dist_pkgdata_DATA += tools/pole_analysis.py
dist_pkgdata_DATA += tools/read_dlp.py
dist_pkgdata_DATA += tools/ucar_weather.py
186 changes: 186 additions & 0 deletions tools/create_childs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
# Syntax: create_childs [-i|--input=INPUTFILE] [-o|--output=GLMFILE] [OPTIONS ...]
"""Syntax: create_childs [-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)

Description
-----------

The `create_childs` tool adds child 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_childs -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_childs]:",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"]
for obj,data in OBJECTS.items():
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)
OBJECTS[name] = dict(parent=obj,name=name)
for prop,value in CHILDS.items():
if not prop in ["class"]:
OBJECTS[name][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 ["-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)