# Path: padasip/filters/base_filter.py
# class AdaptiveFilterAP(AdaptiveFilter):
#     """
#     This class modifies the AdaptiveFilter class
#     to allow AP filtering.
#     """
#     def __init__(self, *args, order=5, ifc=0.001, **kwargs):
#         """
#         **Kwargs:**
# 
#         * `order` : projection order (integer) - how many input vectors
#           are in one input matrix
# 
#         * `ifc` : initial offset covariance (float) - regularization term
#           to prevent problems with inverse matrix
# 
#         """
#         super().__init__(*args, **kwargs)
#         self.order = order
#         self.x_mem = np.zeros((self.n, self.order))
#         self.d_mem = np.zeros(order)
#         self.ide_ifc = ifc * np.identity(self.order)
#         self.ide = np.identity(self.order)
#         self.y_mem = False
#         self.e_mem = False
# 
#     def learning_rule(self, e_mem, x_mem):
#         """
#         This functions computes the increment of adaptive weights.
# 
#         **Args:**
# 
#         * `e_mem` : error of the adaptive filter (1d array)
# 
#         * `x_mem` : input matrix (2d array)
# 
#         **Returns**
# 
#         * increments of adaptive weights - result of adaptation
#         """
#         return np.zeros(len(x_mem))
# 
#     def adapt(self, d, x):
#         """
#         Adapt weights according one desired value and its input.
# 
#         **Args:**
# 
#         * `d` : desired value (float)
# 
#         * `x` : input array (1-dimensional array)
#         """
#         # create input matrix and target vector
#         self.x_mem[:, 1:] = self.x_mem[:, :-1]
#         self.x_mem[:, 0] = x
#         self.d_mem[1:] = self.d_mem[:-1]
#         self.d_mem[0] = d
#         # estimate output and error
#         self.y_mem = np.dot(self.x_mem.T, self.w)
#         self.e_mem = self.d_mem - self.y_mem
#         # update
#         dw_part1 = np.dot(self.x_mem.T, self.x_mem) + self.ide_ifc
#         dw_part2 = np.linalg.solve(dw_part1, self.ide)
#         dw = np.dot(self.x_mem, np.dot(dw_part2, self.e_mem))
#         self.w += self.mu * dw
# 
#     def run(self, d, x):
#         """
#         This function filters multiple samples in a row.
# 
#         **Args:**
# 
#         * `d` : desired value (1 dimensional array)
# 
#         * `x` : input matrix (2-dimensional array). Rows are samples,
#           columns are input arrays.
# 
#         **Returns:**
# 
#         * `y` : output value (1 dimensional array).
#           The size corresponds with the desired value.
# 
#         * `e` : filter error for every sample (1 dimensional array).
#           The size corresponds with the desired value.
# 
#         * `w` : history of all weights (2 dimensional array).
#           Every row is set of the weights for given sample.
# 
#         """
#         # measure the data and check if the dimmension agree
#         N = len(x)
#         if not len(d) == N:
#             raise ValueError('The length of vector d and matrix x must agree.')
#         self.n = len(x[0])
#         # prepare data
#         try:
#             x = np.array(x)
#             d = np.array(d)
#         except:
#             raise ValueError('Impossible to convert x or d to a numpy array')
#         # create empty arrays
#         y = np.zeros(N)
#         e = np.zeros(N)
#         self.w_history = np.zeros((N, self.n))
#         # adaptation loop
#         for k in range(N):
#             self.w_history[k, :] = self.w
#             # create input matrix and target vector
#             self.x_mem[:, 1:] = self.x_mem[:, :-1]
#             self.x_mem[:, 0] = x[k]
#             self.d_mem[1:] = self.d_mem[:-1]
#             self.d_mem[0] = d[k]
#             # estimate output and error
#             self.y_mem = np.dot(self.x_mem.T, self.w)
#             self.e_mem = self.d_mem - self.y_mem
#             y[k] = self.y_mem[0]
#             e[k] = self.e_mem[0]
#             # update
#             self.w += self.learning_rule(self.e_mem, self.x_mem)
#         return y, e, self.w_history


import numpy as np
from padasip.filters.base_filter import AdaptiveFilterAP
    filt = pa.filters.FilterAP(3, mu=1.)
    for k in range(N):
        # measure input
        x = measure_x()
        # predict new value
        y = filt.predict(x)
        # do the important stuff with prediction output
        pass
        # measure output
        d = measure_d(x)
        # update filter
        filt.adapt(d, x)
        # log values
        log_d[k] = d
        log_y[k] = y

    ### show results
    plt.figure(figsize=(15,9))
    plt.subplot(211);plt.title("Adaptation");plt.xlabel("samples - k")
    plt.plot(log_d,"b", label="d - target")
    plt.plot(log_y,"g", label="y - output");plt.legend()
    plt.subplot(212);plt.title("Filter error");plt.xlabel("samples - k")
    plt.plot(10*np.log10((log_d-log_y)**2),"r", label="e - error [dB]")
    plt.legend(); plt.tight_layout(); plt.show()

Code Explanation
======================================
"""



# Path: padasip/filters/base_filter.py
# class AdaptiveFilterAP(AdaptiveFilter):
#     """
#     This class modifies the AdaptiveFilter class
#     to allow AP filtering.
#     """
#     def __init__(self, *args, order=5, ifc=0.001, **kwargs):
#         """
#         **Kwargs:**
# 
#         * `order` : projection order (integer) - how many input vectors
#           are in one input matrix
# 
#         * `ifc` : initial offset covariance (float) - regularization term
#           to prevent problems with inverse matrix
# 
#         """
#         super().__init__(*args, **kwargs)
#         self.order = order
#         self.x_mem = np.zeros((self.n, self.order))
#         self.d_mem = np.zeros(order)
#         self.ide_ifc = ifc * np.identity(self.order)
#         self.ide = np.identity(self.order)
#         self.y_mem = False
#         self.e_mem = False
# 
#     def learning_rule(self, e_mem, x_mem):
#         """
#         This functions computes the increment of adaptive weights.
# 
#         **Args:**
# 
#         * `e_mem` : error of the adaptive filter (1d array)
# 
#         * `x_mem` : input matrix (2d array)
# 
#         **Returns**
# 
#         * increments of adaptive weights - result of adaptation
#         """
#         return np.zeros(len(x_mem))
# 
#     def adapt(self, d, x):
#         """
#         Adapt weights according one desired value and its input.
# 
#         **Args:**
# 
#         * `d` : desired value (float)
# 
#         * `x` : input array (1-dimensional array)
#         """
#         # create input matrix and target vector
#         self.x_mem[:, 1:] = self.x_mem[:, :-1]
#         self.x_mem[:, 0] = x
#         self.d_mem[1:] = self.d_mem[:-1]
#         self.d_mem[0] = d
#         # estimate output and error
#         self.y_mem = np.dot(self.x_mem.T, self.w)
#         self.e_mem = self.d_mem - self.y_mem
#         # update
#         dw_part1 = np.dot(self.x_mem.T, self.x_mem) + self.ide_ifc
#         dw_part2 = np.linalg.solve(dw_part1, self.ide)
#         dw = np.dot(self.x_mem, np.dot(dw_part2, self.e_mem))
#         self.w += self.mu * dw
# 
#     def run(self, d, x):
#         """
#         This function filters multiple samples in a row.
# 
#         **Args:**
# 
#         * `d` : desired value (1 dimensional array)
# 
#         * `x` : input matrix (2-dimensional array). Rows are samples,
#           columns are input arrays.
# 
#         **Returns:**
# 
#         * `y` : output value (1 dimensional array).
#           The size corresponds with the desired value.
# 
#         * `e` : filter error for every sample (1 dimensional array).
#           The size corresponds with the desired value.
# 
#         * `w` : history of all weights (2 dimensional array).
#           Every row is set of the weights for given sample.
# 
#         """
#         # measure the data and check if the dimmension agree
#         N = len(x)
#         if not len(d) == N:
#             raise ValueError('The length of vector d and matrix x must agree.')
#         self.n = len(x[0])
#         # prepare data
#         try:
#             x = np.array(x)
#             d = np.array(d)
#         except:
#             raise ValueError('Impossible to convert x or d to a numpy array')
#         # create empty arrays
#         y = np.zeros(N)
#         e = np.zeros(N)
#         self.w_history = np.zeros((N, self.n))
#         # adaptation loop
#         for k in range(N):
#             self.w_history[k, :] = self.w
#             # create input matrix and target vector
#             self.x_mem[:, 1:] = self.x_mem[:, :-1]
#             self.x_mem[:, 0] = x[k]
#             self.d_mem[1:] = self.d_mem[:-1]
#             self.d_mem[0] = d[k]
#             # estimate output and error
#             self.y_mem = np.dot(self.x_mem.T, self.w)
#             self.e_mem = self.d_mem - self.y_mem
#             y[k] = self.y_mem[0]
#             e[k] = self.e_mem[0]
#             # update
#             self.w += self.learning_rule(self.e_mem, self.x_mem)
#         return y, e, self.w_history


# Path: padasip/filters/ap.py
import numpy as np
from padasip.filters.base_filter import AdaptiveFilterAP

    filt = pa.filters.FilterAP(3, mu=1.)
    for k in range(N):
        # measure input
        x = measure_x()
        # predict new value
        y = filt.predict(x)
        # do the important stuff with prediction output
        pass
        # measure output
        d = measure_d(x)
        # update filter
        filt.adapt(d, x)
        # log values
        log_d[k] = d
        log_y[k] = y

    ### show results
    plt.figure(figsize=(15,9))
    plt.subplot(211);plt.title("Adaptation");plt.xlabel("samples - k")
    plt.plot(log_d,"b", label="d - target")
    plt.plot(log_y,"g", label="y - output");plt.legend()
    plt.subplot(212);plt.title("Filter error");plt.xlabel("samples - k")
    plt.plot(10*np.log10((log_d-log_y)**2),"r", label="e - error [dB]")
    plt.legend(); plt.tight_layout(); plt.show()

Code Explanation
======================================
"""



class FilterAP(AdaptiveFilterAP):