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

Commit

Permalink
Add create filter utility (#1107)
Browse files Browse the repository at this point in the history
* Create create_filter.py

* Add create_filter utility

* Fix docs

* Add continuous option

Co-authored-by: Alyona Teyber <Ivanova.alyona5@gmail.com>
  • Loading branch information
dchassin and aivanova5 committed Mar 7, 2022
1 parent cee1b74 commit ef5126e
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 0 deletions.
49 changes: 49 additions & 0 deletions docs/Utilities/Create_filter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
Create filter tool

# Synopsis

Shell:

bash$ gridlabd create_filter NAME NUMERATOR DENOMINATOR [OPTIONS ...]

GLM:

#create_filter NAME NUMERATOR DENOMINATOR [OPTIONS ...]

Options:

- `-c|--continuous`: flag to indicate numerator and denominator are in continuous time

- `-m|--minimum=MINIMUM`: specify the minimum output value (default is none)

- `-M|--maximum=MAXIMUM`: specify the maximum output value (default is none)

- `-o|--output=GLMFILE`: specify the output GLM file name (default is `/dev/stdout`)

- `-r|--resolution=RESOLUTION`: specify the output resolution (default is none)

- `-s|--skewtime=SKEW`: specify the time skew (default is 0)

- `-t|--timestep=TIMESTEP`: specify the time step (default is 1s)

# Description

The `create_filter` tool creates filter for use as inputs to GridLAB-D object
properties. The numerator and denominator must be specified as a comma-delimited
list of values in order of ascending order, e.g., `a_0,a_1,...,a_N` for an N-order
system. The order of numerator must be less than or equal to the order of the
denominator (i.e., the filter must be causal).

When RESOLUTION is specified, then both MINIMUM and MAXIMUM must also be specified.

# Example

The following example creates a filter, from a second-order continuous-time
model, with 8 bits of resolution and a dynamic range of (0,1):

bash% gridlabd create_filter test 1,2,3 4,5,6 -r=8 -m=0 -M=1 -c
filter test(z,1,0,resolution=8,minimum=0.0,maximum=1.0) = (+1-0.529623z+0.286505z^2) / (+0.25+0.0651926z+0.0632483z^2);

# See also

* [[/Utilities/Fit_filter]]
1 change: 1 addition & 0 deletions python_extras/Makefile.mk
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
dist_pkgdata_DATA += python_extras/create_filter.py
dist_pkgdata_DATA += python_extras/create_player.py
dist_pkgdata_DATA += python_extras/create_poles.py
dist_pkgdata_DATA += python_extras/csv_merge/csv_merge.py
Expand Down
232 changes: 232 additions & 0 deletions python_extras/create_filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
"""[[/Utilities/Create_filter]] - Create a filter from a transfer function
SYNOPSIS
Shell:
bash$ gridlabd create_filter NAME NUMERATOR DENOMINATOR [OPTIONS ...]
GLM:
#create_filter NAME NUMERATOR DENOMINATOR [OPTIONS ...]
Options:
`-c|--continuous`: flag to indicate numerator and denominator are in continuous time
`-m|--minimum=MINIMUM`: specify the minimum output value (default is none)
`-M|--maximum=MAXIMUM`: specify the maximum output value (default is none)
`-o|--output=GLMFILE`: specify the output GLM file name (default is `/dev/stdout`)
`-r|--resolution=RESOLUTION`: specify the output resolution (default is none)
`-s|--skewtime=SKEW`: specify the time skew (default is 0)
`-t|--timestep=TIMESTEP`: specify the time step (default is 1s)
DESCRIPTION
The `create_filter` tool creates filter for use as inputs to GridLAB-D object
properties. The numerator and denominator must be specified as a comma-delimited
list of values in order of ascending order, e.g., `a_0,a_1,...,a_N` for an N-order
system.
When RESOLUTION is specified, then both MINIMUM and MAXIMUM must also be specified.
EXAMPLE
The following example creates a filter, from a second-order continuous-time
model, with 8 bits of resolution and a dynamic range of (0,1):
bash% gridlabd create_filter test 1 1,0.2 -c -r=8 -m=0 -M=1 -c
filter test(z,1,0,resolution=8,minimum=0.0,maximum=1.0) = (+1-0.529623z+0.286505z^2) / (+0.25+0.0651926z+0.0632483z^2);
SEE ALSO
* [[/Utilities/Fit_filter]]
"""

import sys, os
import control

E_OK = 0 # success
E_INVALID = 1 # invalid command option
E_FAILED = 2 # processing failed

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

CONTINUOUS = False
MINIMUM = None
MAXIMUM = None
OUTPUTGLM = "/dev/stdout"
RESOLUTION = None
SKEW = None
TIMESTEP = '1'
FLOAT_FORMAT = '+g'

def main(argv={}):

# no arguments provided
if len(argv) > 1 and argv[1] in ["-h","--help"]:
print(__doc__)
exit(E_OK)
elif len(argv) < 4:
print("Syntax: gridlabd create_filter NAME NUMERATOR DENOMINATOR [OPTIONS ...]",file=sys.stderr)
exit(E_FAILED)

NAME = argv[1]
NUMERATOR = [float(x) for x in argv[2].split(',')]
DENOMINATOR = [float(x) for x in argv[3].split(',')]

# parse argument list
if len(argv) > 4:
for arg in [x.split('=') for x in argv[4:]]:
tag = arg[0]
if len(arg) == 1:
value = None
else:
value = '='.join(arg[1:]).split(',')
if len(value) == 1:
value = value[0]
if tag in ["-v","--verbose"]:
global VERBOSE
VERBOSE = True
elif tag in ["-w","--warn","--warning"]:
global WARNING
WARNING = False
elif tag in ["-q","--quiet","-s","--silent"]:
global QUIET
QUIET = True
elif tag in ["-d","--debug"]:
global DEBUG
DEBUG = True
elif tag in ["-i","--input"]:
global INPUTCSV
if type(value) is list:
INPUTCSV = value
else:
INPUTCSV = [value]
elif tag in ["-o","--output"]:
global OUTPUTGLM
OUTPUTGLM = value
elif tag in ["-c","--continuous"]:
global CONTINUOUS
if value != None:
if value.lower() in ["true","false","yes","no"]:
CONTINUOUS = ( value.lower() in ["true","yes"])
else:
error(f"value '{VALUE}' for continuous flag is not boolean")
elif value == None:
CONTINUOUS = True
else:
error(f"option '{tag}={value}' is invalid",E_INVALID)
elif tag in ["-m","--minimum"]:
global MINIMUM
MINIMUM = float(value)
elif tag in ["-M","--maximum"]:
global MAXIMUM
MAXIMUM = float(value)
elif tag in ["-r","--resolution"]:
global RESOLUTION
RESOLUTION = int(value)
elif tag in ["-s","--skew"]:
global SKEW
SKEW = value
elif tag in ["-t","--timestep"]:
global TIMESTEP
TIMESTEP = value
else:
error(f"option '{'='.join(arg)}' is not valid",E_INVALID)

if CONTINUOUS:

H = control.tf(NUMERATOR,DENOMINATOR)
G = control.c2d(H,float(TIMESTEP))
NUMERATOR = G.num[0][0].tolist()
DENOMINATOR = G.den[0][0].tolist()

with open(OUTPUTGLM,"w") as glm:
SPEC = f"z,{TIMESTEP}"

if SKEW == None:
SPEC += ",0"
else:
SPEC += f",{SKEW}"

if RESOLUTION != None:
if RESOLUTION <= 0:
error("resolution must be a positive integer",E_INVALID)
if None in [MINIMUM,MAXIMUM]:
error("cannot use resolution unless minimum and maximum are also specified",E_INVALID)
SPEC += f",resolution={RESOLUTION}"

if MINIMUM != None:
SPEC += f",minimum={MINIMUM}"

if MAXIMUM != None:
if MINIMUM != None and MINIMUM >= MAXIMUM:
error("minimum must be stricly less than maximum",E_INVALID)
SPEC += f",maximum={MAXIMUM}"

if len(NUMERATOR) > len(DENOMINATOR):
error("filter must be strictly causal (numerator order must be less than or equal to denominator order)",E_INVALID)

NUM = ""
for n,a in enumerate(DENOMINATOR):
if n == 0:
NUM += f"{a:+g}"
elif n == 1:
NUM += f"{a:+g}z"
else:
NUM += f"{a:+g}z^{n}"

DEN = ""
for n,b in enumerate(NUMERATOR):
if n == 0:
DEN += f"{b:+g}"
elif n == 1:
DEN += f"{b:+g}z"
else:
DEN += f"{b:+g}z^{n}"

glm.write(f"""// generated by `{' '.join(argv)}`
filter {NAME}({SPEC}) = ({NUM}) / ({DEN});
""")

exit(E_OK)

def error(msg,code=None,exception=None):
"""Error processing
msg - error message to display when QUIET is not enabled
code - exit code to use (None if no exit is desired)
err - base exception is use when DEBUG is enabled
"""
if DEBUG:
raise Exception(msg) from exception
elif not QUIET:
print(f"ERROR [{EXENAME}]: {msg}",file=sys.stderr)
if type(code) is int:
exit(code)

def warning(msg):
if WARNING:
print(f"WARNING [{EXENAME}]: {msg}",file=sys.stderr)

def verbose(msg):
if VERBOSE:
print(f"VERBOSE [{EXENAME}]: {msg}",file=sys.stderr)

def debug(msg):
if DEBUG:
print(f"DEBUG [{EXENAME}]: {msg}",file=sys.stderr)

if __name__ == "__main__":

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

0 comments on commit ef5126e

Please sign in to comment.