#!/usr/bin/env python
# -*- coding: utf-8 -*-
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: © 2021 Massachusetts Institute of Technology.
# SPDX-FileCopyrightText: © 2021 Lee McCuller <mcculler@caltech.edu>
# NOTICE: authors should document their contributions in concisely in NOTICE
# with details inline in source files, comments, and docstrings.
"""
"""
from wield import declarative
import numpy as np
from ... import fitters_ZPK
from ...utilities import args
from ... import representations
try:
from collections.abc import Mapping as MappingABC
except ImportError:
from collections import Mapping as MappingABC
[docs]
class ArgumentError(ValueError):
pass
[docs]
def mapcheck_bool(aid, aname, val):
if isinstance(val, str):
if val.lower() in ["true", "yes", "1"]:
val = True
elif val.lower() in ["false", "no", "0"]:
val = False
else:
raise ArgumentError(
("Argument {} has unrecognized bool specifier {}").format(aname, val)
)
return bool(val)
[docs]
def float_check(aid, aname, val):
try:
return float(val)
except ValueError:
raise ArgumentError(("argument {}={} must be a float").format(aname, val))
[docs]
def mapcheck_float(aid, aname, val):
try:
return float(val)
except ValueError:
raise ArgumentError(("argument {}={} must be a float").format(aname, val))
[docs]
def mapcheck_positive_float(aid, aname, val):
val = float(val)
if not (val > 0):
raise ArgumentError(("argument {}={} must be positive").format(aname, val))
return val
[docs]
def mapcheck_nonnegative_float(aid, aname, val):
val = float(val)
if not (val >= 0):
raise ArgumentError(("argument {}={} must not be negative").format(aname, val))
return val
[docs]
def mapcheck_nonnegative_float_orNone(aid, aname, val):
if isinstance(val, str):
if val.lower() in ["none", "null"]:
val = None
if val is None:
return val
return mapcheck_nonnegative_float(aid, aname, val)
[docs]
def mapcheck_positive_float_orNone(aid, aname, val):
if isinstance(val, str):
if val.lower() in ["none", "null"]:
val = None
if val is None:
return val
return mapcheck_positive_float(aid, aname, val)
[docs]
def mapcheck_int(aid, aname, val):
try:
val = int(val)
except ValueError:
raise ArgumentError(("argument {}={} must be an integer").format(aname, val))
return val
[docs]
def mapcheck_positive_int(aid, aname, val):
try:
val = int(val)
except ValueError:
raise ArgumentError(("argument {}={} must be an integer").format(aname, val))
# TODO error reporting
if not (val > 0):
raise ArgumentError(("argument {}={} must be positive").format(aname, val))
if not (val - float(val) == 0):
raise ArgumentError(("argument {}={} must be an integer").format(aname, val))
return val
[docs]
def mapcheck_nonnegative_int(aid, aname, val):
try:
val = int(val)
except ValueError:
raise ArgumentError(("argument {}={} must be an integer").format(aname, val))
# TODO error reporting
if not (val >= 0):
raise ArgumentError(("argument {}={} must be non-negative").format(aname, val))
if not (val - float(val) == 0):
raise ArgumentError(("argument {}={} must be an integer").format(aname, val))
return val
[docs]
def mapcheck_positive_int_orNone(aid, aname, val):
if isinstance(val, str):
if val.lower() in ["none", "null"]:
val = None
if val is None:
return val
return mapcheck_positive_int(aid, aname, val)
[docs]
def mapcheck_nonnegative_int_orNone(aid, aname, val):
if isinstance(val, str):
if val.lower() in ["none", "null"]:
val = None
if val is None:
return val
return mapcheck_nonnegative_int(aid, aname, val)
[docs]
def mapcheck_int_orNone(aid, aname, val):
if isinstance(val, str):
if val.lower() in ["none", "null"]:
val = None
if val is None:
return val
return mapcheck_int(aid, aname, val)
[docs]
def kw_ZPKrep_build(args, kwargs):
""" """
kw = dict()
ZPKrep = None
for arg in args:
if isinstance(arg, MappingABC):
kw.update(arg)
elif isinstance(
arg,
(
representations.ZPKwData,
fitters_ZPK.MultiReprFilterBase,
),
):
if ZPKrep is None:
ZPKrep = arg.ZPKrep
else:
raise ArgumentError(
"Only a single ZPKrep may be specified" " in positional arguments"
)
else:
raise ArgumentError(
"Positional Arguments are not allowed,"
" except for a single master ZPKrep, or a set of"
" default keyword argument dictionaries"
)
kw.update(kwargs)
return kw, ZPKrep
[docs]
def grab_kwarg_hints(aid, kw, kwdesc, kwput=None):
def eval_hint(hint_name):
kwmeta = kwdesc[hint_name]
reqs = kwmeta.get("require_hints", None)
if reqs is not None:
for hname in reqs:
if not aid.hint_has(hname):
eval_hint(hname)
if not aid.hint_has(hname):
raise RuntimeError("Hint/Kwarg evaluation out of order. BUG!")
found = _grab_kwargs(aid, kw, kwmeta, hint_name, kwput=kwput)
if found:
aid.hint_setdefault(hint_name, next(iter(found.values())))
# TODO, make better error message for case when default is not given
try:
default = kwmeta["default"]
except KeyError:
pass
else:
if callable(default):
default = default(aid, hint_name)
aid.hint_setdefault(hint_name, default)
for hint_name in kwdesc.keys():
eval_hint(hint_name)
[docs]
def grab_kwargs(aid, kw, kwdesc, argname, kwput=None):
kwmeta = kwdesc[argname]
found = _grab_kwargs(aid, kw, kwmeta, argname, kwput=kwput)
if found:
return next(iter(found.values()))
# TODO, make better error message for case when default is not given
default = kwmeta["default"]
if callable(default):
default = default(aid, argname)
return default
def _grab_kwargs(aid, kw, kwmeta, argname, kwput=None):
reqs = kwmeta.get("require_hints", None)
if reqs is not None:
for hname in reqs:
if not aid.hint_has(hname):
raise RuntimeError("Hint/Kwarg evaluation out of order. BUG!")
drop_val = kwmeta.get("drop_val", args.UNSPEC)
drop_vals = list(kwmeta.get("drop_vals", []))
if drop_val is not args.UNSPEC:
drop_vals.extend(drop_val)
pop = kwmeta.get("pop", True)
mapcheck = kwmeta.get("mapcheck", None)
found = {}
# use a list to modify it from the function
discrepancy = [False]
def check_find(aname):
if pop:
val = kw.pop(aname, args.UNSPEC)
else:
val = kw.get(aname, args.UNSPEC)
if val is args.UNSPEC:
return False
if kwput is not None:
kwput[aname] = val
if val in drop_vals:
return False
if mapcheck is not None:
val = mapcheck(aid, aname, val)
if found:
if np.any(next(iter(found.values())) != val):
discrepancy[0] = True
found[aname] = val
return True
prefname = kwmeta.get("name", argname)
check_find(prefname)
for aname in kwmeta.get("aliases", []):
check_find(aname)
for aname in kwmeta.get("aliases_bad", []):
if check_find(aname):
aid.log_warn(
2,
"Argument '{}' is a deprecated alias."
" Use '{}' instead".format(aname, prefname),
)
if discrepancy[0]:
kv_strs = []
for k, v in found.items():
kv_strs.append("{}: {}".format(k, v))
raise ArgumentError(
"Inconsistent values given for aliased arguments\n" + "\n".join(kv_strs)
)
return found
[docs]
def check_remaining_arguments(kw, kwdict):
names = set()
alias_map = {}
bad_map = {}
for hname, hdict in kwdict.items():
name = hdict.get("name", hname)
names.add(name)
for alias in hdict.get("aliases", []):
alias_map[alias] = name
for alias in hdict.get("aliases_bad", []):
bad_map[alias] = name
allnames = set(names)
allnames.update(alias_map.keys())
allnames.update(bad_map.keys())
import difflib
k_lines = dict()
for k in kw.keys():
l = []
k_lines[k] = l
matches = difflib.get_close_matches(k, allnames, n=5)
print("Matches: ", k, matches)
mainset = set()
for match in matches:
if match in names:
mainset.add(name)
l.append("'{}'".format(match))
elif match in alias_map:
mainmatch = alias_map[match]
l.append("'{}', alias for '{}'".format(match, mainmatch))
else:
mainmatch = bad_map[match]
l.append("'{}', deprecated alias for '{}'".format(match, mainmatch))
stack_lines = []
for k, lines in sorted(k_lines.items()):
if len(lines) > 0:
stack_lines.append("'{}':\n\t".format(k) + "\n\t".join(lines))
else:
stack_lines.append("'{}': <no similar arguments recognized>".format(k))
raise ArgumentError(
(
"Unrecognized keyword arguments. Listed with potential matches below,"
"\nor call with argument 'help'=True for more details\n"
"{}"
).format("\n".join(stack_lines))
)
return
[docs]
def cplx_iIjJ(val):
val = val.replace("i", "j")
val = val.replace("I", "j")
val = val.replace("J", "j")
return complex(val)
[docs]
def cplx_iIjJ_list_inner(val):
vals = val.split()
vals2 = []
for v in vals:
vals2.extend(v.split(","))
vals3 = []
for v in vals2:
vals3.extend(v.split(";"))
return [cplx_iIjJ(v) for v in vals3 if v != ""]
[docs]
def cplx_iIjJ_list(val):
vals = val.split(":")
val = vals[0]
vals = vals[1:]
val = cplx_iIjJ_list_inner(val)
if vals:
val_c = vals[0]
vals = vals[1:]
assert(not vals)
val_c = cplx_iIjJ_list_inner(val_c)
val.extend(val_c)
val.extend(np.asarray(val_c).conjugate())
return val
[docs]
def transfer_kw(kwA, kwB, kwdict, pop=False):
for hname, hdict in kwdict.items():
if pop:
name = hdict.pop("name", hname)
else:
name = hdict.get("name", hname)
normalize = hdict.get("normalize", None)
val = kwA.get(name, declarative.NOARG)
if val is not declarative.NOARG:
if normalize is not None:
val = normalize(val)
kwB[name] = val
for aname in hdict.get("aliases", []):
if pop:
val = kwA.pop(aname, declarative.NOARG)
else:
val = kwA.get(aname, declarative.NOARG)
if val is not declarative.NOARG:
if normalize is not None:
val = normalize(val)
kwB[aname] = val
for aname in hdict.get("aliases_bad", []):
if pop:
val = kwA.pop(aname, declarative.NOARG)
else:
val = kwA.get(aname, declarative.NOARG)
if val is not declarative.NOARG:
if normalize is not None:
val = normalize(val)
kwB[aname] = val