Skip to content

Commit 1c2c3e6

Browse files
committed
Test example added. Introduced ar(1) and WGN generators. Normalized misalignment function added in echoproc
1 parent 6c78338 commit 1c2c3e6

19 files changed

+420
-5
lines changed

build/lib/lmso_algorithm/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import *

build/lib/lmso_algorithm/algorithm.py

+143
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@author: Alexandru - George Rusu (2019). All Rights Reserved.
5+
"""
6+
import numpy as np
7+
8+
9+
class cir_buf:
10+
"""
11+
Class that defines the concept of circular buffer.
12+
"""
13+
def __init__(self, cblen):
14+
self.cblen = cblen
15+
self.cb = np.zeros([cblen, 1])
16+
17+
def cb_push(self, x):
18+
"""
19+
Pushes a sample in the circula buffer.
20+
:param x: input sample.
21+
"""
22+
self.cb = np.vstack((x, self.cb[0 : self.cblen - 1]))
23+
24+
def dot_product(self):
25+
"""
26+
Performs the scalar product of the circular buffer and its self.
27+
"""
28+
return ((self.cb).T).dot(self.cb)
29+
30+
31+
class fir(cir_buf):
32+
"""
33+
Class that defines the FIR filter.
34+
"""
35+
def __init__(self, ford):
36+
self.ord = ford
37+
self.coeffs = np.zeros([ford, 1])
38+
39+
def ffir(self, x):
40+
"""
41+
Performs the filtering of an input signal of the same length.
42+
:param x: input circular buffer.
43+
"""
44+
return ((x.cb).T).dot(self.coeffs)
45+
46+
47+
class lmso(fir, cir_buf):
48+
def __init__(self, ford = 512, cst = 2.165e-6, lam = 0.95, sgmv = 0, sgmw = 0, lniproc = 2, alpha = 1.0, delta = 1e-6):
49+
"""
50+
Constructor that initializes lmso object parameters.
51+
:param ford: the length L of the adaptive filter, which is the same as for the input circular buffer.
52+
:param cst: small constant that initializes the MSD.
53+
:param lam: represents the forgetting factor of the exponential window.
54+
:param sgmv: represents the variance of the measurement noise.
55+
:param sgmw: represents the variance of the system noise.
56+
:param lniproc: how long the initialization process lasts [e.g., int(lniproc) * L].
57+
:param alpha: the fixed step-size of the NLMS algorithm used in the initialization process
58+
for a period of int(lniproc) * L samples.
59+
:param delta: the regularization parameter of the NLMS algorithm used in the initialization
60+
process for a period of int(lniproc) * L samples.
61+
62+
"""
63+
id_vec = np.ones([ford, 1])
64+
id_matrix = np.diagflat(id_vec)
65+
66+
self.h = fir(ford)
67+
self.c = np.sqrt(cst) * id_vec
68+
self.ccorr_mat = cst * id_matrix
69+
self.gamma_mat = self.ccorr_mat + sgmw * id_matrix
70+
71+
self.x = cir_buf(ford)
72+
self.xcorr_mat = np.zeros([ford, ford])
73+
74+
self.msd = cst
75+
self.tmp = 0
76+
self.sx = 0
77+
self.sv = sgmv
78+
self.sw = sgmw
79+
self.sw_idmat = sgmw * id_matrix
80+
81+
self.ln_init_proc = lniproc * ford
82+
self.alpha = alpha
83+
self.delta = delta
84+
self.lam = lam
85+
86+
def lmso_w(self, echo):
87+
"""
88+
This function implements the white version of the LMSO algorithm.
89+
:param echo: echo sample.
90+
"""
91+
if(self.ln_init_proc == 0):
92+
self.msd = np.trace(self.ccorr_mat) + self.h.ord * self.sw
93+
self.ln_init_proc = -1
94+
95+
err = echo - self.h.ffir(self.x)
96+
if(self.ln_init_proc > 0):
97+
self.sx = self.x.dot_product()
98+
sz = self.alpha / (self.sx + self.delta)
99+
u = sz * err * self.x.cb
100+
self.c += u
101+
self.ccorr_mat = self.lam * self.ccorr_mat + (1 - self.lam) * (self.c).dot((self.c).T)
102+
self.ln_init_proc -= 1
103+
else:
104+
self.sx = self.x.dot_product() / self.h.ord
105+
sz = self.msd / (self.msd * self.sx * (self.h.ord + 2) + self.h.ord * self.sv)
106+
u = sz * err * self.x.cb
107+
self.tmp = self.msd * (1 - sz * self.sx)
108+
self.msd = self.tmp + self.h.ord * self.sw
109+
110+
self.h.coeffs += u
111+
return err
112+
113+
def lmso_g(self, echo):
114+
"""
115+
This function implements the general version of the LMSO algorithm.
116+
:param echo: echo sample.
117+
"""
118+
self.xcorr_mat = self.lam * self.xcorr_mat + (1 - self.lam) * (self.x.cb).dot((self.x.cb).T)
119+
err = echo - self.h.ffir(self.x)
120+
121+
if(self.ln_init_proc > 0):
122+
self.sx = self.x.dot_product()
123+
sz = self.alpha / (self.sx + self.delta)
124+
u = sz * err * self.x.cb
125+
self.c += u
126+
self.ccorr_mat = self.lam * self.ccorr_mat + (1 - self.lam) * (self.c).dot((self.c).T)
127+
self.ln_init_proc -= 1
128+
else:
129+
self.sx = self.x.dot_product() / self.h.ord
130+
gr_prod = (self.gamma_mat).dot(self.xcorr_mat)
131+
rg_prod = (self.xcorr_mat).dot(self.gamma_mat)
132+
tp = np.trace(gr_prod)
133+
sz = tp / (self.h.ord * self.sx * tp + 2 * np.trace(gr_prod.dot(self.xcorr_mat)) \
134+
+ self.h.ord * self.sx * self.sv)
135+
u = sz * err * self.x.cb
136+
self.ccorr_mat = self.gamma_mat - sz * (gr_prod + rg_prod) \
137+
+ sz**2 * (2 * rg_prod.dot(self.xcorr_mat) + self.xcorr_mat * tp) \
138+
+ sz**2 * self.sv * self.xcorr_mat
139+
140+
self.h.coeffs += u
141+
self.gamma_mat = self.ccorr_mat + self.sw_idmat
142+
return err
143+

build/lib/lmso_algorithm/common.py

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@author: Alexandru - George Rusu (2019). All Rights Reserved.
5+
"""
6+
import scipy.signal as ss
7+
import numpy as np
8+
9+
10+
def time_dif(time, all = False, string = False):
11+
hours, rest = divmod(time, 3600)
12+
minutes, seconds = divmod(rest, 60)
13+
if(all == True):
14+
return hours, minutes, seconds, str(int(hours)).zfill(2) + ":" + str(int(minutes)).zfill(2) + ":" + str(round(seconds)).zfill(2)
15+
elif(string == True):
16+
return str(int(hours)).zfill(2) + ":" + str(int(minutes)).zfill(2) + ":" + str(round(seconds)).zfill(2)
17+
else:
18+
return hours, minutes, seconds
19+
20+
21+
def vrms(seq):
22+
"""
23+
Computes the root mean square (RMS) power.
24+
:param seq: the input sequence for which the VRMS is returned.
25+
"""
26+
return ((seq.astype(float)**2).sum() / len(seq))**0.5
27+
28+
29+
def wgn(length):
30+
"""
31+
Generates a white Gaussian noise sequence.
32+
:param length: the length of the output sequence.
33+
"""
34+
return np.random.randn(length, 1).ravel()
35+
36+
37+
def ar1(length, beta):
38+
"""
39+
Generates an AR(1) process resulted by filtering a white Gaussian
40+
noise through a first-order system.
41+
:param length: the length of the output sequence.
42+
:param beta: correlation factor.
43+
"""
44+
b = np.array([1.0])
45+
a = np.array([1.0, -beta])
46+
ar1_process = wgn(length)
47+
ar1_process = ar1_process * np.sqrt(1 - beta**2)
48+
ar1_process = ss.lfilter(b, a, ar1_process)
49+
50+
return ar1_process

build/lib/lmso_algorithm/echoproc.py

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
"""
4+
@author: Alexandru - George Rusu (2019). All Rights Reserved.
5+
"""
6+
from . import common
7+
import scipy.signal as ss
8+
import numpy as np
9+
10+
11+
def shift(seq, n = 0):
12+
"""
13+
Shifts the input sequence with n samples.
14+
:param n: shift positions.
15+
"""
16+
a = n % len(seq)
17+
return np.concatenate((seq[-a:], seq[:-a]), axis = 0)
18+
19+
20+
def normalized_misalignment(estimated_echo_path, real_echo_path):
21+
"""
22+
Computes the normalized misalignment between the real echo path
23+
and the estimated echo path.
24+
:param esimated_echo_path: coeffs of the estimated echo path.
25+
:param real_echo_path: coeffs of the real echo path.
26+
"""
27+
return np.linalg.norm((estimated_echo_path).T - real_echo_path) / np.linalg.norm(real_echo_path)
28+
29+
30+
def generate_echo(input_signal, echo_path, ENR):
31+
"""
32+
Filters the input signal through the echo path and sets the measurement
33+
noise according to the input echo to noise ratio (ENR).
34+
:param input_signal: the input signal that must be filtered.
35+
:param echo_path: fir filter coeffs.
36+
:param ENR: the echo to noise ratio in dB.
37+
"""
38+
comfort_noise = np.random.randn(len(input_signal), 1).ravel()
39+
echo_signal = ss.lfilter(echo_path, 1, input_signal)
40+
echo_power = common.vrms(echo_signal)
41+
noise_power = common.vrms(comfort_noise)
42+
factor = echo_power / noise_power * 10**(-ENR/20)
43+
measurement_noise = factor * comfort_noise
44+
echo_signal += measurement_noise
45+
sgmv = np.var(measurement_noise)
46+
47+
return echo_signal, sgmv
1023 Bytes
Binary file not shown.

dist/lmso_algorithm-1.1.tar.gz

919 Bytes
Binary file not shown.

example/ir_1.mat

8.18 KB
Binary file not shown.

example/test.py

+44-2
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,48 @@
44
@author: Alexandru - George Rusu (2019). All Rights Reserved.
55
"""
66
from lmso_algorithm import algorithm
7+
from lmso_algorithm import common
8+
from lmso_algorithm import echoproc
9+
import matplotlib.pyplot as plt
10+
import scipy.io as sio
11+
import numpy as np
712

8-
a = algorithm.lmso()
9-
b = algorithm.lmso()
13+
14+
fs = 8000 # sample rate
15+
ford = 512 # adpative filter length
16+
sim_len = 24000 # Simulation length in sample count
17+
test_signal = 0 # 0 for WGN as input signal, 1 for AR(1) process
18+
beta = 0.9 # a1 coeff of the first order system
19+
ENR = 20 # echo-to-noise-ratio
20+
nmse_w = np.zeros([sim_len, 1]) # computed normalized misalignment
21+
nmse_g = np.zeros([sim_len, 1]) # computed normalized misalignment
22+
data = sio.loadmat('ir_1.mat') # measured echo path
23+
echo_path = data['g1'].ravel()[0 : ford] # read only the first 512 coeffs
24+
eps = abs(echo_path[0]) # initialization constant
25+
26+
27+
if(test_signal):
28+
far_end_signal = common.ar1(sim_len, beta)
29+
else:
30+
far_end_signal = common.wgn(sim_len)
31+
32+
echo_signal, sgmv = echoproc.generate_echo(far_end_signal, echo_path, ENR)
33+
34+
35+
if __name__ == "__main__":
36+
lmsow_obj = algorithm.lmso(ford = ford, cst = eps, lam = 0.95, sgmv = sgmv, sgmw = 0.0, lniproc = 2, alpha = 1.0, delta = 1e-6)
37+
lmsog_obj = algorithm.lmso(ford = ford, cst = eps, lam = 0.95, sgmv = sgmv, sgmw = 0.0, lniproc = 2, alpha = 1.0, delta = 1e-6)
38+
39+
for i in range(0, sim_len):
40+
lmsow_obj.x.cb_push(far_end_signal[i])
41+
lmsog_obj.x.cb_push(far_end_signal[i])
42+
ew = lmsow_obj.lmso_w(echo_signal[i])
43+
eg = lmsog_obj.lmso_g(echo_signal[i])
44+
nmse_w[i] = echoproc.normalized_misalignment(lmsow_obj.h.coeffs, echo_path)
45+
nmse_g[i] = echoproc.normalized_misalignment(lmsog_obj.h.coeffs, echo_path)
46+
47+
t = np.linspace(0, len(far_end_signal)/fs, len(far_end_signal))
48+
fig = plt.figure()
49+
line_1, = plt.plot(t, 20 * np.log10(nmse_w), label = r'$LMSO-W$')
50+
line_2, = plt.plot(t, 20 * np.log10(nmse_g), label = r'$LMSO-G$')
51+
plt.legend()

lmso_algorithm.egg-info/PKG-INFO

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Metadata-Version: 1.1
2+
Name: lmso-algorithm
3+
Version: 1.1
4+
Summary: An Optimized LMS Algorithm
5+
Home-page: https://github.com/alexgrusu/lmso_algorithm
6+
Author: Alexandru - George Rusu
7+
Author-email: [email protected]
8+
License: GPLv3+
9+
Description: # lmso_algorithm
10+
11+
The least-mean-square (LMS) and the normalized least-mean-square (NLMS) algorithms require a trade-off between fast convergence
12+
and low misadjustment, obtained by choosing the control parameters. In general, time variable parameters are proposed
13+
according to different rules. Many studies on the optimization of the NLMS algorithm imply time variable control parameters
14+
according some specific criteria.
15+
16+
The optimized LMS (LMSO) algorithm [1] for system identification is developed in the context of a state variable model, assuming
17+
that the unknown system acts as a time-varying system, following a first-order Markov model [2].
18+
19+
The proposed algorithm follows an optimization problem and introduces a variable step-size in order to minimize the system misalignment
20+
21+
22+
[1] A. G. Rusu, S. Ciochină, and C. Paleologu, “On the step-size optimization of the LMS algorithm,” in Proc. IEEE TSP, 2019, 6 pages.
23+
24+
[2] G. Enzner, H. Buchner, A. Favrot, and F. Kuech, “Acoustic echo control,” in Academic Press Library in Signal Processing,
25+
vol. 4, ch. 30, pp. 807–877, Academic Press 2014.
26+
27+
28+
Keywords: Adaptive filters,Echo cancellation,System identification
29+
Platform: UNKNOWN
30+
Classifier: Development Status :: 4 - Beta
31+
Classifier: Intended Audience :: Researchers, Developers
32+
Classifier: Natural Language :: English
33+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
34+
Classifier: Operating System :: OS Independent
35+
Classifier: Programming Language :: Python :: 3
36+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
37+
Classifier: Topic :: Scientific/Engineering

lmso_algorithm.egg-info/SOURCES.txt

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
README.md
2+
setup.py
3+
lmso_algorithm/__init__.py
4+
lmso_algorithm/algorithm.py
5+
lmso_algorithm/common.py
6+
lmso_algorithm/echoproc.py
7+
lmso_algorithm.egg-info/PKG-INFO
8+
lmso_algorithm.egg-info/SOURCES.txt
9+
lmso_algorithm.egg-info/dependency_links.txt
10+
lmso_algorithm.egg-info/requires.txt
11+
lmso_algorithm.egg-info/top_level.txt
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+

lmso_algorithm.egg-info/requires.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
numpy
2+
scipy

lmso_algorithm.egg-info/top_level.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
lmso_algorithm

lmso_algorithm/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import *
1.64 KB
Binary file not shown.

lmso_algorithm/algorithm.py

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"""
66
import numpy as np
77

8+
89
class cir_buf:
910
"""
1011
Class that defines the concept of circular buffer.

0 commit comments

Comments
 (0)