# -*- coding: utf-8 -*-
#
# This file is part of the pyFDA project hosted at https://github.com/chipmuenk/pyfda
#
# Copyright © pyFDA Project Contributors
# Licensed under the terms of the MIT License
# (see file LICENSE in root directory for details)
"""
Dynamic parameters and settings are exchanged via the dictionaries in this file.
Importing ``filterbroker.py`` runs the module once, defining all module variables
which have a global scope like class variables and can be imported like
>>> import filter_factory as ff
>>> myfil = ff.fil_factory
"""
import importlib
import logging
from . import filterbroker as fb
logger = logging.getLogger(__name__)
fil_inst = None
"""
Instance of current filter design class (e.g. "cheby1"), globally accessible
>>> import filter_factory as ff
>>> ff.fil_factory.create_fil_instance('cheby1') # create instance of dynamic class
>>> ff.fil_inst.LPmin(fil[0]) # design a filter
"""
#------------------------------------------------------------------------------
[docs]
class FilterFactory(object):
"""
This class implements a filter factory that (re)creates the globally accessible
filter instance ``fil_inst`` from module path and class name, passed as strings.
"""
def __init__(self):
#--------------------------------------
# return error codes for class instantiation and method
self.err_code = 0
[docs]
def create_fil_inst(self, fc, mod = None):
# TODO: need to pass both module and class name for more flexibility
"""
Create an instance of the filter design class passed as a string ``fc``
from the module found in ``fb.filter_classes[fc]``.
This dictionary has been collected by ``tree_builder.py``.
The instance can afterwards be globally referenced as ``fil_inst``.
Parameters
----------
fc : str
The name of the filter design class to be instantiated (e.g. 'cheby1' or 'equiripple')
mod : str (optional, default = None)
Fully qualified name of the filter module. When not specified, it is
read from the global dict ``fb.filter_classes[fc]['mod']``
Returns
-------
err_code : int
one of the following error codes:
:-1: filter design class was instantiated successfully
:0: filter instance exists, no re-instantiation necessary
:1: filter module not found by FilterTreeBuilder
:2: filter module found by FilterTreeBuilder but could not be imported
:3: filter class could not be instantiated
:4: unknown error during instantiation
Examples
--------
>>> create_fil_instance('cheby1')
>>> fil_inst.LPmin(fil[0])
The example first creates an instance of the filter class 'cheby1' and
then performs the actual filter design by calling the method 'LPmin',
passing the global filter dictionary fil[0] as the parameter.
"""
global fil_inst # allow writing to variable
try:
# Try to dynamically import the module fc, i.e. do the following
# import pyfda.<filter_package>.<fc> as fc_module
if not mod:
mod = fb.filter_classes[fc]['mod']
#------------------------------------------------------------------
fc_module = importlib.import_module(mod)
#------------------------------------------------------------------
except KeyError:
err_string =("\nKeyError in 'FilterFactory.create_fil_inst()':\n"
"Filter design class '%s' is not in dict 'fb.filter_classes',\n"
"i.e. it was not found by 'FilterTreeBuilder'."%fc)
self.err_code = 1
logger.warning(err_string)
return self.err_code
except ImportError as e:
# Filter module mod is in dictionary 'fb.filter_classes', but could not be imported.
err_string =("\nImportError in 'FilterFactory.create_fil_inst()':\n"
"Filter design module '%s' could not be imported."%str(mod))
self.err_code = 2
logger.warning(err_string)
return self.err_code
# Check whether create_fil_inst has been called for the first time .
# (= no filter object and hence no attribute 'name' exists) or whether
# the design method has been changed since last time.
# In both cases, a (new) filter object is instantiated.
if fil_inst is None or fc != fil_inst.__class__.__name__:
err_string = ""
self.err_code = -1
# get attribute fc from fc_module, here, this returns the class fc
fil_class = getattr(fc_module, fc, None) # or None if not in fc_module
if fil_class is None: # fc is not a class of fc_module
err_string = ("\nERROR in 'FilterFactory.create_fil_inst()':\n"
"Unknown design class '{0}', could not be created.".format(fc))
logger.warning(err_string)
self.err_code = 3
else:
try:
fil_inst = fil_class() # instantiate an object
self.err_code = 0 # filter instance has been created / changed successfully
logger.debug("FilterFactory.create_fil_inst(): successfully created {0}".format(fc))
except Exception as e:
self.err_code = 4
logger.warning("Error during instantiation of filter class {0}:\n{1}".format(fc,e))
return self.err_code
#------------------------------------------------------------------------------
[docs]
def call_fil_method(self, method, fil_dict, fc = None):
"""
Instantiate the filter design class passed as string ``fc`` with the
globally accessible handle ``fil_inst``. If ``fc = None``, use the previously
instantiated filter design class.
Next, call the design method passed as string ``method`` of the instantiated
filter design class.
Parameters
----------
method : string
The name of the design method to be called (e.g. 'LPmin')
fil_dict : dictionary
A dictionary with all the filter specs that is passed to the actual
filter design routine. This is usually a copy of ``fb.fil[0]``
The results of the filter design routine are written back to the same dict.
fc : string (optional, default: None)
The name of the filter design class to be instantiated. When nothing
is specified, the last filter selection is used.
Returns
-------
err_code : int
one of the following error codes:
:-1: filter design operation has been cancelled by user
:0: filter design method exists and is callable
:16: passed method name is not a string
:17: filter design method does not exist in class
:18: filter design error containing "order is too high"
:19: filter design error containing "failure to converge"
:99: unknown error
Examples
--------
>>> call_fil_method("LPmin", fil[0], fc="cheby1")
The example first creates an instance of the filter class 'cheby1' and
then performs the actual filter design by calling the method 'LPmin',
passing the global filter dictionary ``fil[0]`` as the parameter.
"""
if self.err_code >= 16 or self.err_code < 0:
self.err_code = 0 # # clear previous method call error
err_string = ""
if fc: # filter design class was part of the argument, (re-)create class instance
self.err_code = self.create_fil_inst(fc)
# Error during filter design class instantiation (class fc could not be instantiated)
if self.err_code > 0:
err_string = "Filter design class could not be instantiated, see previous error message."
# Test whether 'method' is a string (Py3):
elif not isinstance(method, str):
err_string = "Method name '{0}' is not a string.".format(method)
self.err_code = 16
# method does not exist in filter class:
elif not hasattr(fil_inst, method):
err_string = "Method '{0}' doesn't exist in class '{1}'.".format(method, fil_inst)
self.err_code = 17
else: # everything ok so far, try calling method with the filter dict as argument
# err_code = -1 means "operation cancelled"
try:
#------------------------------------------------------------------
self.err_code = getattr(fil_inst, method)(fil_dict)
#------------------------------------------------------------------
except Exception as e:
err_string = "Method '{0}' of class '{1}':\n{2}"\
.format(method, type(fil_inst).__name__, e)
if e:
err_string += "\n" # add line break to error message
if "order n is too high" in str(e).lower():
self.err_code = 18
err_string += "Try relaxing the specifications."
elif "failure to converge" in str(e).lower():
self.err_code = 19
err_string += "Try relaxing the specifications."
else:
self.err_code = 99
if self.err_code is None:
self.err_code = 0
elif self.err_code > 0:
logger.error("ErrCode {0}: {1}".format(self.err_code, err_string))
return self.err_code
#------------------------------------------------------------------------------
fil_factory = FilterFactory() #: Class instance of FilterFactory that can be accessed in other modules
######################################################################
if __name__ == '__main__':
print("\nfb.filter_classes\n", fb.filter_classes)
print("aaa:", fil_factory.create_fil_inst("aaa"),"\n") # class doesn't exist
print("cheby1:", fil_factory.create_fil_inst("cheby1"),"\n") # first time inst.
print("cheby1:", fil_factory.create_fil_inst("cheby1"),"\n") # second time inst.
print("cheby2:", fil_factory.create_fil_inst("cheby2"),"\n") # new class
print("bbb:", fil_factory.create_fil_inst("bbb"),"\n") # class doesn't exist
print("LPman, fc = cheby2:", fil_factory.call_fil_method("LPman", fb.fil[0], fc = "cheby2"),"\n")
print("LPmax:", fil_factory.call_fil_method("LPmax", fb.fil[0]),"\n") # doesn't exist
print("Int 1:", fil_factory.call_fil_method(1, fb.fil[0]),"\n") # not a string
print("LPmin:", fil_factory.call_fil_method("LPmin", fb.fil[0]),"\n") # changed method
print("LPmin:", fil_factory.call_fil_method("LPmin", fb.fil[0]),"\n")
print("LP:", fil_factory.call_fil_method("LP", fb.fil[0]),"\n")
print("LPman, fc = cheby1:", fil_factory.call_fil_method("LPman", fb.fil[0], fc = "cheby1"),"\n")
print("LPman, fc = cheby1:", fil_factory.call_fil_method("LPman", fc = "cheby1"),"\n") # fails