import numpy as np
from .dict import Dict
import foxes.variables as FV
[docs]
class Factory:
"""
Constructs objects from a choice of allowed
constructor parameters
Attributes
----------
base: class
The class of which objects are to be created
name_template: str
The name template, e.g. 'name_<A>_<B>_<C>' for
variables A, B, C
args: tuple
Fixed arguments for the base class
kwargs: dict
Fixed arguments for the base class
var2arg: dict
Mapping from variable to constructor argument
hints: dict
Hints for print_toc, only for variables for which the
options are functions or missing
example: str
An example name
options: dict
For each variable, e.g. A, B or C, the list or dict
or function that maps a str to the actual value
:group: utils
"""
[docs]
def __init__(
self,
base,
name_template,
args=(),
kwargs={},
var2arg={},
hints={},
example_vars=None,
**options,
):
"""
Constructor.
Parameters
----------
base: class
The class of which objects are to be created
name_template: str
The name template, e.g. 'name_<A>_<B>_<C>' for
variables A, B, C
args: tuple
Fixed arguments for the base class
kwargs: dict
Fixed arguments for the base class
var2arg: dict
Mapping from variable to constructor argument
hints: dict
Hints for print_toc, only for variables for which the
options are functions or missing
example_vars: dict, optional
Variable values for creating an example
options: dict
For each variable, e.g. A, B or C, the list or dict
or function that maps a str to the actual value
"""
self.base = base
self.name_template = name_template
self.args = args
self.kwargs = kwargs
self.var2arg = var2arg
self.hints = hints
self._vars = []
self._pre = []
parts = name_template.split(">")
for i, p in enumerate(parts):
if i < len(parts) - 1:
parts2 = p.split("<")
if len(parts2) != 2:
raise ValueError(
f"Factory '{name_template}': incomplete pattern brackets '<..>' between variables, e.g. '_'"
)
if i > 0 and len(parts2[0]) == 0:
raise ValueError(
f"Factory '{name_template}': Missing seperator like '_' in template between variables '{self._vars[-1]}' and '{parts[1]}'"
)
self._pre.append(parts2[0])
self._vars.append(parts2[1])
else:
self._pre.append(p)
if len(self.variables) > 1:
for vi, v in enumerate(self.variables):
p = self._pre[vi]
if vi < len(self.variables) - 1 and p == "":
raise ValueError(
f"Factory '{name_template}': Require indicator before variable '{v}' in template, e.g. '{v}<{v}>'"
)
self.options = Dict(name=f"{self._pre[0]}_options")
for v, o in options.items():
if v not in self.variables:
raise KeyError(
f"Factory '{name_template}': Variable '{v}' found in options, but not in template"
)
if isinstance(o, list) or isinstance(o, tuple):
o = {str(k): k for k in o}
if isinstance(o, dict):
for k in o.keys():
if not isinstance(k, str):
raise TypeError(
f"Factory '{name_template}': Found option for variable '{v}' that is not a str, {k}"
)
self.options[v] = Dict(name=f"{self._pre[0]}_options_{v}", **o)
elif hasattr(o, "__call__"):
self.options[v] = o
else:
raise ValueError(
f"Factory '{name_template}': Variable '{v}' has option of type '{type(v).__name__}'. Only list, tuple, dict or function are supported"
)
exvars = dict(
n=5,
n2=9,
superposition="linear",
kTI=0.2,
kb=0.001,
step=100,
dx=100,
dt="10s",
)
if example_vars is not None:
exvars.update(example_vars)
try:
self.example = ""
for i, v in enumerate(self._vars):
self.example += f"{self._pre[i]}{exvars[v]}"
self.example += self._pre[-1]
if not self.check_match(self.example, error=False):
raise ValueError(
f"Example '{self.example}' does not match template '{self.name_template}'"
)
except KeyError:
self.example = None
@property
def name_prefix(self):
"""
The beginning of the name template
Returns
-------
nbase: str
The beginning of the name template
"""
return self._pre[0]
@property
def name_suffix(self):
"""
The ending of the name template
Returns
-------
nbase: str
The ending of the name template
"""
return self._pre[-1]
@property
def variables(self):
"""
The list of variables
Returns
-------
vrs: list of str
The variables
"""
return self._vars
[docs]
def __str__(self):
"""String representation"""
s = f"{self.name_template}: {self.base.__name__} with"
for k, d in self.kwargs.items():
s += f"\n {k}={d}"
for v in self.variables:
if v in self.options and isinstance(self.options[v], dict):
s += f"\n {v} from {list(self.options[v])}"
else:
s += f"\n {v}={self.hints.get(v, '(value)')}"
if self.example is not None:
s += f"\nExample: {self.example}"
return s
[docs]
def get_examples(self, **var_values):
"""
Create example names from given values
Parameters
----------
var_values: dict
Variables values. Key: Variable,
value: list or value
Returns
-------
examples: list of str
The examples
"""
def gete(i, vals, vars, values, examples):
if i >= len(vars):
e = ""
for i, v in enumerate(self._vars):
e += f"{self._pre[i]}{vals[v]}"
e += self._pre[-1]
self.check_match(e, error=True)
examples.append(e)
else:
v = vars[i]
if v in self._vars:
vls = np.atleast_1d(values[i])
for x in vls:
vals[v] = x
gete(i + 1, vals, vars, values, examples)
else:
gete(i + 1, vals, vars, values, examples)
examples = []
gete(
0,
{},
list(var_values.keys()),
list(var_values.values()),
examples,
)
return examples
[docs]
def check_match(self, name, error=False, ret_pars=False):
"""
Tests if a name matches the template and constructs
parameters
Parameters
----------
name: str
The name to be checked
error: bool
Flag for raising a Value error in case of
mismatch
ret_pars: bool
Flag for returning the parameters
Returns
-------
success: bool
True if the template is matched
pars: dict, optional
The constructed parameters
"""
j = 0
wlist = []
for pi, p in enumerate(self._pre):
if len(p) > 0:
i = name[j:].find(p)
if i < 0 or (pi == 0 and i > 0):
if error:
raise ValueError(
f"Factory '{self.name_template}': Name '{name}' not matching template"
)
elif ret_pars:
return False, {}
else:
return False
w = name[j : j + i]
j += i + len(p)
else:
w = name[j:]
if pi > 0:
wlist.append(w)
if ret_pars:
kwargs = {}
for vi, v in enumerate(self.variables):
w = self.var2arg.get(v, v)
data = wlist[vi]
if v in self.options:
o = self.options[v]
if hasattr(o, "__call__"):
kwargs[w] = o(data)
else:
kwargs[w] = self.options[v][data]
else:
kwargs[w] = data
kwargs.update(self.kwargs)
return True, kwargs
return True
[docs]
def construct(self, name):
"""
Create an object of the base class.
Parameters
----------
name: str
The name, matching the template
Returns
-------
obj: object
The instance of the base class
"""
__, kwargs = self.check_match(name, error=True, ret_pars=True)
return self.base(*self.args, **kwargs)
[docs]
class WakeKFactory:
"""
A factory that automatically handles
wake_k parameters
Attributes
----------
factories: list of Factory
The individual factories
:group: utils
"""
[docs]
def __init__(self, base, name_template, *args, hints={}, **kwargs):
"""
Constructor.
Parameters
----------
base: class
The class of which objects are to be created
name_template: str
The name template, e.g. 'name_<A>_<B>_<C>' for
variables A, B, C. Indicate wake_k part by '_[wake_k]'
args: tuple, optional
Additional arguments for Factory
hints: dict
Hints for print_toc, only for variables for which the
options are functions or missing
kwargs: dict
Additional arguments for Factory
"""
self._base = base
self._kwargs = kwargs
self._template0 = name_template
self.factories = []
i0 = name_template.find("_[wake_k]")
i1 = i0 + len("_[wake_k]")
kw = kwargs.pop("kwargs", {})
v2a = kwargs.pop("var2arg", {})
if i0 < 0:
raise ValueError(
f"String '_[wake_k]' not found in name template '{name_template}'"
)
exvars = dict(k=0.04, ka=0.2, ambka=0.4, kb=0.001)
if "example_vars" in kwargs:
exvars.update(kwargs.pop("example_vars"))
# add case ka, kb:
t0 = name_template[:i0]
t = t0 + "_ka<ka>_kb<kb>"
if len(name_template) > i1:
t += name_template[i1:]
h = hints.copy()
h["ka"] = "(Value, e.g. 04 for 0.4)"
h["kb"] = "(Value, e.g. 001 for 0.01)"
kw["ti_var"] = FV.TI
self.factories.append(
Factory(
base,
t,
*args,
hints=h,
kwargs=kw.copy(),
**kwargs,
ka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
kb=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
example_vars=exvars,
)
)
# add case ambient ambka, kb:
t = name_template[:i0] + "_ambka<ambka>_kb<kb>"
if len(name_template) > i1:
t += name_template[i1:]
h = hints.copy()
h["ambka"] = "(Value, e.g. 04 for 0.4)"
h["kb"] = "(Value, e.g. 001 for 0.01)"
kw["ti_var"] = FV.AMB_TI
hv2a = v2a.copy()
hv2a["ambka"] = "ka"
self.factories.append(
Factory(
base,
t,
*args,
hints=h,
kwargs=kw.copy(),
var2arg=hv2a,
**kwargs,
ambka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
kb=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
example_vars=exvars,
)
)
# add case ka:
t = name_template[:i0] + "_ka<ka>"
if len(name_template) > i1:
t += name_template[i1:]
h = hints.copy()
h["ka"] = "(Value, e.g. 04 for 0.4)"
kw["ti_var"] = FV.TI
self.factories.append(
Factory(
base,
t,
*args,
hints=h,
kwargs=kw.copy(),
**kwargs,
ka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
example_vars=exvars,
)
)
# add case ambka:
t = name_template[:i0] + "_ambka<ambka>"
if len(name_template) > i1:
t += name_template[i1:]
h = hints.copy()
h["ambka"] = "(Value, e.g. 04 for 0.4)"
kw["ti_var"] = FV.AMB_TI
hv2a = v2a.copy()
hv2a["ambka"] = "ka"
self.factories.append(
Factory(
base,
t,
*args,
hints=h,
kwargs=kw.copy(),
var2arg=hv2a,
**kwargs,
ambka=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
example_vars=exvars,
)
)
# add case k:
t = name_template[:i0] + "_k<k>"
if len(name_template) > i1:
t += name_template[i1:]
h = hints.copy()
h["k"] = "(Value, e.g. 004 for 0.04)"
kw["ti_var"] = FV.TI
self.factories.append(
Factory(
base,
t,
*args,
hints=h,
kwargs=kw.copy(),
**kwargs,
k=lambda x: float(f"0.{x[1:]}" if x[0] == "0" else float(x)),
example_vars=exvars,
)
)
# add case without k:
t = name_template[:i0]
if len(name_template) > i1:
t += name_template[i1:]
kw["ti_var"] = FV.TI
self.factories.append(
Factory(
base,
t,
*args,
hints=hints,
kwargs=kw.copy(),
**kwargs,
example_vars=exvars,
)
)
[docs]
def __str__(self):
"""String representation"""
s = f"{self._template0}: {self._base.__name__} with"
for k, d in self._kwargs.items():
s += f"\n {k}={d}"
f0 = self.factories[-1]
for v in f0.variables:
if v in f0.options and isinstance(f0.options[v], dict):
s += f"\n {v} from {list(f0.options[v])}"
else:
s += f"\n {v}={f0.hints.get(v, '(value)')}"
s += (
f"\n [wake_k]=(None or k<k> or ka<ka> or ka<ka>_kb<kb>, e.g. 004 for 0.04)"
)
return s
[docs]
class FDict(Dict):
"""
A dictionary with factory support
Attributes
----------
store_created: bool
Flag for storing created objects
factories: list of foxes.utils.Factory
The factories
:group: utils
"""
[docs]
def __init__(self, *args, store_created=True, **kwargs):
"""
Constructor.
Parameters
----------
args: tuple, optional
Parameters for the base class
store_created: bool
Flag for storing created objects
kwargs: dict, optional
Parameters for the base class
"""
super().__init__(*args, **kwargs)
self.store_created = store_created
self.factories = []
[docs]
def add_factory(self, *args, factory=None, **kwargs):
"""
Adds a Factory object.
Parameters
----------
args: tuple, optional
Parameters for the Factory constructor
factory: Factory, optional
The factory object
kwargs: dict, optional
Parameters for the Factory constructor
"""
f = Factory(*args, **kwargs) if factory is None else factory
i = len(self.factories)
for gi in range(len(self.factories) - 1, -1, -1):
g = self.factories[gi]
if (
g.name_prefix == f.name_prefix
and g.name_suffix == f.name_suffix
and len(f.variables) > len(g.variables)
):
i = gi
if i == len(self.factories):
self.factories.append(f)
else:
self.factories.insert(i, f)
[docs]
def add_k_factory(self, *args, **kwargs):
"""
Adds a WakeKFactory.
Parameters
----------
args: tuple, optional
Parameters for the Factory constructor
kwargs: dict, optional
Parameters for the Factory constructor
"""
for f in WakeKFactory(*args, **kwargs).factories:
self.add_factory(factory=f)
[docs]
def __contains__(self, key):
found = super().__contains__(key)
if not found:
for f in self.factories:
if f.check_match(key):
return True
return found
[docs]
def get_item(self, key, prnt=True):
"""
Gets an item, prints readable error if not found
Parameters
----------
key: immutable object
The key
prnt: bool
Flag for message printing
"""
try:
return self[key]
except KeyError as e:
if prnt:
print(
f"\n{self.name}: Cannot find key '{key}', also no factory matches.\n"
)
print("Known keys:")
for k in self.keys():
print(" ", k)
if len(self.factories):
print("\nKnown factories:")
for f in self.factories:
print(" ", f.name_template)
print()
raise e
[docs]
def __getitem__(self, key):
try:
return super().__getitem__(key)
except KeyError:
for f in self.factories:
try:
obj = f.construct(key)
if self.store_created:
self[key] = obj
return obj
except ValueError:
pass
k = ", ".join(sorted(list(self.keys())))
e = f"{self.name}: Cannot find key '{key}', also no factory matches. Known keys: {k}. Known factories: {[f.name_template for f in self.factories]}"
raise KeyError(e)