#!/usr/bin/env python
# encoding: utf-8
"""
SL0 from http://ee.sharif.ir/~SLzero/
Authors: Massoud Babaie-Zadeh and Hossein Mohimani
Version: 1.3
Last modified: 4 August 2008.
adapted to numpy by Marc-André on 2011-11-18.
Copyright (c) 2011 IGBMC. All rights reserved.
"""
from __future__ import print_function
import numpy as np
import scipy.fftpack as fft
import unittest
# import numpy.fft as fft
[docs]def SL0(A, x, sigma_min, sigma_decrease_factor=0.5, mu_0=2, L=3, A_pinv=None, true_s=None):
"""
SL0(A, x, sigma_min, sigma_decrease_factor, mu_0, L, A_pinv, true_s)
Returns the sparsest vector s which satisfies underdetermined system of
linear equations A*s=x, using Smoothed L0 (SL0) algorithm. Note that
the matrix A should be a 'wide' matrix (more columns than rows). The
number of the rows of matrix A should be equal to the length of the
column vector x.
The first 3 arguments should necessarily be provided by the user. The
other parameters have defult values calculated within the function, or
may be provided by the user.
Sequence of Sigma (sigma_min and sigma_decrease_factor):
This is a decreasing geometric sequence of positive numbers:
- The first element of the sequence of sigma is calculated
automatically. The last element is given by 'sigma_min', and the
change factor for decreasing sigma is given by 'sigma_decrease_factor'.
- The default value of 'sigma_decrease_factor' is 0.5. Larger value
gives better results for less sparse sources, but it uses more steps
on sigma to reach sigma_min, and hence it requires higher
computational cost.
- There is no default value for 'sigma_min', and it should be
provided by the user (depending on his/her estimated source noise
level, or his/her desired accuracy). By `noise' we mean here the
noise in the sources, that is, the energy of the inactive elements of
's'. For example, by the noiseless case, we mean the inactive
elements of 's' are exactly equal to zero. As a rule of tumb, for the
noisy case, sigma_min should be about 2 to 4 times of the standard
deviation of this noise. For the noiseless case, smaller 'sigma_min'
results in better estimation of the sparsest solution, and hence its
value is determined by the desired accuracy.
mu_0:
The value of mu_0 scales the sequence of mu. For each vlue of
sigma, the value of mu is chosen via mu=mu_0*sigma^2. Note that this
value effects Convergence.
The default value is mu_0=2 (see the paper).
L:
number of iterations of the internal (steepest ascent) loop. The
default value is L=3.
A_pinv:
is the pseudo-inverse of matrix A defined by A_pinv=A'*inv(A*A').
If it is not provided, it will be calculated within the function. If
you use this function for solving x(t)=A s(t) for different values of
't', it would be a good idea to calculate A_pinv outside the function
to prevent its re-calculation for each 't'.
true_s:
is the true value of the sparse solution. This argument is for
simulation purposes. If it is provided by the user, then the function
will calculate the SNR of the estimation for each value of sigma and
it provides a progress report.
Authors: Massoud Babaie-Zadeh and Hossein Mohimani
Version: 1.4
Last modified: 4 April 2010.
Web-page:
------------------
http://ee.sharif.ir/~SLzero
Code History:
--------------
MODIF MAD : FT if true, spacity is evaluated in Fourier space
Version 2.0: 4 April 2010
Doing a few small modifications that enable the code to work also
for complex numbers (not only for real numbers).
Version 1.3: 13 Sep 2008
Just a few modifications in the comments
Version 1.2: Adding some more comments in the help section
Version 1.1: 4 August 2008
- Using MATLAB's pseudo inverse function to generalize for the case
the matrix A is not full-rank.
Version 1.0 (first official version): 4 July 2008.
First non-official version and algorithm development: Summer 2006
"""
if A_pinv is None:
A_pinv = np.linalg.pinv(A)
s = np.dot(A_pinv, x)
sigma = 2*max(np.abs(s))
# Main Loop
while sigma>sigma_min:
for i in range(L):
delta = OurDelta(s, sigma)
s -= mu_0*delta
s -= np.dot(A_pinv, np.dot(A,s)-x) # Projection
if true_s is not None:
print(' sigma=%f, SNR=%f' % (sigma,estimate_SNR(s,true_s)))
sigma = sigma * sigma_decrease_factor
return s
[docs]def SL0FT(A, x, sigma_min, sigma_decrease_factor=0.5, mu_0=2, L=3, A_pinv=None, true_s=None):
"""
Simplistic implementation of FT in SL0
same argument as SL0,
A is a sampling/scrambling matrix
if A is identity, trunctation or random sampling, then A_pinv should be provided as A.T
"""
def trans(s):
"transform s to x"
return np.dot(A, fft.ifft(s) )
def ttrans(x):
"ttransform x to s"
return fft.fft(np.dot(A_pinv, x))
if A_pinv is None:
A_pinv = np.linalg.pinv(A)
s = ttrans(x)
sigma = 2*max(np.abs(s))
# Main Loop
while sigma>sigma_min:
for i in range(L):
delta = OurDelta(s, sigma)
s -= mu_0*delta
s -= ttrans(trans(s)-x) # Projection
if true_s is not None:
print(' sigma=%f, SNR=%f' % (sigma,estimate_SNR(s,true_s)))
sigma = sigma * sigma_decrease_factor
return s
[docs]def SL0gene(transf, ttransf, x, sigma_min, sigma_decrease_factor=0.5, mu_0=2, L=3, true_s=None):
"""
general implementation of SL0
most arguments as SL0
transform and ttransform are generalized linear operation
simplistic implementation could be :
def trans(s):
"transform s to x"
return np.dot(A, fft.ifft(s) )
def ttrans(x):
"ttransform x to s"
return fft.fft(np.dot(A_pinv, x))
just write your own (eventually algorithmic) or used provided ones
"""
s = ttransf(x)
sigma = 2*max(np.abs(s))
print("sigma initial:", sigma)
# Main Loop
while sigma>sigma_min:
for i in range(L):
delta = OurDelta(s, sigma)
s -= np.dot(mu_0, delta)
s -= ttransf(transf(s)-x) # Projection
if true_s is not None:
print(' sigma=%f, SNR=%f' % (sigma,estimate_SNR(s,true_s)))
else:
print(' sigma=%f' % (sigma))
sigma = sigma * sigma_decrease_factor
return s
[docs]def OurDelta(s,sigma):
# return s*np.exp(-(s*np.conj(s))/sigma**2)
return s*np.exp(-abs(s)**2/sigma**2)
[docs]def estimate_SNR(estim_s, true_s):
err = true_s - estim_s
# return 10*np.log10(np.sum(true_s*np.conj(true_s))/sum(err*np.conj(err)))
return 10*np.log10(sum(abs(true_s)**2)/sum(abs(err)**2))
[docs]def norm(v):
return np.real(np.sqrt(np.sum(v * v.conj())))
[docs]class SL0_Tests(unittest.TestCase):
[docs] def test_igor1(self):
"""
from http://nuit-blanche.blogspot.com/2011/11/how-to-wow-your-friends-in-high-places.html
code from Igor Carron
"""
from ..Display import testplot
plt = testplot.plot()
M = 20
N = 500
noise = 0.01
A = np.random.randn(M,N)
x = np.zeros(N)
x[10] = 1
x[140] = 1
y1 = np.dot(A, x) + noise*np.random.randn(M)
plt.subplot(2,2,1)
plt.plot(x,'o')
plt.title(' original')
x2 = np.linalg.lstsq(A, y1)[0]
plt.subplot(2,2,2)
plt.plot(x2,'*')
plt.title(' using lstsq')
A3 = np.dot(A.T.conj(), np.linalg.inv(np.dot(A, A.T.conj())))
# A3 = linalg.pinv(A)
x3 = np.dot(A3, y1)
plt.subplot(2,2,3)
plt.plot(x3,'*')
plt.title(' using pinv')
plt.subplot(2,2,4)
# following parameters for all my computations: sigma_min = 0.00004, sdf= 0.95 )
sigma_min = 0.0001
sdf= 0.95
mu = 2
L = 3
x4 = SL0(A, y1, sigma_min, sdf, mu, L, true_s=x)
plt.plot(x4,'*')
plt.title(' using SL0 solver')
plt.show()
if __name__ == '__main__':
unittest.main()